Performance Implications of Collision-Mesh and Collision-Body Components in Pyrite64
Collision-Mesh components provide high-precision static geometry with BVH acceleration but incur significant memory and traversal costs, while Collision-Body components offer lightweight primitive shapes ideal for dynamic objects and triggers with minimal CPU overhead.
Pyrite64 is an open-source Nintendo 64 engine that treats collision geometry as two distinct component types to optimize for the console's limited resources. Understanding the performance implications of Collision-Mesh and Collision-Body components is essential for maintaining 60 FPS while handling complex level geometry and interactive gameplay objects.
What Are Collision-Mesh and Collision-Body Components?
Collision-Mesh: High-Fidelity Static Geometry
A Collision-Mesh stores a full triangle mesh extracted from a GLTF model, converted at build time into a binary chunk containing vertex lists, normal lists, and a BVH (bounding-volume hierarchy). In src/project/assets/collision.h, the createBVH function constructs this acceleration structure【/tmp/instagit_0o_ymwp6/src/project/assets/collision.h#L12-L16】. The CollisionMesh::build method in src/build/collisionBuilder.cpp writes this data to the level binary【/tmp/instagit_0o_ymwp6/src/build/collisionBuilder.cpp#L77-L87】.
Collision-Body: Lightweight Primitives
A Collision-Body represents simple primitives—box, sphere, or cylinder—along with trigger flags, collision masks, and positional offsets. Unlike meshes, bodies require no BVH. The CompCollBody::build method in src/project/component/types/compCollBody.cpp serializes these as fixed-size records containing half-extents, offsets, type enums, and flag bytes【/tmp/instagit_0o_ymwp6/src/project/component/types/compCollBody.cpp#L70-L85】.
Performance Implications and Memory Costs
CPU Traversal Costs
Collision-Mesh components incur high CPU costs during runtime. Every ray-cast or collision test requires traversing the BVH structure built by createBVH, then performing triangle-level intersection tests. This makes mesh collision suitable for static geometry that does not move, as the BVH remains valid throughout the frame.
Collision-Body components offer low CPU overhead. Collision tests resolve to simple AABB, sphere, or cylinder checks—constant-time operations requiring no tree traversal. The runtime collision resolver in n64/engine/src/collision/resolver.cpp performs bitwise flag checks using BCSFlags defined in n64/engine/include/collision/flags.h【/tmp/instagit_0o_ymwp6/n64/engine/include/collision/flags.h#L17-L24】:
bool isFixedA = bcsA.flags & Coll::BCSFlags::FIXED_XYZ;
bool isTrigger = bcs.flags & Coll::BCSFlags::TRIGGER;
Memory Footprint
On memory-constrained platforms like the N64, the distinction is critical. A Collision-Mesh can consume several kilobytes to megabytes per static mesh due to vertex data, indices, and BVH nodes. In contrast, a Collision-Body requires fewer than 32 bytes per instance, making it feasible to have hundreds of dynamic bodies active simultaneously.
Use Cases: When to Use Each Component
Static World Geometry: Use Collision-Mesh
For level terrain, static walls, floors, and decorative architecture that never moves, Collision-Mesh provides the necessary precision. The mesh is built once during the asset pipeline in collisionBuilder.cpp, and the BVH is generated by Project::Assets::Collision::createBVH. This approach ensures that players and objects collide accurately with visual geometry without runtime construction costs.
Dynamic Objects and Physics: Use Collision-Body
For moving platforms, doors, physics props, or any entity that changes position or rotation each frame, Collision-Body is the correct choice. The primitive shape eliminates the need to rebuild or transform a BVH hierarchy every frame. You can also mark bodies as FIXED_XYZ to prevent displacement by other objects, handled in the resolver via bitwise operations on the flags field.
Trigger Zones: Use Collision-Body with Trigger Flag
Invisible gameplay zones—such as collectible pickups, cutscene triggers, or damage areas—should use Collision-Body components with the TRIGGER flag set. This configuration allows the engine to detect overlap without generating physical response impulses. The flag is serialized in compCollBody.cpp and checked in the runtime resolver using BCSFlags::TRIGGER.
Hybrid Approaches
Some entities benefit from both components simultaneously. For example, an enemy character might use a Collision-Mesh for precise hit-detection against weapons (visual fidelity), while also possessing a Collision-Body sphere for cheap AI proximity checks (aggro range). This dual-component approach balances accuracy and performance.
Implementation Details and Source Code
Component Registration
Both types are registered in src/project/component/components.h with distinct IDs and editor icons【/tmp/instagit_0o_ymwp6/src/project/component/components.h#L128-L142】. The editor distinguishes them in the viewport: Collision-Mesh debug drawing can be toggled via showCollMesh in src/editor/pages/parts/viewport3D.cpp【/tmp/instagit_0o_ymwp6/src/editor/pages/parts/viewport3D.cpp#L216-L230】, while bodies are always rendered due to their low cost.
Building Collision-Mesh Assets
When building a level, CompCollMesh::build prepares the mesh data and invokes Build::buildT3DCollision to generate the BVH if not already cached【/tmp/instagit_0o_ymwp6/src/project/component/types/compCollMesh.cpp#L66-L86】:
// From collisionBuilder.cpp - writing the binary chunk
void CollisionMesh::build(BuildContext& ctx) {
// ... vertex and index extraction from GLTF ...
ctx.writeChunk(C4B_CHUNK_COLMESH,
vertices.data(), vertices.size() * sizeof(Vec3),
indices.data(), indices.size() * sizeof(uint16_t),
bvhNodes.data(), bvhNodes.size() * sizeof(BVHNode)
);
}
Serializing Collision-Body Components
For dynamic bodies, CompCollBody::build writes a fixed-size record containing the primitive type, half-extents, offset, and packed flags【/tmp/instagit_0o_ymwp6/src/project/component/types/compCollBody.cpp#L70-L85】:
// Serialization in compCollBody.cpp
void CompCollBody::build(BuildContext& ctx) {
uint8_t flags = 0;
if (isTrigger.value) flags |= BCSFlags::TRIGGER;
if (isFixed.value) flags |= BCSFlags::FIXED_XYZ;
ctx.writeRecord(
type.value, // SHAPE_BOX, SHAPE_SPHERE, etc.
halfExtend.value, // Vec3 half-extents
offset.value, // Vec3 position offset
flags // Packed bit-flags
);
}
Runtime Flag Evaluation
The engine's collision resolver uses these flags to determine behavior without accessing geometry data. In n64/engine/src/collision/resolver.cpp, the system checks flags from n64/engine/include/collision/flags.h:
// Runtime collision logic
bool isTrigger = (body.flags & Coll::BCSFlags::TRIGGER) != 0;
bool isFixed = (body.flags & Coll::BCSFlags::FIXED_XYZ) != 0;
if (isTrigger) {
// Notify gameplay systems, apply no physical impulse
triggerCallback(body.id);
} else if (!isFixed) {
// Resolve penetration, apply forces
resolvePhysics(bodyA, bodyB);
}
Summary
- Collision-Mesh components provide triangle-level precision for static world geometry using pre-built BVHs, but consume significant memory and require tree traversal for every query.
- Collision-Body components use simple primitives (box, sphere, cylinder) with minimal memory footprint (<32 bytes), enabling cheap per-frame updates and bitwise flag checks for triggers and fixed objects.
- Static geometry (floors, walls) should use Collision-Mesh for accuracy, while dynamic objects, triggers, and physics props should use Collision-Body for performance.
- The distinction is enforced at build time in
collisionBuilder.cppandcompCollBody.cpp, with runtime behavior controlled by flags defined inn64/engine/include/collision/flags.h.
Frequently Asked Questions
Can a single entity have both a Collision-Mesh and a Collision-Body?
Yes. An entity can possess both components simultaneously to serve different purposes. For example, a boss enemy might use a Collision-Mesh for precise weapon hit detection against its visual geometry, while also having a Collision-Body sphere for cheap AI proximity checks like aggro range detection. This hybrid approach balances visual fidelity with computational efficiency.
Why does Collision-Mesh require a BVH while Collision-Body does not?
Collision-Mesh represents arbitrary triangle soup from GLTF models, which requires a Bounding Volume Hierarchy (BVH) to accelerate ray-casting and collision queries from O(n) to O(log n). The BVH is built once at compile time by Project::Assets::Collision::createBVH in src/project/assets/collision.h. Collision-Body uses simple convex primitives (boxes, spheres) that require only a single AABB or distance test, eliminating the need for complex spatial acceleration structures.
How do trigger zones work with Collision-Body components?
Trigger zones are implemented by setting the TRIGGER flag bit in the Collision-Body component's flag byte. During serialization in CompCollBody::build (src/project/component/types/compCollBody.cpp), the flag is packed into the binary record. At runtime, the collision resolver in n64/engine/src/collision/resolver.cpp checks this flag using Coll::BCSFlags::TRIGGER and routes the collision to a callback system rather than applying physical response impulses, allowing for gameplay logic like collectible pickups or cutscene triggers.
What happens if I use Collision-Mesh for moving objects?
Using Collision-Mesh for dynamic objects is strongly discouraged because the BVH is built for a specific static spatial configuration. Moving the object would require either rebuilding the BVH every frame (prohibitively expensive on N64 hardware) or accepting incorrect collision results as the hierarchy no longer matches the transformed geometry. For any object that changes position, rotation, or scale at runtime, Collision-Body with a primitive shape is the correct choice.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →