@d5techs/3dgs-lib 1.4.19 → 1.4.21

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
@@ -5977,7 +5977,7 @@ function unzipSync(data, opts) {
5977
5977
  return files;
5978
5978
  }
5979
5979
  const SH_C0$1 = 0.28209479177387814;
5980
- const COEFFS_PER_BAND = [3, 8, 15];
5980
+ const COEFFS_PER_BAND$1 = [3, 8, 15];
5981
5981
  function lerp(a, b, t) {
5982
5982
  return a + (b - a) * t;
5983
5983
  }
@@ -6007,7 +6007,7 @@ async function decodeWebP(bytes) {
6007
6007
  return { width: w, height: h, data: ctx.getImageData(0, 0, w, h).data };
6008
6008
  }
6009
6009
  function decodeSHPalette(centroidsImg, codebook, paletteSize, bands) {
6010
- const numCoeffs = COEFFS_PER_BAND[bands - 1];
6010
+ const numCoeffs = COEFFS_PER_BAND$1[bands - 1];
6011
6011
  const palette = new Array(paletteSize);
6012
6012
  for (let n = 0; n < paletteSize; n++) {
6013
6013
  const entry = new Float32Array(numCoeffs * 3);
@@ -6074,7 +6074,7 @@ async function deserializeSOG(data, onProgress) {
6074
6074
  let shPalette = null;
6075
6075
  let shBandCoeffs = 0;
6076
6076
  if (meta.shN && centroidsImg) {
6077
- shBandCoeffs = COEFFS_PER_BAND[meta.shN.bands - 1];
6077
+ shBandCoeffs = COEFFS_PER_BAND$1[meta.shN.bands - 1];
6078
6078
  shPalette = decodeSHPalette(
6079
6079
  centroidsImg,
6080
6080
  meta.shN.codebook,
@@ -6147,10 +6147,57 @@ async function deserializeSOG(data, onProgress) {
6147
6147
  return { count, positions, scales, rotations, colors, opacities, shCoeffs };
6148
6148
  }
6149
6149
  const SH_C0 = 0.28209479177387814;
6150
+ const COEFFS_PER_BAND = [3, 8, 15];
6150
6151
  function symLog(x) {
6151
6152
  return Math.sign(x) * Math.log(1 + Math.abs(x));
6152
6153
  }
6153
- function kmeansppInit(values, k) {
6154
+ function inverseSHTransformYZSwap(sh, base, perChannel) {
6155
+ const SQRT3_2 = Math.sqrt(3) / 2;
6156
+ {
6157
+ for (let ch = 0; ch < 3; ch++) {
6158
+ const a = sh[base + ch];
6159
+ const b = sh[base + 3 + ch];
6160
+ sh[base + ch] = -b;
6161
+ sh[base + 3 + ch] = a;
6162
+ }
6163
+ }
6164
+ {
6165
+ for (let ch = 0; ch < 3; ch++) {
6166
+ const g0 = sh[base + 9 + ch];
6167
+ const g1 = sh[base + 12 + ch];
6168
+ const g2 = sh[base + 15 + ch];
6169
+ const g3 = sh[base + 18 + ch];
6170
+ const g4 = sh[base + 21 + ch];
6171
+ sh[base + 9 + ch] = -g3;
6172
+ sh[base + 12 + ch] = -g1;
6173
+ sh[base + 15 + ch] = -0.5 * g2 - SQRT3_2 * g4;
6174
+ sh[base + 18 + ch] = g0;
6175
+ sh[base + 21 + ch] = -SQRT3_2 * g2 + 0.5 * g4;
6176
+ }
6177
+ }
6178
+ {
6179
+ const A = Math.sqrt(10) / 4;
6180
+ const B = Math.sqrt(6) / 4;
6181
+ const P = Math.sqrt(15) / 4;
6182
+ for (let ch = 0; ch < 3; ch++) {
6183
+ const g0 = sh[base + 24 + ch];
6184
+ const g1 = sh[base + 27 + ch];
6185
+ const g2 = sh[base + 30 + ch];
6186
+ const g3 = sh[base + 33 + ch];
6187
+ const g4 = sh[base + 36 + ch];
6188
+ const g5 = sh[base + 39 + ch];
6189
+ const g6 = sh[base + 42 + ch];
6190
+ sh[base + 24 + ch] = A * g3 - B * g5;
6191
+ sh[base + 27 + ch] = -g1;
6192
+ sh[base + 30 + ch] = B * g3 + A * g5;
6193
+ sh[base + 33 + ch] = -A * g0 - B * g2;
6194
+ sh[base + 36 + ch] = -0.25 * g4 - P * g6;
6195
+ sh[base + 39 + ch] = B * g0 - A * g2;
6196
+ sh[base + 42 + ch] = -P * g4 + 0.25 * g6;
6197
+ }
6198
+ }
6199
+ }
6200
+ function kmeansppInit1D(values, k) {
6154
6201
  const n = values.length;
6155
6202
  const centers = new Float64Array(k);
6156
6203
  centers[0] = values[Math.floor(Math.random() * n)];
@@ -6176,18 +6223,19 @@ function kmeansppInit(values, k) {
6176
6223
  }
6177
6224
  return centers;
6178
6225
  }
6179
- function buildCodebook(values, size = 256, iterations = 20) {
6226
+ function buildCodebook(values, size = 256, iterations = 10) {
6180
6227
  if (values.length === 0) return new Array(size).fill(0);
6181
6228
  if (values.length <= size) {
6182
6229
  const cb = Array.from(new Set(Array.from(values))).sort((a, b) => a - b);
6183
6230
  while (cb.length < size) cb.push(cb[cb.length - 1]);
6184
6231
  return cb.slice(0, size);
6185
6232
  }
6186
- const codebook = kmeansppInit(values, size);
6187
- const assignments = new Uint8Array(values.length);
6233
+ const codebook = kmeansppInit1D(values, size);
6188
6234
  const sums = new Float64Array(size);
6189
6235
  const counts = new Uint32Array(size);
6190
6236
  for (let iter = 0; iter < iterations; iter++) {
6237
+ sums.fill(0);
6238
+ counts.fill(0);
6191
6239
  for (let i = 0; i < values.length; i++) {
6192
6240
  let bestIdx = 0;
6193
6241
  let bestDist = Math.abs(values[i] - codebook[0]);
@@ -6198,20 +6246,15 @@ function buildCodebook(values, size = 256, iterations = 20) {
6198
6246
  bestIdx = j;
6199
6247
  }
6200
6248
  }
6201
- assignments[i] = bestIdx;
6202
- }
6203
- sums.fill(0);
6204
- counts.fill(0);
6205
- for (let i = 0; i < values.length; i++) {
6206
- sums[assignments[i]] += values[i];
6207
- counts[assignments[i]]++;
6249
+ sums[bestIdx] += values[i];
6250
+ counts[bestIdx]++;
6208
6251
  }
6209
6252
  let changed = false;
6210
6253
  for (let j = 0; j < size; j++) {
6211
6254
  if (counts[j] > 0) {
6212
- const newVal = sums[j] / counts[j];
6213
- if (newVal !== codebook[j]) {
6214
- codebook[j] = newVal;
6255
+ const v = sums[j] / counts[j];
6256
+ if (v !== codebook[j]) {
6257
+ codebook[j] = v;
6215
6258
  changed = true;
6216
6259
  }
6217
6260
  }
@@ -6222,18 +6265,114 @@ function buildCodebook(values, size = 256, iterations = 20) {
6222
6265
  result.sort((a, b) => a - b);
6223
6266
  return result;
6224
6267
  }
6225
- function findNearest(sortedCodebook, value) {
6226
- let lo = 0, hi = sortedCodebook.length - 1;
6268
+ function findNearest(sortedCB, value) {
6269
+ let lo = 0, hi = sortedCB.length - 1;
6227
6270
  while (lo < hi) {
6228
6271
  const mid = lo + hi >> 1;
6229
- if (sortedCodebook[mid] < value) lo = mid + 1;
6272
+ if (sortedCB[mid] < value) lo = mid + 1;
6230
6273
  else hi = mid;
6231
6274
  }
6232
- if (lo > 0 && Math.abs(sortedCodebook[lo - 1] - value) <= Math.abs(sortedCodebook[lo] - value)) {
6275
+ if (lo > 0 && Math.abs(sortedCB[lo - 1] - value) <= Math.abs(sortedCB[lo] - value)) {
6233
6276
  return lo - 1;
6234
6277
  }
6235
6278
  return lo;
6236
6279
  }
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;
6288
+ }
6289
+ while (centroids2.length < k) centroids2.push(new Float32Array(dim));
6290
+ return { centroids: centroids2, labels };
6291
+ }
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];
6307
+ }
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;
6315
+ }
6316
+ }
6317
+ centroids.push(new Float32Array(vectors[chosen]));
6318
+ }
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;
6335
+ }
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];
6343
+ }
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
+ }
6354
+ }
6355
+ }
6356
+ if (!changed) break;
6357
+ }
6358
+ for (let i = 0; i < n; i++) {
6359
+ let bestK = 0;
6360
+ let bestD = Infinity;
6361
+ for (let c = 0; c < k; c++) {
6362
+ let d2 = 0;
6363
+ for (let d = 0; d < dim; d++) {
6364
+ const diff = vectors[i][d] - centroids[c][d];
6365
+ d2 += diff * diff;
6366
+ }
6367
+ if (d2 < bestD) {
6368
+ bestD = d2;
6369
+ bestK = c;
6370
+ }
6371
+ }
6372
+ labels[i] = bestK;
6373
+ }
6374
+ return { centroids: centroids.map((c) => new Float32Array(c)), labels };
6375
+ }
6237
6376
  function mortonSort(positions, count) {
6238
6377
  let minx = Infinity, miny = Infinity, minz = Infinity;
6239
6378
  let maxx = -Infinity, maxy = -Infinity, maxz = -Infinity;
@@ -6303,9 +6442,11 @@ async function encodeWebP(width, height, pixelData) {
6303
6442
  }
6304
6443
  return new Uint8Array(await blob.arrayBuffer());
6305
6444
  }
