@d5techs/3dgs-lib 1.4.18 → 1.4.20

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
@@ -5975,7 +5975,7 @@ function unzipSync(data, opts) {
5975
5975
  return files;
5976
5976
  }
5977
5977
  const SH_C0$1 = 0.28209479177387814;
5978
- const COEFFS_PER_BAND = [3, 8, 15];
5978
+ const COEFFS_PER_BAND$1 = [3, 8, 15];
5979
5979
  function lerp(a, b, t) {
5980
5980
  return a + (b - a) * t;
5981
5981
  }
@@ -6005,7 +6005,7 @@ async function decodeWebP(bytes) {
6005
6005
  return { width: w, height: h, data: ctx.getImageData(0, 0, w, h).data };
6006
6006
  }
6007
6007
  function decodeSHPalette(centroidsImg, codebook, paletteSize, bands) {
6008
- const numCoeffs = COEFFS_PER_BAND[bands - 1];
6008
+ const numCoeffs = COEFFS_PER_BAND$1[bands - 1];
6009
6009
  const palette = new Array(paletteSize);
6010
6010
  for (let n = 0; n < paletteSize; n++) {
6011
6011
  const entry = new Float32Array(numCoeffs * 3);
@@ -6072,7 +6072,7 @@ async function deserializeSOG(data, onProgress) {
6072
6072
  let shPalette = null;
6073
6073
  let shBandCoeffs = 0;
6074
6074
  if (meta.shN && centroidsImg) {
6075
- shBandCoeffs = COEFFS_PER_BAND[meta.shN.bands - 1];
6075
+ shBandCoeffs = COEFFS_PER_BAND$1[meta.shN.bands - 1];
6076
6076
  shPalette = decodeSHPalette(
6077
6077
  centroidsImg,
6078
6078
  meta.shN.codebook,
@@ -6145,18 +6145,95 @@ async function deserializeSOG(data, onProgress) {
6145
6145
  return { count, positions, scales, rotations, colors, opacities, shCoeffs };
6146
6146
  }
6147
6147
  const SH_C0 = 0.28209479177387814;
6148
+ const COEFFS_PER_BAND = [3, 8, 15];
6148
6149
  function symLog(x) {
6149
6150
  return Math.sign(x) * Math.log(1 + Math.abs(x));
6150
6151
  }
6151
- function buildCodebook(values, size = 256, iterations = 8) {
6152
- if (values.length === 0) return new Array(size).fill(0);
6153
- const sorted = Float32Array.from(values).sort();
6154
- const codebook = new Float64Array(size);
6155
- for (let i = 0; i < size; i++) {
6156
- codebook[i] = sorted[Math.floor(i / (size - 1) * (sorted.length - 1))];
6152
+ function inverseSHTransformYZSwap(sh, base, perChannel) {
6153
+ const SQRT3_2 = Math.sqrt(3) / 2;
6154
+ {
6155
+ for (let ch = 0; ch < 3; ch++) {
6156
+ const a = sh[base + ch];
6157
+ const b = sh[base + 3 + ch];
6158
+ sh[base + ch] = -b;
6159
+ sh[base + 3 + ch] = a;
6160
+ }
6161
+ }
6162
+ {
6163
+ for (let ch = 0; ch < 3; ch++) {
6164
+ const g0 = sh[base + 9 + ch];
6165
+ const g1 = sh[base + 12 + ch];
6166
+ const g2 = sh[base + 15 + ch];
6167
+ const g3 = sh[base + 18 + ch];
6168
+ const g4 = sh[base + 21 + ch];
6169
+ sh[base + 9 + ch] = -g3;
6170
+ sh[base + 12 + ch] = -g1;
6171
+ sh[base + 15 + ch] = -0.5 * g2 - SQRT3_2 * g4;
6172
+ sh[base + 18 + ch] = g0;
6173
+ sh[base + 21 + ch] = -SQRT3_2 * g2 + 0.5 * g4;
6174
+ }
6157
6175
  }
6158
- const assignments = new Uint8Array(values.length);
6176
+ {
6177
+ const A = Math.sqrt(10) / 4;
6178
+ const B = Math.sqrt(6) / 4;
6179
+ const P = Math.sqrt(15) / 4;
6180
+ for (let ch = 0; ch < 3; ch++) {
6181
+ const g0 = sh[base + 24 + ch];
6182
+ const g1 = sh[base + 27 + ch];
6183
+ const g2 = sh[base + 30 + ch];
6184
+ const g3 = sh[base + 33 + ch];
6185
+ const g4 = sh[base + 36 + ch];
6186
+ const g5 = sh[base + 39 + ch];
6187
+ const g6 = sh[base + 42 + ch];
6188
+ sh[base + 24 + ch] = A * g3 - B * g5;
6189
+ sh[base + 27 + ch] = -g1;
6190
+ sh[base + 30 + ch] = B * g3 + A * g5;
6191
+ sh[base + 33 + ch] = -A * g0 - B * g2;
6192
+ sh[base + 36 + ch] = -0.25 * g4 - P * g6;
6193
+ sh[base + 39 + ch] = B * g0 - A * g2;
6194
+ sh[base + 42 + ch] = -P * g4 + 0.25 * g6;
6195
+ }
6196
+ }
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);
6159
6234
  for (let iter = 0; iter < iterations; iter++) {
6235
+ sums.fill(0);
6236
+ counts.fill(0);
6160
6237
  for (let i = 0; i < values.length; i++) {
6161
6238
  let bestIdx = 0;
6162
6239
  let bestDist = Math.abs(values[i] - codebook[0]);
@@ -6167,31 +6244,167 @@ function buildCodebook(values, size = 256, iterations = 8) {
6167
6244
  bestIdx = j;
6168
6245
  }
6169
6246
  }
6170
- assignments[i] = bestIdx;
6171
- }
6172
- const sums = new Float64Array(size);
6173
- const counts = new Uint32Array(size);
6174
- for (let i = 0; i < values.length; i++) {
6175
- sums[assignments[i]] += values[i];
6176
- counts[assignments[i]]++;
6247
+ sums[bestIdx] += values[i];
6248
+ counts[bestIdx]++;
6177
6249
  }
6250
+ let changed = false;
6178
6251
  for (let j = 0; j < size; j++) {
6179
- if (counts[j] > 0) codebook[j] = sums[j] / counts[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;
6257
+ }
6258
+ }
6180
6259
  }
6260
+ if (!changed) break;
6261
+ }
6262
+ const result = Array.from(codebook);
6263
+ result.sort((a, b) => a - b);
6264
+ return result;
6265
+ }
6266
+ function findNearest(sortedCB, value) {
6267
+ let lo = 0, hi = sortedCB.length - 1;
6268
+ while (lo < hi) {
6269
+ const mid = lo + hi >> 1;
6270
+ if (sortedCB[mid] < value) lo = mid + 1;
6271
+ else hi = mid;
6181
6272
  }
6182
- return Array.from(codebook);
6273
+ if (lo > 0 && Math.abs(sortedCB[lo - 1] - value) <= Math.abs(sortedCB[lo] - value)) {
6274
+ return lo - 1;
6275
+ }
6276
+ return lo;
6183
6277
  }
6184
- function findNearest(codebook, value) {
6185
- let bestIdx = 0;
6186
- let bestDist = Math.abs(value - codebook[0]);
6187
- for (let j = 1; j < codebook.length; j++) {
6188
- const d = Math.abs(value - codebook[j]);
6189
- if (d < bestDist) {
6190
- bestDist = d;
6191
- bestIdx = j;
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;
6286
+ }
6287
+ while (centroids2.length < k) centroids2.push(new Float32Array(dim));
6288
+ return { centroids: centroids2, labels };
6289
+ }
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];
6305
+ }
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;
6313
+ }
6314
+ }
6315
+ centroids.push(new Float32Array(vectors[chosen]));
6316
+ }
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;
6333
+ }
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];
6341
+ }
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
+ }
6352
+ }
6353
+ }
6354
+ if (!changed) break;
6355
+ }
6356
+ for (let i = 0; i < n; i++) {
6357
+ let bestK = 0;
6358
+ let bestD = Infinity;
6359
+ for (let c = 0; c < k; c++) {
6360
+ let d2 = 0;
6361
+ for (let d = 0; d < dim; d++) {
6362
+ const diff = vectors[i][d] - centroids[c][d];
6363
+ d2 += diff * diff;
6364
+ }
6365
+ if (d2 < bestD) {
6366
+ bestD = d2;
6367
+ bestK = c;
6368
+ }
6192
6369
  }
6370
+ labels[i] = bestK;
6193
6371
  }
