WBrenderer JS API
A high-fidelity software implementation of the classic BRender engine, written in pure ES6 with zero runtime dependencies. Optimized for high-performance pixel manipulation and nostalgic 90s-era 3D rendering.
Core Principles
- Zero Dependencies: No npm installs required. Drop the folder and go.
- Data-Oriented Math: Matrices and Vectors are standard
Float32Arraybuffers for speed and minimal GC pressure. - Software Rasterization: Custom C-style scanline loops for flat, Gouraud, and textured paths.
Data Structures
The engine relies on a small set of core classes and enums to manage scene state and assets.
Actor
The Actor is the fundamental node of the scene graph. It handles hierarchy and spatial transformation.
| Property | Type | Description |
|---|---|---|
type | ACTOR_TYPE | Determines actor behavior (Model, Camera, etc). |
transform | Mat34 | Local-to-parent transformation matrix. |
model | Model | The geometry to render (if type is MODEL). |
material | Number | Fallback 0xRRGGBB color if no materials are set. |
materials | Material[] | Array of materials indexed by the model's face data. |
camera | Camera | Camera settings (if type is CAMERA). |
light | Light | Light settings (if type is LIGHT). |
Model
A Model contains the geometry data loaded from .DAT files or generated via primitives.
| Property | Type | Description |
|---|---|---|
name | String | Identifier used for registry lookups. |
vertices | Float32Array | Packed vertex data (x, y, z, nx, ny, nz, u, v). |
faces | Uint16Array | Vertex indices forming triangles. |
materialNames | String[] | List of material identifiers referenced by faces. |
faceMaterialIndex | Uint16Array | Index into the actor's material array per face. |
Example: Manual Model Construction
You can create custom geometry by packing raw arrays into the interleaved format expected by the renderer.
import { Model, makeVertices } from 'wbrenderer';
// 1. Raw geometric data
const positions = new Float32Array([
-1, -1, 0, // v0
1, -1, 0, // v1
0, 1, 0 // v2
]);
const uvs = new Float32Array([
0, 0,
1, 0,
0.5, 1
]);
// 2. Pack into interleaved vertex buffer (stride 8: x,y,z, nx,ny,nz, u,v)
// Pass null for normals to let the renderer auto-calculate face normals.
const vertices = makeVertices(positions, null, uvs);
// 3. Create the model with an index array (faces)
const faces = new Uint16Array([0, 1, 2]);
const myModel = new Model(vertices, faces, { name: 'triangle.dat' });
Example: Per-Face Materials
To render a model with multiple materials, assign an array to actor.materials. The renderer uses the faceMaterialIndex from the model to look up which material to use for each triangle.
import { Actor, ACTOR_TYPE, Br } from 'wbrenderer';
const car = new Actor({ type: ACTOR_TYPE.MODEL, model: carModel });
// Option A: Resolve materials from the global registry using model metadata.
// This is the standard approach after using BrFmtMaterialLoad().
car.materials = carModel.materialNames.map(name => Br.BrMaterialFind(name));
// Option B: Manually define a material list.
// The model's faceMaterialIndex values will index into this array.
car.materials = [bodyMat, tireMat, glassMat];
Material
Defines the surface appearance of geometry.
| Property | Type | Description |
|---|---|---|
colour | Number | Base 0xRRGGBB color. |
flags | MATF | Bitset for lighting, transparency, and culling. |
colourMap | Pixelmap | The texture to apply (palette-indexed). |
palette | Uint8Array | 256-entry RGB palette (stride 3) for the texture. |
Pixelmap
A generic buffer for image or depth data.
| Property | Type | Description |
|---|---|---|
width / height | Number | Dimensions in pixels. |
type | PixelmapType | Memory layout (e.g. RGBX_8888 or DEPTH_F32). |
pixels | TypedArray | The raw pixel buffer. |
Constants & Enums
ACTOR_TYPE
| Value | Description |
|---|---|
NONE | Empty container node. |
MODEL | Visual geometry node. |
CAMERA | Viewpoint node. |
LIGHT | Light source node. |
PixelmapType
| Value | Description |
|---|---|
RGBX_8888 | 32-bit color (8888). |
DEPTH_F32 | 32-bit floating point depth. |
Math Library
All operations are pure functions to avoid object allocation in hot paths.
Vec3 (3-Element Vector)
import { Vec3 } from 'wbrenderer';
// Create a Float32Array(3)
const pos = Vec3.create(0, 10, 0);
// Standard operations (out, a, b)
Vec3.add(pos, pos, [1, 0, 0]);
Vec3.normalize(pos, pos);
| Function | Parameters | Description |
|---|---|---|
create(x, y, z) |
x, y, z: Numbers (Optional) |
Creates a new Float32Array(3). |
add / sub(out, a, b) |
out, a, b: Vec3 |
Vector addition or subtraction. |
scale(out, a, s) |
out, a: Vec3, s: Number |
Scales vector a by scalar s. |
dot(a, b) |
a, b: Vec3 |
Returns the dot product (scalar). |
cross(out, a, b) |
out, a, b: Vec3 |
Calculates cross product. |
length(a) / normalize(out, a) |
out, a: Vec3 |
Calculates magnitude or unit vector. |
Mat4 (4x4 Matrix)
Used primarily for projection and view transformations. Column-major 16-element array.
| Function | Parameters | Description |
|---|---|---|
create() / identity(out) |
out: Mat4 |
Allocates or resets a 4x4 identity matrix. |
multiply(out, a, b) |
out, a, b: Mat4 |
Matrix multiplication (out = a * b). |
perspective(out, fovY, asp, n, f) |
out: Mat4, fovY, asp, n, f: Numbers |
Builds a perspective projection matrix. |
lookAt(out, eye, tgt, up) |
out: Mat4, eye, tgt, up: Vec3 |
Builds a view matrix looking from eye to target. |
transformPoint(out, m, p) |
out: Vec4, m: Mat4, p: Vec3 |
Transforms a 3D point into clip space. |
Mat34 (3x4 Affine Matrix)
Used for all actor transformations. Compact representation of rotation and translation.
const m = Mat34.create(); // Identity
Mat34.rotationY(m, Math.PI / 4);
| Function | Parameters | Description |
|---|---|---|
create() / identity(out) |
out: Mat34 |
Allocates or resets a 3x4 identity affine matrix. |
translation(out, x, y, z) |
out: Mat34, x, y, z: Numbers |
Sets a translation matrix. |
rotationX/Y/Z(out, rad) |
out: Mat34, rad: Number (Radians) |
Sets a rotation matrix. |
multiply(out, a, b) |
out, a, b: Mat34 |
Multiplies two affine matrices. |
transformPoint(out, m, p) |
out: Vec3, m: Mat34, p: Vec3 |
Transforms a 3D point. |
toMat4(out, m) |
out: Mat4, m: Mat34 |
Promotes a 3x4 affine matrix to a full 4x4. |
Pixelmaps & Framebuffers
Pixelmaps wrap raw buffers for color and depth data.
import { Pixelmap, TYPE } from 'wbrenderer';
const color = new Pixelmap(320, 240, TYPE.RGBX_8888);
const depth = new Pixelmap(320, 240, TYPE.DEPTH_F32);
color.clear(0x101820); // Dark navy clear
Scene Graph
WBrenderer uses an Actor hierarchy similar to BRender's V1DB system.
const scene = new SceneRoot();
const car = new Actor({
type: ACTOR_TYPE.MODEL,
model: eagleModel,
material: 0xff0000
});
scene.add(car);
Traversal & Custom Properties
The walkTree utility provides a way to traverse the entire hierarchy starting from a specific
root. Because Actor is a standard class, you can attach arbitrary data to actors for use
during these traversals.
import { walkTree } from 'wbrenderer';
// 1. Tagging actors with custom metadata during setup
const car = new Actor({ type: ACTOR_TYPE.MODEL, model: carModel });
car.isVehicleBody = true;
car.health = 100;
// 2. Using walkTree to find and process tagged actors
walkTree(scene, (actor) => {
// Identify actors by custom properties or model names
if (actor.isVehicleBody) {
// Perform vehicle-specific logic
actor.health -= 10;
}
// modelName is often populated by loaders for Actors in a hierarchy
if (actor.modelName === 'WHEEL.DAT') {
// Update wheel rotation, or link specialized wheel materials
}
});
This pattern is highly effective for post-processing hierarchies loaded from .ACT files,
where you may need to resolve materials or attach physics proxy objects to specific sub-models based on
their names or tags.
Cameras & Lights
Settings for viewpoints and illumination sources within the scene hierarchy.
CAMERA_TYPE
Determines the projection model used by the camera actor.
| Type | Description |
|---|---|
CAMERA_TYPE.PERSPECTIVE | Standard perspective projection. Requires fovY and aspect. |
CAMERA_TYPE.PARALLEL | Orthographic projection. Distances do not affect object size. |
Camera Properties
| Property | Type | Description |
|---|---|---|
fovY | Number | Vertical field of view in radians. Default is ~60° (Math.PI / 3). |
aspect | Number | Width / Height ratio (e.g., 800 / 600). |
hither | Number | Near clipping plane. Minimal recommended value: 0.05. |
yon | Number | Far clipping plane. Geometry beyond this is culled. |
LIGHT_TYPE
Determines how light is emitted from the actor's position or orientation.
| Type | Description |
|---|---|
LIGHT_TYPE.DIRECT | Infinite distance light (Sun). Parallel rays based on actor orientation. |
LIGHT_TYPE.POINT | Omnidirectional light source at actor position. Subject to attenuation. |
LIGHT_TYPE.SPOT | Conical light source. Uses innerAngle and outerAngle cones. |
Light Properties
| Property | Type | Description |
|---|---|---|
colour | Number | Light color as 0xRRGGBB. |
intensity | Number | Brightness multiplier (typically 0.0 to 1.0). |
attenC / attenL / attenQ | Number | Attenuation coefficients (Constant, Linear, Quadratic). Formula: 1 / (C + L·d + Q·d²). |
innerAngle / outerAngle | Number | Spotlight cone half-angles in radians. |
Br.BrLightAllocate(opts) to quickly create a Light actor
with these properties in one call.
Camera Movement
Implementing input-driven camera movement involves updating the Actor.transform based on user input. Here are two common patterns.
Orbit Camera
An orbit camera rotates around a target point. This is typically implemented using spherical coordinates (Yaw, Pitch, Distance).
let yaw = 0.3, pitch = 0.35, dist = 14;
const target = [0, 0, -10];
// Update your camera transform every frame
function setOrbitCamera(camActor) {
const cy = Math.cos(yaw), sy = Math.sin(yaw);
const cp = Math.cos(pitch), sp = Math.sin(pitch);
// Calculate eye position
const eye = [
target[0] + dist * cp * sy,
target[1] + dist * sp,
target[2] + dist * cp * cy
];
// Build camera basis manually (LookAt target)
const fx = target[0] - eye[0], fy = target[1] - eye[1], fz = target[2] - eye[2];
const fl = Math.hypot(fx, fy, fz) || 1;
const fnx = fx / fl, fny = fy / fl, fnz = fz / fl;
// Right = Forward x Up (0,1,0)
let rx = -fnz, ry = 0, rz = fnx;
const rl = Math.hypot(rx, ry, rz) || 1;
rx /= rl; rz /= rl;
// Up = Right x Forward
const ux = ry * fnz - rz * fny;
const uy = rz * fnx - rx * fnz;
const uz = rx * fny - ry * fnx;
// Apply to Actor transform (Mat34 basis)
const m = camActor.transform;
m[0] = rx; m[1] = ry; m[2] = rz; // X basis
m[3] = ux; m[4] = uy; m[5] = uz; // Y basis
m[6] = -fnx; m[7] = -fny; m[8] = -fnz; // Z basis (-Forward)
m[9] = eye[0]; m[10] = eye[1]; m[11] = eye[2]; // Translation
}
FPS (First-Person) Camera
An FPS camera rotates based on mouse movement and moves relative to its current orientation.
import { Mat34, Vec3 } from 'wbrenderer';
let pos = Vec3.create(0, 1.5, 5);
let yaw = 0, pitch = 0;
function updateFPSCamera(camActor, mouseDelta, keys) {
// 1. Handle Rotation
yaw -= mouseDelta.x * 0.005;
pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch - mouseDelta.y * 0.005));
// 2. Create orientation matrix
const rotY = Mat34.create();
const rotX = Mat34.create();
Mat34.rotationY(rotY, yaw);
Mat34.rotationX(rotX, pitch);
Mat34.multiply(camActor.transform, rotY, rotX);
// 3. Handle Movement (relative to orientation)
const m = camActor.transform;
const forward = [-m[6], 0, -m[8]]; // Forward vector on XZ plane
const right = [m[0], 0, m[2]];
Vec3.normalize(forward, forward);
Vec3.normalize(right, right);
const move = Vec3.create();
if (keys['w']) Vec3.add(move, move, forward);
if (keys['s']) Vec3.sub(move, move, forward);
if (keys['a']) Vec3.sub(move, move, right);
if (keys['d']) Vec3.add(move, move, right);
Vec3.normalize(move, move);
Vec3.scale(move, move, 0.1); // Walking speed
Vec3.add(pos, pos, move);
// 4. Apply Translation
m[9] = pos[0]; m[10] = pos[1]; m[11] = pos[2];
}
Materials & Flags
The Material class defines the surface properties of a model, including its color, texture, and shading behavior.
MATF Flags
The MATF bitset controls how the renderer processes the material's geometry. These can be combined using bitwise OR (|).
| Flag | Description |
|---|---|
MATF.LIGHT |
Enables lighting calculations. If unset, the material is rendered at full intensity using its base colour. |
MATF.SMOOTH / MATF.GOURAUD |
Enables Gouraud shading, which interpolates light intensity or vertex colors across the face for a smooth appearance. |
MATF.TWO_SIDED |
Disables back-face culling. Both sides of the face will be rendered, essential for sprites or thin geometry. |
MATF.ALWAYS_VISIBLE |
Ensures the face is always rendered by bypassing certain culling optimizations. Often used for important scene elements or effects. |
MATF.DISABLE_COLOUR_KEY |
By default, palette index 0 is treated as transparent for textured materials. Setting this flag makes index 0 opaque. |
The Renderer
The Renderer class processes the scene tree and executes the rasterization pipelines.
const r = new Renderer(color, depth);
// One-shot render of a full tree from a specific camera
r.renderTree(scene, cameraActor);
// Blit to a Canvas2D context
color.blitToImageData(myImageData);
Handling Window Resizing
When the browser window or container changes size, you must update the framebuffers and the camera's projection to prevent stretching or pixelation.
window.addEventListener('resize', () => {
const W = window.innerWidth;
const H = window.innerHeight;
// 1. Re-allocate Pixelmaps (Renderer expects these to match in size)
// 'color' and 'depth' should be accessible in your render loop
color = new Pixelmap(W, H, TYPE.RGBX_8888);
depth = new Pixelmap(W, H, TYPE.DEPTH_F32);
// 2. Update Renderer with new buffers
renderer = new Renderer(color, depth);
// 3. Update Camera aspect ratio to prevent stretching
camActor.camera.aspect = W / H;
// 4. Update canvas element dimensions and ImageData cache
canvas.width = W;
canvas.height = H;
imgData = ctx.createImageData(W, H);
});
Br.BrZbSceneRender facade, the renderer is instantiated per-frame, so you only need to update the global pixelmap variables and the camera aspect ratio.
Format Loaders
Native support for binary Carmageddon/BRender assets.
| Method | Target Format | Returns |
|---|---|---|
loadDat(buffer) |
.DAT | Array of Model objects |
loadMat(buffer) |
.MAT | Array of Material objects |
loadPix(buffer) |
.PIX | Array of Pixelmap (Bitmaps) |
loadAct(buffer) |
.ACT | Actor (Root of the hierarchy) |
Example: Loading a Hierarchical Actor
The loadAct function reconstructs complex actor hierarchies from .ACT files. It is commonly used alongside walkTree to initialize materials and transformations for sub-models (e.g., wheels on a car).
import { loadAct, walkTree, Br } from 'wbrenderer';
// Load and parse the ACT hierarchy
const res = await fetch('EAGLE.ACT');
const carRoot = loadAct(await res.arrayBuffer());
// Traverse the tree to set up materials for specific sub-models
walkTree(carRoot, (actor) => {
if (actor.modelName === 'WHEEL.DAT') {
// Resolve materials from the global registry
actor.materials = [Br.BrMaterialFind('BGLWEEL.MAT')];
}
});
scene.add(carRoot);
Example: Manual Texture and Material Setup
For fine-grained control outside of the Br.* facade, you can use the raw loaders and manually
link textures to materials.
import { loadMat, loadPix, Br } from 'wbrenderer';
const matAB = await (await fetch('LEVEL.MAT')).arrayBuffer();
const pixAB = await (await fetch('LEVEL.PIX')).arrayBuffer();
// 1. Load materials and pixelmaps into memory
const materials = loadMat(matAB);
const textures = loadPix(pixAB);
// 2. Register textures in the global map registry so they can be found by name
textures.forEach(pm => Br.BrMapAdd(pm));
// 3. Link textures and apply a shared palette to each material
for (const mat of materials) {
if (mat.colourMapName) {
mat.colourMap = Br.BrMapFind(mat.colourMapName);
mat.palette = levelPalette; // 768-byte RGB palette (Uint8Array)
}
Br.BrMaterialAdd(mat);
}
Asset Registry
The Registry system provides a global, case-insensitive lookup mechanism for assets. This is critical for resolving named references found in .DAT, .MAT, and .ACT files.
Global Singletons
WBrenderer provides three specialized registry instances:
modelRegistry: StoresModelobjects (geometry).materialRegistry: StoresMaterialobjects (surface properties).mapRegistry: StoresPixelmapobjects (textures and palettes).
Methods
| Method | Parameters | Description |
|---|---|---|
add(name, item) |
name: String, item: Object |
Registers an asset. Names are normalized to uppercase internally. |
find(name) |
name: String |
Retrieves an asset by name. Lookup is case-insensitive. Returns undefined if not found. |
remove(name) |
name: String |
Removes an asset from the registry. |
Example: Custom Registration
import { modelRegistry, Model } from 'wbrenderer';
// Manually register a custom model
modelRegistry.add('PROC_CUBE', myCustomCube);
// Retrieve it later (case does not matter)
const model = modelRegistry.find('proc_cube');
BRender Facade (Br.*)
The Br namespace provides a C-style entry point that mirrors BRender 1.3.2. This is the
recommended path for porting legacy logic.
Lifecycle
| Function | Parameters | Description |
|---|---|---|
BrBegin() |
None | Initializes the internal engine state. |
BrEnd() |
None | Shuts down the engine and clears state. |
isInitialized() |
None | Returns true if BrBegin is active. |
Pixelmaps
| Function | Parameters | Description |
|---|---|---|
BrPixelmapAllocate(type, w, h) |
type: PixelmapTypew: Number (Width)h: Number (Height)
|
Creates a new Pixelmap buffer. |
BrPixelmapFill(pm, value) |
pm: Pixelmapvalue: Number (color/depth)
|
Clears the pixelmap with a specific value. |
Resource Registry
| Function | Parameters | Description |
|---|---|---|
BrModelAdd(model) / BrModelFind(name) |
model: Model / name: String |
Register or retrieve models by name. |
BrMaterialAdd(mat) / BrMaterialFind(name) |
mat: Material / name: String |
Register or retrieve materials. |
BrMapAdd(pm) / BrMapFind(name) |
pm: Pixelmap / name: String |
Register or retrieve textures/palettes. |
The Br.BrFmt*Load functions (like BrFmtModelLoad) are wrappers around the raw loaders (like loadDat). The key differences are:
- Side Effects:
loadDatsimply parses the buffer.BrFmtModelLoadparses the buffer AND callsBrModelAddfor every resulting model. - Name Resolution: By using the facade loaders, models and materials are indexed in the global
Registry. This allows theloadActfunction to automatically "stitch" models onto actors when it encounters a name like"EAGLE.DAT". - Palette Handling:
BrFmtPixelmapLoadregisters palettes found in.PIXfiles into the map registry. WhileloadPixreturns them as raw objects, registration allows them to be found viaBrMapFind. Note: Palettes often require manual repacking from 4-byte (XRGB) to 3-byte (RGB) formats for use in materials.
// The Raw Way: Manual management
const [model] = loadDat(buffer);
const actor = new Actor({ type: ACTOR_TYPE.MODEL, model });
// The BRender Way: Automatic registration
Br.BrFmtModelLoad(buffer);
// Later, loading an .ACT file will find "EAGLE.DAT" automatically
const carRoot = loadAct(actBuffer);
Example: Palette Repacking
Many legacy palettes (like DRRENDER.PAL) are stored as RGBX pixelmaps. You must
repack these for use in Material.palette:
const palPm = Br.BrMapFind('DRRENDER.PAL');
const palData = new Uint8Array(256 * 3);
for (let i = 0; i < 256; i++) {
const o = i * 4;
palData[i * 3 + 0] = palPm.pixels[o + 1]; // R
palData[i * 3 + 1] = palPm.pixels[o + 2]; // G
palData[i * 3 + 2] = palPm.pixels[o + 3]; // B
}
// Link to material manually
mat.palette = palData;
Scene Graph
| Function | Parameters | Description |
|---|---|---|
BrActorAllocate(type) |
type: ACTOR_TYPE |
Creates a new actor of the specified type. |
BrActorAdd(parent, child) |
parent: Actor, child: Actor |
Links a child actor to a parent. Returns child. |
BrSceneRootAllocate() |
None | Allocates a root container for the scene. |
Rendering
| Function | Parameters | Description |
|---|---|---|
BrZbSceneRender(root, cam, color, depth) |
root, cam, color, depth |
Executes a Z-buffered render of the actor tree. |
BrZbSceneRenderPrimitives(vp, prims, color, depth) |
vp: Mat4, prims: Array, color, depth |
Renders a flat list of meshes using a precomputed matrix. |
Lights & Fog
| Function | Parameters | Description |
|---|---|---|
BrLightAllocate(opts) |
opts: Object |
Creates a Light actor (e.g. {type, color, intensity}). |
BrLightEnable(actor) / BrLightDisable(actor) |
actor: Actor (Light) |
Toggles the active state of a light. |
BrFogSet(scene, opts) |
scene: SceneRoot, opts: Object |
Configures linear fog ({colour, hither, yon}). |
Quickstart: Plane and Cube
This example demonstrates how to set up a basic scene with a camera, a light, a plane, and a rotating cube.
import {
Pixelmap, TYPE, Renderer, SceneRoot, Actor, ACTOR_TYPE,
Camera, Light, LIGHT_TYPE, makeCube, makePlane, Mat34
} from 'wbrenderer';
export async function createScene({ canvas, wbr }) {
const W = canvas.width;
const H = canvas.height;
const color = new Pixelmap(W, H, TYPE.RGBX_8888);
const depth = new Pixelmap(W, H, TYPE.DEPTH_F32);
const renderer = new Renderer(color, depth);
const scene = new SceneRoot();
scene.ambient = 0.2; // Some ambient light
// Camera setup
const cam = new Actor({ type: ACTOR_TYPE.CAMERA });
cam.camera = new Camera({ fovY: Math.PI / 3, aspect: W / H, hither: 0.1, yon: 100 });
Mat34.translation(cam.transform, 0, 1.5, 5);
Mat34.rotationX(cam.transform, -Math.PI / 10); // Look slightly down
scene.add(cam);
// Light setup
const light = new Actor({ type: ACTOR_TYPE.LIGHT });
light.light = new Light(LIGHT_TYPE.DIRECT);
light.light.color = 0xffffff;
light.light.intensity = 1.0;
Mat34.rotationY(light.transform, Math.PI / 4);
Mat34.rotationX(light.transform, -Math.PI / 3);
scene.add(light);
// Create a plane
const planeModel = makePlane(10);
const planeActor = new Actor({ type: ACTOR_TYPE.MODEL, model: planeModel, material: 0x404040 });
scene.add(planeActor);
// Create a rotating cube
const cubeModel = makeCube(1);
const cubeActor = new Actor({ type: ACTOR_TYPE.MODEL, model: cubeModel, material: 0x0077ff });
Mat34.translation(cubeActor.transform, 0, 0.5, 0);
scene.add(cubeActor);
// Animation loop (optional)
const tick = (t) => {
Mat34.rotationY(cubeActor.transform, t);
Mat34.translation(cubeActor.transform, 0, 0.5 + Math.sin(t * 2) * 0.2, 0);
};
return { renderer, scene, camera: cam, color, depth, tick };
}
Physics Actors
While WBrenderer is primarily a visual engine, its dynamic Actor properties make it easy to
integrate with a physics simulation. You can attach state like velocity and mass directly to actors and
process them in your update loop.
Physical State
Initialize physical properties on your actors. Use Vec3 for vector state to maintain
performance.
import { Vec3 } from 'wbrenderer';
const car = new Actor({ type: ACTOR_TYPE.MODEL, model: carModel });
car.velocity = Vec3.create(0, 0, 0);
car.mass = 1500;
car.isStatic = false;
Integration Loop
Perform physics calculations before rendering. Use walkTree to update all dynamic objects in
the scene.
function physicsStep(scene, dt) {
walkTree(scene, (actor) => {
if (actor.isStatic || !actor.velocity) return;
// 1. Apply Forces (e.g. Gravity)
actor.velocity[1] -= 9.8 * dt;
// 2. Integrate Position
const m = actor.transform;
m[9] += actor.velocity[0] * dt;
m[10] += actor.velocity[1] * dt;
m[11] += actor.velocity[2] * dt;
// 3. Simple Collision (Floor)
if (m[10] < 0) {
m[10] = 0;
actor.velocity[1] *= -0.5; // Bounce with energy loss
}
});
}
Collision Proxies
You can use invisible actors as collision shapes. This is common when loading .ACT files
that contain simplified geometry meant specifically for the physics engine.
walkTree(carRoot, (actor) => {
// Identify collision actors by name
if (actor.name.includes('BOUNDS')) {
actor.isCollisionProxy = true;
// Set type to NONE so it doesn't render, but stays in the tree
actor.type = ACTOR_TYPE.NONE;
}
});