6306
- async function serializeSOG(data, coordinateSystem = "blender") {
6445
+ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6446
+ const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6307
6447
  const { count, colors, opacities } = data;
6308
6448
  let { positions, scales, rotations } = data;
6449
+ let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6309
6450
  if (coordinateSystem === "blender") {
6310
6451
  positions = new Float32Array(positions);
6311
6452
  scales = new Float32Array(scales);
@@ -6322,43 +6463,51 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6322
6463
  rotations[i4 + 2] = -rz;
6323
6464
  rotations[i4 + 3] = ry;
6324
6465
  }
6466
+ if (shCoeffs) {
6467
+ for (let i = 0; i < count; i++) {
6468
+ inverseSHTransformYZSwap(shCoeffs, i * 45);
6469
+ }
6470
+ }
6325
6471
  }
6326
6472
  const sortOrder = mortonSort(positions, count);
6327
- const sortedPositions = new Float32Array(count * 3);
6328
- const sortedScales = new Float32Array(count * 3);
6329
- const sortedRotations = new Float32Array(count * 4);
6330
- const sortedColors = new Float32Array(count * 3);
6331
- const sortedOpacities = new Float32Array(count);
6473
+ const sPos = new Float32Array(count * 3);
6474
+ const sSca = new Float32Array(count * 3);
6475
+ const sRot = new Float32Array(count * 4);
6476
+ const sCol = new Float32Array(count * 3);
6477
+ const sOpa = new Float32Array(count);
6478
+ const sSH = shCoeffs ? new Float32Array(count * 45) : void 0;
6332
6479
  for (let dst = 0; dst < count; dst++) {
6333
6480
  const src = sortOrder[dst];
6334
6481
  const s3 = src * 3, d3 = dst * 3;
6335
- sortedPositions[d3] = positions[s3];
6336
- sortedPositions[d3 + 1] = positions[s3 + 1];
6337
- sortedPositions[d3 + 2] = positions[s3 + 2];
6338
- sortedScales[d3] = scales[s3];
6339
- sortedScales[d3 + 1] = scales[s3 + 1];
6340
- sortedScales[d3 + 2] = scales[s3 + 2];
6341
- sortedColors[d3] = colors[s3];
6342
- sortedColors[d3 + 1] = colors[s3 + 1];
6343
- sortedColors[d3 + 2] = colors[s3 + 2];
6482
+ sPos[d3] = positions[s3];
6483
+ sPos[d3 + 1] = positions[s3 + 1];
6484
+ sPos[d3 + 2] = positions[s3 + 2];
6485
+ sSca[d3] = scales[s3];
6486
+ sSca[d3 + 1] = scales[s3 + 1];
6487
+ sSca[d3 + 2] = scales[s3 + 2];
6488
+ sCol[d3] = colors[s3];
6489
+ sCol[d3 + 1] = colors[s3 + 1];
6490
+ sCol[d3 + 2] = colors[s3 + 2];
6344
6491
  const s4 = src * 4, d4 = dst * 4;
6345
- sortedRotations[d4] = rotations[s4];
6346
- sortedRotations[d4 + 1] = rotations[s4 + 1];
6347
- sortedRotations[d4 + 2] = rotations[s4 + 2];
6348
- sortedRotations[d4 + 3] = rotations[s4 + 3];
6349
- sortedOpacities[dst] = opacities[src];
6492
+ sRot[d4] = rotations[s4];
6493
+ sRot[d4 + 1] = rotations[s4 + 1];
6494
+ sRot[d4 + 2] = rotations[s4 + 2];
6495
+ sRot[d4 + 3] = rotations[s4 + 3];
6496
+ sOpa[dst] = opacities[src];
6497
+ if (sSH && shCoeffs) {
6498
+ const sb = src * 45, db = dst * 45;
6499
+ for (let j = 0; j < 45; j++) sSH[db + j] = shCoeffs[sb + j];
6500
+ }
6350
6501
  }
