Meshes & materials
ShadedGeometry is the ECS component for a single draw call — a Three.js BufferGeometry + Material pair with flags for shadow participation and visibility, managed by ShadedGeometrySystem.
ShadedGeometry is Meep’s mesh component. It pairs a THREE.BufferGeometry with a THREE.Material, records flags that control shadow casting and visibility, and is tracked by ShadedGeometrySystem in a BVH so the system can cull and batch draw calls each frame.
ShadedGeometry
import { ShadedGeometry } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometryFlags } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js";
import { ShadedGeometrySystem } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
import { Transform } from "@woosh/meep-engine/src/engine/ecs/transform/Transform.js";
import { Entity } from "@woosh/meep-engine/src/engine/ecs/Entity.js";
// Create the component from a geometry + material pair.
const sg = ShadedGeometry.from(geometry, material);
// Flags default to CastShadow | ReceiveShadow | Visible — override as needed.
sg.setFlag(ShadedGeometryFlags.CastShadow);
sg.setFlag(ShadedGeometryFlags.ReceiveShadow);
sg.setFlag(ShadedGeometryFlags.Visible);
const t = new Transform();
t.position.set(0, 1, 0);
new Entity().add(t).add(sg).build(ecd);
The geometry and material are plain Three.js objects. Meep does not impose a custom material type for basic use — any THREE.Material works.
Static factory
ShadedGeometry.from(geometry, material, drawMode?) is the most common construction path. It calls geometry.computeBoundingBox() if the bounding box is absent, which is required for the BVH insertion that happens when the system links the component.
Fields
| Field | Type | Description |
|---|---|---|
geometry | THREE.BufferGeometry | The geometry to render |
material | THREE.Material | The material to render with |
depth_material | THREE.Material | null | Optional override used for the shadow-depth pass |
mode | DrawMode | Triangles (default), Lines, LineSegments, or Points |
flags | number | Bitmask of ShadedGeometryFlags |
Flags
import { ShadedGeometryFlags } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js";
| Flag | Default | Effect |
|---|---|---|
CastShadow | on | The object casts shadows into shadow maps |
ReceiveShadow | on | The object receives shadows from shadow maps |
Visible | on | The object is submitted for rendering |
DrawMethodLocked | off | Prevents the runtime optimizer from switching draw methods (see below) |
InView | internal | Set by the system when the object passes frustum culling; do not set manually |
Flag helpers: sg.setFlag(f), sg.clearFlag(f), sg.writeFlag(f, bool), sg.getFlag(f).
ShadedGeometrySystem
ShadedGeometrySystem is the system that manages all active ShadedGeometry components. It tracks the BVH, chooses the draw method (generic or instanced), and collects the visible set each frame.
import { ShadedGeometrySystem } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
em.addSystem(new ShadedGeometrySystem(engine));
When a ShadedGeometry + Transform pair is linked, the system subscribes to transform changes and updates the component’s world-space AABB in the BVH on every move. When the last entity referencing a given geometry is removed, the system calls geometry.dispose() to release GPU memory.
Hardware instancing
ShadedGeometrySystem includes a RuntimeDrawMethodOptimizer that runs as a background task and automatically promotes draw calls to instanced rendering when the same geometry + material combination is used by more than 16 entities. Below 4 entities the optimizer reverts to generic rendering. The threshold is evaluated by the InstancedRendererAdapter.score() method:
instance_count > 16 → score +1 (prefer instanced)
instance_count < 4 → score -1 (prefer generic)
otherwise → score 0 (neutral)
This means that if you spawn 500 identical crates — all sharing the same BufferGeometry reference and the same Material reference — the optimizer eventually batches them into a single instanced draw call at no cost to you. The optimizer works entity-by-entity across frames so it does not spike the frame time when a large batch is first added.
To prevent the optimizer from changing the draw method on a specific component, set the DrawMethodLocked flag:
sg.setFlag(ShadedGeometryFlags.DrawMethodLocked);
In practice the optimizer converges within a few frames of the scene stabilizing, so locking is rarely needed.
Static geometry merging
For static scene geometry loaded from a GLTF or built procedurally, merge_geometry_hierarchy collapses a Three.js Object3D tree into the minimum number of draw calls by merging sub-meshes that share the same material and attribute layout:
import { merge_geometry_hierarchy } from "@woosh/meep-engine/src/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js";
// Load or build a Three.js object hierarchy, then:
const optimized = merge_geometry_hierarchy(inputObject);
// `optimized` is a Mesh or Group with fewer draw calls.
Internally the function traverses the hierarchy, groups meshes by material, bakes each mesh’s local transform into its vertex positions, and calls Three.js’s mergeBufferGeometries on each group. Meshes with more than 1 024 triangles that share identical geometry are left separate and expected to be instanced instead.
The merged result is a plain Three.js object. To use it with the ECS, decompose it into ShadedGeometry components — or use it directly via Three.js scene-graph APIs if you do not need ECS features on those meshes.
MikkT tangent generation
Normal-mapped materials require a tangent-space basis at each vertex. Meep ships a JavaScript port of the reference MikkTSpace algorithm so tangents can be generated at load time without a native binary or a pre-baked asset:
import { buffer_geometry_ensure_tangents } from "@woosh/meep-engine/src/engine/graphics/geometry/MikkT/buffer_geometry_ensure_tangents.js";
// Adds a `tangent` attribute (Float32, 4 components) if not already present.
// Requires: indexed geometry, position, normal, and uv attributes.
buffer_geometry_ensure_tangents(geometry);
buffer_geometry_ensure_tangents is a no-op if the geometry already has a tangent attribute, is not indexed, or is missing normals or UVs. When conditions are met it calls buffer_geometry_generate_tangents, which creates a BufferAttribute of Float32Array with 4 components per vertex (xyz = tangent direction, w = handedness) and runs the MikkTSpace solver over it.
The tangent attribute is then available to any material that reads attribute vec4 tangent.
Where to go next
- Rendering overview — the Forward+ pipeline and BVH visibility.
- Lights & shadows — the
Lightcomponent and variance shadow maps.