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