6351
6502
  const { width, height } = calcImageDimensions(count);
6352
6503
  const totalPixels = width * height;
6353
- const logPositions = new Float32Array(count * 3);
6354
- for (let i = 0; i < count * 3; i++) {
6355
- logPositions[i] = symLog(sortedPositions[i]);
6356
- }
6504
+ const logPos = new Float32Array(count * 3);
6505
+ for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6357
6506
  const mins = [Infinity, Infinity, Infinity];
6358
6507
  const maxs = [-Infinity, -Infinity, -Infinity];
6359
6508
  for (let i = 0; i < count; i++) {
6360
6509
  for (let c = 0; c < 3; c++) {
6361
- const v = logPositions[i * 3 + c];
6510
+ const v = logPos[i * 3 + c];
6362
6511
  if (v < mins[c]) mins[c] = v;
6363
6512
  if (v > maxs[c]) maxs[c] = v;
6364
6513
  }
@@ -6371,7 +6520,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6371
6520
  const off = i * 4;
6372
6521
  for (let c = 0; c < 3; c++) {
6373
6522
  const range = maxs[c] - mins[c];
6374
- const norm = range < 1e-10 ? 0 : (logPositions[i * 3 + c] - mins[c]) / range;
6523
+ const norm = range < 1e-10 ? 0 : (logPos[i * 3 + c] - mins[c]) / range;
6375
6524
  const q16 = Math.round(norm * 65535);
6376
6525
  meansLData[off + c] = q16 & 255;
6377
6526
  meansUData[off + c] = q16 >> 8 & 255;
@@ -6380,15 +6529,13 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6380
6529
  meansUData[off + 3] = 255;
6381
6530
  }
6382
6531
  const logScales = new Float32Array(count * 3);
6383
- for (let i = 0; i < count * 3; i++) {
6384
- logScales[i] = Math.log(Math.max(1e-8, sortedScales[i]));
6385
- }
6386
- const scaleCodebook = buildCodebook(logScales);
6532
+ 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);
6387
6534
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6388
6535
  scalesData.fill(255);
