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