@gridspace/raster-path 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * ═══════════════════════════════════════════════════════════════════════════
3
+ * Raster Config - Shared WebGPU State and Configuration
4
+ * ═══════════════════════════════════════════════════════════════════════════
5
+ *
6
+ * Central module providing shared state, configuration, and utilities for all
7
+ * worker modules. All WebGPU resources and pipelines are initialized here.
8
+ *
9
+ * EXPORTS:
10
+ * ────────
11
+ * State Variables:
12
+ * - config - Worker configuration (memory limits, tiling, etc.)
13
+ * - device - WebGPU device handle
14
+ * - deviceCapabilities - Device limits and capabilities
15
+ * - isInitialized - Initialization status flag
16
+ *
17
+ * Cached Pipelines:
18
+ * - cachedRasterizePipeline - Planar rasterization compute pipeline
19
+ * - cachedToolpathPipeline - Planar toolpath generation pipeline
20
+ * - cachedRadialBatchPipeline - Radial rasterization pipeline
21
+ * - cachedRasterizeShaderModule - Compiled planar rasterize shader
22
+ * - cachedToolpathShaderModule - Compiled planar toolpath shader
23
+ * - cachedRadialBatchShaderModule - Compiled radial shader
24
+ *
25
+ * Constants:
26
+ * - EMPTY_CELL - Sentinel value for empty raster cells (-1e10)
27
+ * - log_pre - Log prefix string "[Worker]"
28
+ * - diagnostic - Debug mode flag
29
+ *
30
+ * Utilities:
31
+ * - debug - Logging object (error, warn, log, ok)
32
+ * - round(v, d) - Round number to d decimal places
33
+ *
34
+ * Functions:
35
+ * - initWebGPU() - Initialize WebGPU device and compile pipelines
36
+ * - setConfig(obj) - Replace entire config object
37
+ * - updateConfig(obj) - Merge updates into existing config
38
+ *
39
+ * ARCHITECTURE:
40
+ * ─────────────
41
+ * This module acts as a singleton state container. All worker modules import
42
+ * from here to access GPU resources. The initWebGPU() function is called once
43
+ * during worker initialization and pre-compiles all compute pipelines to avoid
44
+ * runtime compilation overhead.
45
+ *
46
+ * Shader code placeholders ('SHADER:xxx') are replaced during build by the
47
+ * build-shaders.js script, which bundles modules with esbuild then injects
48
+ * WGSL shader source code.
49
+ *
50
+ * ═══════════════════════════════════════════════════════════════════════════
51
+ */
52
+
53
+ // WebGPU state
54
+ export let config = {};
55
+ export let device = null;
56
+ export let deviceCapabilities = null;
57
+ export let isInitialized = false;
58
+
59
+ // Cached pipelines (created during initialization)
60
+ export let cachedRasterizePipeline = null;
61
+ export let cachedRasterizeShaderModule = null;
62
+ export let cachedToolpathPipeline = null;
63
+ export let cachedToolpathShaderModule = null;
64
+ export let cachedRadialBatchPipeline = null;
65
+ export let cachedRadialBatchShaderModule = null;
66
+
67
+ // Constants
68
+ export const EMPTY_CELL = -1e10;
69
+ export const log_pre = '[Worker]';
70
+ export const diagnostic = false;
71
+
72
+ // Logging utilities
73
+ let lastlog;
74
+ export const debug = {
75
+ error: function() { console.error(log_pre, ...arguments) },
76
+ warn: function() { console.warn(log_pre, ...arguments) },
77
+ log: function() {
78
+ if (!config.quiet) {
79
+ let now = performance.now();
80
+ let since = ((now - (lastlog ?? now)) | 0).toString().padStart(4,' ');
81
+ console.log(log_pre, `[${since}]`, ...arguments);
82
+ lastlog = now;
83
+ }
84
+ },
85
+ ok: function() { console.log(log_pre, '✅', ...arguments) },
86
+ };
87
+
88
+ // Utility functions
89
+ export function round(v, d = 1) {
90
+ return parseFloat(v.toFixed(d));
91
+ }
92
+
93
+ // Shader code placeholders (replaced by build script)
94
+ const rasterizeShaderCode = 'SHADER:planar-rasterize';
95
+ const toolpathShaderCode = 'SHADER:planar-toolpath';
96
+ const radialRasterizeShaderCode = 'SHADER:radial-raster';
97
+
98
+ // Initialize WebGPU device in worker context
99
+ export async function initWebGPU() {
100
+ if (isInitialized) return true;
101
+
102
+ if (!navigator.gpu) {
103
+ debug.warn('WebGPU not supported');
104
+ return false;
105
+ }
106
+
107
+ try {
108
+ const adapter = await navigator.gpu.requestAdapter();
109
+ if (!adapter) {
110
+ debug.warn('WebGPU adapter not available');
111
+ return false;
112
+ }
113
+
114
+ // Request device with higher limits for large meshes
115
+ const adapterLimits = adapter.limits;
116
+ debug.log('Adapter limits:', {
117
+ maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
118
+ maxBufferSize: adapterLimits.maxBufferSize
119
+ });
120
+
121
+ device = await adapter.requestDevice({
122
+ requiredLimits: {
123
+ maxStorageBufferBindingSize: Math.min(
124
+ adapterLimits.maxStorageBufferBindingSize,
125
+ 1024 * 1024 * 1024 // Request up to 1GB
126
+ ),
127
+ maxBufferSize: Math.min(
128
+ adapterLimits.maxBufferSize,
129
+ 1024 * 1024 * 1024 // Request up to 1GB
130
+ )
131
+ }
132
+ });
133
+
134
+ // Pre-compile rasterize shader module (expensive operation)
135
+ cachedRasterizeShaderModule = device.createShaderModule({ code: rasterizeShaderCode });
136
+
137
+ // Pre-create rasterize pipeline (very expensive operation)
138
+ cachedRasterizePipeline = device.createComputePipeline({
139
+ layout: 'auto',
140
+ compute: { module: cachedRasterizeShaderModule, entryPoint: 'main' },
141
+ });
142
+
143
+ // Pre-compile toolpath shader module
144
+ cachedToolpathShaderModule = device.createShaderModule({ code: toolpathShaderCode });
145
+
146
+ // Pre-create toolpath pipeline
147
+ cachedToolpathPipeline = device.createComputePipeline({
148
+ layout: 'auto',
149
+ compute: { module: cachedToolpathShaderModule, entryPoint: 'main' },
150
+ });
151
+
152
+ // Pre-compile radial batch shader module
153
+ cachedRadialBatchShaderModule = device.createShaderModule({ code: radialRasterizeShaderCode });
154
+
155
+ // Pre-create radial batch pipeline
156
+ cachedRadialBatchPipeline = device.createComputePipeline({
157
+ layout: 'auto',
158
+ compute: { module: cachedRadialBatchShaderModule, entryPoint: 'main' },
159
+ });
160
+
161
+ // Store device capabilities
162
+ deviceCapabilities = {
163
+ maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
164
+ maxBufferSize: device.limits.maxBufferSize,
165
+ maxComputeWorkgroupSizeX: device.limits.maxComputeWorkgroupSizeX,
166
+ maxComputeWorkgroupSizeY: device.limits.maxComputeWorkgroupSizeY,
167
+ };
168
+
169
+ isInitialized = true;
170
+ debug.log('Initialized (pipelines cached)');
171
+ return true;
172
+ } catch (error) {
173
+ debug.error('Failed to initialize:', error);
174
+ return false;
175
+ }
176
+ }
177
+
178
+ // Mutators for config (called from worker main)
179
+ export function setConfig(newConfig) {
180
+ config = newConfig;
181
+ }
182
+
183
+ export function updateConfig(updates) {
184
+ Object.assign(config, updates);
185
+ }
@@ -48,13 +48,10 @@
48
48
  * @property {'planar'|'radial'} mode - Rasterization mode (default: 'planar')
