@d5techs/3dgs-lib 1.4.21 → 1.4.23

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/dist/3dgs-lib.cjs CHANGED
@@ -6197,171 +6197,337 @@ function inverseSHTransformYZSwap(sh, base, perChannel) {
6197
6197
  }
6198
6198
  }
6199
6199
  }
6200
- function kmeansppInit1D(values, k) {
6201
- const n = values.length;
6202
- const centers = new Float64Array(k);
6203
- centers[0] = values[Math.floor(Math.random() * n)];
6204
- const dist = new Float64Array(n);
6205
- dist.fill(Infinity);
6206
- for (let c = 1; c < k; c++) {
6207
- let totalDist = 0;
6208
- for (let i = 0; i < n; i++) {
6209
- const d = (values[i] - centers[c - 1]) ** 2;
6210
- if (d < dist[i]) dist[i] = d;
6211
- totalDist += dist[i];
6212
- }
6213
- let target = Math.random() * totalDist;
6214
- let chosen = 0;
6215
- for (let i = 0; i < n; i++) {
6216
- target -= dist[i];
6217
- if (target <= 0) {
6218
- chosen = i;
6219
- break;
6220
- }
6221
- }
6222
- centers[c] = values[chosen];
6223
- }
6224
- return centers;
6225
- }
6226
- function buildCodebook(values, size = 256, iterations = 10) {
6227
- if (values.length === 0) return new Array(size).fill(0);
6228
- if (values.length <= size) {
6229
- const cb = Array.from(new Set(Array.from(values))).sort((a, b) => a - b);
6230
- while (cb.length < size) cb.push(cb[cb.length - 1]);
6231
- return cb.slice(0, size);
6232
- }
6233
- const codebook = kmeansppInit1D(values, size);
6234
- const sums = new Float64Array(size);
6235
- const counts = new Uint32Array(size);
6236
- for (let iter = 0; iter < iterations; iter++) {
6237
- sums.fill(0);
6238
- counts.fill(0);
6239
- for (let i = 0; i < values.length; i++) {
6240
- let bestIdx = 0;
6241
- let bestDist = Math.abs(values[i] - codebook[0]);
6242
- for (let j = 1; j < size; j++) {
6243
- const d = Math.abs(values[i] - codebook[j]);
6244
- if (d < bestDist) {
6245
- bestDist = d;
6246
- bestIdx = j;
6247
- }
6248
- }
6249
- sums[bestIdx] += values[i];
6250
- counts[bestIdx]++;
6251
- }
6252
- let changed = false;
6253
- for (let j = 0; j < size; j++) {
6254
- if (counts[j] > 0) {
6255
- const v = sums[j] / counts[j];
6256
- if (v !== codebook[j]) {
6257
- codebook[j] = v;
6258
- changed = true;
6200
+ function buildCodebook(values, k = 256) {
6201
+ const N = values.length;
6202
+ if (N === 0) return new Array(k).fill(0);
6203
+ const sorted = new Float32Array(values);
6204
+ sorted.sort();
6205
+ const vMin = sorted[0];
6206
+ const vMax = sorted[N - 1];
6207
+ if (vMax - vMin < 1e-20) return new Array(k).fill(vMin);
6208
+ const H = Math.min(1024, N);
6209
+ const vRange = vMax - vMin;
6210
+ const iqr = sorted[Math.floor(N * 0.75)] - sorted[Math.floor(N * 0.25)];
6211
+ const beta = Math.max(0.5, Math.min(0.999, 1 - iqr / vRange));
6212
+ const counts = new Float64Array(H);
6213
+ const sums = new Float64Array(H);
6214
+ for (let i = 0; i < N; i++) {
6215
+ const uniformPos = (sorted[i] - vMin) / vRange;
6216
+ const quantilePos = i / N;
6217
+ const bin = Math.min(H - 1, Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos)));
6218
+ counts[bin]++;
6219
+ sums[bin] += sorted[i];
6220
+ }
6221
+ const centers = new Float64Array(H);
6222
+ for (let i = 0; i < H; i++) {
6223
+ centers[i] = counts[i] > 0 ? sums[i] / counts[i] : vMin + (i + 0.5) / H * vRange;
6224
+ }
6225
+ const alpha = 0.5;
6226
+ const weights = new Float64Array(H);
6227
+ for (let i = 0; i < H; i++) {
6228
+ weights[i] = counts[i] > 0 ? Math.pow(counts[i], alpha) : 0;
6229
+ }
6230
+ const prefW = new Float64Array(H + 1);
6231
+ const prefWX = new Float64Array(H + 1);
6232
+ const prefWXX = new Float64Array(H + 1);
6233
+ for (let i = 0; i < H; i++) {
6234
+ prefW[i + 1] = prefW[i] + weights[i];
6235
+ prefWX[i + 1] = prefWX[i] + weights[i] * centers[i];
6236
+ prefWXX[i + 1] = prefWXX[i] + weights[i] * centers[i] * centers[i];
6237
+ }
6238
+ const rangeCost = (a, b) => {
6239
+ const w = prefW[b + 1] - prefW[a];
6240
+ if (w <= 0) return 0;
6241
+ const wx = prefWX[b + 1] - prefWX[a];
6242
+ const wxx = prefWXX[b + 1] - prefWXX[a];
6243
+ return wxx - wx * wx / w;
6244
+ };
6245
+ const rangeMean = (a, b) => {
6246
+ const w = prefW[b + 1] - prefW[a];
6247
+ if (w <= 0) return (centers[a] + centers[b]) * 0.5;
6248
+ return (prefWX[b + 1] - prefWX[a]) / w;
6249
+ };
6250
+ let nonEmpty = 0;
6251
+ for (let i = 0; i < H; i++) if (counts[i] > 0) nonEmpty++;
6252
+ const effectiveK = Math.min(k, nonEmpty);
6253
+ const INF = 1e30;
6254
+ let dpPrev = new Float64Array(H).fill(INF);
6255
+ let dpCurr = new Float64Array(H).fill(INF);
6256
+ const splitTable = new Array(effectiveK + 1);
6257
+ const split1 = new Int32Array(H);
6258
+ for (let j = 0; j < H; j++) {
6259
+ dpPrev[j] = rangeCost(0, j);
6260
+ split1[j] = -1;
6261
+ }
6262
+ splitTable[1] = split1;
6263
+ for (let m = 2; m <= effectiveK; m++) {
6264
+ dpCurr.fill(INF);
6265
+ const splitM = new Int32Array(H);
6266
+ for (let j = m - 1; j < H; j++) {
6267
+ let bestCost = INF;
6268
+ let bestS = m - 2;
6269
+ for (let s = m - 2; s < j; s++) {
6270
+ const cost = dpPrev[s] + rangeCost(s + 1, j);
6271
+ if (cost < bestCost) {
6272
+ bestCost = cost;
6273
+ bestS = s;
6259
6274
  }
6260
6275
  }
6261
- }
6262
- if (!changed) break;
6263
- }
6264
- const result = Array.from(codebook);
6265
- result.sort((a, b) => a - b);
6276
+ dpCurr[j] = bestCost;
6277
+ splitM[j] = bestS;
6278
+ }
6279
+ splitTable[m] = splitM;
6280
+ const tmp = dpPrev;
6281
+ dpPrev = dpCurr;
6282
+ dpCurr = tmp;
6283
+ }
6284
+ const centroidValues = new Float32Array(effectiveK);
6285
+ let jj = H - 1;
6286
+ for (let m = effectiveK; m >= 1; m--) {
6287
+ const s = m > 1 ? splitTable[m][jj] : -1;
6288
+ centroidValues[m - 1] = rangeMean(s + 1, jj);
6289
+ jj = s;
6290
+ }
6291
+ centroidValues.sort();
6292
+ const result = new Array(k);
6293
+ for (let i = 0; i < effectiveK; i++) result[i] = centroidValues[i];
6294
+ for (let i = effectiveK; i < k; i++) result[i] = centroidValues[effectiveK - 1];
6266
6295
  return result;
6267
6296
  }
6268
6297
  function findNearest(sortedCB, value) {
6269
6298
  let lo = 0, hi = sortedCB.length - 1;
6270
6299
  while (lo < hi) {
6271
6300
  const mid = lo + hi >> 1;
6272
- if (sortedCB[mid] < value) lo = mid + 1;
6273
- else hi = mid;
6274
- }
6275
- if (lo > 0 && Math.abs(sortedCB[lo - 1] - value) <= Math.abs(sortedCB[lo] - value)) {
6276
- return lo - 1;
6301
+ if (value < (sortedCB[mid] + sortedCB[mid + 1]) * 0.5) hi = mid;
6302
+ else lo = mid + 1;
6277
6303
  }
6278
6304
  return lo;
6279
6305
  }
6280
- function kmeansVectors(vectors, dim, k, iterations) {
6281
- const n = vectors.length;
6282
- const labels = new Uint16Array(n);
6283
- if (n <= k) {
6284
- const centroids2 = [];
6285
- for (let i = 0; i < n; i++) {
6286
- centroids2.push(new Float32Array(vectors[i]));
6287
- labels[i] = i;
6306
+ function getGpuClusterShader(dim) {
6307
+ return (
6308
+ /* wgsl */
6309
+ `
6310
+ struct Uniforms {
6311
+ numPoints: u32,
6312
+ numCentroids: u32
6313
+ };
6314
+
6315
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
6316
+ @group(0) @binding(1) var<storage, read> points: array<f32>;
6317
+ @group(0) @binding(2) var<storage, read> centroids: array<f32>;
6318
+ @group(0) @binding(3) var<storage, read_write> results: array<u32>;
6319
+
6320
+ const DIM = ${dim}u;
6321
+ const CHUNK_SIZE = 128u;
6322
+ var<workgroup> sharedChunk: array<f32, ${dim * 128}>;
6323
+
6324
+ fn calcDistSqr(point: array<f32, ${dim}>, cIdx: u32) -> f32 {
6325
+ var result = 0.0;
6326
+ var ci = cIdx * DIM;
6327
+ for (var i = 0u; i < DIM; i++) {
6328
+ let v = point[i] - sharedChunk[ci + i];
6329
+ result += v * v;
6330
+ }
6331
+ return result;
6332
+ }
6333
+
6334
+ @compute @workgroup_size(64)
6335
+ fn main(
6336
+ @builtin(local_invocation_index) localId: u32,
6337
+ @builtin(global_invocation_id) globalId: vec3u,
6338
+ @builtin(num_workgroups) numWgs: vec3u
6339
+ ) {
6340
+ let pointIdx = globalId.x + globalId.y * numWgs.x * 64u;
6341
+
6342
+ var point: array<f32, ${dim}>;
6343
+ if (pointIdx < uniforms.numPoints) {
6344
+ for (var i = 0u; i < DIM; i++) {
6345
+ point[i] = points[pointIdx * DIM + i];
6288
6346
  }
6289
- while (centroids2.length < k) centroids2.push(new Float32Array(dim));
6290
- return { centroids: centroids2, labels };
6291
6347
  }
6292
- const centroids = [];
6293
- centroids.push(new Float32Array(vectors[Math.floor(Math.random() * n)]));
6294
- const dist = new Float64Array(n);
6295
- dist.fill(Infinity);
6296
- for (let c = 1; c < k; c++) {
6297
- const last = centroids[c - 1];
6298
- let totalDist = 0;
6299
- for (let i = 0; i < n; i++) {
6300
- let d2 = 0;
6301
- for (let d = 0; d < dim; d++) {
6302
- const diff = vectors[i][d] - last[d];
6303
- d2 += diff * diff;
6304
- }
6305
- if (d2 < dist[i]) dist[i] = d2;
6306
- totalDist += dist[i];
6348
+
6349
+ var mind = 1e20;
6350
+ var mini = 0u;
6351
+
6352
+ let numChunks = (uniforms.numCentroids + CHUNK_SIZE - 1u) / CHUNK_SIZE;
6353
+ for (var chunk = 0u; chunk < numChunks; chunk++) {
6354
+
6355
+ let rowsPerThread = CHUNK_SIZE / 64u;
6356
+ let dstRow = localId * rowsPerThread;
6357
+ let srcRow = min(uniforms.numCentroids, chunk * CHUNK_SIZE + dstRow);
6358
+ let numRows = min(uniforms.numCentroids, srcRow + rowsPerThread) - srcRow;
6359
+
6360
+ var dst = dstRow * DIM;
6361
+ var src = srcRow * DIM;
6362
+ for (var c = 0u; c < numRows * DIM; c++) {
6363
+ sharedChunk[dst + c] = centroids[src + c];
6307
6364
  }
6308
- let target = Math.random() * totalDist;
6309
- let chosen = 0;
6310
- for (let i = 0; i < n; i++) {
6311
- target -= dist[i];
6312
- if (target <= 0) {
6313
- chosen = i;
6314
- break;
6365
+
6366
+ workgroupBarrier();
6367
+
6368
+ if (pointIdx < uniforms.numPoints) {
6369
+ let thisChunkSize = min(CHUNK_SIZE, uniforms.numCentroids - chunk * CHUNK_SIZE);
6370
+ for (var c = 0u; c < thisChunkSize; c++) {
6371
+ let d = calcDistSqr(point, c);
6372
+ if (d < mind) {
6373
+ mind = d;
6374
+ mini = chunk * CHUNK_SIZE + c;
6375
+ }
6315
6376
  }
6316
6377
  }
6317
- centroids.push(new Float32Array(vectors[chosen]));
6378
+
6379
+ workgroupBarrier();
6318
6380
  }
6319
- for (let iter = 0; iter < iterations; iter++) {
6320
- for (let i = 0; i < n; i++) {
6321
- let bestK = 0;
6322
- let bestD = Infinity;
6323
- for (let c = 0; c < k; c++) {
6324
- let d2 = 0;
6325
- for (let d = 0; d < dim; d++) {
6326
- const diff = vectors[i][d] - centroids[c][d];
6327
- d2 += diff * diff;
6328
- }
6329
- if (d2 < bestD) {
6330
- bestD = d2;
6331
- bestK = c;
6332
- }
6333
- }
6334
- labels[i] = bestK;
6381
+
6382
+ if (pointIdx < uniforms.numPoints) {
6383
+ results[pointIdx] = mini;
6384
+ }
6385
+ }`
6386
+ );
6387
+ }
6388
+ let _cachedDevice = null;
6389
+ let _deviceFailed = false;
6390
+ async function getGpuDevice() {
6391
+ if (_cachedDevice) return _cachedDevice;
6392
+ if (_deviceFailed) return null;
6393
+ try {
6394
+ if (typeof navigator === "undefined" || !navigator.gpu) {
6395
+ _deviceFailed = true;
6396
+ return null;
6335
6397
  }
6336
- const sums = [];
6337
- for (let c = 0; c < k; c++) sums.push(new Float64Array(dim));
6338
- const counts = new Uint32Array(k);
6339
- for (let i = 0; i < n; i++) {
6340
- const l = labels[i];
6341
- counts[l]++;
6342
- for (let d = 0; d < dim; d++) sums[l][d] += vectors[i][d];
6398
+ const adapter = await navigator.gpu.requestAdapter();
6399
+ if (!adapter) {
6400
+ _deviceFailed = true;
6401
+ return null;
6343
6402
  }
6344
- let changed = false;
6345
- for (let c = 0; c < k; c++) {
6346
- if (counts[c] > 0) {
6347
- for (let d = 0; d < dim; d++) {
6348
- const v = sums[c][d] / counts[c];
6349
- if (v !== centroids[c][d]) {
6350
- centroids[c][d] = v;
6351
- changed = true;
6352
- }
6353
- }
6403
+ _cachedDevice = await adapter.requestDevice({
6404
+ requiredLimits: {
6405
+ maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,
6406
+ maxBufferSize: adapter.limits.maxBufferSize
6354
6407
  }
6408
+ });
6409
+ _cachedDevice.lost.then(() => {
6410
+ _cachedDevice = null;
6411
+ });
6412
+ return _cachedDevice;
6413
+ } catch {
6414
+ _deviceFailed = true;
6415
+ return null;
6416
+ }
6417
+ }
6418
+ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, numCentroids, dim) {
6419
+ const WORKGROUP_SIZE2 = 64;
6420
+ const BATCH_SIZE = 1024 * WORKGROUP_SIZE2;
6421
+ const numBatches = Math.ceil(numPoints / BATCH_SIZE);
6422
+ const shaderModule = device.createShaderModule({ code: getGpuClusterShader(dim) });
6423
+ const bindGroupLayout = device.createBindGroupLayout({
6424
+ entries: [
6425
+ { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
6426
+ { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6427
+ { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6428
+ { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } }
6429
+ ]
6430
+ });
6431
+ const pipeline = device.createComputePipeline({
6432
+ layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
6433
+ compute: { module: shaderModule, entryPoint: "main" }
6434
+ });
6435
+ const uniformBuf = device.createBuffer({ size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
6436
+ const centroidsBuf = device.createBuffer({ size: centroidsFlat.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
6437
+ device.queue.writeBuffer(centroidsBuf, 0, centroidsFlat.buffer);
6438
+ const pointsBufSize = BATCH_SIZE * dim * 4;
6439
+ const pointsBuf = device.createBuffer({ size: pointsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
6440
+ const resultsBufSize = BATCH_SIZE * 4;
6441
+ const resultsBuf = device.createBuffer({ size: resultsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
6442
+ const readbackBuf = device.createBuffer({ size: resultsBufSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
6443
+ const labels = new Uint32Array(numPoints);
6444
+ const bindGroup = device.createBindGroup({
6445
+ layout: bindGroupLayout,
6446
+ entries: [
6447
+ { binding: 0, resource: { buffer: uniformBuf } },
6448
+ { binding: 1, resource: { buffer: pointsBuf } },
6449
+ { binding: 2, resource: { buffer: centroidsBuf } },
6450
+ { binding: 3, resource: { buffer: resultsBuf } }
6451
+ ]
6452
+ });
6453
+ for (let batch = 0; batch < numBatches; batch++) {
6454
+ const offset = batch * BATCH_SIZE;
6455
+ const currentBatchSize = Math.min(numPoints - offset, BATCH_SIZE);
6456
+ const groups = Math.ceil(currentBatchSize / WORKGROUP_SIZE2);
6457
+ device.queue.writeBuffer(uniformBuf, 0, new Uint32Array([currentBatchSize, numCentroids]));
6458
+ device.queue.writeBuffer(pointsBuf, 0, pointsFlat.buffer, offset * dim * 4, currentBatchSize * dim * 4);
6459
+ const encoder = device.createCommandEncoder();
6460
+ const pass = encoder.beginComputePass();
6461
+ pass.setPipeline(pipeline);
6462
+ pass.setBindGroup(0, bindGroup);
6463
+ pass.dispatchWorkgroups(groups);
6464
+ pass.end();
6465
+ encoder.copyBufferToBuffer(resultsBuf, 0, readbackBuf, 0, currentBatchSize * 4);
6466
+ device.queue.submit([encoder.finish()]);
6467
+ await readbackBuf.mapAsync(GPUMapMode.READ);
6468
+ const mapped = new Uint32Array(readbackBuf.getMappedRange(0, currentBatchSize * 4));
6469
+ labels.set(mapped, offset);
6470
+ readbackBuf.unmap();
6471
+ }
6472
+ uniformBuf.destroy();
6473
+ centroidsBuf.destroy();
6474
+ pointsBuf.destroy();
6475
+ resultsBuf.destroy();
6476
+ readbackBuf.destroy();
6477
+ return labels;
6478
+ }
6479
+ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
6480
+ const labels = new Uint32Array(n);
6481
+ if (n <= k) {
6482
+ const centroids2 = new Float32Array(k * dim);
6483
+ for (let i = 0; i < n; i++) {
6484
+ centroids2.set(pointsFlat.subarray(i * dim, i * dim + dim), i * dim);
6485
+ labels[i] = i;
6355
6486
  }
6356
- if (!changed) break;
6487
+ return { centroids: centroids2, labels };
6357
6488
  }
6489
+ const centroids = new Float32Array(k * dim);
6490
+ const indices = pickRandomIndices(n, k);
6491
+ for (let i = 0; i < k; i++) {
6492
+ centroids.set(pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim), i * dim);
6493
+ }
6494
+ const device = await getGpuDevice();
6495
+ for (let iter = 0; iter < iterations; iter++) {
6496
+ onProgress == null ? void 0 : onProgress(iter, iterations);
6497
+ if (device) {
6498
+ const newLabels = await gpuAssignLabels(device, pointsFlat, centroids, n, k, dim);
6499
+ labels.set(newLabels);
6500
+ } else {
6501
+ assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
6502
+ }
6503
+ updateCentroids(pointsFlat, centroids, labels, n, k, dim);
6504
+ }
6505
+ onProgress == null ? void 0 : onProgress(iterations, iterations);
6506
+ if (device) {
6507
+ const finalLabels = await gpuAssignLabels(device, pointsFlat, centroids, n, k, dim);
6508
+ labels.set(finalLabels);
6509
+ } else {
6510
+ assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
6511
+ }
6512
+ return { centroids, labels };
6513
+ }
6514
+ function pickRandomIndices(n, m) {
6515
+ const chosen = /* @__PURE__ */ new Set();
6516
+ for (let j = n - m; j < n; j++) {
6517
+ const t = Math.floor(Math.random() * (j + 1));
6518
+ chosen.add(chosen.has(t) ? j : t);
6519
+ }
6520
+ return [...chosen];
6521
+ }
6522
+ function assignLabelsCpu(points, centroids, labels, n, k, dim) {
6358
6523
  for (let i = 0; i < n; i++) {
6359
- let bestK = 0;
6360
- let bestD = Infinity;
6524
+ const pOff = i * dim;
6525
+ let bestK = 0, bestD = Infinity;
6361
6526
  for (let c = 0; c < k; c++) {
6527
+ const cOff = c * dim;
6362
6528
  let d2 = 0;
6363
6529
  for (let d = 0; d < dim; d++) {
6364
- const diff = vectors[i][d] - centroids[c][d];
6530
+ const diff = points[pOff + d] - centroids[cOff + d];
6365
6531
  d2 += diff * diff;
6366
6532
  }
6367
6533
  if (d2 < bestD) {
@@ -6371,7 +6537,25 @@ function kmeansVectors(vectors, dim, k, iterations) {
6371
6537
  }
6372
6538
  labels[i] = bestK;
6373
6539
  }
6374
- return { centroids: centroids.map((c) => new Float32Array(c)), labels };
6540
+ }
6541
+ function updateCentroids(points, centroids, labels, n, k, dim) {
6542
+ const sums = new Float64Array(k * dim);
6543
+ const counts = new Uint32Array(k);
6544
+ for (let i = 0; i < n; i++) {
6545
+ const l = labels[i];
6546
+ counts[l]++;
6547
+ const pOff = i * dim, sOff = l * dim;
6548
+ for (let d = 0; d < dim; d++) sums[sOff + d] += points[pOff + d];
6549
+ }
6550
+ for (let c = 0; c < k; c++) {
6551
+ if (counts[c] > 0) {
6552
+ const off = c * dim;
6553
+ for (let d = 0; d < dim; d++) centroids[off + d] = sums[off + d] / counts[c];
6554
+ } else {
6555
+ const idx = Math.floor(Math.random() * n);
6556
+ centroids.set(points.subarray(idx * dim, idx * dim + dim), c * dim);
6557
+ }
6558
+ }
6375
6559
  }
6376
6560
  function mortonSort(positions, count) {
6377
6561
  let minx = Infinity, miny = Infinity, minz = Infinity;
@@ -6442,11 +6626,12 @@ async function encodeWebP(width, height, pixelData) {
6442
6626
  }
6443
6627
  return new Uint8Array(await blob.arrayBuffer());
6444
6628
  }
6445
- async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6629
+ async function serializeSOG(data, coordinateSystem = "blender", options = {}, onProgress) {
6446
6630
  const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6447
6631
  const { count, colors, opacities } = data;
6448
6632
  let { positions, scales, rotations } = data;
6449
6633
  let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6634
+ onProgress == null ? void 0 : onProgress("坐标系变换", 0);
6450
6635
  if (coordinateSystem === "blender") {
6451
6636
  positions = new Float32Array(positions);
6452
6637
  scales = new Float32Array(scales);
@@ -6469,6 +6654,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6469
6654
  }
6470
6655
  }
6471
6656
  }
6657
+ onProgress == null ? void 0 : onProgress("Morton 排序", 0.05);
6472
6658
  const sortOrder = mortonSort(positions, count);
6473
6659
  const sPos = new Float32Array(count * 3);
6474
6660
  const sSca = new Float32Array(count * 3);
@@ -6501,6 +6687,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6501
6687
  }
6502
6688
  const { width, height } = calcImageDimensions(count);
6503
6689
  const totalPixels = width * height;
6690
+ onProgress == null ? void 0 : onProgress("编码位置", 0.1);
6504
6691
  const logPos = new Float32Array(count * 3);
6505
6692
  for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6506
6693
  const mins = [Infinity, Infinity, Infinity];
@@ -6528,9 +6715,10 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6528
6715
  meansLData[off + 3] = 255;
6529
6716
  meansUData[off + 3] = 255;
6530
6717
  }
6718
+ onProgress == null ? void 0 : onProgress("编码缩放", 0.2);
6531
6719
  const logScales = new Float32Array(count * 3);
6532
6720
  for (let i = 0; i < count * 3; i++) logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
6533
- const scaleCodebook = buildCodebook(logScales, 256, iterations);
6721
+ const scaleCodebook = buildCodebook(logScales, 256);
6534
6722
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6535
6723
  scalesData.fill(255);
6536
6724
  for (let i = 0; i < count; i++) {
@@ -6540,6 +6728,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6540
6728
  scalesData[off + 2] = findNearest(scaleCodebook, logScales[i * 3 + 2]);
6541
6729
  scalesData[off + 3] = 255;
6542
6730
  }
6731
+ onProgress == null ? void 0 : onProgress("编码四元数", 0.3);
6543
6732
  const quatsData = new Uint8ClampedArray(totalPixels * 4);
6544
6733
  quatsData.fill(255);
6545
6734
  const SQRT2 = Math.SQRT2;
@@ -6592,13 +6781,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6592
6781
  quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
6593
6782
  quatsData[off + 3] = largest + 252;
6594
6783
  }
6784
+ onProgress == null ? void 0 : onProgress("编码颜色", 0.4);
6595
6785
  const dcValues = new Float32Array(count * 3);
6596
6786
  for (let i = 0; i < count; i++) {
6597
6787
  dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
6598
6788
  dcValues[i * 3 + 1] = (sCol[i * 3 + 1] - 0.5) / SH_C0;
6599
6789
  dcValues[i * 3 + 2] = (sCol[i * 3 + 2] - 0.5) / SH_C0;
6600
6790
  }
6601
- const dcCodebook = buildCodebook(dcValues, 256, iterations);
6791
+ const dcCodebook = buildCodebook(dcValues, 256);
6602
6792
  const sh0Data = new Uint8ClampedArray(totalPixels * 4);
6603
6793
  sh0Data.fill(255);
6604
6794
  for (let i = 0; i < count; i++) {
@@ -6620,33 +6810,43 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6620
6810
  encodeWebP(width, height, sh0Data)
6621
6811
  ];
6622
6812
  if (actualBands > 0 && sSH) {
6623
- const vectors = new Array(count);
6813
+ onProgress == null ? void 0 : onProgress("压缩球谐系数 (GPU)", 0.5);
6814
+ const pointsFlat = new Float32Array(count * shDim);
6624
6815
  for (let i = 0; i < count; i++) {
6625
- const v = new Float32Array(shDim);
6626
6816
  const b = i * 45;
6627
- for (let j = 0; j < shDim; j++) v[j] = sSH[b + j];
6628
- vectors[i] = v;
6817
+ for (let j = 0; j < shDim; j++) pointsFlat[i * shDim + j] = sSH[b + j];
6629
6818
  }
6630
6819
  const effectivePaletteSize = Math.min(paletteSize, count);
6631
- const { centroids, labels } = kmeansVectors(vectors, shDim, effectivePaletteSize, iterations);
6632
- const allCoeffValues = new Float32Array(centroids.length * shDim);
6633
- for (let c = 0; c < centroids.length; c++) {
6634
- for (let d = 0; d < shDim; d++) allCoeffValues[c * shDim + d] = centroids[c][d];
6820
+ const { centroids, labels } = await kmeansGpu(
6821
+ pointsFlat,
6822
+ count,
6823
+ shDim,
6824
+ effectivePaletteSize,
6825
+ iterations,
6826
+ (iter, total) => {
6827
+ const p = 0.5 + 0.35 * (iter / total);
6828
+ onProgress == null ? void 0 : onProgress(`球谐聚类 ${iter}/${total}`, p);
6829
+ }
6830
+ );
6831
+ onProgress == null ? void 0 : onProgress("量化球谐 codebook", 0.88);
6832
+ const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
6833
+ for (let c = 0; c < effectivePaletteSize; c++) {
6834
+ for (let d = 0; d < shDim; d++) allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
6635
6835
  }
6636
- const shCodebook = buildCodebook(allCoeffValues, 256, iterations);
6836
+ const shCodebook = buildCodebook(allCoeffValues, 256);
6637
6837
  const centW = 64 * numCoeffs;
6638
6838
  const centH = Math.ceil(effectivePaletteSize / 64);
6639
6839
  const centData = new Uint8ClampedArray(centW * centH * 4);
6640
6840
  centData.fill(255);
6641
6841
  for (let n = 0; n < effectivePaletteSize; n++) {
6642
- const entry = centroids[n];
6643
6842
  for (let c = 0; c < numCoeffs; c++) {
6644
6843
  const u = n % 64 * numCoeffs + c;
6645
6844
  const v = Math.floor(n / 64);
6646
6845
  const off = (v * centW + u) * 4;
6647
- centData[off] = findNearest(shCodebook, entry[c * 3]);
6648
- centData[off + 1] = findNearest(shCodebook, entry[c * 3 + 1]);
6649
- centData[off + 2] = findNearest(shCodebook, entry[c * 3 + 2]);
6846
+ const cOff = n * shDim;
6847
+ centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
6848
+ centData[off + 1] = findNearest(shCodebook, centroids[cOff + c * 3 + 1]);
6849
+ centData[off + 2] = findNearest(shCodebook, centroids[cOff + c * 3 + 2]);
6650
6850
  centData[off + 3] = 255;
6651
6851
  }
6652
6852
  }
@@ -6670,6 +6870,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6670
6870
  files: ["sh_centroids.webp", "sh_labels.webp"]
6671
6871
  };
6672
6872
  }
6873
+ onProgress == null ? void 0 : onProgress("编码 WebP 图像", 0.92);
6673
6874
  const webps = await Promise.all(webpPromises);
6674
6875
  const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
6675
6876
  const meta = {
@@ -6695,7 +6896,9 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6695
6896
  zipEntries["sh_centroids.webp"] = webps[5];
6696
6897
  zipEntries["sh_labels.webp"] = webps[6];
6697
6898
  }
6899
+ onProgress == null ? void 0 : onProgress("打包 ZIP", 0.98);
6698
6900
  const zipData = zipSync(zipEntries, { level: 0 });
6901
+ onProgress == null ? void 0 : onProgress("完成", 1);
6699
6902
  return zipData.buffer;
6700
6903
  }
6701
6904
  const WORKGROUP_SIZE$1 = 256;