Examples | TS
Raycast Rendering
Batch ray casting with Lambertian shading, producing a BMP image entirely with NDArray operations.
Renders a mesh to a BMP image using orthographic ray casting. All shading math is computed with NDArray operations — no per-pixel loops.
Loading and Camera Setup
Load a mesh, compute its bounding box, and set up an orthographic camera looking along -Z.
Features Showcased
- Loading STL/OBJ files with
tf.readStl,tf.readObj - Bounding box via
tf.min,tf.maxreductions - Building spatial tree with
mesh.buildTree()
Example Code
import * as tf from "@polydera/trueform";
import { readFileSync } from "node:fs";
const mesh = tf.readStl(readFileSync("model.stl"));
mesh.buildTree();
// Compute bounding box
const ptsMin = tf.min(mesh.points, 0);
const ptsMax = tf.max(mesh.points, 0);
const center = ptsMin.add(ptsMax).mul(0.5);
const extent = ptsMax.sub(ptsMin);
const diagonal = tf.norm(extent);
Building the Ray Grid
Construct an orthographic grid of rays using NDArray meshgrid operations.
Features Showcased
tf.linspacefor uniform samplingtf.tilefor meshgrid constructiontf.stackto compose ray originstf.raybatch constructor
Example Code
import * as tf from "@polydera/trueform";
const n = 1024;
const halfSpan = 0.5; // half-width of view
// 1D grid
const u = tf.linspace(-halfSpan, halfSpan, n);
// Meshgrid via tile
const gx = tf.tile(u, [n]); // [n*n]
const uc = u.clone();
uc.shape = [n, 1];
const gy = tf.tile(uc, [1, n]).flatten(); // [n*n]
// Ray origins on XY plane, far in +Z
const origins = tf.stack([gx, gy, tf.full("float32", [n * n], 10)], 1);
// All rays point in -Z
const dir = tf.ndarray([0, 0, -1], [1, 3]);
const dirs = tf.tile(dir, [n * n, 1]);
const rays = tf.ray(origins, dirs);
Batch Ray Casting
Cast all rays in a single call and retrieve hit masks and face IDs.
Features Showcased
tf.rayCastbatch call returning{ hits, elementIds }tf.sumto count hits
Example Code
import * as tf from "@polydera/trueform";
const { hits, elementIds } = tf.rayCast(rays, mesh);
console.log(`Hits: ${tf.sum(hits)} / ${n * n}`);
Lambertian Shading with NDArray
Compute shading entirely with NDArray operations: index normals by hit IDs, dot with light directions, blend, and compose the final image.
Features Showcased
- Indexing face normals with
.take(elementIds, 0) - Batch dot products with
tf.dot(broadcasts[N,3] x [1,3] → [N]) .clip,.mul,.addfor shading mathtf.wherefor hit/miss compositingtf.stackto interleave BGR channelstf.roundand.as("int8")for byte conversion
Example Code
import * as tf from "@polydera/trueform";
// Face normals indexed by hit face IDs
const hitNormals = mesh.normals.take(elementIds, 0); // [N, 3]
// Two directional lights + ambient
const light0 = tf.ndarray([0.577, 0.577, 0.577], [1, 3]);
const light1 = tf.ndarray([-0.707, 0.707, 0], [1, 3]);
const d0 = tf.dot(hitNormals, light0).clip(0, 1).mul(0.7);
const d1 = tf.dot(hitNormals, light1).clip(0, 1).mul(0.3);
const shade = d0.add(d1).add(0.15).clip(0, 1);
// Compose BGR image — teal mesh on dark background
const bCh = tf.where(hits, shade.mul(190), tf.full("float32", [n * n], 52));
const gCh = tf.where(hits, shade.mul(213), tf.full("float32", [n * n], 43));
const rCh = tf.where(hits, shade.mul(0), tf.full("float32", [n * n], 27));
// Interleave to [N, 3], round, and extract as byte array
const bgr = tf.round(tf.stack([bCh, gCh, rCh], 1).clip(0, 255));
const int8 = bgr.as("int8").data;
const pixels = new Uint8Array(int8.buffer, int8.byteOffset, int8.byteLength);
All shading runs inside WASM —
tf.dot, tf.where, and tf.stack process millions of pixels without crossing the JS/WASM boundary per element.Writing the BMP
The pixel data is already a flat byte array. Write a 54-byte BMP header followed by the raw BGR pixels.
Example Code
import { writeFileSync } from "node:fs";
const headerSize = 54;
const pixelDataSize = n * n * 3;
const header = Buffer.alloc(headerSize);
// BMP file header
header.write("BM", 0);
header.writeUInt32LE(headerSize + pixelDataSize, 2);
header.writeUInt32LE(headerSize, 10);
// DIB header
header.writeUInt32LE(40, 14);
header.writeInt32LE(n, 18);
header.writeInt32LE(n, 22);
header.writeUInt16LE(1, 26);
header.writeUInt16LE(24, 28);
writeFileSync("output.bmp", Buffer.concat([header, Buffer.from(pixels)]));
