Fast browser-based terrain + tool path generator using WebGPU compute shaders.
- Dual Mode Rasterization: Planar (traditional XY grid) and Radial (cylindrical unwrap) modes
- CNC Toolpath Generation: Generate toolpaths by simulating tool movement over terrain
- GPU Accelerated: 20-100× faster than CPU-based solutions
- Unified API: Clean three-method interface that works uniformly across both modes
- ESM Module: Importable package for browser applications
import { RasterPath } from '@gridspace/raster-path';
// Initialize for planar mode
const raster = new RasterPath({
mode: 'planar',
resolution: 0.1 // 0.1mm grid resolution
});
await raster.init();
// 1. Load tool (from STL triangles)
const toolTriangles = parseSTL(toolSTLBuffer);
const toolData = await raster.loadTool({
triangles: toolTriangles
});
// 2. Load terrain (rasterizes immediately in planar mode)
const terrainTriangles = parseSTL(terrainSTLBuffer);
const terrainData = await raster.loadTerrain({
triangles: terrainTriangles,
zFloor: -100
});
// 3. Generate toolpaths
const toolpathData = await raster.generateToolpaths({
xStep: 5, // Sample every 5th point in X
yStep: 5, // Sample every 5th point in Y
zFloor: -100
});
console.log(`Generated ${toolpathData.pathData.length} toolpath points`);
// Cleanup
raster.terminate();// Initialize for radial mode
const raster = new RasterPath({
mode: 'radial',
resolution: 0.1, // Radial resolution (mm)
rotationStep: 1.0 // 1 degree between rays
});
await raster.init();
// Load tool and terrain (same API!)
await raster.loadTool({ triangles: toolTriangles });
await raster.loadTerrain({
triangles: terrainTriangles,
zFloor: 0
});
// Generate toolpaths
const toolpathData = await raster.generateToolpaths({
xStep: 5,
yStep: 5,
zFloor: 0
});
// Output is array of strips (one per rotation angle)
console.log(`Generated ${toolpathData.numStrips} strips, ${toolpathData.totalPoints} points`);npm install
npm run devOpen http://localhost:3000 and drag STL files onto the interface.
- Tool Rasterization: Create XY grid at specified resolution and rasterize tool geometry (keeps min Z per grid cell)
- Terrain Rasterization: Rasterize terrain geometry on matching XY grid (keeps max Z per grid cell)
- Toolpath Generation:
- Scan tool over terrain in XY grid with configurable step sizes (xStep, yStep)
- At each position, calculate minimum Z-offset where tool doesn't collide with terrain
- Output scanline-based toolpath as array of Z-heights
- Tool Rasterization: Rasterize tool in planar mode (same as above)
- Terrain Preparation: Center terrain in YZ plane and store triangles
- Toolpath Generation:
- Cast rays from origin at specified rotation angles (e.g., every 1°)
- For each ray, rasterize terrain triangles along that angle
- Use X-bucketing optimization to partition triangles spatially
- Calculate tool-terrain collisions along each radial strip
- Output array of strips (one per angle), each containing Z-heights along X-axis
Example (84×84×28mm model, 6,120 triangles):
| Step Size | Points | WebGPU Time | CPU Time (WASM) |
|---|---|---|---|
| 0.5mm | 48K | 0.8s | 20-80s |
| 0.1mm | 1.2M | 2s | 280s |
Speedup: 20-100× faster with WebGPU
src/
index.js # Main RasterPath API (ESM export)
web/
webgpu-worker.js # WebGPU worker (GPU compute shaders)
app.js # Demo web application
index.html # Demo UI entry point
style.css # Demo styles
parse-stl.js # STL file parser utility
test/
planar-test.cjs # Planar mode regression test
planar-tiling-test.cjs # Planar high-resolution test
radial-test.cjs # Radial mode regression test
benchmark/
fixtures/ # Test STL files (terrain.stl, tool.stl)
build/ # Built files (generated by npm run build)
Constructor: new RasterPath(options)
Options:
mode(string):'planar'or'radial'resolution(number): Grid resolution in mm (e.g., 0.1)rotationStep(number, radial only): Degrees between rays (e.g., 1.0)
Initialize WebGPU worker. Must be called before other methods.
Returns: Promise<void>
Example:
const raster = new RasterPath({ mode: 'planar', resolution: 0.1 });
await raster.init();Load tool geometry for toolpath generation.
Parameters (one required):
triangles(Float32Array, optional): STL triangle data (9 floats per triangle: v0.xyz, v1.xyz, v2.xyz)sparseData(object, optional): Pre-computed raster data with{ bounds, positions, pointCount }
Returns: Promise<object> - Tool raster data with { bounds, positions, pointCount }
Example:
// From STL triangles
const toolData = await raster.loadTool({
triangles: toolTriangles
});
// From pre-computed sparse data (Kiri:Moto integration)
const toolData = await raster.loadTool({
sparseData: { bounds, positions, pointCount }
});Load terrain geometry. Behavior depends on mode:
- Planar mode: Rasterizes immediately and returns terrain data
- Radial mode: Stores triangles for later, returns
null
Parameters:
triangles(Float32Array): STL triangle datazFloor(number, optional): Z floor value for out-of-bounds areasboundsOverride(object, optional): Override bounding box{min: {x, y, z}, max: {x, y, z}}onProgress(function, optional): Progress callback(progress: number) => void
Returns:
- Planar mode:
Promise<object>- Terrain raster data with{ bounds, positions, pointCount } - Radial mode:
Promise<null>
Example:
// Planar mode - returns terrain data immediately
const terrainData = await raster.loadTerrain({
triangles: terrainTriangles,
zFloor: -100
});
// Radial mode - stores for later
await raster.loadTerrain({
triangles: terrainTriangles,
zFloor: 0
});Generate toolpaths from loaded tool and terrain. Must call loadTool() and loadTerrain() first.
Parameters:
xStep(number): Sample every Nth point in X directionyStep(number): Sample every Nth point in Y directionzFloor(number): Z floor value for out-of-bounds areasonProgress(function, optional): Progress callback(progress: number) => void
Returns:
-
Planar mode:
Promise<object>with:pathData(Float32Array): Z-heights in scanline orderwidth(number): Points per scanlineheight(number): Number of scanlines
-
Radial mode:
Promise<object>with:strips(Array): Array of strip objects, each containing:angle(number): Rotation angle in degreespathData(Float32Array): Z-heights along X-axis
numStrips(number): Total number of stripstotalPoints(number): Sum of all points across strips
Example:
// Works for both planar and radial modes!
const toolpathData = await raster.generateToolpaths({
xStep: 5,
yStep: 5,
zFloor: -100,
radiusOffset: 20 // radial mode only
});Terminate WebGPU worker and cleanup resources.
Example:
raster.terminate();- Modern browser with WebGPU support (Chrome 113+, Edge 113+)
- For testing: Electron (provides headless WebGPU environment)
# Install dependencies
npm install
# Build (copies web files to build/)
npm run build
# Run demo
npm run serve
# Test
npm testMIT