6389
6536
  for (let i = 0; i < count; i++) {
6390
6537
  const off = i * 4;
6391
- scalesData[off + 0] = findNearest(scaleCodebook, logScales[i * 3 + 0]);
6538
+ scalesData[off] = findNearest(scaleCodebook, logScales[i * 3]);
6392
6539
  scalesData[off + 1] = findNearest(scaleCodebook, logScales[i * 3 + 1]);
6393
6540
  scalesData[off + 2] = findNearest(scaleCodebook, logScales[i * 3 + 2]);
6394
6541
  scalesData[off + 3] = 255;
@@ -6398,10 +6545,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6398
6545
  const SQRT2 = Math.SQRT2;
6399
6546
  for (let i = 0; i < count; i++) {
6400
6547
  const off = i * 4;
6401
- let qw = sortedRotations[i * 4 + 0];
6402
- let qx = sortedRotations[i * 4 + 1];
6403
- let qy = sortedRotations[i * 4 + 2];
6404
- let qz = sortedRotations[i * 4 + 3];
6548
+ let qw = sRot[i * 4], qx = sRot[i * 4 + 1], qy = sRot[i * 4 + 2], qz = sRot[i * 4 + 3];
6405
6549
  const len = Math.sqrt(qw * qw + qx * qx + qy * qy + qz * qz);
6406
6550
  if (len > 0) {
6407
6551
  const inv = 1 / len;
@@ -6411,8 +6555,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6411
6555
  qz *= inv;
6412
6556
  }
6413
6557
  const comps = [qw, qx, qy, qz];
6414
- let largest = 0;
6415
- let largestAbs = Math.abs(comps[0]);
6558
+ let largest = 0, largestAbs = Math.abs(comps[0]);
6416
6559
  for (let j = 1; j < 4; j++) {
6417
6560
  const a = Math.abs(comps[j]);
6418
6561
  if (a > largestAbs) {
@@ -6444,65 +6587,115 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6444
6587
  r1 = qx;
6445
6588
  r2 = qy;
6446
6589
  }
6447
- quatsData[off + 0] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6590
+ quatsData[off] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6448
6591
  quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6449
6592
  quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
6450
6593
  quatsData[off + 3] = largest + 252;
6451
6594
  }
6452
6595
  const dcValues = new Float32Array(count * 3);
6453
6596
  for (let i = 0; i < count; i++) {
6454
- dcValues[i * 3 + 0] = (sortedColors[i * 3 + 0] - 0.5) / SH_C0;
6455
- dcValues[i * 3 + 1] = (sortedColors[i * 3 + 1] - 0.5) / SH_C0;
6456
- dcValues[i * 3 + 2] = (sortedColors[i * 3 + 2] - 0.5) / SH_C0;
6597
+ dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
6598
+ dcValues[i * 3 + 1] = (sCol[i * 3 + 1] - 0.5) / SH_C0;
6599
+ dcValues[i * 3 + 2] = (sCol[i * 3 + 2] - 0.5) / SH_C0;
6457
6600
  }
6458
- const dcCodebook = buildCodebook(dcValues);
6601
+ const dcCodebook = buildCodebook(dcValues, 256, iterations);
6459
6602
  const sh0Data = new Uint8ClampedArray(totalPixels * 4);
6460
6603
  sh0Data.fill(255);
6461
6604
  for (let i = 0; i < count; i++) {
6462
6605
  const off = i * 4;
6463
- sh0Data[off + 0] = findNearest(dcCodebook, dcValues[i * 3 + 0]);
6606
+ sh0Data[off] = findNearest(dcCodebook, dcValues[i * 3]);
6464
6607
  sh0Data[off + 1] = findNearest(dcCodebook, dcValues[i * 3 + 1]);
6465
6608
  sh0Data[off + 2] = findNearest(dcCodebook, dcValues[i * 3 + 2]);
6466
- sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sortedOpacities[i] * 255)));
6609
+ sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sOpa[i] * 255)));
6467
6610
  }