6194
- return bestIdx;
6372
+ return { centroids: centroids.map((c) => new Float32Array(c)), labels };
6373
+ }
6374
+ function mortonSort(positions, count) {
6375
+ let minx = Infinity, miny = Infinity, minz = Infinity;
6376
+ let maxx = -Infinity, maxy = -Infinity, maxz = -Infinity;
6377
+ for (let i = 0; i < count; i++) {
6378
+ const x = positions[i * 3], y = positions[i * 3 + 1], z = positions[i * 3 + 2];
6379
+ if (x < minx) minx = x;
6380
+ if (x > maxx) maxx = x;
6381
+ if (y < miny) miny = y;
6382
+ if (y > maxy) maxy = y;
6383
+ if (z < minz) minz = z;
6384
+ if (z > maxz) maxz = z;
6385
+ }
6386
+ const xlen = maxx - minx || 1;
6387
+ const ylen = maxy - miny || 1;
6388
+ const zlen = maxz - minz || 1;
6389
+ const part1By2 = (x) => {
6390
+ x &= 1023;
6391
+ x = (x ^ x << 16) & 4278190335;
6392
+ x = (x ^ x << 8) & 50393103;
6393
+ x = (x ^ x << 4) & 51130563;
6394
+ x = (x ^ x << 2) & 153391689;
6395
+ return x;
6396
+ };
6397
+ const morton = new Uint32Array(count);
6398
+ const indices = new Uint32Array(count);
6399
+ for (let i = 0; i < count; i++) {
6400
+ const ix = Math.min(1023, Math.floor(1024 * (positions[i * 3] - minx) / xlen));
6401
+ const iy = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen));
6402
+ const iz = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen));
6403
+ morton[i] = (part1By2(iz) << 2) + (part1By2(iy) << 1) + part1By2(ix);
6404
+ indices[i] = i;
6405
+ }
6406
+ indices.sort((a, b) => morton[a] - morton[b]);
6407
+ return indices;
6195
6408
  }
