@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.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,18 +6147,95 @@ 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 buildCodebook(values, size = 256, iterations = 8) {
6154
- if (values.length === 0) return new Array(size).fill(0);
6155
- const sorted = Float32Array.from(values).sort();
6156
- const codebook = new Float64Array(size);
6157
- for (let i = 0; i < size; i++) {
6158
- codebook[i] = sorted[Math.floor(i / (size - 1) * (sorted.length - 1))];
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
+ }
6159
6177
  }
6160
- const assignments = new Uint8Array(values.length);
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) {
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);
6161
6236
  for (let iter = 0; iter < iterations; iter++) {
6237
+ sums.fill(0);
6238
+ counts.fill(0);
6162
6239
  for (let i = 0; i < values.length; i++) {
6163
6240
  let bestIdx = 0;
6164
6241
  let bestDist = Math.abs(values[i] - codebook[0]);
@@ -6169,31 +6246,167 @@ function buildCodebook(values, size = 256, iterations = 8) {
6169
6246
  bestIdx = j;
6170
6247
  }
6171
6248
  }
6172
- assignments[i] = bestIdx;
6173
- }
6174
- const sums = new Float64Array(size);
6175
- const counts = new Uint32Array(size);
6176
- for (let i = 0; i < values.length; i++) {
6177
- sums[assignments[i]] += values[i];
6178
- counts[assignments[i]]++;
6249
+ sums[bestIdx] += values[i];
6250
+ counts[bestIdx]++;
6179
6251
  }
6252
+ let changed = false;
6180
6253
  for (let j = 0; j < size; j++) {
6181
- if (counts[j] > 0) codebook[j] = sums[j] / counts[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;
6259
+ }
6260
+ }
6182
6261
  }
6262
+ if (!changed) break;
6263
+ }
6264
+ const result = Array.from(codebook);
6265
+ result.sort((a, b) => a - b);
6266
+ return result;
6267
+ }
6268
+ function findNearest(sortedCB, value) {
6269
+ let lo = 0, hi = sortedCB.length - 1;
6270
+ while (lo < hi) {
6271
+ const mid = lo + hi >> 1;
6272
+ if (sortedCB[mid] < value) lo = mid + 1;
6273
+ else hi = mid;
6183
6274
  }
6184
- return Array.from(codebook);
6275
+ if (lo > 0 && Math.abs(sortedCB[lo - 1] - value) <= Math.abs(sortedCB[lo] - value)) {
6276
+ return lo - 1;
6277
+ }
6278
+ return lo;
6185
6279
  }
6186
- function findNearest(codebook, value) {
6187
- let bestIdx = 0;
6188
- let bestDist = Math.abs(value - codebook[0]);
6189
- for (let j = 1; j < codebook.length; j++) {
6190
- const d = Math.abs(value - codebook[j]);
6191
- if (d < bestDist) {
6192
- bestDist = d;
6193
- bestIdx = j;
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
+ }
6194
6371
  }
6372
+ labels[i] = bestK;
6195
6373
  }
6196
- return bestIdx;
6374
+ return { centroids: centroids.map((c) => new Float32Array(c)), labels };
6375
+ }
6376
+ function mortonSort(positions, count) {
6377
+ let minx = Infinity, miny = Infinity, minz = Infinity;
6378
+ let maxx = -Infinity, maxy = -Infinity, maxz = -Infinity;
6379
+ for (let i = 0; i < count; i++) {
6380
+ const x = positions[i * 3], y = positions[i * 3 + 1], z = positions[i * 3 + 2];
6381
+ if (x < minx) minx = x;
6382
+ if (x > maxx) maxx = x;
6383
+ if (y < miny) miny = y;
6384
+ if (y > maxy) maxy = y;
6385
+ if (z < minz) minz = z;
6386
+ if (z > maxz) maxz = z;
6387
+ }
6388
+ const xlen = maxx - minx || 1;
6389
+ const ylen = maxy - miny || 1;
6390
+ const zlen = maxz - minz || 1;
6391
+ const part1By2 = (x) => {
6392
+ x &= 1023;
6393
+ x = (x ^ x << 16) & 4278190335;
6394
+ x = (x ^ x << 8) & 50393103;
6395
+ x = (x ^ x << 4) & 51130563;
6396
+ x = (x ^ x << 2) & 153391689;
6397
+ return x;
6398
+ };
6399
+ const morton = new Uint32Array(count);
6400
+ const indices = new Uint32Array(count);
6401
+ for (let i = 0; i < count; i++) {
6402
+ const ix = Math.min(1023, Math.floor(1024 * (positions[i * 3] - minx) / xlen));
6403
+ const iy = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen));
6404
+ const iz = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen));
6405
+ morton[i] = (part1By2(iz) << 2) + (part1By2(iy) << 1) + part1By2(ix);
6406
+ indices[i] = i;
6407
+ }
6408
+ indices.sort((a, b) => morton[a] - morton[b]);
6409
+ return indices;
6197
6410
  }
