Meshes & Geometry
loadMesh() Flow
loadMesh(const char* path) loads a glTF/GLB file:
- Parse:
cgltf_parse_file()+cgltf_load_buffers()(header-only cgltf library) - Extract geometry: Iterates all meshes → primitives (triangles only). For each primitive:
- Reads POSITION accessor (required)
- Reads NORMAL accessor (optional, defaults to (0, 1, 0))
- Reads TEXCOORD accessor (optional, defaults to (0, 0))
- Reads base color factor from PBR metallic-roughness material (defaults to (0.7, 0.7, 0.8))
- Reads indices (or generates sequential indices if unindexed)
- Load texture: Scans primitives for
base_color_texture:- Embedded (GLB): reads from
buffer_view->buffer->data + offset - External URI: resolves relative to glTF file path, reads file
- Calls
loadTextureFromMemory()to create GPU texture + material
- Embedded (GLB): reads from
- Auto-center and scale: Computes AABB, centers at origin, scales to fit within 2.0 units
- Add to combined buffer: Calls
addMesh()with the processed vertices/indices - Assigns material ID to the mesh
Returns the mesh ID (index into meshes_ array), or -1 on failure.
addMesh()
- Records
vertexOffset= current size ofallVertices_,indexOffset= current size ofallIndices_ - Appends vertices and indices to the combined arrays
- Creates a
MeshDataentry with offset/count and default material - Sets
buffersNeedRebuild_ = true - Returns the new mesh ID
rebuildGeometryBuffers()
Called at the start of renderFrame() when buffersNeedRebuild_ is true:
vkDeviceWaitIdle()— ensure no in-flight frames reference the old buffers- Destroy existing vertex/index buffers
- Create staging buffer (host-visible) → copy
allVertices_data - Create device-local vertex buffer →
copyBuffer()from staging - Destroy staging buffer
- Repeat for index buffer with
allIndices_ - Clear
buffersNeedRebuild_flag
This is a full rebuild — all geometry is re-uploaded. Fine for loading, but not suitable for per-frame mesh changes.
Procedural Primitives
All primitives return a mesh ID and use addMesh() internally.
Box (createBoxMesh)
- Parameters: width, height, length, RGB color
- 24 vertices (4 per face x 6 faces), 36 indices
- Each face has correct outward normal and UV mapping (0,0)-(1,1)
Sphere (createSphereMesh)
- Parameters: radius, segments (longitude), rings (latitude), RGB color
- Generates
(rings + 1) x (segments + 1)vertices using spherical coordinates - UV: u = seg/segments, v = ring/rings
- Normals point radially outward
Plane (createPlaneMesh)
- Parameters: width, height, RGB color
- 4 vertices, 6 indices (2 triangles)
- Lies in the XZ plane at Y=0, normal pointing up (0, 1, 0)
- UV maps (0,0)-(1,1)
Cylinder (createCylinderMesh)
- Parameters: radius, height, segments, RGB color
- Side: 2 rings of
(segments + 1)vertices with horizontal normals - Top/bottom caps: center vertex + rim vertices with up/down normals
- Cap UVs use circular mapping:
(0.5 + cos*0.5, 0.5 + sin*0.5)
Capsule (createCapsuleMesh)
- Parameters: radius, height (body), segments, rings, RGB color
- Top hemisphere (offset up by halfH), cylinder body, bottom hemisphere (offset down by halfH)
- Rings must be even (split equally between hemispheres)
- Shared indexing across all sections for seamless normals
Adding a new primitive shape: Create a new createXxxMesh() method in renderer.h and scene/mesh.cpp. Generate vertices with positions, normals, colors, and UVs. Call addMesh(verts, inds) and return the result. Add the bridge function in bridge.cpp and managed/rendering/NativeBridge.cs.
Changing mesh loading behavior: Modify loadMesh() in scene/mesh.cpp. The auto-center/scale logic is at the end — remove or adjust the 2.0 / extent scaling factor if you want original-size meshes.