6196
6409
  function calcImageDimensions(count) {
6197
6410
  const width = Math.ceil(Math.sqrt(count));
@@ -6227,9 +6440,11 @@ async function encodeWebP(width, height, pixelData) {
6227
6440
  }
6228
6441
  return new Uint8Array(await blob.arrayBuffer());
6229
6442
  }
6230
- async function serializeSOG(data, coordinateSystem = "blender") {
6443
+ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6444
+ const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6231
6445
  const { count, colors, opacities } = data;
6232
6446
  let { positions, scales, rotations } = data;
6447
+ let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6233
6448
  if (coordinateSystem === "blender") {
6234
6449
  positions = new Float32Array(positions);
6235
6450
  scales = new Float32Array(scales);
@@ -6246,18 +6461,51 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6246
6461
  rotations[i4 + 2] = -rz;
6247
6462
  rotations[i4 + 3] = ry;
6248
6463
  }
6464
+ if (shCoeffs) {
6465
+ for (let i = 0; i < count; i++) {
6466
+ inverseSHTransformYZSwap(shCoeffs, i * 45);
6467
+ }
6468
+ }
6469
+ }
6470
+ const sortOrder = mortonSort(positions, count);
6471
+ const sPos = new Float32Array(count * 3);
6472
+ const sSca = new Float32Array(count * 3);
6473
+ const sRot = new Float32Array(count * 4);
6474
+ const sCol = new Float32Array(count * 3);
6475
+ const sOpa = new Float32Array(count);
6476
+ const sSH = shCoeffs ? new Float32Array(count * 45) : void 0;
6477
+ for (let dst = 0; dst < count; dst++) {
6478
+ const src = sortOrder[dst];
6479
+ const s3 = src * 3, d3 = dst * 3;
6480
+ sPos[d3] = positions[s3];
6481
+ sPos[d3 + 1] = positions[s3 + 1];
6482
+ sPos[d3 + 2] = positions[s3 + 2];
6483
+ sSca[d3] = scales[s3];
6484
+ sSca[d3 + 1] = scales[s3 + 1];
6485
+ sSca[d3 + 2] = scales[s3 + 2];
6486
+ sCol[d3] = colors[s3];
6487
+ sCol[d3 + 1] = colors[s3 + 1];
6488
+ sCol[d3 + 2] = colors[s3 + 2];
6489
+ const s4 = src * 4, d4 = dst * 4;
6490
+ sRot[d4] = rotations[s4];
6491
+ sRot[d4 + 1] = rotations[s4 + 1];
6492
+ sRot[d4 + 2] = rotations[s4 + 2];
6493
+ sRot[d4 + 3] = rotations[s4 + 3];
6494
+ sOpa[dst] = opacities[src];
6495
+ if (sSH && shCoeffs) {
6496
+ const sb = src * 45, db = dst * 45;
6497
+ for (let j = 0; j < 45; j++) sSH[db + j] = shCoeffs[sb + j];
6498
+ }
6249
6499
  }