6468
- const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = await Promise.all([
6611
+ const actualBands = sSH && maxSHBands > 0 ? maxSHBands : 0;
6612
+ const numCoeffs = actualBands > 0 ? COEFFS_PER_BAND[actualBands - 1] : 0;
6613
+ const shDim = numCoeffs * 3;
6614
+ let shNMeta;
6615
+ const webpPromises = [
6469
6616
  encodeWebP(width, height, meansLData),
6470
6617
  encodeWebP(width, height, meansUData),
6471
6618
  encodeWebP(width, height, scalesData),
6472
6619
  encodeWebP(width, height, quatsData),
6473
6620
  encodeWebP(width, height, sh0Data)
6474
- ]);
6621
+ ];
6622
+ if (actualBands > 0 && sSH) {
6623
+ const vectors = new Array(count);
6624
+ for (let i = 0; i < count; i++) {
6625
+ const v = new Float32Array(shDim);
6626
+ const b = i * 45;
6627
+ for (let j = 0; j < shDim; j++) v[j] = sSH[b + j];
6628
+ vectors[i] = v;
6629
+ }
6630
+ 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];
6635
+ }
6636
+ const shCodebook = buildCodebook(allCoeffValues, 256, iterations);
6637
+ const centW = 64 * numCoeffs;
6638
+ const centH = Math.ceil(effectivePaletteSize / 64);
6639
+ const centData = new Uint8ClampedArray(centW * centH * 4);
6640
+ centData.fill(255);
6641
+ for (let n = 0; n < effectivePaletteSize; n++) {
6642
+ const entry = centroids[n];
6643
+ for (let c = 0; c < numCoeffs; c++) {
6644
+ const u = n % 64 * numCoeffs + c;
6645
+ const v = Math.floor(n / 64);
6646
+ 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]);
6650
+ centData[off + 3] = 255;
6651
+ }
6652
+ }
6653
+ const labelsData = new Uint8ClampedArray(totalPixels * 4);
6654
+ labelsData.fill(255);
6655
+ for (let i = 0; i < count; i++) {
6656
+ const off = i * 4;
6657
+ labelsData[off] = labels[i] & 255;
6658
+ labelsData[off + 1] = labels[i] >> 8 & 255;
6659
+ labelsData[off + 2] = 255;
6660
+ labelsData[off + 3] = 255;
6661
+ }
6662
+ webpPromises.push(
6663
+ encodeWebP(centW, centH, centData),
6664
+ encodeWebP(width, height, labelsData)
6665
+ );
6666
+ shNMeta = {
6667
+ count: effectivePaletteSize,
6668
+ bands: actualBands,
6669
+ codebook: shCodebook,
6670
+ files: ["sh_centroids.webp", "sh_labels.webp"]
6671
+ };
6672
+ }
6673
+ const webps = await Promise.all(webpPromises);
6674
+ const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
6475
6675
  const meta = {
6476
6676
  version: 2,
6477
6677
  asset: { generator: "d5techs/3dgs-lib" },
6478
6678
  count,
6479
6679
  antialias: false,
6480
- means: {
6481
- mins,
6482
- maxs,
6483
- files: ["means_l.webp", "means_u.webp"]
6484
- },
6485
- scales: {
6486
- codebook: scaleCodebook,
6487
- files: ["scales.webp"]
6488
- },
6489
- quats: {
6490
- files: ["quats.webp"]
6491
- },
6492
- sh0: {
6493
- codebook: dcCodebook,
6494
- files: ["sh0.webp"]
6495
- }
6680
+ means: { mins, maxs, files: ["means_l.webp", "means_u.webp"] },
6681
+ scales: { codebook: scaleCodebook, files: ["scales.webp"] },
6682
+ quats: { files: ["quats.webp"] },
6683
+ sh0: { codebook: dcCodebook, files: ["sh0.webp"] }
6496
6684
  };