6198
6411
  function calcImageDimensions(count) {
6199
6412
  const width = Math.ceil(Math.sqrt(count));
@@ -6229,9 +6442,11 @@ async function encodeWebP(width, height, pixelData) {
6229
6442
  }
6230
6443
  return new Uint8Array(await blob.arrayBuffer());
6231
6444
  }
6232
- async function serializeSOG(data, coordinateSystem = "blender") {
6445
+ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6446
+ const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6233
6447
  const { count, colors, opacities } = data;
6234
6448
  let { positions, scales, rotations } = data;
6449
+ let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6235
6450
  if (coordinateSystem === "blender") {
6236
6451
  positions = new Float32Array(positions);
6237
6452
  scales = new Float32Array(scales);
@@ -6248,18 +6463,51 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6248
6463
  rotations[i4 + 2] = -rz;
6249
6464
  rotations[i4 + 3] = ry;
6250
6465
  }
6466
+ if (shCoeffs) {
6467
+ for (let i = 0; i < count; i++) {
6468
+ inverseSHTransformYZSwap(shCoeffs, i * 45);
6469
+ }
6470
+ }
6471
+ }
6472
+ const sortOrder = mortonSort(positions, 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;
6479
+ for (let dst = 0; dst < count; dst++) {
6480
+ const src = sortOrder[dst];
6481
+ const s3 = src * 3, d3 = dst * 3;
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];
6491
+ const s4 = src * 4, d4 = dst * 4;
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
+ }
6251
6501
  }
6252
6502
  const { width, height } = calcImageDimensions(count);
6253
6503
  const totalPixels = width * height;
6254
- const logPositions = new Float32Array(count * 3);
6255
- for (let i = 0; i < count * 3; i++) {
6256
- logPositions[i] = symLog(positions[i]);
6257
- }
6504
+ const logPos = new Float32Array(count * 3);
6505
+ for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6258
6506
  const mins = [Infinity, Infinity, Infinity];
6259
6507
  const maxs = [-Infinity, -Infinity, -Infinity];
6260
6508
  for (let i = 0; i < count; i++) {
6261
6509
  for (let c = 0; c < 3; c++) {
6262
- const v = logPositions[i * 3 + c];
6510
+ const v = logPos[i * 3 + c];
6263
6511
  if (v < mins[c]) mins[c] = v;
6264
6512
  if (v > maxs[c]) maxs[c] = v;
6265
6513
  }
@@ -6272,7 +6520,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6272
6520
  const off = i * 4;
6273
6521
  for (let c = 0; c < 3; c++) {
6274
6522
  const range = maxs[c] - mins[c];
6275
- 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;
6276
6524
  const q16 = Math.round(norm * 65535);
6277
6525
  meansLData[off + c] = q16 & 255;
6278
6526
  meansUData[off + c] = q16 >> 8 & 255;
@@ -6281,15 +6529,13 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6281
6529
  meansUData[off + 3] = 255;
6282
6530
  }
6283
6531
  const logScales = new Float32Array(count * 3);
6284
- for (let i = 0; i < count * 3; i++) {
6285
- logScales[i] = Math.log(Math.max(1e-8, scales[i]));
6286
- }
6287
- 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);
6288
6534
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6289
6535
  scalesData.fill(255);
6290
6536
  for (let i = 0; i < count; i++) {
6291
6537
  const off = i * 4;
6292
- scalesData[off + 0] = findNearest(scaleCodebook, logScales[i * 3 + 0]);
6538
+ scalesData[off] = findNearest(scaleCodebook, logScales[i * 3]);
6293
6539
  scalesData[off + 1] = findNearest(scaleCodebook, logScales[i * 3 + 1]);
6294
6540
  scalesData[off + 2] = findNearest(scaleCodebook, logScales[i * 3 + 2]);
6295
6541
  scalesData[off + 3] = 255;
@@ -6299,13 +6545,17 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6299
6545
  const SQRT2 = Math.SQRT2;
6300
6546
  for (let i = 0; i < count; i++) {
6301
6547
  const off = i * 4;
6302
- let qw = rotations[i * 4 + 0];
6303
- let qx = rotations[i * 4 + 1];
6304
- let qy = rotations[i * 4 + 2];
6305
- let qz = rotations[i * 4 + 3];
6548
+ let qw = sRot[i * 4], qx = sRot[i * 4 + 1], qy = sRot[i * 4 + 2], qz = sRot[i * 4 + 3];
6549
+ const len = Math.sqrt(qw * qw + qx * qx + qy * qy + qz * qz);
6550
+ if (len > 0) {
6551
+ const inv = 1 / len;
6552
+ qw *= inv;
6553
+ qx *= inv;
6554
+ qy *= inv;
6555
+ qz *= inv;
6556
+ }
6306
6557
  const comps = [qw, qx, qy, qz];
6307
- let largest = 0;
6308
- let largestAbs = Math.abs(comps[0]);
6558
+ let largest = 0, largestAbs = Math.abs(comps[0]);
6309
6559
  for (let j = 1; j < 4; j++) {
6310
6560
  const a = Math.abs(comps[j]);
6311
6561
  if (a > largestAbs) {
@@ -6337,65 +6587,115 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6337
6587
  r1 = qx;
6338
6588
  r2 = qy;
6339
6589
  }
6340
- 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)));
6341
6591
  quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6342