6250
6500
  const { width, height } = calcImageDimensions(count);
6251
6501
  const totalPixels = width * height;
6252
- const logPositions = new Float32Array(count * 3);
6253
- for (let i = 0; i < count * 3; i++) {
6254
- logPositions[i] = symLog(positions[i]);
6255
- }
6502
+ const logPos = new Float32Array(count * 3);
6503
+ for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6256
6504
  const mins = [Infinity, Infinity, Infinity];
6257
6505
  const maxs = [-Infinity, -Infinity, -Infinity];
6258
6506
  for (let i = 0; i < count; i++) {
6259
6507
  for (let c = 0; c < 3; c++) {
6260
- const v = logPositions[i * 3 + c];
6508
+ const v = logPos[i * 3 + c];
6261
6509
  if (v < mins[c]) mins[c] = v;
6262
6510
  if (v > maxs[c]) maxs[c] = v;
6263
6511
  }
@@ -6270,7 +6518,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6270
6518
  const off = i * 4;
6271
6519
  for (let c = 0; c < 3; c++) {
6272
6520
  const range = maxs[c] - mins[c];
6273
- const norm = range < 1e-10 ? 0 : (logPositions[i * 3 + c] - mins[c]) / range;
6521
+ const norm = range < 1e-10 ? 0 : (logPos[i * 3 + c] - mins[c]) / range;
6274
6522
  const q16 = Math.round(norm * 65535);
6275
6523
  meansLData[off + c] = q16 & 255;
6276
6524
  meansUData[off + c] = q16 >> 8 & 255;
@@ -6279,15 +6527,13 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6279
6527
  meansUData[off + 3] = 255;
6280
6528
  }
6281
6529
  const logScales = new Float32Array(count * 3);
6282
- for (let i = 0; i < count * 3; i++) {
6283
- logScales[i] = Math.log(Math.max(1e-8, scales[i]));
6284
- }
6285
- const scaleCodebook = buildCodebook(logScales);
6530
+ 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);
6286
6532
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6287
6533
  scalesData.fill(255);
