-
-
Notifications
You must be signed in to change notification settings - Fork 4
Pipeline: Visualising VMF files
QtPyHammer (v0.0.4) reads text-based .vmf
files and converts brushes into 3D geometry (drawn with OpenGL)
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)
|-- 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)