@d5techs/3dgs-lib 1.4.19 → 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,10 +6145,57 @@ 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 kmeansppInit(values, k) {
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
+ }
6175
+ }
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) {
6152
6199
  const n = values.length;
6153
6200
  const centers = new Float64Array(k);
6154
6201
  centers[0] = values[Math.floor(Math.random() * n)];
@@ -6174,18 +6221,19 @@ function kmeansppInit(values, k) {
6174
6221
  }
6175
6222
  return centers;
6176
6223
  }
6177
- function buildCodebook(values, size = 256, iterations = 20) {
6224
+ function buildCodebook(values, size = 256, iterations = 10) {
6178
6225
  if (values.length === 0) return new Array(size).fill(0);
6179
6226
  if (values.length <= size) {
6180
6227
  const cb = Array.from(new Set(Array.from(values))).sort((a, b) => a - b);
6181
6228
  while (cb.length < size) cb.push(cb[cb.length - 1]);
6182
6229
  return cb.slice(0, size);
6183
6230
  }
6184
- const codebook = kmeansppInit(values, size);
6185
- const assignments = new Uint8Array(values.length);
6231
+ const codebook = kmeansppInit1D(values, size);
6186
6232
  const sums = new Float64Array(size);
6187
6233
  const counts = new Uint32Array(size);
6188
6234
  for (let iter = 0; iter < iterations; iter++) {
6235
+ sums.fill(0);
6236
+ counts.fill(0);
6189
6237
  for (let i = 0; i < values.length; i++) {
6190
6238
  let bestIdx = 0;
6191
6239
  let bestDist = Math.abs(values[i] - codebook[0]);
@@ -6196,20 +6244,15 @@ function buildCodebook(values, size = 256, iterations = 20) {
6196
6244
  bestIdx = j;
6197
6245
  }
6198
6246
  }
6199
- assignments[i] = bestIdx;
6200
- }
6201
- sums.fill(0);
6202
- counts.fill(0);
6203
- for (let i = 0; i < values.length; i++) {
6204
- sums[assignments[i]] += values[i];
6205
- counts[assignments[i]]++;
6247
+ sums[bestIdx] += values[i];
6248
+ counts[bestIdx]++;
6206
6249
  }
6207
6250
  let changed = false;
6208
6251
  for (let j = 0; j < size; j++) {
6209
6252
  if (counts[j] > 0) {
6210
- const newVal = sums[j] / counts[j];
6211
- if (newVal !== codebook[j]) {
6212
- codebook[j] = newVal;
6253
+ const v = sums[j] / counts[j];
6254
+ if (v !== codebook[j]) {
6255
+ codebook[j] = v;
6213
6256
  changed = true;
6214
6257
  }
6215
6258
  }
@@ -6220,18 +6263,114 @@ function buildCodebook(values, size = 256, iterations = 20) {
6220
6263
  result.sort((a, b) => a - b);
6221
6264
  return result;
6222
6265
  }
6223
- function findNearest(sortedCodebook, value) {
6224
- let lo = 0, hi = sortedCodebook.length - 1;
6266
+ function findNearest(sortedCB, value) {
6267
+ let lo = 0, hi = sortedCB.length - 1;
6225
6268
  while (lo < hi) {
6226
6269
  const mid = lo + hi >> 1;
6227
- if (sortedCodebook[mid] < value) lo = mid + 1;
6270
+ if (sortedCB[mid] < value) lo = mid + 1;
6228
6271
  else hi = mid;
6229
6272
  }
6230
- if (lo > 0 && Math.abs(sortedCodebook[lo - 1] - value) <= Math.abs(sortedCodebook[lo] - value)) {
6273
+ if (lo > 0 && Math.abs(sortedCB[lo - 1] - value) <= Math.abs(sortedCB[lo] - value)) {
6231
6274
  return lo - 1;
6232
6275
  }
6233
6276
  return lo;
6234
6277
  }
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
+ }
6369
+ }
6370
+ labels[i] = bestK;
6371
+ }
6372
+ return { centroids: centroids.map((c) => new Float32Array(c)), labels };
6373
+ }
6235
6374
  function mortonSort(positions, count) {
6236
6375
  let minx = Infinity, miny = Infinity, minz = Infinity;
6237
6376
  let maxx = -Infinity, maxy = -Infinity, maxz = -Infinity;
@@ -6301,9 +6440,11 @@ async function encodeWebP(width, height, pixelData) {
6301
6440
  }
6302
6441
  return new Uint8Array(await blob.arrayBuffer());
6303
6442
  }
6304
- async function serializeSOG(data, coordinateSystem = "blender") {
6443
+ async function serializeSOG(data, coordinateSystem = "blender", options = {}) {
6444
+ const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6305
6445
  const { count, colors, opacities } = data;
6306
6446
  let { positions, scales, rotations } = data;
6447
+ let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6307
6448
  if (coordinateSystem === "blender") {
6308
6449
  positions = new Float32Array(positions);
6309
6450
  scales = new Float32Array(scales);
@@ -6320,43 +6461,51 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6320
6461
  rotations[i4 + 2] = -rz;
6321
6462
  rotations[i4 + 3] = ry;
6322
6463
  }
6464
+ if (shCoeffs) {
6465
+ for (let i = 0; i < count; i++) {
6466
+ inverseSHTransformYZSwap(shCoeffs, i * 45);
6467
+ }
6468
+ }
6323
6469
  }
6324
6470
  const sortOrder = mortonSort(positions, count);
6325
- const sortedPositions = new Float32Array(count * 3);
6326
- const sortedScales = new Float32Array(count * 3);
6327
- const sortedRotations = new Float32Array(count * 4);
6328
- const sortedColors = new Float32Array(count * 3);
6329
- const sortedOpacities = new Float32Array(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;
6330
6477
  for (let dst = 0; dst < count; dst++) {
6331
6478
  const src = sortOrder[dst];
6332
6479
  const s3 = src * 3, d3 = dst * 3;
6333
- sortedPositions[d3] = positions[s3];
6334
- sortedPositions[d3 + 1] = positions[s3 + 1];
6335
- sortedPositions[d3 + 2] = positions[s3 + 2];
6336
- sortedScales[d3] = scales[s3];
6337
- sortedScales[d3 + 1] = scales[s3 + 1];
6338
- sortedScales[d3 + 2] = scales[s3 + 2];
6339
- sortedColors[d3] = colors[s3];
6340
- sortedColors[d3 + 1] = colors[s3 + 1];
6341
- sortedColors[d3 + 2] = colors[s3 + 2];
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];
6342
6489
  const s4 = src * 4, d4 = dst * 4;
6343
- sortedRotations[d4] = rotations[s4];
6344
- sortedRotations[d4 + 1] = rotations[s4 + 1];
6345
- sortedRotations[d4 + 2] = rotations[s4 + 2];
6346
- sortedRotations[d4 + 3] = rotations[s4 + 3];
6347
- sortedOpacities[dst] = opacities[src];
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
+ }
6348
6499
  }
6349
6500
  const { width, height } = calcImageDimensions(count);
6350
6501
  const totalPixels = width * height;
6351
- const logPositions = new Float32Array(count * 3);
6352
- for (let i = 0; i < count * 3; i++) {
6353
- logPositions[i] = symLog(sortedPositions[i]);
6354
- }
6502
+ const logPos = new Float32Array(count * 3);
6503
+ for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6355
6504
  const mins = [Infinity, Infinity, Infinity];
6356
6505
  const maxs = [-Infinity, -Infinity, -Infinity];
6357
6506
  for (let i = 0; i < count; i++) {
6358
6507
  for (let c = 0; c < 3; c++) {
6359
- const v = logPositions[i * 3 + c];
6508
+ const v = logPos[i * 3 + c];
6360
6509
  if (v < mins[c]) mins[c] = v;
6361
6510
  if (v > maxs[c]) maxs[c] = v;
6362
6511
  }
@@ -6369,7 +6518,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6369
6518
  const off = i * 4;
6370
6519
  for (let c = 0; c < 3; c++) {
6371
6520
  const range = maxs[c] - mins[c];
6372
- 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;
6373
6522
  const q16 = Math.round(norm * 65535);
6374
6523
  meansLData[off + c] = q16 & 255;
6375
6524
  meansUData[off + c] = q16 >> 8 & 255;
@@ -6378,15 +6527,13 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6378
6527
  meansUData[off + 3] = 255;
6379
6528
  }
6380
6529
  const logScales = new Float32Array(count * 3);
6381
- for (let i = 0; i < count * 3; i++) {
6382
- logScales[i] = Math.log(Math.max(1e-8, sortedScales[i]));
6383
- }
6384
- 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);
6385
6532
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6386
6533
  scalesData.fill(255);
6387
6534
  for (let i = 0; i < count; i++) {
6388
6535
  const off = i * 4;
6389
- scalesData[off + 0] = findNearest(scaleCodebook, logScales[i * 3 + 0]);
6536
+ scalesData[off] = findNearest(scaleCodebook, logScales[i * 3]);
6390
6537
  scalesData[off + 1] = findNearest(scaleCodebook, logScales[i * 3 + 1]);
6391
6538
  scalesData[off + 2] = findNearest(scaleCodebook, logScales[i * 3 + 2]);
6392
6539
  scalesData[off + 3] = 255;
@@ -6396,10 +6543,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6396
6543
  const SQRT2 = Math.SQRT2;
6397
6544
  for (let i = 0; i < count; i++) {
6398
6545
  const off = i * 4;
6399
- let qw = sortedRotations[i * 4 + 0];
6400
- let qx = sortedRotations[i * 4 + 1];
6401
- let qy = sortedRotations[i * 4 + 2];
6402
- let qz = sortedRotations[i * 4 + 3];
6546
+ let qw = sRot[i * 4], qx = sRot[i * 4 + 1], qy = sRot[i * 4 + 2], qz = sRot[i * 4 + 3];
6403
6547
  const len = Math.sqrt(qw * qw + qx * qx + qy * qy + qz * qz);
6404
6548
  if (len > 0) {
6405
6549
  const inv = 1 / len;
@@ -6409,8 +6553,7 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6409
6553
  qz *= inv;
6410
6554
  }
6411
6555
  const comps = [qw, qx, qy, qz];
6412
- let largest = 0;
6413
- let largestAbs = Math.abs(comps[0]);
6556
+ let largest = 0, largestAbs = Math.abs(comps[0]);
6414
6557
  for (let j = 1; j < 4; j++) {
6415
6558
  const a = Math.abs(comps[j]);
6416
6559
  if (a > largestAbs) {
@@ -6442,65 +6585,115 @@ async function serializeSOG(data, coordinateSystem = "blender") {
6442
6585
  r1 = qx;
6443
6586
  r2 = qy;
6444
6587
  }
6445
- 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)));
6446
6589
  quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6447
6590
  quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
6448
6591
  quatsData[off + 3] = largest + 252;
6449
6592
  }
6450
6593
  const dcValues = new Float32Array(count * 3);
6451
6594
  for (let i = 0; i < count; i++) {
6452
- dcValues[i * 3 + 0] = (sortedColors[i * 3 + 0] - 0.5) / SH_C0;
6453
- dcValues[i * 3 + 1] = (sortedColors[i * 3 + 1] - 0.5) / SH_C0;
6454
- dcValues[i * 3 + 2] = (sortedColors[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;
6455
6598
  }
6456
- const dcCodebook = buildCodebook(dcValues);
6599
+ const dcCodebook = buildCodebook(dcValues, 256, iterations);
6457
6600
  const sh0Data = new Uint8ClampedArray(totalPixels * 4);
6458
6601
  sh0Data.fill(255);
6459
6602
  for (let i = 0; i < count; i++) {
6460
6603
  const off = i * 4;
6461
- sh0Data[off + 0] = findNearest(dcCodebook, dcValues[i * 3 + 0]);
6604
+ sh0Data[off] = findNearest(dcCodebook, dcValues[i * 3]);
6462
6605
  sh0Data[off + 1] = findNearest(dcCodebook, dcValues[i * 3 + 1]);
6463
6606
  sh0Data[off + 2] = findNearest(dcCodebook, dcValues[i * 3 + 2]);
6464
- sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sortedOpacities[i] * 255)));
6607
+ sh0Data[off + 3] = Math.round(Math.max(0, Math.min(255, sOpa[i] * 255)));
6465
6608
  }
6466
- 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 = [
6467
6614
  encodeWebP(width, height, meansLData),
6468
6615
  encodeWebP(width, height, meansUData),
6469
6616
  encodeWebP(width, height, scalesData),
6470
6617
  encodeWebP(width, height, quatsData),
6471
6618
  encodeWebP(width, height, sh0Data)
6472
- ]);
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;
6473
6673
  const meta = {
6474
6674
  version: 2,
6475
6675
  asset: { generator: "d5techs/3dgs-lib" },
6476
6676
  count,
6477
6677
  antialias: false,
6478
- means: {
6479
- mins,
6480
- maxs,
6481
- files: ["means_l.webp", "means_u.webp"]
6482
- },
6483
- scales: {
6484
- codebook: scaleCodebook,
6485
- files: ["scales.webp"]
6486
- },
6487
- quats: {
6488
- files: ["quats.webp"]
6489
- },
6490
- sh0: {
6491
- codebook: dcCodebook,
6492
- files: ["sh0.webp"]
6493
- }
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"] }
6494
6682
  };