49
49
  * @property {boolean} autoTiling - Automatically tile large datasets (default: true)
50
50
  * @property {number} gpuMemorySafetyMargin - Safety margin as percentage (default: 0.8 = 80%)
51
- * @property {number} maxConcurrentTiles - Max concurrent tiles for radial rasterization (default: 50)
52
51
  * @property {number} maxGPUMemoryMB - Maximum GPU memory per tile (default: 256MB)
53
- * @property {number} minTileSize - Minimum tile dimension (default: 50mm)
54
- * @property {number} radialRotationOffset - Radial mode: rotation offset in degrees (default: 0, use 90 to start at Z-axis)
55
52
  * @property {number} resolution - Grid step size in mm (required)
56
53
  * @property {number} rotationStep - Radial mode only: degrees between rays (e.g., 1.0 = 360 rays)
57
- * @property {number} trianglesPerTile - Target triangles per tile for radial rasterization (default: calculated)
54
+ * @property {number} batchDivisor - Testing parameter to artificially divide batch size (default: 1)
58
55
  * @property {boolean} debug - Enable debug logging (default: false)
59
56
  * @property {boolean} quiet - Suppress log output (default: false)
60
57
  */
@@ -103,26 +100,22 @@ export class RasterPath {
103
100
  this.deviceCapabilities = null;
104
101
 
105
102
  // Configure debug output
106
- let urlOpt = [];
107
103
  if (config.quiet) {
108
104
  debug.log = function() {};
109
- urlOpt.push('quiet');
110
- }
111
- if (config.debug) {
112
- urlOpt.push('debug');
113
105
  }
114
106
 
115
107
  // Configuration with defaults
116
108
  this.config = {
117
- workerName: (config.workerName ?? "webgpu-worker.js") + (urlOpt.length ? "?"+urlOpt.join('&') : ""),
109
+ workerName: config.workerName ?? "raster-worker.js",
118
110
  maxGPUMemoryMB: config.maxGPUMemoryMB ?? 256,
119
111
  gpuMemorySafetyMargin: config.gpuMemorySafetyMargin ?? 0.8,
120
112
  autoTiling: config.autoTiling ?? true,
121
- minTileSize: config.minTileSize ?? 50,
122
- maxConcurrentTiles: config.maxConcurrentTiles ?? 10,
123
- trianglesPerTile: config.trianglesPerTile, // undefined = auto-calculate
124
- radialRotationOffset: config.radialRotationOffset ?? 0, // degrees
113
+ batchDivisor: config.batchDivisor ?? 1, // For testing batching overhead
114
+ debug: config.debug,
115
+ quiet: config.quiet
125
116
  };
117
+
118
+ debug.log('config', this.config);
126
119
  }
