Rendering

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

FieldTypeDescription
geometryTHREE.BufferGeometryThe geometry to render
materialTHREE.MaterialThe material to render with
depth_materialTHREE.Material | nullOptional override used for the shadow-depth pass
modeDrawModeTriangles (default), Lines, LineSegments, or Points
flagsnumberBitmask of ShadedGeometryFlags

Flags

import { ShadedGeometryFlags } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js";
FlagDefaultEffect
CastShadowonThe object casts shadows into shadow maps
ReceiveShadowonThe object receives shadows from shadow maps
VisibleonThe object is submitted for rendering
DrawMethodLockedoffPrevents the runtime optimizer from switching draw methods (see below)
InViewinternalSet 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