6495
- const metaBytes = new TextEncoder().encode(JSON.stringify(meta));
6496
- const zipData = zipSync({
6497
- "meta.json": metaBytes,
6683
+ if (shNMeta) meta.shN = shNMeta;
6684
+ const zipEntries = {
6685
+ "meta.json": new TextEncoder().encode(JSON.stringify(meta)),
6498
6686
  "means_l.webp": meansLWebP,
6499
6687
  "means_u.webp": meansUWebP,
6500
6688
  "scales.webp": scalesWebP,
6501
6689
  "quats.webp": quatsWebP,
6502
6690
  "sh0.webp": sh0WebP
6503
- }, { 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 });
6504
6697
  return zipData.buffer;
6505
6698
  }
6506
6699
  const WORKGROUP_SIZE$1 = 256;
@@ -17327,6 +17520,8 @@ class SplatEditor {
17327
17520
  const rotations = new Float32Array(keepCount * 4);
17328
17521
  const colors = new Float32Array(keepCount * 3);
17329
17522
  const opacities = new Float32Array(keepCount);
17523
+ const hasSH = !!src.shCoeffs;
17524
+ const shCoeffs = hasSH ? new Float32Array(keepCount * 45) : void 0;
17330
17525
  let dst = 0;
17331
17526
  for (let i = 0; i < count; i++) {
17332
17527
  if (data[i] & State.deleted) continue;
@@ -17346,9 +17541,13 @@ class SplatEditor {
17346
17541
  rotations[d4 + 2] = src.rotations[i4 + 2];
17347
17542
  rotations[d4 + 3] = src.rotations[i4 + 3];
17348
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
+ }
17349
17548
  dst++;
17350
17549
  }
17351
- return { count: keepCount, positions, scales, rotations, colors, opacities };
17550
+ return { count: keepCount, positions, scales, rotations, colors, opacities, shCoeffs };
17352
17551
  }
17353
17552
  downloadPLY(filename = "edited.ply") {
17354
17553
  const buffer = this.exportPLY();