Skip to content

Pipeline: Visualising VMF files

Jared Ketterer edited this page Feb 13, 2020 · 2 revisions

QtPyHammer (v0.0.4) reads text-based .vmf files and converts brushes into 3D geometry (drawn with OpenGL)

The namespace class

When a user opens a .vmf file, it is converted into a namespace class (defined in utilities/vmf.py)
This new object holds all data recorded in the initial .vmf in something similar to a nested dictionary
It also records the line numbers of defined sub-structures in the original .vmf & changes any repeatedly defined sub-structures into arrays (sides of brushes, etc.)

In utilities/render.py this namespace is passed as an argument for loading the .vmf as renderable geometry into a viewport

brushes = []  
for brush in vmf.world.solids  
    brushes.append(solid.solid(brush))

In utilities/solid.py the 'solid' class takes the namespace form of a .vmf brush and creates renderable geometry in a method similar to the following:

class solid:  
    def __init__(self, namespace):  
        # triangle_of = side.plane: "(X Y Z) (X Y Z) (X Y Z)" --> A.xyz, B.xyz, C.xyz
        raw_planes = [triangle_of(s) for s in namespace.sides]  
        raw_vertices = list(itertools.chain(*raw_planes))  
        self.faces = [] # cheap & dirty method  
        # self.planes = the unit-vector & distance representing each namespace.side.plane
        for tri, plane in zip(string_planes, self.planes):  
            this_face = [*tri]  
            normal, distance = plane  
            for v in raw_vertices:  
                if v not in tri:  
                    v_dist = vector.dot(v, normal)  
                    if distance - .5 < v_dist < distance + .5: # is on plane within .5hu  
                        this_face.append(v)  
            this_face = vector.sort_clockwise(this_face, normal)  
            self.faces.append(this_face)  

This method is inaccurate as it relies on the incorrect assumption that every vertex that makes up a brush is stored in the namespace (plenty of brushes don't do this)
The planned replacement generates large quads on each plane and slices them by every other plane which makes up that brush

Each vertex's position the UVs are calculated from the uaxis & vaxis of each side of the brush.

Now having the position, normal, uvs and editor_colour for each vertex, this information can be formatted into the vertex format byte-stream.

These vertices are sorted so that each unique vertex is only stored once per brush
Indices referring to each vertex are then stored in a GL_ELEMENT_ARRAY_BUFFER which records a series of triangles resembling brushes
However, sometimes a face is not recorded as a clean set of triangles so huge random triangles between brushes appear
This is partly a symptom of drawing the entire .vmf with a single draw call, but it's also a result of being lazy when converting brushes to triangles

This system could certainly use a lot of refinement and a clearer rendering pipeline to allow for hiding objects while editing, changing the geometry of brushes & drawing objects with different vertex formats (i.e. models)

Appendix

A rough anatomy of the VMF structure

|-- versioninfo
|   |- editorversion
|   |- editorbuild
|   |- mapversion (number of times this file has been saved)
|   |- formatversion
|___|- prefab (binary flag, 0 or 1)
|-- viewsettings
|   |- bSnapToGrid (binary flag)
|   |- bShowGrid (binary flag)
|   |- bShowLogicalGrid (binary flag)
|   |- nGridSpacing (grid scale)
|___|- bShow3DGrid (binary flag)
|-- world
|   |- id
|   |- mapversion
|   |- classname (always worldspawn, this means "world" is technically an entity)
|   |- skyname
|   |- maxpropscreenwidth ("size on screen" of props to fade for optimisation)
|   |- detailvbsp
|   |- detailmaterial
|   |-- solid
|   |   |- id
|   |   |-- side
|   |   |   |- id
|   |   |   |- plane
|   |   |   |- material
|   |   |   |- uaxis
|   |   |   |- vaxis
|   |   |-- side
|   |   |-- side
|   |   |-- side
|   |-- editor
|   |___|- color
|   |-- solid
|   |   |-- side
|___|___|-- ...
|-- entity
|-- entity
|-- entity
|-- cameras
|-- cordon
|   |- mins
|   |- maxs
|___|- active (binary flag)
Clone this wiki locally