127
120
 
128
121
  /**
@@ -137,14 +130,14 @@ export class RasterPath {
137
130
 
138
131
  return new Promise((resolve, reject) => {
139
132
  try {
140
- // Create worker from the webgpu-worker.js file
133
+ // Create worker from the raster-worker.js file
141
134
  const workerName = this.config.workerName;
142
135
  const isBuildVersion = import.meta.url.includes('/build/') || import.meta.url.includes('raster-path.js');
143
136
  const workerPath = workerName
144
137
  ? new URL(workerName, import.meta.url)
145
138
  : isBuildVersion
146
- ? new URL(`./webgpu-worker.js`, import.meta.url)
147
- : new URL(`./web/webgpu-worker.js`, import.meta.url);
139
+ ? new URL(`./raster-worker.js`, import.meta.url)
140
+ : new URL(`../core/raster-worker.js`, import.meta.url);
148
141
  this.worker = new Worker(workerPath, { type: 'module' });
149
142
 
150
143
  // Set up message handler
@@ -238,8 +231,8 @@ export class RasterPath {
238
231
  const originalBounds = boundsOverride || this.#calculateBounds(triangles);
239
232
 
240
233
  // Center model in YZ plane (required for radial rasterization)
241
- // Radial mode casts rays from origin, so terrain must be centered at (0,0) in YZ
242
- // to ensure rays intersect the geometry symmetrically around the rotation axis
234
+ // Radial mode casts rays from max_radius distance inward toward the X-axis,
235
+ // and centering ensures the geometry is symmetric around the rotation axis
243
236
  const centerY = (originalBounds.min.y + originalBounds.max.y) / 2;
244
237
  const centerZ = (originalBounds.min.z + originalBounds.max.z) / 2;
245
238
 
@@ -273,12 +266,10 @@ export class RasterPath {
273
266
  * @param {number} params.xStep - Sample every Nth point in X direction
274
267
  * @param {number} params.yStep - Sample every Nth point in Y direction
275
268
  * @param {number} params.zFloor - Z floor value for out-of-bounds areas
276
- * @param {number} params.radiusOffset - (Radial mode only) Distance from terrain surface to tool tip in mm.
277
- * Used to calculate radial collision offset. Default: 20mm
278
269
  * @param {function} params.onProgress - Optional progress callback (progress: number, info?: string) => void
279
270
  * @returns {Promise<object>} Planar: {pathData, width, height} | Radial: {strips[], numStrips, totalPoints}
280
271
  */
281
- async generateToolpaths({ xStep, yStep, zFloor, radiusOffset = 20, onProgress }) {
272
+ async generateToolpaths({ xStep, yStep, zFloor, onProgress }) {
282
273
  if (!this.isInitialized) {
283
274
  throw new Error('RasterPath not initialized. Call init() first.');
284
275
  }
@@ -287,6 +278,8 @@ export class RasterPath {
287
278
  throw new Error('Tool not loaded. Call loadTool() first.');
288
279
  }
289
280
 
281
+ debug.log('gen.paths', { xStep, yStep, zFloor });
282
+
290
283
  if (this.mode === 'planar') {
291
284
  if (!this.terrainData) {
292
285
  throw new Error('Terrain not loaded. Call loadTerrain() first.');
@@ -444,8 +437,7 @@ export class RasterPath {
444
437
  zFloor: zFloor,
445
438
  bounds,
446
439
  xStep,
447
- yStep,
448
- gridStep: this.resolution
440
+ yStep
449
441
  },
450
442
  'radial-toolpaths-complete',
451
443
  completionHandler