@gridspace/raster-path 1.0.6 → 1.0.8
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.
- package/build/app.js +187 -12
- package/build/index.html +9 -2
- package/build/raster-path.js +125 -11
- package/build/raster-worker.js +952 -7
- package/package.json +6 -3
- package/src/core/path-radial-v3.js +405 -0
- package/src/core/path-radial.js +0 -1
- package/src/core/path-tracing.js +492 -0
- package/src/core/raster-config.js +37 -4
- package/src/core/raster-path.js +125 -11
- package/src/core/raster-worker.js +51 -0
- package/src/core/workload-calibrate.js +57 -3
- package/src/shaders/radial-rasterize-batched.wgsl +164 -0
- package/src/shaders/radial-rotate-triangles.wgsl +70 -0
- package/src/shaders/tracing-toolpath.wgsl +95 -0
- package/src/test/radial-v3-benchmark.cjs +184 -0
- package/src/test/radial-v3-bucket-test.cjs +154 -0
- package/src/test/tracing-test.cjs +307 -0
- package/src/web/app.js +187 -12
- package/src/web/index.html +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridspace/raster-path",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Terrain and Tool Raster Path Finder using WebGPU",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"build:web": "mkdir -p build && cp src/web/*.html src/web/*.css src/web/*.js build/ && cp src/core/raster-path.js build/raster-path.js && cp src/etc/serve.json build/",
|
|
33
33
|
"clean": "rm -rf build/",
|
|
34
34
|
"dev": "npm run build && npm run serve",
|
|
35
|
-
"serve": "npx serve build --config serve.json --listen 9090",
|
|
35
|
+
"serve": "npm run build && npx serve build --config serve.json --listen 9090",
|
|
36
36
|
"publish:public": "npm publish --access public",
|
|
37
37
|
"test": "npm run test:planar && npm run test:radial",
|
|
38
38
|
"test:planar": "npm run build && npx electron src/test/planar-test.cjs",
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
"test:workload-calibration": "npm run build && npx electron src/test/workload-calibration.cjs",
|
|
45
45
|
"test:lathe-cylinder-2-debug": "npm run build && npx electron src/test/lathe-cylinder-2-debug.cjs",
|
|
46
46
|
"test:extreme-work": "npm run build && npx electron src/test/extreme-work-test.cjs",
|
|
47
|
-
"test:radial-thread-limit": "npm run build && npx electron src/test/radial-thread-limit-test.cjs"
|
|
47
|
+
"test:radial-thread-limit": "npm run build && npx electron src/test/radial-thread-limit-test.cjs",
|
|
48
|
+
"test:radial-v3": "npm run build && npx electron src/test/radial-v3-benchmark.cjs",
|
|
49
|
+
"test:tracing": "npm run build && npx electron src/test/tracing-test.cjs"
|
|
48
50
|
},
|
|
49
51
|
"keywords": [
|
|
50
52
|
"cnc",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"license": "MIT",
|
|
61
63
|
"devDependencies": {
|
|
62
64
|
"electron": "^28.0.0",
|
|
65
|
+
"esbuild": "^0.27.0",
|
|
63
66
|
"serve": "^14.2.1"
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
* Path Radial V3 - Bucket-Angle Pipeline with Y-Filtering
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* ALGORITHM CHANGES FROM V2:
|
|
7
|
+
* ──────────────────────────
|
|
8
|
+
* V2 (current): Process all angles simultaneously
|
|
9
|
+
* - Rotate rays on the fly
|
|
10
|
+
* - Test all triangles in bucket (no Y-filtering)
|
|
11
|
+
* - Large memory footprint: numAngles × gridWidth × gridHeight
|
|
12
|
+
*
|
|
13
|
+
* V3 (this file): Process bucket-by-angle pipeline
|
|
14
|
+
* For each bucket:
|
|
15
|
+
* For each angle:
|
|
16
|
+
* 1. Rotate triangles (parallel) → rotated tris + Y-bounds
|
|
17
|
+
* 2. Rasterize with Y-filter (parallel) → dense terrain strip
|
|
18
|
+
* 3. Toolpath generation (parallel) → sparse toolpath
|
|
19
|
+
*
|
|
20
|
+
* BENEFITS:
|
|
21
|
+
* ─────────
|
|
22
|
+
* - Lower memory: Only one angle's data in GPU at a time
|
|
23
|
+
* - Y-axis filtering: Skip triangles outside tool radius
|
|
24
|
+
* - Immediate toolpath generation: No need to store all strips
|
|
25
|
+
* - Better cache locality: Process bucket completely before moving on
|
|
26
|
+
*
|
|
27
|
+
* TODO: Memory Safety
|
|
28
|
+
* ───────────────────
|
|
29
|
+
* V3 currently does NOT have memory safety checks like V2 does. V2 batches angles
|
|
30
|
+
* if total memory (numAngles × gridWidth × gridHeight × 4) exceeds 1800MB.
|
|
31
|
+
*
|
|
32
|
+
* V3 processes one angle at a time (inherently lower memory), but doesn't check if:
|
|
33
|
+
* - Triangle input buffer (triangles.byteLength) exceeds GPU limits
|
|
34
|
+
* - Rotated triangles buffer (numTriangles × 11 × 4) exceeds GPU limits
|
|
35
|
+
* - Output raster buffer (fullGridWidth × gridHeight × 4) exceeds GPU limits
|
|
36
|
+
*
|
|
37
|
+
* This could cause crashes on extremely large models with millions of triangles.
|
|
38
|
+
* Consider adding checks and batching for triangle buffers if needed.
|
|
39
|
+
*
|
|
40
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
device, config, debug, diagnostic,
|
|
45
|
+
cachedRadialV3RotatePipeline,
|
|
46
|
+
cachedRadialV3BatchedRasterizePipeline
|
|
47
|
+
} from './raster-config.js';
|
|
48
|
+
import {
|
|
49
|
+
createReusableToolpathBuffers,
|
|
50
|
+
destroyReusableToolpathBuffers,
|
|
51
|
+
runToolpathComputeWithBuffers
|
|
52
|
+
} from './path-planar.js';
|
|
53
|
+
import { createSparseToolFromPoints } from './raster-tool.js';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Rotate all triangles in a bucket by a single angle
|
|
57
|
+
*/
|
|
58
|
+
async function rotateTriangles({
|
|
59
|
+
triangleBuffer, // GPU buffer with original triangles
|
|
60
|
+
numTriangles,
|
|
61
|
+
angle // Radians
|
|
62
|
+
}) {
|
|
63
|
+
const rotatePipeline = cachedRadialV3RotatePipeline;
|
|
64
|
+
if (!rotatePipeline) {
|
|
65
|
+
throw new Error('Radial V3 pipelines not initialized');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create output buffer for rotated triangles + bounds
|
|
69
|
+
// Layout: 11 floats per triangle (v0, v1, v2, y_min, y_max)
|
|
70
|
+
const outputSize = numTriangles * 11 * 4;
|
|
71
|
+
const rotatedBuffer = device.createBuffer({
|
|
72
|
+
size: outputSize,
|
|
73
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Create uniforms
|
|
77
|
+
const uniformBuffer = device.createBuffer({
|
|
78
|
+
size: 8, // f32 angle + u32 num_triangles
|
|
79
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
80
|
+
mappedAtCreation: true
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const uniformView = new ArrayBuffer(8);
|
|
84
|
+
const floatView = new Float32Array(uniformView);
|
|
85
|
+
const uintView = new Uint32Array(uniformView);
|
|
86
|
+
floatView[0] = angle;
|
|
87
|
+
uintView[1] = numTriangles;
|
|
88
|
+
|
|
89
|
+
new Uint8Array(uniformBuffer.getMappedRange()).set(new Uint8Array(uniformView));
|
|
90
|
+
uniformBuffer.unmap();
|
|
91
|
+
|
|
92
|
+
// Create bind group
|
|
93
|
+
const bindGroup = device.createBindGroup({
|
|
94
|
+
layout: rotatePipeline.getBindGroupLayout(0),
|
|
95
|
+
entries: [
|
|
96
|
+
{ binding: 0, resource: { buffer: triangleBuffer } },
|
|
97
|
+
{ binding: 1, resource: { buffer: rotatedBuffer } },
|
|
98
|
+
{ binding: 2, resource: { buffer: uniformBuffer } }
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Dispatch
|
|
103
|
+
const commandEncoder = device.createCommandEncoder();
|
|
104
|
+
const passEncoder = commandEncoder.beginComputePass();
|
|
105
|
+
passEncoder.setPipeline(rotatePipeline);
|
|
106
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
107
|
+
passEncoder.dispatchWorkgroups(Math.ceil(numTriangles / 64));
|
|
108
|
+
passEncoder.end();
|
|
109
|
+
|
|
110
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
111
|
+
|
|
112
|
+
// Cleanup
|
|
113
|
+
uniformBuffer.destroy();
|
|
114
|
+
|
|
115
|
+
return rotatedBuffer;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Rasterize ALL buckets in one dispatch (batched GPU processing)
|
|
120
|
+
*/
|
|
121
|
+
async function rasterizeAllBuckets({
|
|
122
|
+
rotatedTrianglesBuffer,
|
|
123
|
+
buckets,
|
|
124
|
+
triangleIndices,
|
|
125
|
+
resolution,
|
|
126
|
+
toolRadius,
|
|
127
|
+
fullGridWidth,
|
|
128
|
+
gridHeight,
|
|
129
|
+
globalMinX,
|
|
130
|
+
bucketMinY,
|
|
131
|
+
zFloor
|
|
132
|
+
}) {
|
|
133
|
+
const rasterizePipeline = cachedRadialV3BatchedRasterizePipeline;
|
|
134
|
+
if (!rasterizePipeline) {
|
|
135
|
+
throw new Error('Radial V3 batched pipeline not initialized');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create bucket info buffer (all buckets)
|
|
139
|
+
const bucketInfoSize = buckets.length * 16; // 4 fields × 4 bytes per bucket
|
|
140
|
+
const bucketInfoBuffer = device.createBuffer({
|
|
141
|
+
size: bucketInfoSize,
|
|
142
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
143
|
+
mappedAtCreation: true
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const bucketView = new ArrayBuffer(bucketInfoSize);
|
|
147
|
+
const bucketFloatView = new Float32Array(bucketView);
|
|
148
|
+
const bucketUintView = new Uint32Array(bucketView);
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
151
|
+
const bucket = buckets[i];
|
|
152
|
+
const offset = i * 4;
|
|
153
|
+
bucketFloatView[offset] = bucket.minX;
|
|
154
|
+
bucketFloatView[offset + 1] = bucket.maxX;
|
|
155
|
+
bucketUintView[offset + 2] = bucket.startIndex;
|
|
156
|
+
bucketUintView[offset + 3] = bucket.count;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
new Uint8Array(bucketInfoBuffer.getMappedRange()).set(new Uint8Array(bucketView));
|
|
160
|
+
bucketInfoBuffer.unmap();
|
|
161
|
+
|
|
162
|
+
// Create triangle indices buffer
|
|
163
|
+
const indicesBuffer = device.createBuffer({
|
|
164
|
+
size: triangleIndices.byteLength,
|
|
165
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
166
|
+
mappedAtCreation: true
|
|
167
|
+
});
|
|
168
|
+
new Uint32Array(indicesBuffer.getMappedRange()).set(triangleIndices);
|
|
169
|
+
indicesBuffer.unmap();
|
|
170
|
+
|
|
171
|
+
// Create output buffer for full terrain strip
|
|
172
|
+
const outputSize = fullGridWidth * gridHeight * 4;
|
|
173
|
+
const outputBuffer = device.createBuffer({
|
|
174
|
+
size: outputSize,
|
|
175
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Initialize with zFloor
|
|
179
|
+
const initData = new Float32Array(fullGridWidth * gridHeight);
|
|
180
|
+
initData.fill(zFloor);
|
|
181
|
+
device.queue.writeBuffer(outputBuffer, 0, initData);
|
|
182
|
+
|
|
183
|
+
// Create uniforms
|
|
184
|
+
const uniformBuffer = device.createBuffer({
|
|
185
|
+
size: 32, // 8 fields × 4 bytes
|
|
186
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
187
|
+
mappedAtCreation: true
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const uniformView = new ArrayBuffer(32);
|
|
191
|
+
const floatView = new Float32Array(uniformView);
|
|
192
|
+
const uintView = new Uint32Array(uniformView);
|
|
193
|
+
|
|
194
|
+
floatView[0] = resolution;
|
|
195
|
+
floatView[1] = toolRadius;
|
|
196
|
+
uintView[2] = fullGridWidth;
|
|
197
|
+
uintView[3] = gridHeight;
|
|
198
|
+
floatView[4] = globalMinX;
|
|
199
|
+
floatView[5] = bucketMinY;
|
|
200
|
+
floatView[6] = zFloor;
|
|
201
|
+
uintView[7] = buckets.length;
|
|
202
|
+
|
|
203
|
+
new Uint8Array(uniformBuffer.getMappedRange()).set(new Uint8Array(uniformView));
|
|
204
|
+
uniformBuffer.unmap();
|
|
205
|
+
|
|
206
|
+
// Create bind group
|
|
207
|
+
const bindGroup = device.createBindGroup({
|
|
208
|
+
layout: rasterizePipeline.getBindGroupLayout(0),
|
|
209
|
+
entries: [
|
|
210
|
+
{ binding: 0, resource: { buffer: rotatedTrianglesBuffer } },
|
|
211
|
+
{ binding: 1, resource: { buffer: outputBuffer } },
|
|
212
|
+
{ binding: 2, resource: { buffer: uniformBuffer } },
|
|
213
|
+
{ binding: 3, resource: { buffer: bucketInfoBuffer } },
|
|
214
|
+
{ binding: 4, resource: { buffer: indicesBuffer } }
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Dispatch - covers full grid width (all buckets)
|
|
219
|
+
const commandEncoder = device.createCommandEncoder();
|
|
220
|
+
const passEncoder = commandEncoder.beginComputePass();
|
|
221
|
+
passEncoder.setPipeline(rasterizePipeline);
|
|
222
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
223
|
+
|
|
224
|
+
const dispatchX = Math.ceil(fullGridWidth / 8);
|
|
225
|
+
const dispatchY = Math.ceil(gridHeight / 8);
|
|
226
|
+
passEncoder.dispatchWorkgroups(dispatchX, dispatchY);
|
|
227
|
+
passEncoder.end();
|
|
228
|
+
|
|
229
|
+
// Read back results
|
|
230
|
+
const stagingBuffer = device.createBuffer({
|
|
231
|
+
size: outputSize,
|
|
232
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, outputSize);
|
|
236
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
237
|
+
|
|
238
|
+
await device.queue.onSubmittedWorkDone();
|
|
239
|
+
await stagingBuffer.mapAsync(GPUMapMode.READ);
|
|
240
|
+
const terrainData = new Float32Array(stagingBuffer.getMappedRange().slice());
|
|
241
|
+
stagingBuffer.unmap();
|
|
242
|
+
|
|
243
|
+
// Cleanup
|
|
244
|
+
outputBuffer.destroy();
|
|
245
|
+
stagingBuffer.destroy();
|
|
246
|
+
uniformBuffer.destroy();
|
|
247
|
+
bucketInfoBuffer.destroy();
|
|
248
|
+
indicesBuffer.destroy();
|
|
249
|
+
|
|
250
|
+
return terrainData;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Generate radial toolpaths using bucket-angle pipeline
|
|
254
|
+
*/
|
|
255
|
+
export async function generateRadialToolpathsV3({
|
|
256
|
+
triangles,
|
|
257
|
+
bucketData,
|
|
258
|
+
toolData,
|
|
259
|
+
resolution,
|
|
260
|
+
angleStep,
|
|
261
|
+
numAngles,
|
|
262
|
+
maxRadius,
|
|
263
|
+
toolWidth,
|
|
264
|
+
zFloor,
|
|
265
|
+
bounds,
|
|
266
|
+
xStep,
|
|
267
|
+
yStep
|
|
268
|
+
}) {
|
|
269
|
+
debug.log('radial-v3-generate-toolpaths', { triangles: triangles.length / 9, numAngles, resolution });
|
|
270
|
+
|
|
271
|
+
const pipelineStartTime = performance.now();
|
|
272
|
+
const allStripToolpaths = [];
|
|
273
|
+
let totalToolpathPoints = 0;
|
|
274
|
+
|
|
275
|
+
// Prepare sparse tool once
|
|
276
|
+
const sparseToolData = createSparseToolFromPoints(toolData.positions);
|
|
277
|
+
debug.log(`Created sparse tool: ${sparseToolData.count} points (reusing for all strips)`);
|
|
278
|
+
|
|
279
|
+
const toolRadius = toolWidth / 2;
|
|
280
|
+
|
|
281
|
+
// Calculate full grid dimensions (all buckets)
|
|
282
|
+
const bucketMinX = bucketData.buckets[0].minX;
|
|
283
|
+
const bucketMaxX = bucketData.buckets[bucketData.numBuckets - 1].maxX;
|
|
284
|
+
const fullWidth = bucketMaxX - bucketMinX;
|
|
285
|
+
const fullGridWidth = Math.ceil(fullWidth / resolution);
|
|
286
|
+
const gridHeight = Math.ceil(toolWidth / resolution);
|
|
287
|
+
|
|
288
|
+
// OPTIMIZATION: Upload all triangles to GPU ONCE (reused across all angles)
|
|
289
|
+
debug.log(`Uploading ${triangles.length / 9} triangles to GPU (reused across all angles)...`);
|
|
290
|
+
const allTrianglesBuffer = device.createBuffer({
|
|
291
|
+
size: triangles.byteLength,
|
|
292
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
293
|
+
mappedAtCreation: true
|
|
294
|
+
});
|
|
295
|
+
new Float32Array(allTrianglesBuffer.getMappedRange()).set(triangles);
|
|
296
|
+
allTrianglesBuffer.unmap();
|
|
297
|
+
|
|
298
|
+
// Process angle-by-angle (outer loop)
|
|
299
|
+
for (let angleIdx = 0; angleIdx < numAngles; angleIdx++) {
|
|
300
|
+
const angle = -(angleIdx * angleStep * (Math.PI / 180)); // Convert to radians (negative: rotating terrain vs tool)
|
|
301
|
+
const angleDegrees = angleIdx * angleStep;
|
|
302
|
+
|
|
303
|
+
if (diagnostic) {
|
|
304
|
+
debug.log(`Angle ${angleIdx + 1}/${numAngles}: ${angleDegrees.toFixed(1)}°`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Report progress
|
|
308
|
+
if (angleIdx % 10 === 0 || angleIdx === numAngles - 1) {
|
|
309
|
+
const stripProgress = ((angleIdx + 1) / numAngles) * 98;
|
|
310
|
+
self.postMessage({
|
|
311
|
+
type: 'toolpath-progress',
|
|
312
|
+
data: {
|
|
313
|
+
percent: Math.round(stripProgress),
|
|
314
|
+
current: angleIdx + 1,
|
|
315
|
+
total: numAngles,
|
|
316
|
+
layer: angleIdx + 1
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// OPTIMIZATION: Rotate ALL triangles once per angle (batch rotation)
|
|
322
|
+
const numTotalTriangles = triangles.length / 9;
|
|
323
|
+
const allRotatedTrianglesBuffer = await rotateTriangles({
|
|
324
|
+
triangleBuffer: allTrianglesBuffer,
|
|
325
|
+
numTriangles: numTotalTriangles,
|
|
326
|
+
angle
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// OPTIMIZATION: Rasterize ALL buckets in ONE dispatch (no CPU loop!)
|
|
330
|
+
const fullTerrainStrip = await rasterizeAllBuckets({
|
|
331
|
+
rotatedTrianglesBuffer: allRotatedTrianglesBuffer,
|
|
332
|
+
buckets: bucketData.buckets,
|
|
333
|
+
triangleIndices: bucketData.triangleIndices,
|
|
334
|
+
resolution,
|
|
335
|
+
toolRadius,
|
|
336
|
+
fullGridWidth,
|
|
337
|
+
gridHeight,
|
|
338
|
+
globalMinX: bucketMinX,
|
|
339
|
+
bucketMinY: -toolWidth / 2,
|
|
340
|
+
zFloor
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Cleanup rotated buffer (created per angle)
|
|
344
|
+
allRotatedTrianglesBuffer.destroy();
|
|
345
|
+
|
|
346
|
+
// Step 3: Generate toolpath for this complete angle strip
|
|
347
|
+
const reusableToolpathBuffers = createReusableToolpathBuffers(
|
|
348
|
+
fullGridWidth,
|
|
349
|
+
gridHeight,
|
|
350
|
+
sparseToolData,
|
|
351
|
+
xStep,
|
|
352
|
+
gridHeight
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const stripToolpathResult = await runToolpathComputeWithBuffers(
|
|
356
|
+
fullTerrainStrip,
|
|
357
|
+
fullGridWidth,
|
|
358
|
+
gridHeight,
|
|
359
|
+
xStep,
|
|
360
|
+
gridHeight,
|
|
361
|
+
zFloor,
|
|
362
|
+
reusableToolpathBuffers,
|
|
363
|
+
pipelineStartTime
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
destroyReusableToolpathBuffers(reusableToolpathBuffers);
|
|
367
|
+
|
|
368
|
+
allStripToolpaths.push({
|
|
369
|
+
angle: angleDegrees,
|
|
370
|
+
pathData: stripToolpathResult.pathData,
|
|
371
|
+
numScanlines: stripToolpathResult.numScanlines,
|
|
372
|
+
pointsPerLine: stripToolpathResult.pointsPerLine,
|
|
373
|
+
terrainBounds: {
|
|
374
|
+
min: { x: bucketMinX, y: -toolWidth / 2, z: zFloor },
|
|
375
|
+
max: { x: bucketMaxX, y: toolWidth / 2, z: bounds.max.z }
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
totalToolpathPoints += stripToolpathResult.pathData.length;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Cleanup triangles buffer (reused across all angles)
|
|
383
|
+
allTrianglesBuffer.destroy();
|
|
384
|
+
debug.log(`Destroyed reusable triangle buffer`);
|
|
385
|
+
|
|
386
|
+
const pipelineTotalTime = performance.now() - pipelineStartTime;
|
|
387
|
+
debug.log(`Complete radial V3 toolpath: ${allStripToolpaths.length} strips, ${totalToolpathPoints} total points in ${pipelineTotalTime.toFixed(0)}ms`);
|
|
388
|
+
|
|
389
|
+
// Send final 100% progress
|
|
390
|
+
self.postMessage({
|
|
391
|
+
type: 'toolpath-progress',
|
|
392
|
+
data: {
|
|
393
|
+
percent: 100,
|
|
394
|
+
current: bucketData.numBuckets * numAngles,
|
|
395
|
+
total: bucketData.numBuckets * numAngles,
|
|
396
|
+
layer: numAngles
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
strips: allStripToolpaths,
|
|
402
|
+
totalPoints: totalToolpathPoints,
|
|
403
|
+
numStrips: allStripToolpaths.length
|
|
404
|
+
};
|
|
405
|
+
}
|
package/src/core/path-radial.js
CHANGED