6288
6534
  for (let i = 0; i < count; i++) {
6289
6535
  const off = i * 4;
6290
- scalesData[off + 0] = findNearest(scaleCodebook, logScales[i * 3 + 0]);
6536
+ scalesData[off] = findNearest(scaleCodebook, logScales[i * 3]);
6291
6537
  scalesData[off + 1] = findNearest(scaleCodebook, logScales[i * 3 + 1]);
6292
6538
  scalesData[off + 2] = findNearest(scaleCodebook, logScales[i * 3 + 2]);
6293
6539
  scalesData[off + 3] = 255;
@@ -6297,13 +6543,17 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6297
6543
  const SQRT2 = Math.SQRT2;
6298
6544
  for (let i = 0; i < count; i++) {
6299
6545
  const off = i * 4;
6300
- let qw = rotations[i * 4 + 0];
6301
- let qx = rotations[i * 4 + 1];
6302
- let qy = rotations[i * 4 + 2];
6303
- let qz = rotations[i * 4 + 3];
6546
+ let qw = sRot[i * 4], qx = sRot[i * 4 + 1], qy = sRot[i * 4 + 2], qz = sRot[i * 4 + 3];
6547
+ const len = Math.sqrt(qw * qw + qx * qx + qy * qy + qz * qz);
6548
+ if (len > 0) {
6549
+ const inv = 1 / len;
6550
+ qw *= inv;
6551
+ qx *= inv;
6552
+ qy *= inv;
6553
+ qz *= inv;
6554
+ }
6304
6555
  const comps = [qw, qx, qy, qz];
6305
- let largest = 0;
6306
- let largestAbs = Math.abs(comps[0]);
6556
+ let largest = 0, largestAbs = Math.abs(comps[0]);
6307
6557
  for (let j = 1; j < 4; j++) {
6308
6558
  const a = Math.abs(comps[j]);
6309
6559
  if (a > largestAbs) {
@@ -6335,65 +6585,115 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6335
6585
  r1 = qx;
6336
6586
  r2 = qy;
6337
6587
  }
6338
- quatsData[off + 0] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6588
+ quatsData[off] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6339
6589
  quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6340
6590
  quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
6341
6591
  quatsData[off + 3] = largest + 252;
6342
6592
  }
6343
6593
  const dcValues = new Float32Array(count * 3);
6344
6594
  for (let i = 0; i < count; i++) {
6345
- dcValues[i * 3 + 0] = (colors[i * 3 + 0] - 0.5) / SH_C0;
6346
- dcValues[i * 3 + 1] = (colors[i * 3 + 1] - 0.5) / SH_C0;
6347
- dcValues[i * 3 + 2] = (colors[i * 3 + 2] - 0.5) / SH_C0;
6595
+ dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
6596
+ dcValues[i * 3 + 1] = (sCol[i * 3 + 1] - 0.5) / SH_C0;
6597
+ dcValues[i * 3 + 2] = (sCol[i * 3 + 2] - 0.5) / SH_C0;
6348
6598
  }
6349
- const dcCodebook = buildCodebook(dcValues);
6599
+ const dcCodebook = buildCodebook(dcValues, 256, iterations);
6350
6600
  const sh0Data = new Uint8ClampedArray(totalPixels * 4);
6351
6601
  sh0Data.fill(255);
6352
6602
  for (let i = 0; i < count; i++) {
6353
6603
  const off = i * 4;
6354
- sh0Data[off + 0] = findNearest(dcCodebook, dcValues[i * 3 + 0]);
6604
+ sh0Data[off] = findNearest(dcCodebook, dcValues[i * 3]);
6355
6605
  sh0Data[off + 1] = findNearest(dcCodebook, dcValues[i * 3 + 1]);
6356
6606
  sh0Data[off + 2] = findNearest(dcCodebook, dcValues[i * 3 + 2]);
6357
- sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, opacities[i] * 255)));
6607
+ sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sOpa[i] * 255)));
6358
6608
  }
6359
- const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = await Promise.all([
6609
+ const actualBands = sSH && maxSHBands > 0 ? maxSHBands : 0;
6610
+ const numCoeffs = actualBands > 0 ? COEFFS_PER_BAND[actualBands - 1] : 0;
6611
+ const shDim = numCoeffs * 3;
6612
+ let shNMeta;
6613
+ const webpPromises = [
6360
6614
  encodeWebP(width, height, meansLData),
6361
6615
  encodeWebP(width, height, meansUData),
6362
6616
  encodeWebP(width, height, scalesData),
6363
6617
  encodeWebP(width, height, quatsData),
6364
6618
  encodeWebP(width, height, sh0Data)
6365
- ]);
6619
+ ];
6620
+ if (actualBands > 0 && sSH) {
6621
+ const vectors = new Array(count);
6622
+ for (let i = 0; i < count; i++) {
6623
+ const v = new Float32Array(shDim);
6624
+ const b = i * 45;
6625
+ for (let j = 0; j < shDim; j++) v[j] = sSH[b + j];
6626
+ vectors[i] = v;
6627
+ }
6628
+ 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];
6633
+ }
6634
+ const shCodebook = buildCodebook(allCoeffValues, 256, iterations);
6635
+ const centW = 64 * numCoeffs;
6636
+ const centH = Math.ceil(effectivePaletteSize / 64);
6637
+ const centData = new Uint8ClampedArray(centW * centH * 4);
6638
+ centData.fill(255);
6639
+ for (let n = 0; n < effectivePaletteSize; n++) {
6640
+ const entry = centroids[n];
6641
+ for (let c = 0; c < numCoeffs; c++) {
6642
+ const u = n % 64 * numCoeffs + c;
6643
+ const v = Math.floor(n / 64);
6644
+ 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]);
6648
+ centData[off + 3] = 255;
6649
+ }
6650
+ }
6651
+ const labelsData = new Uint8ClampedArray(totalPixels * 4);
6652
+ labelsData.fill(255);
6653
+ for (let i = 0; i < count; i++) {
6654
+ const off = i * 4;
6655
+ labelsData[off] = labels[i] & 255;
6656
+ labelsData[off + 1] = labels[i] >> 8 & 255;
6657
+ labelsData[off + 2] = 255;
6658
+ labelsData[off + 3] = 255;
6659
+ }
6660
+ webpPromises.push(
6661
+ encodeWebP(centW, centH, centData),
6662
+ encodeWebP(width, height, labelsData)
6663
+ );
6664
+ shNMeta = {
6665
+ count: effectivePaletteSize,
6666
+ bands: actualBands,
6667
+ codebook: shCodebook,
6668
+ files: ["sh_centroids.webp", "sh_labels.webp"]
6669
+ };
6670
+ }
6671
+ const webps = await Promise.all(webpPromises);
6672
+ const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
6366
6673
  const meta = {
6367
6674
  version: 2,
6368
6675
  asset: { generator: "d5techs/3dgs-lib" },
6369
6676
  count,
6370
6677
  antialias: false,
6371
- means: {
6372
- mins,
6373
- maxs,
6374
- files: ["means_l.webp", "means_u.webp"]
6375
- },
6376
- scales: {
6377
- codebook: scaleCodebook,
6378
- files: ["scales.webp"]
6379
- },
6380
- quats: {
6381
- files: ["quats.webp"]
6382
- },
6383
- sh0: {
6384
- codebook: dcCodebook,
6385
- files: ["sh0.webp"]
6386
- }
6678
+ means: { mins, maxs, files: ["means_l.webp", "means_u.webp"] },
6679
+ scales: { codebook: scaleCodebook, files: ["scales.webp"] },
6680
+ quats: { files: ["quats.webp"] },
6681
+ sh0: { codebook: dcCodebook, files: ["sh0.webp"] }
6387
6682
  };