6592
  quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
6343
6593
  quatsData[off + 3] = largest + 252;
6344
6594
  }
6345
6595
  const dcValues = new Float32Array(count * 3);
6346
6596
  for (let i = 0; i < count; i++) {
6347
- dcValues[i * 3 + 0] = (colors[i * 3 + 0] - 0.5) / SH_C0;
6348
- dcValues[i * 3 + 1] = (colors[i * 3 + 1] - 0.5) / SH_C0;
6349
- dcValues[i * 3 + 2] = (colors[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;
6350
6600
  }
6351
- const dcCodebook = buildCodebook(dcValues);
6601
+ const dcCodebook = buildCodebook(dcValues, 256, iterations);
6352
6602
  const sh0Data = new Uint8ClampedArray(totalPixels * 4);
6353
6603
  sh0Data.fill(255);
6354
6604
  for (let i = 0; i < count; i++) {
6355
6605
  const off = i * 4;
6356
- sh0Data[off + 0] = findNearest(dcCodebook, dcValues[i * 3 + 0]);
6606
+ sh0Data[off] = findNearest(dcCodebook, dcValues[i * 3]);
6357
6607
  sh0Data[off + 1] = findNearest(dcCodebook, dcValues[i * 3 + 1]);
6358
6608
  sh0Data[off + 2] = findNearest(dcCodebook, dcValues[i * 3 + 2]);
6359
- sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, opacities[i] * 255)));
6609
+ sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sOpa[i] * 255)));
6360
6610
  }
6361
- 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 = [
6362
6616
  encodeWebP(width, height, meansLData),
6363
6617
  encodeWebP(width, height, meansUData),
6364
6618
  encodeWebP(width, height, scalesData),
6365
6619
  encodeWebP(width, height, quatsData),
6366
6620
  encodeWebP(width, height, sh0Data)
6367
- ]);
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;
6368
6675
  const meta = {
6369
6676
  version: 2,
6370
6677
  asset: { generator: "d5techs/3dgs-lib" },
6371
6678
  count,
6372
6679
  antialias: false,
6373
- means: {
6374
- mins,
6375
- maxs,
6376
- files: ["means_l.webp", "means_u.webp"]
6377
- },
6378
- scales: {
6379
- codebook: scaleCodebook,
6380
- files: ["scales.webp"]
6381
- },
6382
- quats: {
6383
- files: ["quats.webp"]
6384
- },
6385
- sh0: {
6386
- codebook: dcCodebook,
6387
- files: ["sh0.webp"]
6388
- }
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"] }
6389
6684
  };
6390
- const metaBytes = new TextEncoder().encode(JSON.stringify(meta));
6391
- const zipData = zipSync({
6392
- "meta.json": metaBytes,
6685
+ if (shNMeta) meta.shN = shNMeta;
6686
+ const zipEntries = {
6687
+ "meta.json": new TextEncoder().encode(JSON.stringify(meta)),
6393
6688
  "means_l.webp": meansLWebP,
6394
6689
  "means_u.webp": meansUWebP,
6395
6690
  "scales.webp": scalesWebP,
6396
6691
  "quats.webp": quatsWebP,
6397
6692
  "sh0.webp": sh0WebP
6398
- }, { 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 });
6399
6699
  return zipData.buffer;
6400
6700
  }
6401
6701
  const WORKGROUP_SIZE$1 = 256;
@@ -17222,6 +17522,8 @@ class SplatEditor {
17222
17522
  const rotations = new Float32Array(keepCount * 4);
17223
17523
  const colors = new Float32Array(keepCount * 3);
17224
17524
  const opacities = new Float32Array(keepCount);
17525
+ const hasSH = !!src.shCoeffs;
17526
+ const shCoeffs = hasSH ? new Float32Array(keepCount * 45) : void 0;
17225
17527
  let dst = 0;
17226
17528
  for (let i = 0; i < count; i++) {
17227
17529
  if (data[i] & State.deleted) continue;
@@ -17241,9 +17543,13 @@ class SplatEditor {
17241
17543
  rotations[d4 + 2] = src.rotations[i4 + 2];
17242
17544
  rotations[d4 + 3] = src.rotations[i4 + 3];
17243
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
+ }
17244
17550
  dst++;
17245
17551
  }
17246
- return { count: keepCount, positions, scales, rotations, colors, opacities };
17552
+ return { count: keepCount, positions, scales, rotations, colors, opacities, shCoeffs };
17247
17553
  }
17248
17554
  downloadPLY(filename = "edited.ply") {
17249
17555
  const buffer = this.exportPLY();