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.max reductions
  • 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.linspace for uniform sampling
  • tf.tile for meshgrid construction
  • tf.stack to compose ray origins
  • tf.ray batch 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.rayCast batch call returning { hits, elementIds }
  • tf.sum to 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, .add for shading math
  • tf.where for hit/miss compositing
  • tf.stack to interleave BGR channels
  • tf.round and .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)]));