6388
- const metaBytes = new TextEncoder().encode(JSON.stringify(meta));
6389
- const zipData = zipSync({
6390
- "meta.json": metaBytes,
6683
+ if (shNMeta) meta.shN = shNMeta;
6684
+ const zipEntries = {
6685
+ "meta.json": new TextEncoder().encode(JSON.stringify(meta)),
6391
6686
  "means_l.webp": meansLWebP,
6392
6687
  "means_u.webp": meansUWebP,
6393
6688
  "scales.webp": scalesWebP,
6394
6689
  "quats.webp": quatsWebP,
6395
6690
  "sh0.webp": sh0WebP
6396
- }, { level: 0 });
6691
+ };
6692
+ if (webps.length > 5) {
6693
+ zipEntries["sh_centroids.webp"] = webps[5];
6694
+ zipEntries["sh_labels.webp"] = webps[6];
6695
+ }
6696
+ const zipData = zipSync(zipEntries, { level: 0 });
6397
6697
  return zipData.buffer;
6398
6698
  }
6399
6699
  const WORKGROUP_SIZE$1 = 256;
@@ -17220,6 +17520,8 @@ class SplatEditor {
17220
17520
  const rotations = new Float32Array(keepCount * 4);
17221
17521
  const colors = new Float32Array(keepCount * 3);
17222
17522
  const opacities = new Float32Array(keepCount);
17523
+ const hasSH = !!src.shCoeffs;
17524
+ const shCoeffs = hasSH ? new Float32Array(keepCount * 45) : void 0;
17223
17525
  let dst = 0;
17224
17526
  for (let i = 0; i < count; i++) {
17225
17527
  if (data[i] & State.deleted) continue;
@@ -17239,9 +17541,13 @@ class SplatEditor {
17239
17541
  rotations[d4 + 2] = src.rotations[i4 + 2];
17240
17542
  rotations[d4 + 3] = src.rotations[i4 + 3];
17241
17543
  opacities[dst] = src.opacities[i];
17544
+ if (shCoeffs && src.shCoeffs) {
17545
+ const sb = i * 45, db = dst * 45;
17546
+ for (let j = 0; j < 45; j++) shCoeffs[db + j] = src.shCoeffs[sb + j];
17547
+ }
17242
17548
  dst++;
17243
17549
  }
17244
- return { count: keepCount, positions, scales, rotations, colors, opacities };
17550
+ return { count: keepCount, positions, scales, rotations, colors, opacities, shCoeffs };
17245
17551
  }
17246
17552
  downloadPLY(filename = "edited.ply") {
17247
17553
  const buffer = this.exportPLY();