6497
- const metaBytes = new TextEncoder().encode(JSON.stringify(meta));
6498
- const zipData = zipSync({
6499
- "meta.json": metaBytes,
6685
+ if (shNMeta) meta.shN = shNMeta;
6686
+ const zipEntries = {
6687
+ "meta.json": new TextEncoder().encode(JSON.stringify(meta)),
6500
6688
  "means_l.webp": meansLWebP,
6501
6689
  "means_u.webp": meansUWebP,
6502
6690
  "scales.webp": scalesWebP,
6503
6691
  "quats.webp": quatsWebP,
6504
6692
  "sh0.webp": sh0WebP
6505
- }, { level: 0 });
6693
+ };
6694
+ if (webps.length > 5) {
6695
+ zipEntries["sh_centroids.webp"] = webps[5];
6696
+ zipEntries["sh_labels.webp"] = webps[6];
6697
+ }
6698
+ const zipData = zipSync(zipEntries, { level: 0 });
6506
6699
  return zipData.buffer;
6507
6700
  }
6508
6701
  const WORKGROUP_SIZE$1 = 256;
@@ -17329,6 +17522,8 @@ class SplatEditor {
17329
17522
  const rotations = new Float32Array(keepCount * 4);
17330
17523
  const colors = new Float32Array(keepCount * 3);
17331
17524
  const opacities = new Float32Array(keepCount);
17525
+ const hasSH = !!src.shCoeffs;
17526
+ const shCoeffs = hasSH ? new Float32Array(keepCount * 45) : void 0;
17332
17527
  let dst = 0;
17333
17528
  for (let i = 0; i < count; i++) {
17334
17529
  if (data[i] & State.deleted) continue;
@@ -17348,9 +17543,13 @@ class SplatEditor {
17348
17543
  rotations[d4 + 2] = src.rotations[i4 + 2];
17349
17544
  rotations[d4 + 3] = src.rotations[i4 + 3];
17350
17545
  opacities[dst] = src.opacities[i];
17546
+ if (shCoeffs && src.shCoeffs) {
17547
+ const sb = i * 45, db = dst * 45;
17548
+ for (let j = 0; j < 45; j++) shCoeffs[db + j] = src.shCoeffs[sb + j];
17549
+ }
17351
17550
  dst++;
17352
17551
  }
17353
- return { count: keepCount, positions, scales, rotations, colors, opacities };
17552
+ return { count: keepCount, positions, scales, rotations, colors, opacities, shCoeffs };
17354
17553
  }
17355
17554
  downloadPLY(filename = "edited.ply") {
17356
17555
  const buffer = this.exportPLY();