@gridspace/raster-path 1.0.6 → 1.0.7
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 +151 -12
- package/build/index.html +6 -2
- package/build/raster-path.js +118 -9
- package/build/raster-worker.js +421 -7
- package/package.json +4 -2
- package/src/core/path-radial.js +0 -1
- package/src/core/path-tracing.js +492 -0
- package/src/core/raster-config.js +13 -4
- package/src/core/raster-path.js +118 -9
- package/src/core/raster-worker.js +41 -0
- package/src/core/workload-calibrate.js +57 -3
- package/src/shaders/tracing-toolpath.wgsl +95 -0
- package/src/test/tracing-test.cjs +307 -0
- package/src/web/app.js +151 -12
- package/src/web/index.html +6 -2
package/build/raster-worker.js
CHANGED
|
@@ -9,6 +9,8 @@ var cachedToolpathPipeline = null;
|
|
|
9
9
|
var cachedToolpathShaderModule = null;
|
|
10
10
|
var cachedRadialBatchPipeline = null;
|
|
11
11
|
var cachedRadialBatchShaderModule = null;
|
|
12
|
+
var cachedTracingPipeline = null;
|
|
13
|
+
var cachedTracingShaderModule = null;
|
|
12
14
|
var EMPTY_CELL = -1e10;
|
|
13
15
|
var log_pre = "[Worker]";
|
|
14
16
|
var diagnostic = false;
|
|
@@ -535,6 +537,102 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
|
535
537
|
}
|
|
536
538
|
}
|
|
537
539
|
`;
|
|
540
|
+
var tracingShaderCode = `// Tracing toolpath generation
|
|
541
|
+
// Follows input polylines and calculates Z-depth at each sampled point
|
|
542
|
+
// Sentinel value for empty terrain cells (must match rasterize shader)
|
|
543
|
+
const EMPTY_CELL: f32 = -1e10;
|
|
544
|
+
const MAX_F32: f32 = 3.402823466e+38;
|
|
545
|
+
|
|
546
|
+
struct SparseToolPoint {
|
|
547
|
+
x_offset: i32,
|
|
548
|
+
y_offset: i32,
|
|
549
|
+
z_value: f32,
|
|
550
|
+
padding: f32,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
struct Uniforms {
|
|
554
|
+
terrain_width: u32,
|
|
555
|
+
terrain_height: u32,
|
|
556
|
+
tool_count: u32,
|
|
557
|
+
point_count: u32, // Number of sampled points to process
|
|
558
|
+
path_index: u32, // Index of current path being processed
|
|
559
|
+
terrain_min_x: f32, // Terrain bounding box (world coordinates)
|
|
560
|
+
terrain_min_y: f32,
|
|
561
|
+
grid_step: f32, // Resolution of terrain rasterization
|
|
562
|
+
oob_z: f32, // Z value for out-of-bounds points (zFloor)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
@group(0) @binding(0) var<storage, read> terrain_map: array<f32>;
|
|
566
|
+
@group(0) @binding(1) var<storage, read> sparse_tool: array<SparseToolPoint>;
|
|
567
|
+
@group(0) @binding(2) var<storage, read> input_points: array<f32>; // XY pairs
|
|
568
|
+
@group(0) @binding(3) var<storage, read_write> output_depths: array<f32>; // Z values
|
|
569
|
+
@group(0) @binding(4) var<storage, read_write> max_z_buffer: array<atomic<i32>>; // Max Z per path (as bits)
|
|
570
|
+
@group(0) @binding(5) var<uniform> uniforms: Uniforms;
|
|
571
|
+
|
|
572
|
+
@compute @workgroup_size(64, 1, 1)
|
|
573
|
+
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
574
|
+
let point_idx = global_id.x;
|
|
575
|
+
|
|
576
|
+
if (point_idx >= uniforms.point_count) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Read input X,Y world coordinates
|
|
581
|
+
let world_x = input_points[point_idx * 2u + 0u];
|
|
582
|
+
let world_y = input_points[point_idx * 2u + 1u];
|
|
583
|
+
|
|
584
|
+
// Convert world coordinates to grid coordinates
|
|
585
|
+
let grid_x_f32 = (world_x - uniforms.terrain_min_x) / uniforms.grid_step;
|
|
586
|
+
let grid_y_f32 = (world_y - uniforms.terrain_min_y) / uniforms.grid_step;
|
|
587
|
+
let tool_center_x = i32(grid_x_f32);
|
|
588
|
+
let tool_center_y = i32(grid_y_f32);
|
|
589
|
+
|
|
590
|
+
// Check if tool center is outside terrain bounds
|
|
591
|
+
let center_oob = tool_center_x < 0 || tool_center_x >= i32(uniforms.terrain_width) ||
|
|
592
|
+
tool_center_y < 0 || tool_center_y >= i32(uniforms.terrain_height);
|
|
593
|
+
|
|
594
|
+
var max_collision_z = uniforms.oob_z;
|
|
595
|
+
var found_collision = false;
|
|
596
|
+
|
|
597
|
+
// Test each tool point for collision with terrain
|
|
598
|
+
for (var i = 0u; i < uniforms.tool_count; i++) {
|
|
599
|
+
let tool_point = sparse_tool[i];
|
|
600
|
+
let terrain_x = tool_center_x + tool_point.x_offset;
|
|
601
|
+
let terrain_y = tool_center_y + tool_point.y_offset;
|
|
602
|
+
|
|
603
|
+
// Bounds check: terrain sample must be within terrain grid
|
|
604
|
+
if (terrain_x < 0 || terrain_x >= i32(uniforms.terrain_width) ||
|
|
605
|
+
terrain_y < 0 || terrain_y >= i32(uniforms.terrain_height)) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let terrain_idx = u32(terrain_y) * uniforms.terrain_width + u32(terrain_x);
|
|
610
|
+
let terrain_z = terrain_map[terrain_idx];
|
|
611
|
+
|
|
612
|
+
// Check if terrain cell has geometry (not empty sentinel value)
|
|
613
|
+
if (terrain_z > EMPTY_CELL + 1.0) {
|
|
614
|
+
// Tool z_value is positive offset from tip (tip=0, shaft=+50)
|
|
615
|
+
// Add to terrain height to find where tool center needs to be
|
|
616
|
+
let collision_z = terrain_z + tool_point.z_value;
|
|
617
|
+
max_collision_z = max(max_collision_z, collision_z);
|
|
618
|
+
found_collision = true;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// If no collision found and center was in-bounds, use oob_z
|
|
623
|
+
var output_z = uniforms.oob_z;
|
|
624
|
+
if (found_collision) {
|
|
625
|
+
output_z = max_collision_z;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
output_depths[point_idx] = output_z;
|
|
629
|
+
|
|
630
|
+
// Update max Z for this path using atomic operation
|
|
631
|
+
// Convert float to int bits for atomic comparison
|
|
632
|
+
let z_bits = bitcast<i32>(output_z);
|
|
633
|
+
atomicMax(&max_z_buffer[uniforms.path_index], z_bits);
|
|
634
|
+
}
|
|
635
|
+
`;
|
|
538
636
|
async function initWebGPU() {
|
|
539
637
|
if (isInitialized)
|
|
540
638
|
return true;
|
|
@@ -549,10 +647,7 @@ async function initWebGPU() {
|
|
|
549
647
|
return false;
|
|
550
648
|
}
|
|
551
649
|
const adapterLimits = adapter.limits;
|
|
552
|
-
debug.log("Adapter limits:",
|
|
553
|
-
maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
|
|
554
|
-
maxBufferSize: adapterLimits.maxBufferSize
|
|
555
|
-
});
|
|
650
|
+
debug.log("Adapter limits:", adapterLimits);
|
|
556
651
|
device = await adapter.requestDevice({
|
|
557
652
|
requiredLimits: {
|
|
558
653
|
maxStorageBufferBindingSize: Math.min(
|
|
@@ -582,6 +677,11 @@ async function initWebGPU() {
|
|
|
582
677
|
layout: "auto",
|
|
583
678
|
compute: { module: cachedRadialBatchShaderModule, entryPoint: "main" }
|
|
584
679
|
});
|
|
680
|
+
cachedTracingShaderModule = device.createShaderModule({ code: tracingShaderCode });
|
|
681
|
+
cachedTracingPipeline = device.createComputePipeline({
|
|
682
|
+
layout: "auto",
|
|
683
|
+
compute: { module: cachedTracingShaderModule, entryPoint: "main" }
|
|
684
|
+
});
|
|
585
685
|
deviceCapabilities = {
|
|
586
686
|
maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
|
|
587
687
|
maxBufferSize: device.limits.maxBufferSize,
|
|
@@ -2037,7 +2137,7 @@ async function generateRadialToolpaths({
|
|
|
2037
2137
|
if (!strip.positions || strip.positions.length === 0)
|
|
2038
2138
|
continue;
|
|
2039
2139
|
if (diagnostic && (globalStripIdx === 0 || globalStripIdx === 360)) {
|
|
2040
|
-
debug.log(`
|
|
2140
|
+
debug.log(`YGWIPII2 | Strip ${globalStripIdx} (${strip.angle.toFixed(1)}\xB0) INPUT terrain first 5 Z values: ${strip.positions.slice(0, 5).map((v) => v.toFixed(3)).join(",")}`);
|
|
2041
2141
|
}
|
|
2042
2142
|
const stripToolpathResult = await runToolpathComputeWithBuffers(
|
|
2043
2143
|
strip.positions,
|
|
@@ -2050,7 +2150,7 @@ async function generateRadialToolpaths({
|
|
|
2050
2150
|
pipelineStartTime
|
|
2051
2151
|
);
|
|
2052
2152
|
if (diagnostic && (globalStripIdx === 0 || globalStripIdx === 360)) {
|
|
2053
|
-
debug.log(`
|
|
2153
|
+
debug.log(`YGWIPII2 | Strip ${globalStripIdx} (${strip.angle.toFixed(1)}\xB0) OUTPUT toolpath first 5 Z values: ${stripToolpathResult.pathData.slice(0, 5).map((v) => v.toFixed(3)).join(",")}`);
|
|
2054
2154
|
}
|
|
2055
2155
|
allStripToolpaths.push({
|
|
2056
2156
|
angle: strip.angle,
|
|
@@ -2069,7 +2169,6 @@ async function generateRadialToolpaths({
|
|
|
2069
2169
|
const batchTotalTime = performance.now() - batchStartTime;
|
|
2070
2170
|
Object.assign(batchInfo, {
|
|
2071
2171
|
"prep": batchInfo.prep || 0,
|
|
2072
|
-
"gpu": batchInfo.gpu || 0,
|
|
2073
2172
|
"stitch": batchInfo.stitch || 0,
|
|
2074
2173
|
"raster": batchInfo.raster || 0,
|
|
2075
2174
|
"paths": toolpathTime | 0,
|
|
@@ -2101,6 +2200,284 @@ async function generateRadialToolpaths({
|
|
|
2101
2200
|
};
|
|
2102
2201
|
}
|
|
2103
2202
|
|
|
2203
|
+
// src/core/path-tracing.js
|
|
2204
|
+
var cachedTracingBuffers = null;
|
|
2205
|
+
function createReusableTracingBuffers(terrainPositions, toolPositions) {
|
|
2206
|
+
if (!isInitialized) {
|
|
2207
|
+
throw new Error("WebGPU not initialized");
|
|
2208
|
+
}
|
|
2209
|
+
if (cachedTracingBuffers) {
|
|
2210
|
+
destroyReusableTracingBuffers();
|
|
2211
|
+
}
|
|
2212
|
+
const sparseToolData = createSparseToolFromPoints(toolPositions);
|
|
2213
|
+
debug.log(`Created reusable tracing buffers: terrain ${terrainPositions.length} floats, tool ${sparseToolData.count} points`);
|
|
2214
|
+
const terrainBuffer = device.createBuffer({
|
|
2215
|
+
size: terrainPositions.byteLength,
|
|
2216
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
2217
|
+
});
|
|
2218
|
+
device.queue.writeBuffer(terrainBuffer, 0, terrainPositions);
|
|
2219
|
+
const toolBufferData = new ArrayBuffer(sparseToolData.count * 16);
|
|
2220
|
+
const toolBufferI32 = new Int32Array(toolBufferData);
|
|
2221
|
+
const toolBufferF32 = new Float32Array(toolBufferData);
|
|
2222
|
+
for (let i = 0; i < sparseToolData.count; i++) {
|
|
2223
|
+
toolBufferI32[i * 4 + 0] = sparseToolData.xOffsets[i];
|
|
2224
|
+
toolBufferI32[i * 4 + 1] = sparseToolData.yOffsets[i];
|
|
2225
|
+
toolBufferF32[i * 4 + 2] = sparseToolData.zValues[i];
|
|
2226
|
+
toolBufferF32[i * 4 + 3] = 0;
|
|
2227
|
+
}
|
|
2228
|
+
const toolBuffer = device.createBuffer({
|
|
2229
|
+
size: toolBufferData.byteLength,
|
|
2230
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
2231
|
+
});
|
|
2232
|
+
device.queue.writeBuffer(toolBuffer, 0, toolBufferData);
|
|
2233
|
+
cachedTracingBuffers = {
|
|
2234
|
+
terrainBuffer,
|
|
2235
|
+
toolBuffer,
|
|
2236
|
+
sparseToolData
|
|
2237
|
+
};
|
|
2238
|
+
return cachedTracingBuffers;
|
|
2239
|
+
}
|
|
2240
|
+
function destroyReusableTracingBuffers() {
|
|
2241
|
+
if (cachedTracingBuffers) {
|
|
2242
|
+
cachedTracingBuffers.terrainBuffer.destroy();
|
|
2243
|
+
cachedTracingBuffers.toolBuffer.destroy();
|
|
2244
|
+
cachedTracingBuffers = null;
|
|
2245
|
+
debug.log("Destroyed reusable tracing buffers");
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function samplePath(pathXY, step) {
|
|
2249
|
+
if (pathXY.length < 2) {
|
|
2250
|
+
return new Float32Array(pathXY);
|
|
2251
|
+
}
|
|
2252
|
+
const numVertices = pathXY.length / 2;
|
|
2253
|
+
const sampledPoints = [];
|
|
2254
|
+
sampledPoints.push(pathXY[0], pathXY[1]);
|
|
2255
|
+
for (let i = 0; i < numVertices - 1; i++) {
|
|
2256
|
+
const x1 = pathXY[i * 2];
|
|
2257
|
+
const y1 = pathXY[i * 2 + 1];
|
|
2258
|
+
const x2 = pathXY[(i + 1) * 2];
|
|
2259
|
+
const y2 = pathXY[(i + 1) * 2 + 1];
|
|
2260
|
+
const dx = x2 - x1;
|
|
2261
|
+
const dy = y2 - y1;
|
|
2262
|
+
const segmentLength = Math.sqrt(dx * dx + dy * dy);
|
|
2263
|
+
if (segmentLength > step) {
|
|
2264
|
+
const numSubdivisions = Math.ceil(segmentLength / step);
|
|
2265
|
+
const subdivisionStep = 1 / numSubdivisions;
|
|
2266
|
+
for (let j = 1; j < numSubdivisions; j++) {
|
|
2267
|
+
const t = j * subdivisionStep;
|
|
2268
|
+
const x = x1 + t * dx;
|
|
2269
|
+
const y = y1 + t * dy;
|
|
2270
|
+
sampledPoints.push(x, y);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (i < numVertices - 1) {
|
|
2274
|
+
sampledPoints.push(x2, y2);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return new Float32Array(sampledPoints);
|
|
2278
|
+
}
|
|
2279
|
+
async function generateTracingToolpaths({
|
|
2280
|
+
paths,
|
|
2281
|
+
terrainPositions,
|
|
2282
|
+
terrainData,
|
|
2283
|
+
toolPositions,
|
|
2284
|
+
step,
|
|
2285
|
+
gridStep,
|
|
2286
|
+
terrainBounds,
|
|
2287
|
+
zFloor,
|
|
2288
|
+
onProgress
|
|
2289
|
+
}) {
|
|
2290
|
+
const startTime = performance.now();
|
|
2291
|
+
debug.log("Generating tracing toolpaths...");
|
|
2292
|
+
debug.log(`Input: ${paths.length} paths, step=${step}, gridStep=${gridStep}, zFloor=${zFloor}`);
|
|
2293
|
+
debug.log(`Terrain: ${terrainData.width}\xD7${terrainData.height}, bounds: min(${terrainBounds.min.x.toFixed(2)}, ${terrainBounds.min.y.toFixed(2)}) max(${terrainBounds.max.x.toFixed(2)}, ${terrainBounds.max.y.toFixed(2)})`);
|
|
2294
|
+
if (!isInitialized) {
|
|
2295
|
+
const success = await initWebGPU();
|
|
2296
|
+
if (!success) {
|
|
2297
|
+
throw new Error("WebGPU not available");
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
let terrainBuffer, toolBuffer, sparseToolData;
|
|
2301
|
+
let shouldCleanupBuffers = false;
|
|
2302
|
+
if (cachedTracingBuffers) {
|
|
2303
|
+
debug.log("Using cached tracing buffers");
|
|
2304
|
+
terrainBuffer = cachedTracingBuffers.terrainBuffer;
|
|
2305
|
+
toolBuffer = cachedTracingBuffers.toolBuffer;
|
|
2306
|
+
sparseToolData = cachedTracingBuffers.sparseToolData;
|
|
2307
|
+
} else {
|
|
2308
|
+
debug.log("Creating temporary tracing buffers");
|
|
2309
|
+
sparseToolData = createSparseToolFromPoints(toolPositions);
|
|
2310
|
+
debug.log(`Created sparse tool: ${sparseToolData.count} points`);
|
|
2311
|
+
terrainBuffer = device.createBuffer({
|
|
2312
|
+
size: terrainPositions.byteLength,
|
|
2313
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
2314
|
+
});
|
|
2315
|
+
device.queue.writeBuffer(terrainBuffer, 0, terrainPositions);
|
|
2316
|
+
const toolBufferData = new ArrayBuffer(sparseToolData.count * 16);
|
|
2317
|
+
const toolBufferI32 = new Int32Array(toolBufferData);
|
|
2318
|
+
const toolBufferF32 = new Float32Array(toolBufferData);
|
|
2319
|
+
for (let i = 0; i < sparseToolData.count; i++) {
|
|
2320
|
+
toolBufferI32[i * 4 + 0] = sparseToolData.xOffsets[i];
|
|
2321
|
+
toolBufferI32[i * 4 + 1] = sparseToolData.yOffsets[i];
|
|
2322
|
+
toolBufferF32[i * 4 + 2] = sparseToolData.zValues[i];
|
|
2323
|
+
toolBufferF32[i * 4 + 3] = 0;
|
|
2324
|
+
}
|
|
2325
|
+
toolBuffer = device.createBuffer({
|
|
2326
|
+
size: toolBufferData.byteLength,
|
|
2327
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
2328
|
+
});
|
|
2329
|
+
device.queue.writeBuffer(toolBuffer, 0, toolBufferData);
|
|
2330
|
+
await device.queue.onSubmittedWorkDone();
|
|
2331
|
+
shouldCleanupBuffers = true;
|
|
2332
|
+
}
|
|
2333
|
+
const SENTINEL_Z = -1e30;
|
|
2334
|
+
const sentinelBits = new Float32Array([SENTINEL_Z]);
|
|
2335
|
+
const sentinelI32 = new Int32Array(sentinelBits.buffer)[0];
|
|
2336
|
+
const maxZInitData = new Int32Array(paths.length).fill(sentinelI32);
|
|
2337
|
+
const maxZBuffer = device.createBuffer({
|
|
2338
|
+
size: maxZInitData.byteLength,
|
|
2339
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
|
|
2340
|
+
});
|
|
2341
|
+
device.queue.writeBuffer(maxZBuffer, 0, maxZInitData);
|
|
2342
|
+
const outputPaths = [];
|
|
2343
|
+
let totalSampledPoints = 0;
|
|
2344
|
+
for (let pathIdx = 0; pathIdx < paths.length; pathIdx++) {
|
|
2345
|
+
const pathStartTime = performance.now();
|
|
2346
|
+
const inputPath = paths[pathIdx];
|
|
2347
|
+
debug.log(`Processing path ${pathIdx + 1}/${paths.length}: ${inputPath.length / 2} input vertices`);
|
|
2348
|
+
const sampledPath = samplePath(inputPath, step);
|
|
2349
|
+
const numSampledPoints = sampledPath.length / 2;
|
|
2350
|
+
totalSampledPoints += numSampledPoints;
|
|
2351
|
+
debug.log(` Sampled to ${numSampledPoints} points`);
|
|
2352
|
+
const firstX = sampledPath[0];
|
|
2353
|
+
const firstY = sampledPath[1];
|
|
2354
|
+
const gridX = (firstX - terrainBounds.min.x) / gridStep;
|
|
2355
|
+
const gridY = (firstY - terrainBounds.min.y) / gridStep;
|
|
2356
|
+
debug.log(` First point: world(${firstX.toFixed(2)}, ${firstY.toFixed(2)}) -> grid(${gridX.toFixed(2)}, ${gridY.toFixed(2)})`);
|
|
2357
|
+
debug.log(` Terrain: ${terrainData.width}x${terrainData.height}, bounds: (${terrainBounds.min.x.toFixed(2)}, ${terrainBounds.min.y.toFixed(2)}) to (${terrainBounds.max.x.toFixed(2)}, ${terrainBounds.max.y.toFixed(2)})`);
|
|
2358
|
+
const inputBufferSize = sampledPath.byteLength;
|
|
2359
|
+
const outputBufferSize = numSampledPoints * 4;
|
|
2360
|
+
const estimatedMemory = inputBufferSize + outputBufferSize;
|
|
2361
|
+
const configuredLimit = config.maxGPUMemoryMB * 1024 * 1024;
|
|
2362
|
+
const deviceLimit = deviceCapabilities.maxStorageBufferBindingSize;
|
|
2363
|
+
const maxSafeSize = Math.min(configuredLimit, deviceLimit) * config.gpuMemorySafetyMargin;
|
|
2364
|
+
if (estimatedMemory > maxSafeSize) {
|
|
2365
|
+
terrainBuffer.destroy();
|
|
2366
|
+
toolBuffer.destroy();
|
|
2367
|
+
throw new Error(
|
|
2368
|
+
`Path ${pathIdx + 1} exceeds GPU memory limits: ${(estimatedMemory / 1024 / 1024).toFixed(1)}MB > ${(maxSafeSize / 1024 / 1024).toFixed(1)}MB safe limit. Consider reducing step parameter or splitting path.`
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
const inputBuffer = device.createBuffer({
|
|
2372
|
+
size: sampledPath.byteLength,
|
|
2373
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
2374
|
+
});
|
|
2375
|
+
device.queue.writeBuffer(inputBuffer, 0, sampledPath);
|
|
2376
|
+
const outputBuffer = device.createBuffer({
|
|
2377
|
+
size: outputBufferSize,
|
|
2378
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
2379
|
+
});
|
|
2380
|
+
const uniformData = new Uint32Array(12);
|
|
2381
|
+
uniformData[0] = terrainData.width;
|
|
2382
|
+
uniformData[1] = terrainData.height;
|
|
2383
|
+
uniformData[2] = sparseToolData.count;
|
|
2384
|
+
uniformData[3] = numSampledPoints;
|
|
2385
|
+
uniformData[4] = pathIdx;
|
|
2386
|
+
const uniformDataFloat = new Float32Array(uniformData.buffer);
|
|
2387
|
+
uniformDataFloat[5] = terrainBounds.min.x;
|
|
2388
|
+
uniformDataFloat[6] = terrainBounds.min.y;
|
|
2389
|
+
uniformDataFloat[7] = gridStep;
|
|
2390
|
+
uniformDataFloat[8] = zFloor;
|
|
2391
|
+
const uniformBuffer = device.createBuffer({
|
|
2392
|
+
size: uniformData.byteLength,
|
|
2393
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
2394
|
+
});
|
|
2395
|
+
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
2396
|
+
await device.queue.onSubmittedWorkDone();
|
|
2397
|
+
const bindGroup = device.createBindGroup({
|
|
2398
|
+
layout: cachedTracingPipeline.getBindGroupLayout(0),
|
|
2399
|
+
entries: [
|
|
2400
|
+
{ binding: 0, resource: { buffer: terrainBuffer } },
|
|
2401
|
+
{ binding: 1, resource: { buffer: toolBuffer } },
|
|
2402
|
+
{ binding: 2, resource: { buffer: inputBuffer } },
|
|
2403
|
+
{ binding: 3, resource: { buffer: outputBuffer } },
|
|
2404
|
+
{ binding: 4, resource: { buffer: maxZBuffer } },
|
|
2405
|
+
{ binding: 5, resource: { buffer: uniformBuffer } }
|
|
2406
|
+
]
|
|
2407
|
+
});
|
|
2408
|
+
const commandEncoder = device.createCommandEncoder();
|
|
2409
|
+
const passEncoder = commandEncoder.beginComputePass();
|
|
2410
|
+
passEncoder.setPipeline(cachedTracingPipeline);
|
|
2411
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2412
|
+
const workgroupsX = Math.ceil(numSampledPoints / 64);
|
|
2413
|
+
passEncoder.dispatchWorkgroups(workgroupsX);
|
|
2414
|
+
passEncoder.end();
|
|
2415
|
+
const stagingBuffer = device.createBuffer({
|
|
2416
|
+
size: outputBufferSize,
|
|
2417
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
2418
|
+
});
|
|
2419
|
+
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, outputBufferSize);
|
|
2420
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
2421
|
+
await device.queue.onSubmittedWorkDone();
|
|
2422
|
+
await stagingBuffer.mapAsync(GPUMapMode.READ);
|
|
2423
|
+
const outputDepths = new Float32Array(stagingBuffer.getMappedRange());
|
|
2424
|
+
const depthsCopy = new Float32Array(outputDepths);
|
|
2425
|
+
stagingBuffer.unmap();
|
|
2426
|
+
const outputXYZ = new Float32Array(numSampledPoints * 3);
|
|
2427
|
+
for (let i = 0; i < numSampledPoints; i++) {
|
|
2428
|
+
outputXYZ[i * 3 + 0] = sampledPath[i * 2 + 0];
|
|
2429
|
+
outputXYZ[i * 3 + 1] = sampledPath[i * 2 + 1];
|
|
2430
|
+
outputXYZ[i * 3 + 2] = depthsCopy[i];
|
|
2431
|
+
}
|
|
2432
|
+
outputPaths.push(outputXYZ);
|
|
2433
|
+
inputBuffer.destroy();
|
|
2434
|
+
outputBuffer.destroy();
|
|
2435
|
+
uniformBuffer.destroy();
|
|
2436
|
+
stagingBuffer.destroy();
|
|
2437
|
+
const pathTime = performance.now() - pathStartTime;
|
|
2438
|
+
debug.log(` Path ${pathIdx + 1} complete: ${numSampledPoints} points in ${pathTime.toFixed(1)}ms`);
|
|
2439
|
+
if (onProgress) {
|
|
2440
|
+
onProgress({
|
|
2441
|
+
type: "tracing-progress",
|
|
2442
|
+
data: {
|
|
2443
|
+
percent: Math.round((pathIdx + 1) / paths.length * 100),
|
|
2444
|
+
current: pathIdx + 1,
|
|
2445
|
+
total: paths.length,
|
|
2446
|
+
pathIndex: pathIdx
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
const maxZStagingBuffer = device.createBuffer({
|
|
2452
|
+
size: maxZInitData.byteLength,
|
|
2453
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
2454
|
+
});
|
|
2455
|
+
const maxZCommandEncoder = device.createCommandEncoder();
|
|
2456
|
+
maxZCommandEncoder.copyBufferToBuffer(maxZBuffer, 0, maxZStagingBuffer, 0, maxZInitData.byteLength);
|
|
2457
|
+
device.queue.submit([maxZCommandEncoder.finish()]);
|
|
2458
|
+
await device.queue.onSubmittedWorkDone();
|
|
2459
|
+
await maxZStagingBuffer.mapAsync(GPUMapMode.READ);
|
|
2460
|
+
const maxZBitsI32 = new Int32Array(maxZStagingBuffer.getMappedRange());
|
|
2461
|
+
const maxZBitsCopy = new Int32Array(maxZBitsI32);
|
|
2462
|
+
maxZStagingBuffer.unmap();
|
|
2463
|
+
const maxZValues = new Float32Array(maxZBitsCopy.buffer);
|
|
2464
|
+
maxZBuffer.destroy();
|
|
2465
|
+
maxZStagingBuffer.destroy();
|
|
2466
|
+
if (shouldCleanupBuffers) {
|
|
2467
|
+
terrainBuffer.destroy();
|
|
2468
|
+
toolBuffer.destroy();
|
|
2469
|
+
debug.log("Cleaned up temporary tracing buffers");
|
|
2470
|
+
}
|
|
2471
|
+
const endTime = performance.now();
|
|
2472
|
+
debug.log(`Tracing complete: ${paths.length} paths, ${totalSampledPoints} total points in ${(endTime - startTime).toFixed(1)}ms`);
|
|
2473
|
+
debug.log(`Max Z values: [${Array.from(maxZValues).map((z) => z.toFixed(2)).join(", ")}]`);
|
|
2474
|
+
return {
|
|
2475
|
+
paths: outputPaths,
|
|
2476
|
+
maxZ: Array.from(maxZValues),
|
|
2477
|
+
generationTime: endTime - startTime
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2104
2481
|
// src/core/workload-calibrate.js
|
|
2105
2482
|
var calibrateShaderCode = `// Workload Calibration Shader
|
|
2106
2483
|
// Tests GPU watchdog limits by doing configurable amount of work per thread
|
|
@@ -2426,6 +2803,43 @@ self.onmessage = async function(e) {
|
|
|
2426
2803
|
data: radialToolpathResult
|
|
2427
2804
|
}, toolpathTransferBuffers);
|
|
2428
2805
|
break;
|
|
2806
|
+
case "tracing-generate-toolpaths":
|
|
2807
|
+
const tracingResult = await generateTracingToolpaths({
|
|
2808
|
+
paths: data.paths,
|
|
2809
|
+
terrainPositions: data.terrainPositions,
|
|
2810
|
+
terrainData: data.terrainData,
|
|
2811
|
+
toolPositions: data.toolPositions,
|
|
2812
|
+
step: data.step,
|
|
2813
|
+
gridStep: data.gridStep,
|
|
2814
|
+
terrainBounds: data.terrainBounds,
|
|
2815
|
+
zFloor: data.zFloor,
|
|
2816
|
+
onProgress: (progressData) => {
|
|
2817
|
+
self.postMessage({
|
|
2818
|
+
type: "tracing-progress",
|
|
2819
|
+
data: progressData.data
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
});
|
|
2823
|
+
const tracingTransferBuffers = tracingResult.paths.map((p) => p.buffer);
|
|
2824
|
+
self.postMessage({
|
|
2825
|
+
type: "tracing-toolpaths-complete",
|
|
2826
|
+
data: tracingResult
|
|
2827
|
+
}, tracingTransferBuffers);
|
|
2828
|
+
break;
|
|
2829
|
+
case "create-tracing-buffers":
|
|
2830
|
+
createReusableTracingBuffers(data.terrainPositions, data.toolPositions);
|
|
2831
|
+
self.postMessage({
|
|
2832
|
+
type: "tracing-buffers-created",
|
|
2833
|
+
data: { success: true }
|
|
2834
|
+
});
|
|
2835
|
+
break;
|
|
2836
|
+
case "destroy-tracing-buffers":
|
|
2837
|
+
destroyReusableTracingBuffers();
|
|
2838
|
+
self.postMessage({
|
|
2839
|
+
type: "tracing-buffers-destroyed",
|
|
2840
|
+
data: { success: true }
|
|
2841
|
+
});
|
|
2842
|
+
break;
|
|
2429
2843
|
case "calibrate":
|
|
2430
2844
|
const calibrationResult = await calibrateGPU(device, data?.options || {});
|
|
2431
2845
|
self.postMessage({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridspace/raster-path",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Terrain and Tool Raster Path Finder using WebGPU",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,8 @@
|
|
|
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:tracing": "npm run build && npx electron src/test/tracing-test.cjs"
|
|
48
49
|
},
|
|
49
50
|
"keywords": [
|
|
50
51
|
"cnc",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"license": "MIT",
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"electron": "^28.0.0",
|
|
64
|
+
"esbuild": "^0.27.0",
|
|
63
65
|
"serve": "^14.2.1"
|
|
64
66
|
}
|
|
65
67
|
}
|
package/src/core/path-radial.js
CHANGED