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