@playcanvas/splat-transform 1.8.3 → 1.9.0
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/README.md +7 -6
- package/dist/cli.mjs +1190 -133
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1185 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1184 -127
- package/dist/index.mjs.map +1 -1
- package/dist/lib/data-table/decimate.d.ts +14 -0
- package/dist/lib/index.d.cts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/process.d.ts +9 -8
- package/dist/lib/spatial/kd-tree.d.ts +4 -0
- package/dist/lib/write.d.ts +2 -1
- package/dist/lib/writers/write-glb.d.ts +15 -0
- package/package.json +1 -1
- package/dist/lib/data-table/filter-visibility.d.ts +0 -17
package/dist/index.cjs
CHANGED
|
@@ -932,20 +932,610 @@ const sortMortonOrder = (dataTable, indices) => {
|
|
|
932
932
|
generate(indices);
|
|
933
933
|
};
|
|
934
934
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
935
|
+
class KdTree {
|
|
936
|
+
centroids;
|
|
937
|
+
root;
|
|
938
|
+
constructor(centroids) {
|
|
939
|
+
const build = (indices, depth) => {
|
|
940
|
+
const { centroids } = this;
|
|
941
|
+
const values = centroids.columns[depth % centroids.numColumns].data;
|
|
942
|
+
indices.sort((a, b) => values[a] - values[b]);
|
|
943
|
+
if (indices.length === 1) {
|
|
944
|
+
return {
|
|
945
|
+
index: indices[0],
|
|
946
|
+
count: 1
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
else if (indices.length === 2) {
|
|
950
|
+
return {
|
|
951
|
+
index: indices[0],
|
|
952
|
+
count: 2,
|
|
953
|
+
right: {
|
|
954
|
+
index: indices[1],
|
|
955
|
+
count: 1
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const mid = indices.length >> 1;
|
|
960
|
+
const left = build(indices.subarray(0, mid), depth + 1);
|
|
961
|
+
const right = build(indices.subarray(mid + 1), depth + 1);
|
|
962
|
+
return {
|
|
963
|
+
index: indices[mid],
|
|
964
|
+
count: 1 + left.count + right.count,
|
|
965
|
+
left,
|
|
966
|
+
right
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
const indices = new Uint32Array(centroids.numRows);
|
|
970
|
+
for (let i = 0; i < indices.length; ++i) {
|
|
971
|
+
indices[i] = i;
|
|
972
|
+
}
|
|
973
|
+
this.centroids = centroids;
|
|
974
|
+
this.root = build(indices, 0);
|
|
975
|
+
}
|
|
976
|
+
findNearest(point, filterFunc) {
|
|
977
|
+
const { centroids } = this;
|
|
978
|
+
const { numColumns } = centroids;
|
|
979
|
+
const calcDistance = (index) => {
|
|
980
|
+
let l = 0;
|
|
981
|
+
for (let i = 0; i < numColumns; ++i) {
|
|
982
|
+
const v = centroids.columns[i].data[index] - point[i];
|
|
983
|
+
l += v * v;
|
|
984
|
+
}
|
|
985
|
+
return l;
|
|
986
|
+
};
|
|
987
|
+
let mind = Infinity;
|
|
988
|
+
let mini = -1;
|
|
989
|
+
let cnt = 0;
|
|
990
|
+
const recurse = (node, depth) => {
|
|
991
|
+
const axis = depth % numColumns;
|
|
992
|
+
const distance = point[axis] - centroids.columns[axis].data[node.index];
|
|
993
|
+
const next = (distance > 0) ? node.right : node.left;
|
|
994
|
+
cnt++;
|
|
995
|
+
if (next) {
|
|
996
|
+
recurse(next, depth + 1);
|
|
997
|
+
}
|
|
998
|
+
// check index
|
|
999
|
+
if (!filterFunc || filterFunc(node.index)) {
|
|
1000
|
+
const thisd = calcDistance(node.index);
|
|
1001
|
+
if (thisd < mind) {
|
|
1002
|
+
mind = thisd;
|
|
1003
|
+
mini = node.index;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
// check the other side
|
|
1007
|
+
if (distance * distance < mind) {
|
|
1008
|
+
const other = next === node.right ? node.left : node.right;
|
|
1009
|
+
if (other) {
|
|
1010
|
+
recurse(other, depth + 1);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
recurse(this.root, 0);
|
|
1015
|
+
return { index: mini, distanceSqr: mind, cnt };
|
|
1016
|
+
}
|
|
1017
|
+
findKNearest(point, k, filterFunc) {
|
|
1018
|
+
if (k <= 0) {
|
|
1019
|
+
return { indices: new Int32Array(0), distances: new Float32Array(0) };
|
|
1020
|
+
}
|
|
1021
|
+
k = Math.min(k, this.centroids.numRows);
|
|
1022
|
+
const { centroids } = this;
|
|
1023
|
+
const { numColumns } = centroids;
|
|
1024
|
+
const calcDistance = (index) => {
|
|
1025
|
+
let l = 0;
|
|
1026
|
+
for (let i = 0; i < numColumns; ++i) {
|
|
1027
|
+
const v = centroids.columns[i].data[index] - point[i];
|
|
1028
|
+
l += v * v;
|
|
1029
|
+
}
|
|
1030
|
+
return l;
|
|
1031
|
+
};
|
|
1032
|
+
// Bounded max-heap: stores (distance, index) pairs sorted so the
|
|
1033
|
+
// farthest element is at position 0, enabling O(1) pruning bound.
|
|
1034
|
+
const heapDist = new Float32Array(k).fill(Infinity);
|
|
1035
|
+
const heapIdx = new Int32Array(k).fill(-1);
|
|
1036
|
+
let heapSize = 0;
|
|
1037
|
+
const heapPush = (dist, idx) => {
|
|
1038
|
+
if (heapSize < k) {
|
|
1039
|
+
// Heap not full yet -- insert via sift-up
|
|
1040
|
+
let pos = heapSize++;
|
|
1041
|
+
heapDist[pos] = dist;
|
|
1042
|
+
heapIdx[pos] = idx;
|
|
1043
|
+
while (pos > 0) {
|
|
1044
|
+
const parent = (pos - 1) >> 1;
|
|
1045
|
+
if (heapDist[parent] < heapDist[pos]) {
|
|
1046
|
+
// swap
|
|
1047
|
+
const td = heapDist[parent];
|
|
1048
|
+
heapDist[parent] = heapDist[pos];
|
|
1049
|
+
heapDist[pos] = td;
|
|
1050
|
+
const ti = heapIdx[parent];
|
|
1051
|
+
heapIdx[parent] = heapIdx[pos];
|
|
1052
|
+
heapIdx[pos] = ti;
|
|
1053
|
+
pos = parent;
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
else if (dist < heapDist[0]) {
|
|
1061
|
+
// Replace root (farthest) and sift-down
|
|
1062
|
+
heapDist[0] = dist;
|
|
1063
|
+
heapIdx[0] = idx;
|
|
1064
|
+
let pos = 0;
|
|
1065
|
+
for (;;) {
|
|
1066
|
+
const left = 2 * pos + 1;
|
|
1067
|
+
const right = 2 * pos + 2;
|
|
1068
|
+
let largest = pos;
|
|
1069
|
+
if (left < k && heapDist[left] > heapDist[largest])
|
|
1070
|
+
largest = left;
|
|
1071
|
+
if (right < k && heapDist[right] > heapDist[largest])
|
|
1072
|
+
largest = right;
|
|
1073
|
+
if (largest === pos)
|
|
1074
|
+
break;
|
|
1075
|
+
const td = heapDist[pos];
|
|
1076
|
+
heapDist[pos] = heapDist[largest];
|
|
1077
|
+
heapDist[largest] = td;
|
|
1078
|
+
const ti = heapIdx[pos];
|
|
1079
|
+
heapIdx[pos] = heapIdx[largest];
|
|
1080
|
+
heapIdx[largest] = ti;
|
|
1081
|
+
pos = largest;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
const recurse = (node, depth) => {
|
|
1086
|
+
const axis = depth % numColumns;
|
|
1087
|
+
const distance = point[axis] - centroids.columns[axis].data[node.index];
|
|
1088
|
+
const next = (distance > 0) ? node.right : node.left;
|
|
1089
|
+
if (next) {
|
|
1090
|
+
recurse(next, depth + 1);
|
|
1091
|
+
}
|
|
1092
|
+
if (!filterFunc || filterFunc(node.index)) {
|
|
1093
|
+
const thisd = calcDistance(node.index);
|
|
1094
|
+
heapPush(thisd, node.index);
|
|
1095
|
+
}
|
|
1096
|
+
const bound = heapSize < k ? Infinity : heapDist[0];
|
|
1097
|
+
if (distance * distance < bound) {
|
|
1098
|
+
const other = next === node.right ? node.left : node.right;
|
|
1099
|
+
if (other) {
|
|
1100
|
+
recurse(other, depth + 1);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
recurse(this.root, 0);
|
|
1105
|
+
// Extract results sorted by distance (ascending)
|
|
1106
|
+
const resultIndices = new Int32Array(heapSize);
|
|
1107
|
+
const resultDist = new Float32Array(heapSize);
|
|
1108
|
+
for (let i = 0; i < heapSize; i++) {
|
|
1109
|
+
resultIndices[i] = heapIdx[i];
|
|
1110
|
+
resultDist[i] = heapDist[i];
|
|
1111
|
+
}
|
|
1112
|
+
// Simple insertion sort by distance (k is small)
|
|
1113
|
+
for (let i = 1; i < heapSize; i++) {
|
|
1114
|
+
const d = resultDist[i];
|
|
1115
|
+
const idx = resultIndices[i];
|
|
1116
|
+
let j = i - 1;
|
|
1117
|
+
while (j >= 0 && resultDist[j] > d) {
|
|
1118
|
+
resultDist[j + 1] = resultDist[j];
|
|
1119
|
+
resultIndices[j + 1] = resultIndices[j];
|
|
1120
|
+
j--;
|
|
1121
|
+
}
|
|
1122
|
+
resultDist[j + 1] = d;
|
|
1123
|
+
resultIndices[j + 1] = idx;
|
|
1124
|
+
}
|
|
1125
|
+
return { indices: resultIndices, distances: resultDist };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const TWO_PI_POW_1_5 = Math.pow(2 * Math.PI, 1.5);
|
|
1130
|
+
const LOG2PI = Math.log(2 * Math.PI);
|
|
1131
|
+
const OPACITY_PRUNE_THRESHOLD = 0.1;
|
|
1132
|
+
const KNN_K = 16;
|
|
1133
|
+
const MC_SAMPLES = 1;
|
|
1134
|
+
const EPS_COV = 1e-8;
|
|
1135
|
+
// ---------- sigmoid / logit ----------
|
|
1136
|
+
const sigmoid$1 = (x) => 1 / (1 + Math.exp(-x));
|
|
1137
|
+
const logit = (p) => {
|
|
1138
|
+
p = Math.max(1e-7, Math.min(1 - 1e-7, p));
|
|
1139
|
+
return Math.log(p / (1 - p));
|
|
1140
|
+
};
|
|
1141
|
+
const logAddExp = (a, b) => {
|
|
1142
|
+
if (a === -Infinity)
|
|
1143
|
+
return b;
|
|
1144
|
+
if (b === -Infinity)
|
|
1145
|
+
return a;
|
|
1146
|
+
const m = Math.max(a, b);
|
|
1147
|
+
return m + Math.log(Math.exp(a - m) + Math.exp(b - m));
|
|
1148
|
+
};
|
|
1149
|
+
// ---------- PRNG ----------
|
|
1150
|
+
const mulberry32 = (seed) => {
|
|
1151
|
+
return () => {
|
|
1152
|
+
let t = (seed += 0x6d2b79f5);
|
|
1153
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
1154
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
1155
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
1156
|
+
};
|
|
1157
|
+
};
|
|
1158
|
+
const makeGaussianSamples = (n, seed) => {
|
|
1159
|
+
const rand = mulberry32(seed >>> 0);
|
|
1160
|
+
const out = [];
|
|
1161
|
+
while (out.length < n) {
|
|
1162
|
+
const u1 = Math.max(rand(), 1e-12);
|
|
1163
|
+
const u2 = rand();
|
|
1164
|
+
const u3 = Math.max(rand(), 1e-12);
|
|
1165
|
+
const u4 = rand();
|
|
1166
|
+
const r1 = Math.sqrt(-2 * Math.log(u1));
|
|
1167
|
+
const t1 = 2 * Math.PI * u2;
|
|
1168
|
+
const r2 = Math.sqrt(-2 * Math.log(u3));
|
|
1169
|
+
const t2 = 2 * Math.PI * u4;
|
|
1170
|
+
out.push(new Float64Array([r1 * Math.cos(t1), r1 * Math.sin(t1), r2 * Math.cos(t2)]));
|
|
1171
|
+
}
|
|
1172
|
+
return out;
|
|
1173
|
+
};
|
|
1174
|
+
// ---------- 3x3 matrix helpers (row-major, 9 floats) ----------
|
|
1175
|
+
const quatToRotmat = (qw, qx, qy, qz, out, o) => {
|
|
1176
|
+
const xx = qx * qx, yy = qy * qy, zz = qz * qz;
|
|
1177
|
+
const wx = qw * qx, wy = qw * qy, wz = qw * qz;
|
|
1178
|
+
const xy = qx * qy, xz = qx * qz, yz = qy * qz;
|
|
1179
|
+
out[o] = 1 - 2 * (yy + zz);
|
|
1180
|
+
out[o + 1] = 2 * (xy - wz);
|
|
1181
|
+
out[o + 2] = 2 * (xz + wy);
|
|
1182
|
+
out[o + 3] = 2 * (xy + wz);
|
|
1183
|
+
out[o + 4] = 1 - 2 * (xx + zz);
|
|
1184
|
+
out[o + 5] = 2 * (yz - wx);
|
|
1185
|
+
out[o + 6] = 2 * (xz - wy);
|
|
1186
|
+
out[o + 7] = 2 * (yz + wx);
|
|
1187
|
+
out[o + 8] = 1 - 2 * (xx + yy);
|
|
1188
|
+
};
|
|
1189
|
+
const transpose3 = (src, so, dst, doff) => {
|
|
1190
|
+
dst[doff] = src[so];
|
|
1191
|
+
dst[doff + 1] = src[so + 3];
|
|
1192
|
+
dst[doff + 2] = src[so + 6];
|
|
1193
|
+
dst[doff + 3] = src[so + 1];
|
|
1194
|
+
dst[doff + 4] = src[so + 4];
|
|
1195
|
+
dst[doff + 5] = src[so + 7];
|
|
1196
|
+
dst[doff + 6] = src[so + 2];
|
|
1197
|
+
dst[doff + 7] = src[so + 5];
|
|
1198
|
+
dst[doff + 8] = src[so + 8];
|
|
1199
|
+
};
|
|
1200
|
+
const sigmaFromRotVar = (R, r, vx, vy, vz, out, o) => {
|
|
1201
|
+
const r00 = R[r], r01 = R[r + 1], r02 = R[r + 2];
|
|
1202
|
+
const r10 = R[r + 3], r11 = R[r + 4], r12 = R[r + 5];
|
|
1203
|
+
const r20 = R[r + 6], r21 = R[r + 7], r22 = R[r + 8];
|
|
1204
|
+
out[o] = r00 * r00 * vx + r01 * r01 * vy + r02 * r02 * vz;
|
|
1205
|
+
out[o + 1] = r00 * r10 * vx + r01 * r11 * vy + r02 * r12 * vz;
|
|
1206
|
+
out[o + 2] = r00 * r20 * vx + r01 * r21 * vy + r02 * r22 * vz;
|
|
1207
|
+
out[o + 3] = out[o + 1];
|
|
1208
|
+
out[o + 4] = r10 * r10 * vx + r11 * r11 * vy + r12 * r12 * vz;
|
|
1209
|
+
out[o + 5] = r10 * r20 * vx + r11 * r21 * vy + r12 * r22 * vz;
|
|
1210
|
+
out[o + 6] = out[o + 2];
|
|
1211
|
+
out[o + 7] = out[o + 5];
|
|
1212
|
+
out[o + 8] = r20 * r20 * vx + r21 * r21 * vy + r22 * r22 * vz;
|
|
1213
|
+
};
|
|
1214
|
+
const det3 = (A, o) => {
|
|
1215
|
+
return (A[o] * (A[o + 4] * A[o + 8] - A[o + 5] * A[o + 7]) -
|
|
1216
|
+
A[o + 1] * (A[o + 3] * A[o + 8] - A[o + 5] * A[o + 6]) +
|
|
1217
|
+
A[o + 2] * (A[o + 3] * A[o + 7] - A[o + 4] * A[o + 6]));
|
|
1218
|
+
};
|
|
1219
|
+
const gaussLogpdfDiagrot = (x, y, z, mx, my, mz, R, ro, invx, invy, invz, logdet) => {
|
|
1220
|
+
const dx = x - mx, dy = y - my, dz = z - mz;
|
|
1221
|
+
const y0 = dx * R[ro] + dy * R[ro + 3] + dz * R[ro + 6];
|
|
1222
|
+
const y1 = dx * R[ro + 1] + dy * R[ro + 4] + dz * R[ro + 7];
|
|
1223
|
+
const y2 = dx * R[ro + 2] + dy * R[ro + 5] + dz * R[ro + 8];
|
|
1224
|
+
const quad = y0 * y0 * invx + y1 * y1 * invy + y2 * y2 * invz;
|
|
1225
|
+
return -0.5 * (3 * LOG2PI + logdet + quad);
|
|
1226
|
+
};
|
|
1227
|
+
// Jacobi eigendecomposition for 3x3 symmetric matrix (full 9-element row-major)
|
|
1228
|
+
const eigenSymmetric3x3 = (Ain) => {
|
|
1229
|
+
const A = new Float64Array(Ain);
|
|
1230
|
+
const V = new Float64Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
|
|
1231
|
+
for (let iter = 0; iter < 24; iter++) {
|
|
1232
|
+
let p = 0, q = 1;
|
|
1233
|
+
let maxAbs = Math.abs(A[1]);
|
|
1234
|
+
if (Math.abs(A[2]) > maxAbs) {
|
|
1235
|
+
p = 0;
|
|
1236
|
+
q = 2;
|
|
1237
|
+
maxAbs = Math.abs(A[2]);
|
|
1238
|
+
}
|
|
1239
|
+
if (Math.abs(A[5]) > maxAbs) {
|
|
1240
|
+
p = 1;
|
|
1241
|
+
q = 2;
|
|
1242
|
+
maxAbs = Math.abs(A[5]);
|
|
1243
|
+
}
|
|
1244
|
+
if (maxAbs < 1e-12)
|
|
1245
|
+
break;
|
|
1246
|
+
const pp = 3 * p + p, qq = 3 * q + q, pq = 3 * p + q;
|
|
1247
|
+
const app = A[pp], aqq = A[qq], apq = A[pq];
|
|
1248
|
+
const tau = (aqq - app) / (2 * apq);
|
|
1249
|
+
const t = Math.sign(tau) / (Math.abs(tau) + Math.sqrt(1 + tau * tau));
|
|
1250
|
+
const c = 1 / Math.sqrt(1 + t * t);
|
|
1251
|
+
const s = t * c;
|
|
1252
|
+
for (let k = 0; k < 3; k++) {
|
|
1253
|
+
if (k === p || k === q)
|
|
1254
|
+
continue;
|
|
1255
|
+
const kp = 3 * k + p, kq = 3 * k + q;
|
|
1256
|
+
const pk = 3 * p + k, qk = 3 * q + k;
|
|
1257
|
+
const akp = A[kp], akq = A[kq];
|
|
1258
|
+
A[kp] = c * akp - s * akq;
|
|
1259
|
+
A[pk] = A[kp];
|
|
1260
|
+
A[kq] = s * akp + c * akq;
|
|
1261
|
+
A[qk] = A[kq];
|
|
1262
|
+
}
|
|
1263
|
+
A[pp] = c * c * app - 2 * s * c * apq + s * s * aqq;
|
|
1264
|
+
A[qq] = s * s * app + 2 * s * c * apq + c * c * aqq;
|
|
1265
|
+
A[pq] = 0;
|
|
1266
|
+
A[3 * q + p] = 0;
|
|
1267
|
+
for (let k = 0; k < 3; k++) {
|
|
1268
|
+
const kp = 3 * k + p, kq = 3 * k + q;
|
|
1269
|
+
const vkp = V[kp], vkq = V[kq];
|
|
1270
|
+
V[kp] = c * vkp - s * vkq;
|
|
1271
|
+
V[kq] = s * vkp + c * vkq;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return { values: [A[0], A[4], A[8]], vectors: V };
|
|
1275
|
+
};
|
|
1276
|
+
const rotmatToQuat = (R, o) => {
|
|
1277
|
+
const m00 = R[o], m11 = R[o + 4], m22 = R[o + 8];
|
|
1278
|
+
const tr = m00 + m11 + m22;
|
|
1279
|
+
let qw, qx, qy, qz;
|
|
1280
|
+
if (tr > 0) {
|
|
1281
|
+
const S = Math.sqrt(tr + 1) * 2;
|
|
1282
|
+
qw = 0.25 * S;
|
|
1283
|
+
qx = (R[o + 7] - R[o + 5]) / S;
|
|
1284
|
+
qy = (R[o + 2] - R[o + 6]) / S;
|
|
1285
|
+
qz = (R[o + 3] - R[o + 1]) / S;
|
|
1286
|
+
}
|
|
1287
|
+
else if (R[o] > R[o + 4] && R[o] > R[o + 8]) {
|
|
1288
|
+
const S = Math.sqrt(1 + R[o] - R[o + 4] - R[o + 8]) * 2;
|
|
1289
|
+
qw = (R[o + 7] - R[o + 5]) / S;
|
|
1290
|
+
qx = 0.25 * S;
|
|
1291
|
+
qy = (R[o + 1] + R[o + 3]) / S;
|
|
1292
|
+
qz = (R[o + 2] + R[o + 6]) / S;
|
|
1293
|
+
}
|
|
1294
|
+
else if (R[o + 4] > R[o + 8]) {
|
|
1295
|
+
const S = Math.sqrt(1 + R[o + 4] - R[o] - R[o + 8]) * 2;
|
|
1296
|
+
qw = (R[o + 2] - R[o + 6]) / S;
|
|
1297
|
+
qx = (R[o + 1] + R[o + 3]) / S;
|
|
1298
|
+
qy = 0.25 * S;
|
|
1299
|
+
qz = (R[o + 5] + R[o + 7]) / S;
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
const S = Math.sqrt(1 + R[o + 8] - R[o] - R[o + 4]) * 2;
|
|
1303
|
+
qw = (R[o + 3] - R[o + 1]) / S;
|
|
1304
|
+
qx = (R[o + 2] + R[o + 6]) / S;
|
|
1305
|
+
qy = (R[o + 5] + R[o + 7]) / S;
|
|
1306
|
+
qz = 0.25 * S;
|
|
1307
|
+
}
|
|
1308
|
+
const n = Math.hypot(qw, qx, qy, qz);
|
|
1309
|
+
const inv = 1 / Math.max(n, 1e-12);
|
|
1310
|
+
return new Float64Array([qw * inv, qx * inv, qy * inv, qz * inv]);
|
|
1311
|
+
};
|
|
1312
|
+
const buildPerSplatCache = (n, cx, cy, cz, cop, cs0, cs1, cs2, cr0, cr1, cr2, cr3) => {
|
|
1313
|
+
const R = new Float64Array(n * 9);
|
|
1314
|
+
const Rt = new Float64Array(n * 9);
|
|
1315
|
+
const v = new Float64Array(n * 3);
|
|
1316
|
+
const invdiag = new Float64Array(n * 3);
|
|
1317
|
+
const logdet = new Float64Array(n);
|
|
1318
|
+
const sigma = new Float64Array(n * 9);
|
|
1319
|
+
const mass = new Float64Array(n);
|
|
1320
|
+
for (let i = 0; i < n; i++) {
|
|
1321
|
+
const i3 = 3 * i;
|
|
1322
|
+
const i9 = 9 * i;
|
|
1323
|
+
const linAlpha = sigmoid$1(cop[i]);
|
|
1324
|
+
const sx = Math.max(Math.exp(cs0[i]), 1e-12);
|
|
1325
|
+
const sy = Math.max(Math.exp(cs1[i]), 1e-12);
|
|
1326
|
+
const sz = Math.max(Math.exp(cs2[i]), 1e-12);
|
|
1327
|
+
const vx = sx * sx + EPS_COV;
|
|
1328
|
+
const vy = sy * sy + EPS_COV;
|
|
1329
|
+
const vz = sz * sz + EPS_COV;
|
|
1330
|
+
v[i3] = vx;
|
|
1331
|
+
v[i3 + 1] = vy;
|
|
1332
|
+
v[i3 + 2] = vz;
|
|
1333
|
+
invdiag[i3] = 1 / Math.max(vx, 1e-30);
|
|
1334
|
+
invdiag[i3 + 1] = 1 / Math.max(vy, 1e-30);
|
|
1335
|
+
invdiag[i3 + 2] = 1 / Math.max(vz, 1e-30);
|
|
1336
|
+
logdet[i] = Math.log(Math.max(vx, 1e-30)) + Math.log(Math.max(vy, 1e-30)) + Math.log(Math.max(vz, 1e-30));
|
|
1337
|
+
// Normalize quaternion before building rotation
|
|
1338
|
+
let qw = cr0[i], qx = cr1[i], qy = cr2[i], qz = cr3[i];
|
|
1339
|
+
const qn = Math.hypot(qw, qx, qy, qz);
|
|
1340
|
+
const invq = 1 / Math.max(qn, 1e-12);
|
|
1341
|
+
qw *= invq;
|
|
1342
|
+
qx *= invq;
|
|
1343
|
+
qy *= invq;
|
|
1344
|
+
qz *= invq;
|
|
1345
|
+
quatToRotmat(qw, qx, qy, qz, R, i9);
|
|
1346
|
+
transpose3(R, i9, Rt, i9);
|
|
1347
|
+
sigmaFromRotVar(R, i9, vx, vy, vz, sigma, i9);
|
|
1348
|
+
mass[i] = TWO_PI_POW_1_5 * linAlpha * sx * sy * sz + 1e-12;
|
|
1349
|
+
}
|
|
1350
|
+
return { R, Rt, v, invdiag, logdet, sigma, mass };
|
|
1351
|
+
};
|
|
1352
|
+
// ====================== COST FUNCTION ======================
|
|
1353
|
+
const _Sigm = new Float64Array(9);
|
|
1354
|
+
const computeEdgeCost = (i, j, cx, cy, cz, cache, Z, appData, appColCount) => {
|
|
1355
|
+
const i3 = 3 * i, j3 = 3 * j;
|
|
1356
|
+
const i9 = 9 * i, j9 = 9 * j;
|
|
1357
|
+
const mux = cx[i], muy = cy[i], muz = cz[i];
|
|
1358
|
+
const mvx = cx[j], mvy = cy[j], mvz = cz[j];
|
|
1359
|
+
const wi = cache.mass[i], wj = cache.mass[j];
|
|
1360
|
+
const W = wi + wj;
|
|
1361
|
+
const Wsafe = W > 0 ? W : 1;
|
|
1362
|
+
let pi = wi / Wsafe;
|
|
1363
|
+
pi = Math.max(1e-12, Math.min(1 - 1e-12, pi));
|
|
1364
|
+
const pj = 1 - pi;
|
|
1365
|
+
const logPi = Math.log(pi);
|
|
1366
|
+
const logPj = Math.log(pj);
|
|
1367
|
+
// Merged mean
|
|
1368
|
+
const mmx = pi * mux + pj * mvx;
|
|
1369
|
+
const mmy = pi * muy + pj * mvy;
|
|
1370
|
+
const mmz = pi * muz + pj * mvz;
|
|
1371
|
+
const dix = mux - mmx, diy = muy - mmy, diz = muz - mmz;
|
|
1372
|
+
const djx = mvx - mmx, djy = mvy - mmy, djz = mvz - mmz;
|
|
1373
|
+
// Merged covariance (full 9-element, reuse preallocated buffer)
|
|
1374
|
+
for (let a = 0; a < 9; a++) {
|
|
1375
|
+
_Sigm[a] = pi * cache.sigma[i9 + a] + pj * cache.sigma[j9 + a];
|
|
1376
|
+
}
|
|
1377
|
+
_Sigm[0] += pi * dix * dix + pj * djx * djx;
|
|
1378
|
+
_Sigm[1] += pi * dix * diy + pj * djx * djy;
|
|
1379
|
+
_Sigm[2] += pi * dix * diz + pj * djx * djz;
|
|
1380
|
+
_Sigm[3] += pi * diy * dix + pj * djy * djx;
|
|
1381
|
+
_Sigm[4] += pi * diy * diy + pj * djy * djy;
|
|
1382
|
+
_Sigm[5] += pi * diy * diz + pj * djy * djz;
|
|
1383
|
+
_Sigm[6] += pi * diz * dix + pj * djz * djx;
|
|
1384
|
+
_Sigm[7] += pi * diz * diy + pj * djz * djy;
|
|
1385
|
+
_Sigm[8] += pi * diz * diz + pj * djz * djz;
|
|
1386
|
+
// Force symmetry + regularize
|
|
1387
|
+
_Sigm[1] = _Sigm[3] = 0.5 * (_Sigm[1] + _Sigm[3]);
|
|
1388
|
+
_Sigm[2] = _Sigm[6] = 0.5 * (_Sigm[2] + _Sigm[6]);
|
|
1389
|
+
_Sigm[5] = _Sigm[7] = 0.5 * (_Sigm[5] + _Sigm[7]);
|
|
1390
|
+
_Sigm[0] += EPS_COV;
|
|
1391
|
+
_Sigm[4] += EPS_COV;
|
|
1392
|
+
_Sigm[8] += EPS_COV;
|
|
1393
|
+
const detm = Math.max(det3(_Sigm, 0), 1e-30);
|
|
1394
|
+
const logdetm = Math.log(detm);
|
|
1395
|
+
// E_p[-log q_m] computed analytically as entropy of merged Gaussian
|
|
1396
|
+
const EpNegLogQ = 0.5 * (3 * LOG2PI + logdetm + 3);
|
|
1397
|
+
// Sample from each component separately with same z-vectors
|
|
1398
|
+
const stdix = Math.sqrt(Math.max(cache.v[i3], 0));
|
|
1399
|
+
const stdiy = Math.sqrt(Math.max(cache.v[i3 + 1], 0));
|
|
1400
|
+
const stdiz = Math.sqrt(Math.max(cache.v[i3 + 2], 0));
|
|
1401
|
+
const stdjx = Math.sqrt(Math.max(cache.v[j3], 0));
|
|
1402
|
+
const stdjy = Math.sqrt(Math.max(cache.v[j3 + 1], 0));
|
|
1403
|
+
const stdjz = Math.sqrt(Math.max(cache.v[j3 + 2], 0));
|
|
1404
|
+
let sumLogpOnI = 0;
|
|
1405
|
+
let sumLogpOnJ = 0;
|
|
1406
|
+
for (let s = 0; s < Z.length; s++) {
|
|
1407
|
+
const z0 = Z[s][0], z1 = Z[s][1], z2 = Z[s][2];
|
|
1408
|
+
// x_i = mu_i + R_i^T * diag(std_i) * z
|
|
1409
|
+
const xix = mux + z0 * stdix * cache.Rt[i9] + z1 * stdiy * cache.Rt[i9 + 3] + z2 * stdiz * cache.Rt[i9 + 6];
|
|
1410
|
+
const xiy = muy + z0 * stdix * cache.Rt[i9 + 1] + z1 * stdiy * cache.Rt[i9 + 4] + z2 * stdiz * cache.Rt[i9 + 7];
|
|
1411
|
+
const xiz = muz + z0 * stdix * cache.Rt[i9 + 2] + z1 * stdiy * cache.Rt[i9 + 5] + z2 * stdiz * cache.Rt[i9 + 8];
|
|
1412
|
+
// x_j = mu_j + R_j^T * diag(std_j) * z
|
|
1413
|
+
const xjx = mvx + z0 * stdjx * cache.Rt[j9] + z1 * stdjy * cache.Rt[j9 + 3] + z2 * stdjz * cache.Rt[j9 + 6];
|
|
1414
|
+
const xjy = mvy + z0 * stdjx * cache.Rt[j9 + 1] + z1 * stdjy * cache.Rt[j9 + 4] + z2 * stdjz * cache.Rt[j9 + 7];
|
|
1415
|
+
const xjz = mvz + z0 * stdjx * cache.Rt[j9 + 2] + z1 * stdjy * cache.Rt[j9 + 5] + z2 * stdjz * cache.Rt[j9 + 8];
|
|
1416
|
+
// Evaluate log p_ij at samples from component i
|
|
1417
|
+
const logNiOnI = gaussLogpdfDiagrot(xix, xiy, xiz, mux, muy, muz, cache.R, i9, cache.invdiag[i3], cache.invdiag[i3 + 1], cache.invdiag[i3 + 2], cache.logdet[i]);
|
|
1418
|
+
const logNjOnI = gaussLogpdfDiagrot(xix, xiy, xiz, mvx, mvy, mvz, cache.R, j9, cache.invdiag[j3], cache.invdiag[j3 + 1], cache.invdiag[j3 + 2], cache.logdet[j]);
|
|
1419
|
+
sumLogpOnI += logAddExp(logPi + logNiOnI, logPj + logNjOnI);
|
|
1420
|
+
// Evaluate log p_ij at samples from component j
|
|
1421
|
+
const logNiOnJ = gaussLogpdfDiagrot(xjx, xjy, xjz, mux, muy, muz, cache.R, i9, cache.invdiag[i3], cache.invdiag[i3 + 1], cache.invdiag[i3 + 2], cache.logdet[i]);
|
|
1422
|
+
const logNjOnJ = gaussLogpdfDiagrot(xjx, xjy, xjz, mvx, mvy, mvz, cache.R, j9, cache.invdiag[j3], cache.invdiag[j3 + 1], cache.invdiag[j3 + 2], cache.logdet[j]);
|
|
1423
|
+
sumLogpOnJ += logAddExp(logPi + logNiOnJ, logPj + logNjOnJ);
|
|
1424
|
+
}
|
|
1425
|
+
const Ei = sumLogpOnI / Z.length;
|
|
1426
|
+
const Ej = sumLogpOnJ / Z.length;
|
|
1427
|
+
const EpLogp = pi * Ei + pj * Ej;
|
|
1428
|
+
const geo = EpLogp + EpNegLogQ;
|
|
1429
|
+
// Appearance cost
|
|
1430
|
+
let cSh = 0;
|
|
1431
|
+
for (let k = 0; k < appColCount; k++) {
|
|
1432
|
+
const d = appData[k][i] - appData[k][j];
|
|
1433
|
+
cSh += d * d;
|
|
1434
|
+
}
|
|
1435
|
+
return geo + cSh;
|
|
1436
|
+
};
|
|
1437
|
+
// ====================== MERGE (MPMM) ======================
|
|
1438
|
+
const momentMatch = (i, j, cx, cy, cz, cop, cs0, cs1, cs2, cr0, cr1, cr2, cr3, out, appData, appColCount) => {
|
|
1439
|
+
const sxi = Math.max(Math.exp(cs0[i]), 1e-12);
|
|
1440
|
+
const syi = Math.max(Math.exp(cs1[i]), 1e-12);
|
|
1441
|
+
const szi = Math.max(Math.exp(cs2[i]), 1e-12);
|
|
1442
|
+
const sxj = Math.max(Math.exp(cs0[j]), 1e-12);
|
|
1443
|
+
const syj = Math.max(Math.exp(cs1[j]), 1e-12);
|
|
1444
|
+
const szj = Math.max(Math.exp(cs2[j]), 1e-12);
|
|
1445
|
+
const alphaI = sigmoid$1(cop[i]);
|
|
1446
|
+
const alphaJ = sigmoid$1(cop[j]);
|
|
1447
|
+
const wi = TWO_PI_POW_1_5 * alphaI * sxi * syi * szi + 1e-12;
|
|
1448
|
+
const wj = TWO_PI_POW_1_5 * alphaJ * sxj * syj * szj + 1e-12;
|
|
1449
|
+
const W = Math.max(wi + wj, 1e-12);
|
|
1450
|
+
// Merged mean
|
|
1451
|
+
const mux = (wi * cx[i] + wj * cx[j]) / W;
|
|
1452
|
+
const muy = (wi * cy[i] + wj * cy[j]) / W;
|
|
1453
|
+
const muz = (wi * cz[i] + wj * cz[j]) / W;
|
|
1454
|
+
// Build per-splat covariance matrices
|
|
1455
|
+
const SigI = new Float64Array(9);
|
|
1456
|
+
const SigJ = new Float64Array(9);
|
|
1457
|
+
const Ri = new Float64Array(9);
|
|
1458
|
+
const Rj = new Float64Array(9);
|
|
1459
|
+
let qwi = cr0[i], qxi = cr1[i], qyi = cr2[i], qzi = cr3[i];
|
|
1460
|
+
let ni = Math.hypot(qwi, qxi, qyi, qzi);
|
|
1461
|
+
ni = 1 / Math.max(ni, 1e-12);
|
|
1462
|
+
qwi *= ni;
|
|
1463
|
+
qxi *= ni;
|
|
1464
|
+
qyi *= ni;
|
|
1465
|
+
qzi *= ni;
|
|
1466
|
+
let qwj = cr0[j], qxj = cr1[j], qyj = cr2[j], qzj = cr3[j];
|
|
1467
|
+
let nj = Math.hypot(qwj, qxj, qyj, qzj);
|
|
1468
|
+
nj = 1 / Math.max(nj, 1e-12);
|
|
1469
|
+
qwj *= nj;
|
|
1470
|
+
qxj *= nj;
|
|
1471
|
+
qyj *= nj;
|
|
1472
|
+
qzj *= nj;
|
|
1473
|
+
quatToRotmat(qwi, qxi, qyi, qzi, Ri, 0);
|
|
1474
|
+
quatToRotmat(qwj, qxj, qyj, qzj, Rj, 0);
|
|
1475
|
+
sigmaFromRotVar(Ri, 0, sxi * sxi, syi * syi, szi * szi, SigI, 0);
|
|
1476
|
+
sigmaFromRotVar(Rj, 0, sxj * sxj, syj * syj, szj * szj, SigJ, 0);
|
|
1477
|
+
const dix = cx[i] - mux, diy = cy[i] - muy, diz = cz[i] - muz;
|
|
1478
|
+
const djx = cx[j] - mux, djy = cy[j] - muy, djz = cz[j] - muz;
|
|
1479
|
+
// Merged covariance
|
|
1480
|
+
const Sig = new Float64Array(9);
|
|
1481
|
+
for (let a = 0; a < 9; a++) {
|
|
1482
|
+
Sig[a] = (wi * SigI[a] + wj * SigJ[a]) / W;
|
|
1483
|
+
}
|
|
1484
|
+
Sig[0] += (wi * dix * dix + wj * djx * djx) / W;
|
|
1485
|
+
Sig[1] += (wi * dix * diy + wj * djx * djy) / W;
|
|
1486
|
+
Sig[2] += (wi * dix * diz + wj * djx * djz) / W;
|
|
1487
|
+
Sig[3] += (wi * diy * dix + wj * djy * djx) / W;
|
|
1488
|
+
Sig[4] += (wi * diy * diy + wj * djy * djy) / W;
|
|
1489
|
+
Sig[5] += (wi * diy * diz + wj * djy * djz) / W;
|
|
1490
|
+
Sig[6] += (wi * diz * dix + wj * djz * djx) / W;
|
|
1491
|
+
Sig[7] += (wi * diz * diy + wj * djz * djy) / W;
|
|
1492
|
+
Sig[8] += (wi * diz * diz + wj * djz * djz) / W;
|
|
1493
|
+
// Force symmetry + regularize
|
|
1494
|
+
Sig[1] = Sig[3] = 0.5 * (Sig[1] + Sig[3]);
|
|
1495
|
+
Sig[2] = Sig[6] = 0.5 * (Sig[2] + Sig[6]);
|
|
1496
|
+
Sig[5] = Sig[7] = 0.5 * (Sig[5] + Sig[7]);
|
|
1497
|
+
Sig[0] += EPS_COV;
|
|
1498
|
+
Sig[4] += EPS_COV;
|
|
1499
|
+
Sig[8] += EPS_COV;
|
|
1500
|
+
// Eigendecomposition
|
|
1501
|
+
const ev = eigenSymmetric3x3(Sig);
|
|
1502
|
+
let vals = ev.values;
|
|
1503
|
+
const vecs = ev.vectors;
|
|
1504
|
+
// Sort eigenvalues descending
|
|
1505
|
+
const order = [0, 1, 2].sort((a, b) => vals[b] - vals[a]);
|
|
1506
|
+
vals = order.map(k => Math.max(vals[k], 1e-18));
|
|
1507
|
+
// Build rotation matrix with sorted eigenvectors as columns
|
|
1508
|
+
const Rm = new Float64Array(9);
|
|
1509
|
+
for (let c = 0; c < 3; c++) {
|
|
1510
|
+
const src = order[c];
|
|
1511
|
+
Rm[0 + c] = vecs[0 + src];
|
|
1512
|
+
Rm[3 + c] = vecs[3 + src];
|
|
1513
|
+
Rm[6 + c] = vecs[6 + src];
|
|
1514
|
+
}
|
|
1515
|
+
if (det3(Rm, 0) < 0) {
|
|
1516
|
+
Rm[2] *= -1;
|
|
1517
|
+
Rm[5] *= -1;
|
|
1518
|
+
Rm[8] *= -1;
|
|
1519
|
+
}
|
|
1520
|
+
const q = rotmatToQuat(Rm, 0);
|
|
1521
|
+
out.mu[0] = mux;
|
|
1522
|
+
out.mu[1] = muy;
|
|
1523
|
+
out.mu[2] = muz;
|
|
1524
|
+
out.sc[0] = Math.log(Math.sqrt(vals[0]));
|
|
1525
|
+
out.sc[1] = Math.log(Math.sqrt(vals[1]));
|
|
1526
|
+
out.sc[2] = Math.log(Math.sqrt(vals[2]));
|
|
1527
|
+
out.q[0] = q[0];
|
|
1528
|
+
out.q[1] = q[1];
|
|
1529
|
+
out.q[2] = q[2];
|
|
1530
|
+
out.q[3] = q[3];
|
|
1531
|
+
// Porter-Duff over opacity
|
|
1532
|
+
out.op = alphaI + alphaJ - alphaI * alphaJ;
|
|
1533
|
+
// Mass-weighted appearance
|
|
1534
|
+
for (let k = 0; k < appColCount; k++) {
|
|
1535
|
+
out.sh[k] = (wi * appData[k][i] + wj * appData[k][j]) / W;
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
// ====================== SORT BY VISIBILITY (legacy) ======================
|
|
949
1539
|
const sortByVisibility = (dataTable, indices) => {
|
|
950
1540
|
const opacityCol = dataTable.getColumnByName('opacity');
|
|
951
1541
|
const scale0Col = dataTable.getColumnByName('scale_0');
|
|
@@ -955,37 +1545,253 @@ const sortByVisibility = (dataTable, indices) => {
|
|
|
955
1545
|
logger.debug('missing required columns for visibility sorting (opacity, scale_0, scale_1, scale_2)');
|
|
956
1546
|
return;
|
|
957
1547
|
}
|
|
958
|
-
if (indices.length === 0)
|
|
1548
|
+
if (indices.length === 0)
|
|
959
1549
|
return;
|
|
960
|
-
}
|
|
961
1550
|
const opacity = opacityCol.data;
|
|
962
1551
|
const scale0 = scale0Col.data;
|
|
963
1552
|
const scale1 = scale1Col.data;
|
|
964
1553
|
const scale2 = scale2Col.data;
|
|
965
|
-
// Compute visibility scores for each splat
|
|
966
1554
|
const scores = new Float32Array(indices.length);
|
|
967
1555
|
for (let i = 0; i < indices.length; i++) {
|
|
968
1556
|
const ri = indices[i];
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const linearOpacity = 1 / (1 + Math.exp(-logitOpacity));
|
|
972
|
-
// Convert log scales to linear and compute volume
|
|
973
|
-
// volume = exp(scale_0) * exp(scale_1) * exp(scale_2) = exp(scale_0 + scale_1 + scale_2)
|
|
974
|
-
const volume = Math.exp(scale0[ri] + scale1[ri] + scale2[ri]);
|
|
975
|
-
// Visibility score is opacity * volume
|
|
976
|
-
scores[i] = linearOpacity * volume;
|
|
977
|
-
}
|
|
978
|
-
// Sort indices by score (descending - most visible first)
|
|
1557
|
+
scores[i] = (1 / (1 + Math.exp(-opacity[ri]))) * Math.exp(scale0[ri] + scale1[ri] + scale2[ri]);
|
|
1558
|
+
}
|
|
979
1559
|
const order = new Uint32Array(indices.length);
|
|
980
|
-
for (let i = 0; i < order.length; i++)
|
|
1560
|
+
for (let i = 0; i < order.length; i++)
|
|
981
1561
|
order[i] = i;
|
|
982
|
-
}
|
|
983
1562
|
order.sort((a, b) => scores[b] - scores[a]);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1563
|
+
const tmp = indices.slice();
|
|
1564
|
+
for (let i = 0; i < indices.length; i++)
|
|
1565
|
+
indices[i] = tmp[order[i]];
|
|
1566
|
+
};
|
|
1567
|
+
// ====================== MAIN: simplifyGaussians ======================
|
|
1568
|
+
/**
|
|
1569
|
+
* Simplifies a Gaussian splat DataTable to a target number of splats using the
|
|
1570
|
+
* NanoGS progressive pairwise merging algorithm.
|
|
1571
|
+
*
|
|
1572
|
+
* Reference: "NanoGS: Training-Free Gaussian Splat Simplification" (Xiong et al.)
|
|
1573
|
+
*
|
|
1574
|
+
* @param dataTable - The input splat DataTable.
|
|
1575
|
+
* @param targetCount - The desired number of output splats.
|
|
1576
|
+
* @returns A new DataTable with approximately `targetCount` splats.
|
|
1577
|
+
*/
|
|
1578
|
+
const simplifyGaussians = (dataTable, targetCount) => {
|
|
1579
|
+
const N = dataTable.numRows;
|
|
1580
|
+
if (N <= targetCount || targetCount <= 0) {
|
|
1581
|
+
return targetCount <= 0 ? dataTable.permuteRows([]) : dataTable;
|
|
1582
|
+
}
|
|
1583
|
+
const requiredCols = ['x', 'y', 'z', 'opacity', 'scale_0', 'scale_1', 'scale_2',
|
|
1584
|
+
'rot_0', 'rot_1', 'rot_2', 'rot_3'];
|
|
1585
|
+
for (const name of requiredCols) {
|
|
1586
|
+
if (!dataTable.hasColumn(name)) {
|
|
1587
|
+
logger.debug(`simplifyGaussians: missing required column '${name}', falling back to visibility pruning`);
|
|
1588
|
+
const indices = new Uint32Array(N);
|
|
1589
|
+
for (let i = 0; i < N; i++)
|
|
1590
|
+
indices[i] = i;
|
|
1591
|
+
sortByVisibility(dataTable, indices);
|
|
1592
|
+
return dataTable.permuteRows(indices.subarray(0, targetCount));
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
// Identify appearance columns
|
|
1596
|
+
const allAppearanceCols = [];
|
|
1597
|
+
for (const name of ['f_dc_0', 'f_dc_1', 'f_dc_2']) {
|
|
1598
|
+
if (dataTable.hasColumn(name))
|
|
1599
|
+
allAppearanceCols.push(name);
|
|
1600
|
+
}
|
|
1601
|
+
for (let i = 0; i < 45; i++) {
|
|
1602
|
+
const name = `f_rest_${i}`;
|
|
1603
|
+
if (dataTable.hasColumn(name))
|
|
1604
|
+
allAppearanceCols.push(name);
|
|
1605
|
+
}
|
|
1606
|
+
// Step 1: Opacity pruning
|
|
1607
|
+
const opacityData = dataTable.getColumnByName('opacity').data;
|
|
1608
|
+
const opsSorted = new Array(N);
|
|
1609
|
+
for (let i = 0; i < N; i++)
|
|
1610
|
+
opsSorted[i] = sigmoid$1(opacityData[i]);
|
|
1611
|
+
opsSorted.sort((a, b) => a - b);
|
|
1612
|
+
const median = opsSorted[N >> 1];
|
|
1613
|
+
const pruneThreshold = Math.min(OPACITY_PRUNE_THRESHOLD, median);
|
|
1614
|
+
const keptIndices = [];
|
|
1615
|
+
for (let i = 0; i < N; i++) {
|
|
1616
|
+
if (sigmoid$1(opacityData[i]) >= pruneThreshold)
|
|
1617
|
+
keptIndices.push(i);
|
|
1618
|
+
}
|
|
1619
|
+
let current;
|
|
1620
|
+
if (keptIndices.length < N && keptIndices.length > targetCount) {
|
|
1621
|
+
current = dataTable.permuteRows(keptIndices);
|
|
1622
|
+
logger.debug(`opacity pruning: ${N} -> ${current.numRows} splats (threshold=${pruneThreshold.toFixed(4)})`);
|
|
988
1623
|
}
|
|
1624
|
+
else {
|
|
1625
|
+
current = dataTable;
|
|
1626
|
+
}
|
|
1627
|
+
// Pre-generate MC samples
|
|
1628
|
+
const Z = makeGaussianSamples(MC_SAMPLES, 0);
|
|
1629
|
+
// Step 2: Iterative merging
|
|
1630
|
+
while (current.numRows > targetCount) {
|
|
1631
|
+
const n = current.numRows;
|
|
1632
|
+
const kEff = Math.min(Math.max(1, KNN_K), Math.max(1, n - 1));
|
|
1633
|
+
logger.debug(`merging iteration: ${n} -> ${targetCount} splats`);
|
|
1634
|
+
const cx = current.getColumnByName('x').data;
|
|
1635
|
+
const cy = current.getColumnByName('y').data;
|
|
1636
|
+
const cz = current.getColumnByName('z').data;
|
|
1637
|
+
const cop = current.getColumnByName('opacity').data;
|
|
1638
|
+
const cs0 = current.getColumnByName('scale_0').data;
|
|
1639
|
+
const cs1 = current.getColumnByName('scale_1').data;
|
|
1640
|
+
const cs2 = current.getColumnByName('scale_2').data;
|
|
1641
|
+
const cr0 = current.getColumnByName('rot_0').data;
|
|
1642
|
+
const cr1 = current.getColumnByName('rot_1').data;
|
|
1643
|
+
const cr2 = current.getColumnByName('rot_2').data;
|
|
1644
|
+
const cr3 = current.getColumnByName('rot_3').data;
|
|
1645
|
+
const cache = buildPerSplatCache(n, cx, cy, cz, cop, cs0, cs1, cs2, cr0, cr1, cr2, cr3);
|
|
1646
|
+
// Build KNN graph
|
|
1647
|
+
const posTable = new DataTable([
|
|
1648
|
+
new Column('x', cx instanceof Float32Array ? cx : new Float32Array(cx)),
|
|
1649
|
+
new Column('y', cy instanceof Float32Array ? cy : new Float32Array(cy)),
|
|
1650
|
+
new Column('z', cz instanceof Float32Array ? cz : new Float32Array(cz))
|
|
1651
|
+
]);
|
|
1652
|
+
const kdTree = new KdTree(posTable);
|
|
1653
|
+
const edgeSet = new Set();
|
|
1654
|
+
const edges = [];
|
|
1655
|
+
const queryPoint = new Float32Array(3);
|
|
1656
|
+
for (let i = 0; i < n; i++) {
|
|
1657
|
+
queryPoint[0] = cx[i];
|
|
1658
|
+
queryPoint[1] = cy[i];
|
|
1659
|
+
queryPoint[2] = cz[i];
|
|
1660
|
+
const knn = kdTree.findKNearest(queryPoint, kEff + 1);
|
|
1661
|
+
for (let ki = 0; ki < knn.indices.length; ki++) {
|
|
1662
|
+
const j = knn.indices[ki];
|
|
1663
|
+
if (j === i || j < 0)
|
|
1664
|
+
continue;
|
|
1665
|
+
const u = Math.min(i, j);
|
|
1666
|
+
const v = Math.max(i, j);
|
|
1667
|
+
const key = `${u},${v}`;
|
|
1668
|
+
if (!edgeSet.has(key)) {
|
|
1669
|
+
edgeSet.add(key);
|
|
1670
|
+
edges.push([u, v]);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (edges.length === 0)
|
|
1675
|
+
break;
|
|
1676
|
+
// Compute edge costs
|
|
1677
|
+
const appData = [];
|
|
1678
|
+
for (let ai = 0; ai < allAppearanceCols.length; ai++) {
|
|
1679
|
+
const col = current.getColumnByName(allAppearanceCols[ai]);
|
|
1680
|
+
if (col)
|
|
1681
|
+
appData.push(col.data);
|
|
1682
|
+
}
|
|
1683
|
+
const costs = new Float32Array(edges.length);
|
|
1684
|
+
for (let e = 0; e < edges.length; e++) {
|
|
1685
|
+
costs[e] = computeEdgeCost(edges[e][0], edges[e][1], cx, cy, cz, cache, Z, appData, appData.length);
|
|
1686
|
+
}
|
|
1687
|
+
// Greedy disjoint pair selection
|
|
1688
|
+
const valid = [];
|
|
1689
|
+
for (let i = 0; i < edges.length; i++) {
|
|
1690
|
+
if (Number.isFinite(costs[i]))
|
|
1691
|
+
valid.push(i);
|
|
1692
|
+
}
|
|
1693
|
+
valid.sort((a, b) => {
|
|
1694
|
+
const d = costs[a] - costs[b];
|
|
1695
|
+
return d !== 0 ? d : a - b;
|
|
1696
|
+
});
|
|
1697
|
+
const mergesNeeded = n - targetCount;
|
|
1698
|
+
const used = new Uint8Array(n);
|
|
1699
|
+
const pairs = [];
|
|
1700
|
+
for (let t = 0; t < valid.length; t++) {
|
|
1701
|
+
const e = valid[t];
|
|
1702
|
+
const u = edges[e][0], v = edges[e][1];
|
|
1703
|
+
if (used[u] || used[v])
|
|
1704
|
+
continue;
|
|
1705
|
+
used[u] = 1;
|
|
1706
|
+
used[v] = 1;
|
|
1707
|
+
pairs.push([u, v]);
|
|
1708
|
+
if (pairs.length >= mergesNeeded)
|
|
1709
|
+
break;
|
|
1710
|
+
}
|
|
1711
|
+
if (pairs.length === 0)
|
|
1712
|
+
break;
|
|
1713
|
+
logger.debug(`selected ${pairs.length} merge pairs from ${edges.length} edges`);
|
|
1714
|
+
// Mark which indices are consumed by merging
|
|
1715
|
+
const usedSet = new Uint8Array(n);
|
|
1716
|
+
for (let p = 0; p < pairs.length; p++) {
|
|
1717
|
+
usedSet[pairs[p][0]] = 1;
|
|
1718
|
+
usedSet[pairs[p][1]] = 1;
|
|
1719
|
+
}
|
|
1720
|
+
const keepIndices = [];
|
|
1721
|
+
for (let i = 0; i < n; i++) {
|
|
1722
|
+
if (!usedSet[i])
|
|
1723
|
+
keepIndices.push(i);
|
|
1724
|
+
}
|
|
1725
|
+
const outCount = keepIndices.length + pairs.length;
|
|
1726
|
+
const cols = current.columns;
|
|
1727
|
+
const newColumns = [];
|
|
1728
|
+
for (let ci = 0; ci < cols.length; ci++) {
|
|
1729
|
+
const c = cols[ci];
|
|
1730
|
+
newColumns.push(new Column(c.name, new c.data.constructor(outCount)));
|
|
1731
|
+
}
|
|
1732
|
+
const newTable = new DataTable(newColumns);
|
|
1733
|
+
// Copy unmerged splats
|
|
1734
|
+
let dst = 0;
|
|
1735
|
+
for (let t = 0; t < keepIndices.length; t++, dst++) {
|
|
1736
|
+
const src = keepIndices[t];
|
|
1737
|
+
for (let c = 0; c < cols.length; c++) {
|
|
1738
|
+
newTable.columns[c].data[dst] = cols[c].data[src];
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
// Merge pairs -- cache column refs and handled set once
|
|
1742
|
+
const mergeOut = {
|
|
1743
|
+
mu: new Float64Array(3),
|
|
1744
|
+
sc: new Float64Array(3),
|
|
1745
|
+
q: new Float64Array(4),
|
|
1746
|
+
op: 0,
|
|
1747
|
+
sh: new Float64Array(allAppearanceCols.length)
|
|
1748
|
+
};
|
|
1749
|
+
const dstXCol = newTable.getColumnByName('x');
|
|
1750
|
+
const dstYCol = newTable.getColumnByName('y');
|
|
1751
|
+
const dstZCol = newTable.getColumnByName('z');
|
|
1752
|
+
const dstS0Col = newTable.getColumnByName('scale_0');
|
|
1753
|
+
const dstS1Col = newTable.getColumnByName('scale_1');
|
|
1754
|
+
const dstS2Col = newTable.getColumnByName('scale_2');
|
|
1755
|
+
const dstR0Col = newTable.getColumnByName('rot_0');
|
|
1756
|
+
const dstR1Col = newTable.getColumnByName('rot_1');
|
|
1757
|
+
const dstR2Col = newTable.getColumnByName('rot_2');
|
|
1758
|
+
const dstR3Col = newTable.getColumnByName('rot_3');
|
|
1759
|
+
const dstOpCol = newTable.getColumnByName('opacity');
|
|
1760
|
+
const dstAppCols = allAppearanceCols.map(name => newTable.getColumnByName(name));
|
|
1761
|
+
const handledCols = new Set([
|
|
1762
|
+
'x', 'y', 'z', 'opacity', 'scale_0', 'scale_1', 'scale_2',
|
|
1763
|
+
'rot_0', 'rot_1', 'rot_2', 'rot_3', ...allAppearanceCols
|
|
1764
|
+
]);
|
|
1765
|
+
const unhandledColPairs = cols
|
|
1766
|
+
.filter(col => !handledCols.has(col.name))
|
|
1767
|
+
.map(col => ({ src: col, dst: newTable.getColumnByName(col.name) }))
|
|
1768
|
+
.filter(pair => pair.dst);
|
|
1769
|
+
for (let p = 0; p < pairs.length; p++, dst++) {
|
|
1770
|
+
const pi = pairs[p][0], pj = pairs[p][1];
|
|
1771
|
+
momentMatch(pi, pj, cx, cy, cz, cop, cs0, cs1, cs2, cr0, cr1, cr2, cr3, mergeOut, appData, appData.length);
|
|
1772
|
+
dstXCol.data[dst] = mergeOut.mu[0];
|
|
1773
|
+
dstYCol.data[dst] = mergeOut.mu[1];
|
|
1774
|
+
dstZCol.data[dst] = mergeOut.mu[2];
|
|
1775
|
+
dstS0Col.data[dst] = mergeOut.sc[0];
|
|
1776
|
+
dstS1Col.data[dst] = mergeOut.sc[1];
|
|
1777
|
+
dstS2Col.data[dst] = mergeOut.sc[2];
|
|
1778
|
+
dstR0Col.data[dst] = mergeOut.q[0];
|
|
1779
|
+
dstR1Col.data[dst] = mergeOut.q[1];
|
|
1780
|
+
dstR2Col.data[dst] = mergeOut.q[2];
|
|
1781
|
+
dstR3Col.data[dst] = mergeOut.q[3];
|
|
1782
|
+
dstOpCol.data[dst] = logit(Math.max(0, Math.min(1, mergeOut.op)));
|
|
1783
|
+
for (let k = 0; k < dstAppCols.length; k++) {
|
|
1784
|
+
if (dstAppCols[k])
|
|
1785
|
+
dstAppCols[k].data[dst] = mergeOut.sh[k];
|
|
1786
|
+
}
|
|
1787
|
+
const dominant = cache.mass[pi] >= cache.mass[pj] ? pi : pj;
|
|
1788
|
+
for (let u = 0; u < unhandledColPairs.length; u++) {
|
|
1789
|
+
unhandledColPairs[u].dst.data[dst] = unhandledColPairs[u].src.data[dominant];
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
current = newTable;
|
|
1793
|
+
}
|
|
1794
|
+
return current;
|
|
989
1795
|
};
|
|
990
1796
|
|
|
991
1797
|
/**
|
|
@@ -4810,7 +5616,7 @@ class CompressedChunk {
|
|
|
4810
5616
|
}
|
|
4811
5617
|
}
|
|
4812
5618
|
|
|
4813
|
-
var version = "1.
|
|
5619
|
+
var version = "1.9.0";
|
|
4814
5620
|
|
|
4815
5621
|
const generatedByString = `Generated by splat-transform ${version}`;
|
|
4816
5622
|
const chunkProps = [
|
|
@@ -4945,6 +5751,341 @@ const writeCsv = async (options, fs) => {
|
|
|
4945
5751
|
await writer.close();
|
|
4946
5752
|
};
|
|
4947
5753
|
|
|
5754
|
+
const SH_C0$1 = 0.2820947917738781;
|
|
5755
|
+
const shRestNames = new Array(45).fill('').map((_, i) => `f_rest_${i}`);
|
|
5756
|
+
// glTF accessor component types
|
|
5757
|
+
const FLOAT = 5126;
|
|
5758
|
+
const UNSIGNED_BYTE = 5121;
|
|
5759
|
+
// glTF buffer view targets
|
|
5760
|
+
const ARRAY_BUFFER = 34962;
|
|
5761
|
+
// GLB chunk types
|
|
5762
|
+
const GLB_MAGIC = 0x46546C67;
|
|
5763
|
+
const GLB_VERSION = 2;
|
|
5764
|
+
const JSON_CHUNK_TYPE = 0x4E4F534A;
|
|
5765
|
+
const BIN_CHUNK_TYPE = 0x004E4942;
|
|
5766
|
+
/**
|
|
5767
|
+
* Determines how many SH bands (0-3) the DataTable contains beyond the DC term.
|
|
5768
|
+
* Band detection uses the same channel-major layout as the rest of the codebase:
|
|
5769
|
+
* N coefficients per channel, 3 channels, stored as f_rest_0..f_rest_(3N-1).
|
|
5770
|
+
*
|
|
5771
|
+
* @param dataTable - The DataTable to inspect.
|
|
5772
|
+
* @returns The number of SH bands (0-3).
|
|
5773
|
+
*/
|
|
5774
|
+
const getSHBands = (dataTable) => {
|
|
5775
|
+
const idx = shRestNames.findIndex(v => !dataTable.hasColumn(v));
|
|
5776
|
+
return { '9': 1, '24': 2, '-1': 3 }[String(idx)] ?? 0;
|
|
5777
|
+
};
|
|
5778
|
+
/**
|
|
5779
|
+
* Computes POSITION accessor min/max bounds required by the glTF spec.
|
|
5780
|
+
*
|
|
5781
|
+
* @param x - X position data.
|
|
5782
|
+
* @param y - Y position data.
|
|
5783
|
+
* @param z - Z position data.
|
|
5784
|
+
* @returns Object with min and max arrays.
|
|
5785
|
+
*/
|
|
5786
|
+
const computePositionBounds = (x, y, z) => {
|
|
5787
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
5788
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
5789
|
+
for (let i = 0; i < x.length; i++) {
|
|
5790
|
+
if (x[i] < minX)
|
|
5791
|
+
minX = x[i];
|
|
5792
|
+
if (x[i] > maxX)
|
|
5793
|
+
maxX = x[i];
|
|
5794
|
+
if (y[i] < minY)
|
|
5795
|
+
minY = y[i];
|
|
5796
|
+
if (y[i] > maxY)
|
|
5797
|
+
maxY = y[i];
|
|
5798
|
+
if (z[i] < minZ)
|
|
5799
|
+
minZ = z[i];
|
|
5800
|
+
if (z[i] > maxZ)
|
|
5801
|
+
maxZ = z[i];
|
|
5802
|
+
}
|
|
5803
|
+
return {
|
|
5804
|
+
min: [minX, minY, minZ],
|
|
5805
|
+
max: [maxX, maxY, maxZ]
|
|
5806
|
+
};
|
|
5807
|
+
};
|
|
5808
|
+
/**
|
|
5809
|
+
* Pads a byte length up to the next multiple of 4.
|
|
5810
|
+
*
|
|
5811
|
+
* @param n - The byte length to align.
|
|
5812
|
+
* @returns The aligned byte length.
|
|
5813
|
+
*/
|
|
5814
|
+
const align4 = (n) => (n + 3) & -4;
|
|
5815
|
+
/**
|
|
5816
|
+
* Builds the binary buffer containing all attribute data, and returns the
|
|
5817
|
+
* list of buffer views / accessor descriptions needed for the glTF JSON.
|
|
5818
|
+
*
|
|
5819
|
+
* @param dataTable - The DataTable containing splat data.
|
|
5820
|
+
* @param numSplats - Number of splats in the table.
|
|
5821
|
+
* @param shBands - Number of SH bands beyond DC (0-3).
|
|
5822
|
+
* @returns Object with segments, offsets, and the assembled binary buffer.
|
|
5823
|
+
*/
|
|
5824
|
+
const buildBinaryBuffer = (dataTable, numSplats, shBands) => {
|
|
5825
|
+
const x = dataTable.getColumnByName('x').data;
|
|
5826
|
+
const y = dataTable.getColumnByName('y').data;
|
|
5827
|
+
const z = dataTable.getColumnByName('z').data;
|
|
5828
|
+
const rot0 = dataTable.getColumnByName('rot_0').data;
|
|
5829
|
+
const rot1 = dataTable.getColumnByName('rot_1').data;
|
|
5830
|
+
const rot2 = dataTable.getColumnByName('rot_2').data;
|
|
5831
|
+
const rot3 = dataTable.getColumnByName('rot_3').data;
|
|
5832
|
+
const scale0 = dataTable.getColumnByName('scale_0').data;
|
|
5833
|
+
const scale1 = dataTable.getColumnByName('scale_1').data;
|
|
5834
|
+
const scale2 = dataTable.getColumnByName('scale_2').data;
|
|
5835
|
+
const opacity = dataTable.getColumnByName('opacity').data;
|
|
5836
|
+
const fdc0 = dataTable.getColumnByName('f_dc_0').data;
|
|
5837
|
+
const fdc1 = dataTable.getColumnByName('f_dc_1').data;
|
|
5838
|
+
const fdc2 = dataTable.getColumnByName('f_dc_2').data;
|
|
5839
|
+
// Coefficients per channel for each SH band beyond DC
|
|
5840
|
+
const coeffsPerChannel = [0, 3, 8, 15][shBands];
|
|
5841
|
+
const shCoefCount = [0, 3, 5, 7]; // per-degree coefficient count
|
|
5842
|
+
const shDegrees = shBands; // degrees 1..shDegrees have rest coefficients
|
|
5843
|
+
const segments = [];
|
|
5844
|
+
// POSITION (VEC3 float)
|
|
5845
|
+
const posData = new Float32Array(numSplats * 3);
|
|
5846
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5847
|
+
posData[i * 3] = x[i];
|
|
5848
|
+
posData[i * 3 + 1] = y[i];
|
|
5849
|
+
posData[i * 3 + 2] = z[i];
|
|
5850
|
+
}
|
|
5851
|
+
const bounds = computePositionBounds(x, y, z);
|
|
5852
|
+
segments.push({
|
|
5853
|
+
name: 'POSITION',
|
|
5854
|
+
data: posData.buffer,
|
|
5855
|
+
componentType: FLOAT,
|
|
5856
|
+
type: 'VEC3',
|
|
5857
|
+
count: numSplats,
|
|
5858
|
+
min: bounds.min,
|
|
5859
|
+
max: bounds.max
|
|
5860
|
+
});
|
|
5861
|
+
// COLOR_0 fallback (VEC4 unsigned byte normalized)
|
|
5862
|
+
const colorData = new Uint8Array(numSplats * 4);
|
|
5863
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5864
|
+
const r = Math.max(0, Math.min(255, Math.round((fdc0[i] * SH_C0$1 + 0.5) * 255)));
|
|
5865
|
+
const g = Math.max(0, Math.min(255, Math.round((fdc1[i] * SH_C0$1 + 0.5) * 255)));
|
|
5866
|
+
const b = Math.max(0, Math.min(255, Math.round((fdc2[i] * SH_C0$1 + 0.5) * 255)));
|
|
5867
|
+
const a = Math.max(0, Math.min(255, Math.round(sigmoid(opacity[i]) * 255)));
|
|
5868
|
+
colorData[i * 4] = r;
|
|
5869
|
+
colorData[i * 4 + 1] = g;
|
|
5870
|
+
colorData[i * 4 + 2] = b;
|
|
5871
|
+
colorData[i * 4 + 3] = a;
|
|
5872
|
+
}
|
|
5873
|
+
segments.push({
|
|
5874
|
+
name: 'COLOR_0',
|
|
5875
|
+
data: colorData.buffer,
|
|
5876
|
+
componentType: UNSIGNED_BYTE,
|
|
5877
|
+
type: 'VEC4',
|
|
5878
|
+
count: numSplats,
|
|
5879
|
+
normalized: true
|
|
5880
|
+
});
|
|
5881
|
+
// KHR_gaussian_splatting:ROTATION (VEC4 float, xyzw order)
|
|
5882
|
+
const rotData = new Float32Array(numSplats * 4);
|
|
5883
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5884
|
+
rotData[i * 4] = rot1[i]; // x
|
|
5885
|
+
rotData[i * 4 + 1] = rot2[i]; // y
|
|
5886
|
+
rotData[i * 4 + 2] = rot3[i]; // z
|
|
5887
|
+
rotData[i * 4 + 3] = rot0[i]; // w
|
|
5888
|
+
}
|
|
5889
|
+
segments.push({
|
|
5890
|
+
name: 'KHR_gaussian_splatting:ROTATION',
|
|
5891
|
+
data: rotData.buffer,
|
|
5892
|
+
componentType: FLOAT,
|
|
5893
|
+
type: 'VEC4',
|
|
5894
|
+
count: numSplats
|
|
5895
|
+
});
|
|
5896
|
+
// KHR_gaussian_splatting:SCALE (VEC3 float, log-space)
|
|
5897
|
+
const scaleData = new Float32Array(numSplats * 3);
|
|
5898
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5899
|
+
scaleData[i * 3] = scale0[i];
|
|
5900
|
+
scaleData[i * 3 + 1] = scale1[i];
|
|
5901
|
+
scaleData[i * 3 + 2] = scale2[i];
|
|
5902
|
+
}
|
|
5903
|
+
segments.push({
|
|
5904
|
+
name: 'KHR_gaussian_splatting:SCALE',
|
|
5905
|
+
data: scaleData.buffer,
|
|
5906
|
+
componentType: FLOAT,
|
|
5907
|
+
type: 'VEC3',
|
|
5908
|
+
count: numSplats
|
|
5909
|
+
});
|
|
5910
|
+
// KHR_gaussian_splatting:OPACITY (SCALAR float, sigmoid-activated)
|
|
5911
|
+
const opacityData = new Float32Array(numSplats);
|
|
5912
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5913
|
+
opacityData[i] = sigmoid(opacity[i]);
|
|
5914
|
+
}
|
|
5915
|
+
segments.push({
|
|
5916
|
+
name: 'KHR_gaussian_splatting:OPACITY',
|
|
5917
|
+
data: opacityData.buffer,
|
|
5918
|
+
componentType: FLOAT,
|
|
5919
|
+
type: 'SCALAR',
|
|
5920
|
+
count: numSplats
|
|
5921
|
+
});
|
|
5922
|
+
// SH_DEGREE_0_COEF_0 (VEC3 float, raw DC coefficients)
|
|
5923
|
+
const shDC = new Float32Array(numSplats * 3);
|
|
5924
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5925
|
+
shDC[i * 3] = fdc0[i];
|
|
5926
|
+
shDC[i * 3 + 1] = fdc1[i];
|
|
5927
|
+
shDC[i * 3 + 2] = fdc2[i];
|
|
5928
|
+
}
|
|
5929
|
+
segments.push({
|
|
5930
|
+
name: 'KHR_gaussian_splatting:SH_DEGREE_0_COEF_0',
|
|
5931
|
+
data: shDC.buffer,
|
|
5932
|
+
componentType: FLOAT,
|
|
5933
|
+
type: 'VEC3',
|
|
5934
|
+
count: numSplats
|
|
5935
|
+
});
|
|
5936
|
+
// Higher-order SH coefficients (degrees 1-3)
|
|
5937
|
+
// Internal layout is channel-major: f_rest[k] for k in [0..N-1] is red,
|
|
5938
|
+
// [N..2N-1] is green, [2N..3N-1] is blue, where N = coeffsPerChannel.
|
|
5939
|
+
if (shDegrees > 0) {
|
|
5940
|
+
const restData = [];
|
|
5941
|
+
for (let k = 0; k < coeffsPerChannel; k++) {
|
|
5942
|
+
restData.push(dataTable.getColumnByName(shRestNames[k]).data); // red
|
|
5943
|
+
restData.push(dataTable.getColumnByName(shRestNames[k + coeffsPerChannel]).data); // green
|
|
5944
|
+
restData.push(dataTable.getColumnByName(shRestNames[k + 2 * coeffsPerChannel]).data); // blue
|
|
5945
|
+
}
|
|
5946
|
+
let coefOffset = 0;
|
|
5947
|
+
for (let degree = 1; degree <= shDegrees; degree++) {
|
|
5948
|
+
const numCoefs = shCoefCount[degree];
|
|
5949
|
+
for (let c = 0; c < numCoefs; c++) {
|
|
5950
|
+
const k = coefOffset + c;
|
|
5951
|
+
const rChannel = restData[k * 3];
|
|
5952
|
+
const gChannel = restData[k * 3 + 1];
|
|
5953
|
+
const bChannel = restData[k * 3 + 2];
|
|
5954
|
+
const buf = new Float32Array(numSplats * 3);
|
|
5955
|
+
for (let i = 0; i < numSplats; i++) {
|
|
5956
|
+
buf[i * 3] = rChannel[i];
|
|
5957
|
+
buf[i * 3 + 1] = gChannel[i];
|
|
5958
|
+
buf[i * 3 + 2] = bChannel[i];
|
|
5959
|
+
}
|
|
5960
|
+
segments.push({
|
|
5961
|
+
name: `KHR_gaussian_splatting:SH_DEGREE_${degree}_COEF_${c}`,
|
|
5962
|
+
data: buf.buffer,
|
|
5963
|
+
componentType: FLOAT,
|
|
5964
|
+
type: 'VEC3',
|
|
5965
|
+
count: numSplats
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5968
|
+
coefOffset += numCoefs;
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
// Compute total binary buffer size (each segment aligned to 4 bytes)
|
|
5972
|
+
let totalSize = 0;
|
|
5973
|
+
const offsets = [];
|
|
5974
|
+
for (const seg of segments) {
|
|
5975
|
+
offsets.push(totalSize);
|
|
5976
|
+
totalSize += align4(seg.data.byteLength);
|
|
5977
|
+
}
|
|
5978
|
+
// Assemble the binary buffer
|
|
5979
|
+
const binBuffer = new Uint8Array(totalSize);
|
|
5980
|
+
for (let i = 0; i < segments.length; i++) {
|
|
5981
|
+
binBuffer.set(new Uint8Array(segments[i].data), offsets[i]);
|
|
5982
|
+
}
|
|
5983
|
+
return { segments, offsets, binBuffer };
|
|
5984
|
+
};
|
|
5985
|
+
/**
|
|
5986
|
+
* Writes Gaussian splat data to a GLB file using the KHR_gaussian_splatting extension.
|
|
5987
|
+
*
|
|
5988
|
+
* @param options - Options including filename and data table to write.
|
|
5989
|
+
* @param fs - File system for writing the output file.
|
|
5990
|
+
* @ignore
|
|
5991
|
+
*/
|
|
5992
|
+
const writeGlb = async (options, fs) => {
|
|
5993
|
+
const { filename, dataTable } = options;
|
|
5994
|
+
const numSplats = dataTable.numRows;
|
|
5995
|
+
const shBands = getSHBands(dataTable);
|
|
5996
|
+
const { segments, offsets, binBuffer } = buildBinaryBuffer(dataTable, numSplats, shBands);
|
|
5997
|
+
// Build glTF JSON
|
|
5998
|
+
const bufferViews = [];
|
|
5999
|
+
const accessors = [];
|
|
6000
|
+
const attributes = {};
|
|
6001
|
+
for (let i = 0; i < segments.length; i++) {
|
|
6002
|
+
const seg = segments[i];
|
|
6003
|
+
bufferViews.push({
|
|
6004
|
+
buffer: 0,
|
|
6005
|
+
byteOffset: offsets[i],
|
|
6006
|
+
byteLength: seg.data.byteLength,
|
|
6007
|
+
target: ARRAY_BUFFER
|
|
6008
|
+
});
|
|
6009
|
+
const accessor = {
|
|
6010
|
+
bufferView: i,
|
|
6011
|
+
byteOffset: 0,
|
|
6012
|
+
componentType: seg.componentType,
|
|
6013
|
+
count: seg.count,
|
|
6014
|
+
type: seg.type
|
|
6015
|
+
};
|
|
6016
|
+
if (seg.normalized) {
|
|
6017
|
+
accessor.normalized = true;
|
|
6018
|
+
}
|
|
6019
|
+
if (seg.min) {
|
|
6020
|
+
accessor.min = seg.min;
|
|
6021
|
+
accessor.max = seg.max;
|
|
6022
|
+
}
|
|
6023
|
+
accessors.push(accessor);
|
|
6024
|
+
attributes[seg.name] = i;
|
|
6025
|
+
}
|
|
6026
|
+
const gltf = {
|
|
6027
|
+
asset: {
|
|
6028
|
+
version: '2.0',
|
|
6029
|
+
generator: `splat-transform ${version}`
|
|
6030
|
+
},
|
|
6031
|
+
extensionsUsed: ['KHR_gaussian_splatting'],
|
|
6032
|
+
scene: 0,
|
|
6033
|
+
scenes: [{ nodes: [0] }],
|
|
6034
|
+
nodes: [{ mesh: 0 }],
|
|
6035
|
+
buffers: [{ byteLength: binBuffer.byteLength }],
|
|
6036
|
+
bufferViews,
|
|
6037
|
+
accessors,
|
|
6038
|
+
meshes: [{
|
|
6039
|
+
primitives: [{
|
|
6040
|
+
attributes,
|
|
6041
|
+
mode: 0,
|
|
6042
|
+
extensions: {
|
|
6043
|
+
KHR_gaussian_splatting: {
|
|
6044
|
+
kernel: 'ellipse',
|
|
6045
|
+
colorSpace: 'srgb_rec709_display',
|
|
6046
|
+
sortingMethod: 'cameraDistance',
|
|
6047
|
+
projection: 'perspective'
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
}]
|
|
6051
|
+
}]
|
|
6052
|
+
};
|
|
6053
|
+
const jsonString = JSON.stringify(gltf);
|
|
6054
|
+
const jsonEncoder = new TextEncoder();
|
|
6055
|
+
const jsonBytes = jsonEncoder.encode(jsonString);
|
|
6056
|
+
const jsonPaddedLength = align4(jsonBytes.byteLength);
|
|
6057
|
+
const binPaddedLength = align4(binBuffer.byteLength);
|
|
6058
|
+
const totalLength = 12 + 8 + jsonPaddedLength + 8 + binPaddedLength;
|
|
6059
|
+
const glb = new ArrayBuffer(totalLength);
|
|
6060
|
+
const view = new DataView(glb);
|
|
6061
|
+
const bytes = new Uint8Array(glb);
|
|
6062
|
+
// GLB header (12 bytes)
|
|
6063
|
+
view.setUint32(0, GLB_MAGIC, true);
|
|
6064
|
+
view.setUint32(4, GLB_VERSION, true);
|
|
6065
|
+
view.setUint32(8, totalLength, true);
|
|
6066
|
+
// JSON chunk header (8 bytes)
|
|
6067
|
+
let offset = 12;
|
|
6068
|
+
view.setUint32(offset, jsonPaddedLength, true);
|
|
6069
|
+
view.setUint32(offset + 4, JSON_CHUNK_TYPE, true);
|
|
6070
|
+
offset += 8;
|
|
6071
|
+
// JSON chunk data (padded with spaces per spec)
|
|
6072
|
+
bytes.set(jsonBytes, offset);
|
|
6073
|
+
for (let i = jsonBytes.byteLength; i < jsonPaddedLength; i++) {
|
|
6074
|
+
bytes[offset + i] = 0x20; // space
|
|
6075
|
+
}
|
|
6076
|
+
offset += jsonPaddedLength;
|
|
6077
|
+
// BIN chunk header (8 bytes)
|
|
6078
|
+
view.setUint32(offset, binPaddedLength, true);
|
|
6079
|
+
view.setUint32(offset + 4, BIN_CHUNK_TYPE, true);
|
|
6080
|
+
offset += 8;
|
|
6081
|
+
// BIN chunk data (padded with zeros per spec)
|
|
6082
|
+
bytes.set(binBuffer, offset);
|
|
6083
|
+
// Write the GLB file
|
|
6084
|
+
const writer = await fs.createWriter(filename);
|
|
6085
|
+
await writer.write(new Uint8Array(glb));
|
|
6086
|
+
await writer.close();
|
|
6087
|
+
};
|
|
6088
|
+
|
|
4948
6089
|
var index$2 = ":root {\n --canvas-opacity: 1;\n}\n\n* {\n margin: 0;\n padding: 0;\n touch-action: none;\n}\n\nbody {\n overflow: hidden;\n font-family: \"Arial\", sans-serif;\n -moz-user-select: none;\n user-select: none;\n -webkit-user-select: none;\n -webkit-touch-callout: none;\n}\n\ncanvas {\n opacity: var(--canvas-opacity);\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\nbutton {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.hidden {\n display: none !important;\n}\n\n.spacer {\n flex-grow: 1;\n}\n\n#ui {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n\n#ui * {\n pointer-events: auto;\n}\n\n.walkHint {\n position: absolute;\n top: max(60px, env(safe-area-inset-top));\n left: 50%;\n transform: translateX(-50%);\n background-color: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 14px;\n padding: 10px 20px;\n border-radius: 8px;\n cursor: pointer;\n pointer-events: auto;\n white-space: nowrap;\n}\n\n/* branding link for third-party embeds */\n#supersplatBranding {\n position: absolute;\n top: max(16px, env(safe-area-inset-top));\n left: max(16px, env(safe-area-inset-left));\n display: flex;\n align-items: center;\n gap: 6px;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n color: #fff;\n}\n#supersplatBranding > svg {\n height: 16px;\n width: auto;\n flex-shrink: 0;\n padding: 8px;\n border-radius: 6px;\n background-color: rgba(0, 0, 0, 0.3);\n}\n#supersplatBranding > svg > g.stroke {\n fill: black;\n stroke: black;\n stroke-width: 2;\n stroke-linejoin: round;\n opacity: 0.4;\n}\n#supersplatBranding > svg > g.fill {\n fill: currentColor;\n stroke: none;\n}\n#supersplatBranding > span {\n font-size: 14px;\n font-weight: bold;\n text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.4), 1px -1px 0 rgba(0, 0, 0, 0.4), -1px 1px 0 rgba(0, 0, 0, 0.4), 1px 1px 0 rgba(0, 0, 0, 0.4);\n}\n\n/* poster */\n#poster {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-image: var(--poster-url);\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n}\n\n/* loadingWrap */\n#loadingWrap {\n position: fixed;\n bottom: 120px;\n left: 50%;\n transform: translate(-50%, 0);\n width: min(380px, 100vw - 32px);\n display: flex;\n flex-direction: column;\n padding: 16px;\n}\n#loadingWrap > #loadingText {\n font-size: 18px;\n color: #fff;\n text-align: center;\n text-shadow: 0 0 4px rgba(0, 0, 0, 0.5);\n}\n#loadingWrap > #loadingBar {\n width: 100%;\n height: 10px;\n margin-top: 8px;\n border-radius: 4px;\n overflow: hidden;\n}\n\n/* controlsWrap */\n#controlsWrap {\n position: absolute;\n left: max(16px, env(safe-area-inset-left));\n right: max(16px, env(safe-area-inset-right));\n bottom: max(16px, env(safe-area-inset-bottom));\n display: flex;\n flex-direction: column;\n}\n#controlsWrap.faded-in {\n visibility: visible;\n opacity: 1;\n transition: opacity 0.5s ease-out;\n}\n#controlsWrap.faded-out {\n visibility: hidden;\n opacity: 0;\n transition: visibility 0s 0.5s, opacity 0.5s ease-out;\n}\n#controlsWrap > #timelineContainer {\n height: 30px;\n cursor: pointer;\n}\n#controlsWrap > #timelineContainer > #line {\n width: 100%;\n height: 50%;\n border-bottom: 4px solid #d9d9d9;\n}\n#controlsWrap > #timelineContainer > #handle {\n position: absolute;\n top: 16.5px;\n width: 12px;\n height: 12px;\n transform: translate(-50%, -50%);\n border: 2px solid #d9d9d9;\n border-radius: 50%;\n background-color: #FFAF50;\n}\n#controlsWrap > #timelineContainer > #time {\n position: absolute;\n top: 0;\n padding: 2px 4px;\n transform: translate(-50%, -100%);\n font-size: 12px;\n border-radius: 4px;\n color: #fff;\n background-color: rgba(40, 40, 40, 0.5);\n}\n#controlsWrap > #buttonsContainer {\n display: flex;\n gap: 8px;\n}\n#controlsWrap > #buttonsContainer .buttonGroup {\n display: flex;\n background-color: rgba(0, 0, 0, 0.3);\n border-radius: 6px;\n}\n#controlsWrap > #buttonsContainer .buttonGroup:not(:has(> :not(.hidden))) {\n display: none;\n}\n#controlsWrap > #buttonsContainer {\n /* controlButton */\n}\n#controlsWrap > #buttonsContainer .controlButton {\n width: 34px;\n height: 34px;\n padding: 0;\n margin: 0;\n border: 0;\n cursor: pointer;\n color: #E0DCDD;\n background-color: transparent;\n}\n#controlsWrap > #buttonsContainer .controlButton:hover {\n color: #fff;\n}\n#controlsWrap > #buttonsContainer .controlButton {\n /* icon styling */\n}\n#controlsWrap > #buttonsContainer .controlButton > svg {\n display: block;\n margin: auto;\n}\n#controlsWrap > #buttonsContainer .controlButton > svg > g.stroke {\n fill: none;\n stroke: black;\n stroke-width: 2;\n stroke-linejoin: round;\n opacity: 0.4;\n}\n#controlsWrap > #buttonsContainer .controlButton > svg > g.fill {\n fill: currentColor;\n stroke: none;\n}\n#controlsWrap > #buttonsContainer .controlButton {\n /* active highlight for simple toggle buttons (e.g. voxel overlay) */\n}\n#controlsWrap > #buttonsContainer .controlButton.active:not(.toggle) {\n color: #F60;\n}\n#controlsWrap > #buttonsContainer .controlButton {\n /* camera toggle styling */\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle {\n background: linear-gradient(90deg, transparent 0%, transparent 50%, #F60 50%, #F60 100%);\n background-size: 200% 100%;\n background-position: 100% 0%;\n background-repeat: no-repeat;\n transition: background-position 0.1s ease-in-out;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.left {\n border-radius: 4px 0px 0px 4px;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.left:not(.active) {\n background-position: 0% 0%;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.middle {\n border-radius: 0;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.middle:not(.active) {\n background-position: 0% 0%;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.right {\n border-radius: 0px 4px 4px 0px;\n}\n#controlsWrap > #buttonsContainer .controlButton.toggle.right:not(.active) {\n background-position: 200% 0%;\n}\n\n/* settingsPanel */\n#settingsPanel {\n position: fixed;\n right: max(16px, env(safe-area-inset-right));\n bottom: calc(max(16px, env(safe-area-inset-bottom)) + 70px);\n padding: 12px;\n border-radius: 16px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n font-size: 14px;\n color: #E0DCDD;\n background-color: rgba(51, 51, 51, 0.8);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n}\n#settingsPanel > .settingsRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 4px;\n width: 100%;\n}\n#settingsPanel > .settingsRow > button {\n flex-grow: 1;\n height: 34px;\n padding: 0 20px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 8px;\n font-size: 13px;\n cursor: pointer;\n color: #AAA;\n background-color: rgba(255, 255, 255, 0.04);\n transition: background-color 250ms ease, color 250ms ease;\n}\n#settingsPanel > .settingsRow > button:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.1);\n}\n#settingsPanel > .settingsRow > div {\n height: 34px;\n display: flex;\n align-items: center;\n padding: 0 8px;\n cursor: pointer;\n color: #AAA;\n}\n#settingsPanel > .settingsRow > div.toggleSwitch {\n padding: 0 4px;\n}\n#settingsPanel > .settingsRow > div.toggleSwitch .toggleTrack {\n width: 36px;\n height: 20px;\n border-radius: 10px;\n background-color: rgba(255, 255, 255, 0.15);\n position: relative;\n transition: background-color 200ms ease;\n}\n#settingsPanel > .settingsRow > div.toggleSwitch .toggleTrack .toggleThumb {\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background-color: #777;\n position: absolute;\n top: 2px;\n left: 2px;\n transition: transform 200ms ease, background-color 200ms ease;\n}\n#settingsPanel > .settingsRow > div.toggleSwitch.active .toggleTrack {\n background-color: rgba(255, 102, 0, 0.35);\n}\n#settingsPanel > .settingsRow > div.toggleSwitch.active .toggleTrack .toggleThumb {\n transform: translateX(16px);\n background-color: #F60;\n}\n#settingsPanel > .settingsRow > div:hover {\n color: #E0DCDD;\n}\n#settingsPanel > .divider {\n width: 100%;\n height: 1px;\n margin: 8px 0;\n background-color: rgba(255, 255, 255, 0.08);\n}\n\n/* infoPanel */\n#infoPanel {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(2px);\n}\n#infoPanel > #infoPanelContent {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n min-height: 280px;\n min-width: 320px;\n padding: 8px;\n border-radius: 16px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n display: flex;\n flex-direction: column;\n color: #E0DCDD;\n background-color: rgba(51, 51, 51, 0.8);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n}\n#infoPanel > #infoPanelContent > #tabs {\n display: flex;\n gap: 4px;\n padding: 4px;\n border-radius: 12px;\n background-color: #282828;\n}\n#infoPanel > #infoPanelContent > #tabs > .tab {\n padding: 8px 12px;\n border-radius: 10px;\n cursor: pointer;\n flex-grow: 1;\n text-align: center;\n font-weight: bold;\n font-size: 14px;\n color: #AAA;\n transition: background-color 250ms ease, color 250ms ease;\n}\n#infoPanel > #infoPanelContent > #tabs > .tab:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.06);\n}\n#infoPanel > #infoPanelContent > #tabs > .tab.active {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.1);\n}\n#infoPanel > #infoPanelContent > #infoPanels {\n padding: 16px;\n}\n#infoPanel > #infoPanelContent > #infoPanels h1 {\n font-size: 11px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 1px;\n padding: 4px 0 8px 0;\n color: #F60;\n}\n#infoPanel > #infoPanelContent > #infoPanels .control-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 32px;\n padding: 5px 8px;\n margin: 0 -8px;\n border-radius: 6px;\n font-size: 13px;\n}\n#infoPanel > #infoPanelContent > #infoPanels .control-item > .control-action {\n text-align: left;\n}\n#infoPanel > #infoPanelContent > #infoPanels .control-item > .control-key {\n text-align: right;\n color: #AAA;\n font-size: 12px;\n}\n#infoPanel > #infoPanelContent > #infoPanels .control-item:nth-child(odd) {\n background-color: rgba(255, 255, 255, 0.03);\n}\n#infoPanel > #infoPanelContent > #infoPanels .control-spacer {\n margin: 14px 0;\n}\n\n#joystickBase {\n position: absolute;\n width: 56px;\n height: 100px;\n transform: translate(-50%, -50%);\n border-radius: 28px;\n touch-action: none;\n background: linear-gradient(180deg, rgba(0, 0, 0, 0.2666666667) 0%, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0) 70%, rgba(0, 0, 0, 0.2666666667) 100%);\n background-color: rgba(0, 0, 0, 0.2);\n border: 2px solid rgba(255, 255, 255, 0.2);\n transition: width 0.2s ease, height 0.2s ease, border-radius 0.2s ease;\n}\n#joystickBase > #joystick {\n position: absolute;\n left: 8px;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n touch-action: none;\n background-color: rgba(255, 255, 255, 0.5333333333);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n transition: left 0.1s ease;\n}\n#joystickBase.mode-2d {\n width: 100px;\n height: 100px;\n border-radius: 50px;\n background: radial-gradient(circle, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.2666666667) 100%);\n background-color: rgba(0, 0, 0, 0.2);\n}\n#joystickBase.mode-2d > #joystick {\n left: 30px;\n}\n\n#tooltip {\n display: none;\n position: absolute;\n border-radius: 4px;\n padding: 4px 4px;\n font-size: 12px;\n color: #E0DCDD;\n background-color: #282828;\n}\n\n#annotations {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n z-index: -1;\n}\n\n#annotations * {\n pointer-events: auto;\n}\n\n/* annotationNav */\n#annotationNav {\n pointer-events: none;\n}\n#annotationNav.faded-in {\n visibility: visible;\n opacity: 1;\n transition: opacity 0.5s ease-out;\n}\n#annotationNav.faded-out {\n visibility: hidden;\n opacity: 0;\n transition: visibility 0s 0.5s, opacity 0.5s ease-out;\n}\n#annotationNav > button {\n pointer-events: auto;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 0;\n padding: 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #E0DCDD;\n background-color: rgba(0, 0, 0, 0.3);\n}\n#annotationNav > button:hover {\n color: #fff;\n background-color: rgba(0, 0, 0, 0.5);\n}\n#annotationNav > button > svg {\n display: block;\n}\n#annotationNav > button > svg > g.stroke {\n fill: none;\n stroke: black;\n stroke-width: 3;\n stroke-linecap: round;\n stroke-linejoin: round;\n opacity: 0.4;\n}\n#annotationNav > button > svg > g.fill {\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n#annotationNav.desktop {\n position: absolute;\n top: max(16px, env(safe-area-inset-top));\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 0;\n border-radius: 6px;\n overflow: hidden;\n background-color: rgba(0, 0, 0, 0.6);\n}\n#annotationNav.desktop > button {\n width: 34px;\n height: 34px;\n background-color: transparent;\n}\n#annotationNav.desktop > button:hover {\n background-color: rgba(255, 255, 255, 0.1);\n}\n#annotationNav.desktop > #annotationInfo {\n pointer-events: none;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 12px;\n white-space: nowrap;\n width: 240px;\n}\n#annotationNav.desktop > #annotationInfo > #annotationNavTitle {\n font-size: 14px;\n color: #fff;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n#annotationNav.touch {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n#annotationNav.touch > #annotationInfo {\n display: none;\n}\n#annotationNav.touch > button {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n width: 44px;\n height: 80px;\n background-color: rgba(0, 0, 0, 0.3);\n}\n#annotationNav.touch > button:active {\n background-color: rgba(0, 0, 0, 0.4);\n}\n#annotationNav.touch > #annotationPrev {\n left: max(0px, env(safe-area-inset-left));\n border-radius: 0 6px 6px 0;\n}\n#annotationNav.touch > #annotationNext {\n right: max(0px, env(safe-area-inset-right));\n border-radius: 6px 0 0 6px;\n}";
|
|
4949
6090
|
|
|
4950
6091
|
var index$1 = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>SuperSplat Viewer</title>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n <base href=\"\">\n <link rel=\"stylesheet\" href=\"./index.css\">\n <script type=\"module\">\n const createImage = (url) => {\n const img = new Image();\n img.src = url;\n return img;\n };\n\n const url = new URL(location.href);\n\n const posterUrl = url.searchParams.get('poster');\n const skyboxUrl = url.searchParams.get('skybox');\n const voxelUrl = url.searchParams.get('voxel');\n const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';\n const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.compressed.ply';\n\n const sseConfig = {\n poster: posterUrl && createImage(posterUrl),\n skyboxUrl,\n voxelUrl,\n contentUrl,\n contents: fetch(contentUrl),\n noui: url.searchParams.has('noui'),\n noanim: url.searchParams.has('noanim'),\n nofx: url.searchParams.has('nofx'),\n hpr: url.searchParams.has('hpr') ? ['', '1', 'true', 'enable'].includes(url.searchParams.get('hpr')) : undefined,\n ministats: url.searchParams.has('ministats'),\n colorize: url.searchParams.has('colorize'),\n unified: url.searchParams.has('unified'),\n webgpu: url.searchParams.has('webgpu'),\n gpusort: url.searchParams.has('gpusort'),\n aa: url.searchParams.has('aa'),\n heatmap: url.searchParams.has('heatmap'),\n };\n\n window.sse = {\n config: sseConfig,\n settings: fetch(settingsUrl).then(response => response.json())\n };\n </script>\n </head>\n <body>\n <canvas id=\"application-canvas\"></canvas>\n\n <div id=\"ui\">\n <div id=\"poster\"></div>\n\n <!-- SuperSplat Branding -->\n <a id=\"supersplatBranding\" class=\"hidden\" target=\"_blank\" rel=\"noopener noreferrer\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 32 32\">\n <g class='stroke'><use href=\"#supersplatIcon\"/></g>\n <g class='fill'><use href=\"#supersplatIcon\"/></g>\n </svg>\n <span>SuperSplat</span>\n </a>\n\n <!-- Loading Indicator -->\n <div id=\"loadingWrap\">\n <div id=\"loadingText\"></div>\n <div id=\"loadingBar\"></div>\n </div>\n\n <div id=\"controlsWrap\" class=\"hidden\">\n\n <!-- Timeline Panel -->\n <div id=\"timelineContainer\" class=\"hidden\">\n <div id=\"line\"></div>\n <div id=\"handle\"></div>\n <div id=\"time\" class=\"hidden\">0:00</div>\n </div>\n\n <!-- Buttons Panel -->\n <div id=\"buttonsContainer\">\n <div class=\"buttonGroup\">\n <button id=\"play\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#playIcon\"/></g>\n <g class='fill'><use href=\"#playIcon\"/></g>\n </svg>\n </button>\n <button id=\"pause\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\">\n <g class='stroke'><use href=\"#pauseIcon\"/></g>\n <g class='fill'><use href=\"#pauseIcon\"/></g>\n </svg>\n </button>\n </div>\n\n <div class=\"spacer\"></div>\n\n <div class=\"buttonGroup\">\n <button id=\"orbitCamera\" class=\"controlButton toggle left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#orbitIcon\"/></g>\n <g class='fill'><use href=\"#orbitIcon\"/></g>\n </svg>\n </button>\n <button id=\"flyCamera\" class=\"controlButton toggle right\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#flyIcon\"/></g>\n <g class='fill'><use href=\"#flyIcon\"/></g>\n </svg>\n </button>\n <button id=\"fpsCamera\" class=\"controlButton toggle right hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#walkIcon\"/></g>\n <g class='fill'><use href=\"#walkIcon\"/></g>\n </svg>\n </button>\n </div>\n\n <div class=\"buttonGroup\">\n <button id=\"arMode\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#arIcon\"/></g>\n <g class='fill'><use href=\"#arIcon\"/></g>\n </svg>\n </button>\n <button id=\"vrMode\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#vrIcon\"/></g>\n <g class='fill'><use href=\"#vrIcon\"/></g>\n </svg>\n </button>\n\n <button id=\"showVoxels\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#voxelIcon\"/></g>\n <g class='fill'><use href=\"#voxelIcon\"/></g>\n </svg>\n </button>\n\n <button id=\"info\" class=\"controlButton\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#infoIcon\"/></g>\n <g class='fill'><use href=\"#infoIcon\"/></g>\n </svg>\n </button>\n <button id=\"settings\" class=\"controlButton\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#settingsIcon\"/></g>\n <g class='fill'><use href=\"#settingsIcon\"/></g>\n </svg>\n </button>\n\n <button id=\"enterFullscreen\" class=\"controlButton\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#enterFullscreenIcon\"/></g>\n <g class='fill'><use href=\"#enterFullscreenIcon\"/></g>\n </svg>\n </button>\n <button id=\"exitFullscreen\" class=\"controlButton hidden\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\">\n <g class='stroke'><use href=\"#exitFullscreenIcon\"/></g>\n <g class='fill'><use href=\"#exitFullscreenIcon\"/></g>\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div id=\"walkHint\" class=\"walkHint hidden\"></div>\n\n <!-- Settings Panel -->\n <div id=\"settingsPanel\" class=\"hidden\">\n <div id=\"retinaDisplayRow\" class=\"settingsRow\">\n <div id=\"retinaDisplayOption\">Retina Display</div>\n <div id=\"retinaDisplayCheck\" class=\"toggleSwitch\"><div class=\"toggleTrack\"><div class=\"toggleThumb\"></div></div></div>\n </div>\n <div id=\"gamingControlsDivider\" class=\"divider\"></div>\n <div id=\"gamingControlsRow\" class=\"settingsRow\">\n <div id=\"gamingControlsOption\">Gaming Controls</div>\n <div id=\"gamingControlsCheck\" class=\"toggleSwitch\"><div class=\"toggleTrack\"><div class=\"toggleThumb\"></div></div></div>\n </div>\n <div class=\"divider\"></div>\n <div class=\"settingsRow\">\n <button id=\"frame\">Frame</button>\n <button id=\"reset\">Reset</button>\n </div>\n </div>\n\n <!-- Info Panel -->\n <div id=\"infoPanel\" class=\"hidden\">\n <div id=\"infoPanelContent\" onpointerdown=\"event.stopPropagation()\">\n <div id=\"tabs\">\n <div id=\"desktopTab\" class=\"tab active\">Desktop</div>\n <div id=\"touchTab\" class=\"tab\">Touch</div>\n </div>\n <div id=\"infoPanels\">\n <div id=\"desktopInfoPanel\">\n <div class=\"control-spacer\"></div>\n <h1>Orbit Mode</h1>\n <div class=\"control-item\">\n <span class=\"control-action\">Orbit</span>\n <span class=\"control-key\">Left Mouse</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Pan</span>\n <span class=\"control-key\">Right Mouse</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Zoom</span>\n <span class=\"control-key\">Mouse Wheel</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Set Focus</span>\n <span class=\"control-key\">Double Click</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Frame Scene</span>\n <span class=\"control-key\">F</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Reset Camera</span>\n <span class=\"control-key\">R</span>\n </div>\n <div class=\"control-spacer\"></div>\n <h1>Fly Mode</h1>\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Click + Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Pan</span>\n <span class=\"control-key\">Right Mouse</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Move</span>\n <span class=\"control-key\">W,S,A,D</span>\n </div>\n <div class=\"control-spacer\"></div>\n <h1>Walk Mode</h1>\n <div id=\"desktopClickToWalk\">\n <div class=\"control-item\">\n <span class=\"control-action\">Walk To</span>\n <span class=\"control-key\">Click</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Click + Drag</span>\n </div>\n </div>\n <div id=\"desktopGamingControls\">\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Mouse</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Move</span>\n <span class=\"control-key\">W,S,A,D</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Jump</span>\n <span class=\"control-key\">Space</span>\n </div>\n </div>\n <div class=\"control-spacer\"></div>\n <h1>Mode Switching</h1>\n <div class=\"control-item\">\n <span class=\"control-action\">Orbit Mode</span>\n <span class=\"control-key\">1</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Fly Mode</span>\n <span class=\"control-key\">2</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Walk Mode</span>\n <span class=\"control-key\">3</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Voxel Overlay</span>\n <span class=\"control-key\">V</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Play / Pause</span>\n <span class=\"control-key\">Space</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Toggle Help</span>\n <span class=\"control-key\">H</span>\n </div>\n </div>\n <div id=\"touchInfoPanel\" class=\"hidden\">\n <div class=\"control-spacer\"></div>\n <h1>Orbit Mode</h1>\n <div class=\"control-item\">\n <span class=\"control-action\">Orbit</span>\n <span class=\"control-key\">One Finger Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Pan</span>\n <span class=\"control-key\">Two Finger Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Zoom</span>\n <span class=\"control-key\">Pinch</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Set Focus</span>\n <span class=\"control-key\">Double Tap</span>\n </div>\n <div class=\"control-spacer\"></div>\n <h1>Fly Mode</h1>\n <div id=\"touchFlyClickToWalk\">\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Touch + Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Move</span>\n <span class=\"control-key\">Pinch / Two Finger Drag</span>\n </div>\n </div>\n <div id=\"touchFlyGamingControls\" class=\"hidden\">\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Touch + Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Move</span>\n <span class=\"control-key\">Joystick</span>\n </div>\n </div>\n <div class=\"control-spacer\"></div>\n <h1>Walk Mode</h1>\n <div id=\"touchClickToWalk\">\n <div class=\"control-item\">\n <span class=\"control-action\">Walk To</span>\n <span class=\"control-key\">Tap</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Touch + Drag</span>\n </div>\n </div>\n <div id=\"touchGamingControls\" class=\"hidden\">\n <div class=\"control-item\">\n <span class=\"control-action\">Look Around</span>\n <span class=\"control-key\">Touch + Drag</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Move</span>\n <span class=\"control-key\">Joystick</span>\n </div>\n <div class=\"control-item\">\n <span class=\"control-action\">Jump</span>\n <span class=\"control-key\">Tap</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Touch Joystick -->\n <div id=\"joystickBase\" class=\"hidden\">\n <div id=\"joystick\"></div>\n </div>\n\n <!-- Annotation Navigator -->\n <div id=\"annotationNav\" class=\"hidden\">\n <button id=\"annotationPrev\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\">\n <g class='stroke'><use href=\"#chevronLeftIcon\"/></g>\n <g class='fill'><use href=\"#chevronLeftIcon\"/></g>\n </svg>\n </button>\n <div id=\"annotationInfo\">\n <span id=\"annotationNavTitle\"></span>\n </div>\n <button id=\"annotationNext\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\">\n <g class='stroke'><use href=\"#chevronRightIcon\"/></g>\n <g class='fill'><use href=\"#chevronRightIcon\"/></g>\n </svg>\n </button>\n </div>\n\n <!-- Tooltip -->\n <div id=\"tooltip\"></div>\n </div>\n\n <!-- SVG Icons -->\n <svg>\n <symbol id=\"supersplatIcon\" viewBox=\"0 0 32 32\">\n <path d=\"M4.2638 11.4207C5.15691 11.4207 5.87964 10.697 5.87964 9.80481C5.87964 8.91262 5.15599 8.18896 4.2638 8.18896C3.3716 8.18896 2.64795 8.91262 2.64795 9.80481C2.64795 10.697 3.3716 11.4207 4.2638 11.4207Z\"/>\n <path d=\"M27.9805 14.8047C26.8392 14.8047 25.9131 15.7308 25.9131 16.8721C25.9131 18.0135 26.8392 18.9396 27.9805 18.9396C29.1219 18.9396 30.048 18.0135 30.048 16.8721C30.048 15.7308 29.1219 14.8047 27.9805 14.8047Z\"/>\n <path d=\"M23.9977 6.30034H23.9995C24.0334 6.29576 24.059 6.28019 24.0883 6.27011C25.6254 6.05759 26.8135 4.75228 26.8135 3.15658C26.8135 1.41249 25.4001 0 23.6569 0C21.9137 0 20.5003 1.41341 20.5003 3.15658C20.5003 3.54955 20.5809 3.92145 20.7128 4.26862C20.7165 4.27961 20.7119 4.29152 20.7165 4.30343C21.6829 6.97636 18.671 6.46705 17.1376 6.20324C17.0928 6.195 17.057 6.19683 17.0167 6.19316C16.6256 6.14187 16.2299 6.10431 15.8232 6.10431C10.8831 6.10431 6.87921 10.1082 6.87921 15.0483C6.87921 15.9496 7.01478 16.818 7.26393 17.6387C7.2676 17.6479 7.26393 17.6543 7.26851 17.6635C8.09018 20.4253 5.91006 19.9984 4.93085 20.0167C4.83558 20.0048 4.7394 19.9874 4.63955 19.9874C3.40202 19.9874 2.3999 20.9895 2.3999 22.2271C2.3999 23.4646 3.40202 24.4667 4.63955 24.4667C5.71037 24.4667 6.60349 23.7137 6.82424 22.7089H6.82608C7.40408 20.275 9.19855 21.0582 9.67946 21.5483C9.69778 21.5666 9.7161 21.5721 9.7335 21.5868C11.331 23.0753 13.4663 23.9922 15.8222 23.9922C15.8836 23.9922 15.945 23.984 16.0064 23.9831C16.0668 23.984 16.1264 23.9867 16.1932 23.9867C17.0818 23.9831 17.8256 24.9861 16.6457 26.7054H16.6576C16.2528 27.256 16.0073 27.9292 16.0073 28.6666C16.0073 30.5069 17.4995 32 19.3406 32C21.1818 32 22.674 30.5078 22.674 28.6666C22.674 27.256 21.7937 26.0569 20.5562 25.5696C20.5269 25.5558 20.5003 25.543 20.4664 25.5284C19.3416 25.0759 18.137 23.9079 19.8353 23.046L19.8316 23.0368C22.7565 21.5657 24.7671 18.5465 24.7671 15.0492C24.7671 13.1118 24.1442 11.3228 23.0972 9.85905C23.0853 9.83798 23.0761 9.816 23.0569 9.79126C20.9363 6.89025 22.8398 6.4872 24.0004 6.30217H23.9995L23.9977 6.30034ZM13.5121 17.6232C13.5121 18.3514 12.9221 18.9404 12.1948 18.9404C11.4675 18.9404 10.8776 18.3505 10.8776 17.6232V15.5209C10.8776 14.7927 11.4675 14.2037 12.1948 14.2037C12.9221 14.2037 13.5121 14.7936 13.5121 15.5209V17.6232ZM20.7476 17.6232C20.7476 18.3514 20.1577 18.9404 19.4304 18.9404C18.7031 18.9404 18.1132 18.3505 18.1132 17.6232V15.5209C18.1132 14.7927 18.7031 14.2037 19.4304 14.2037C20.1577 14.2037 20.7476 14.7936 20.7476 15.5209V17.6232Z\"/>\n </symbol>\n <symbol id=\"playIcon\" viewBox=\"-2 0 24 24\">\n <path\n d=\"M1 1.98725C1 1.20022 1.87789 0.730421 2.5332 1.16694L14.5605 9.18061C15.146 9.57066 15.146 10.4302 14.5605 10.8203L2.5332 18.833C1.87788 19.2695 1 18.7997 1 18.0126V1.98725Z\"\n transform=\"translate(2 2)\"\n />\n </symbol>\n <symbol id=\"pauseIcon\" viewBox=\"0 0 20 20\">\n <path d=\"M5 16V4h3v12H5zm7-12h3v12h-3V4z\"/>\n </symbol>\n <symbol id=\"orbitIcon\" viewBox=\"-2 -2 20 20\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M4.44728 1.7561C6.14077 0.763957 8.53159 1.57857 10.4375 3.58357C12.75 3.02099 14.4585 3.40647 14.6491 4.73071C14.7831 5.66256 14.139 6.87994 12.9942 8.09008C13.7427 10.7586 13.2413 13.2551 11.5527 14.2444C9.85867 15.2369 7.46679 14.4208 5.56056 12.4143C3.34914 12.9521 1.691 12.6254 1.38412 11.4371L1.35092 11.2698C1.21678 10.3379 1.85972 9.11931 3.00457 7.90909C2.28693 5.34963 2.72037 2.94871 4.24741 1.88435L4.44728 1.7561ZM11.9889 9.03995C11.002 9.88136 9.79998 10.6808 8.47918 11.3303L8.13673 11.4937C7.70533 11.6942 7.28148 11.8681 6.8698 12.0172C7.32387 12.4287 7.79226 12.7587 8.25326 12.9983C9.39237 13.5903 10.3231 13.574 10.946 13.2092C11.5745 12.841 12.0613 12.0184 12.1263 10.7086C12.1518 10.1934 12.1065 9.62999 11.9889 9.03995ZM3.46225 9.20206C3.15128 9.5802 2.91825 9.93412 2.76238 10.2489C2.54363 10.6909 2.51958 10.967 2.53842 11.0985C2.54902 11.1721 2.56635 11.2146 2.67058 11.2763C2.81832 11.3636 3.12484 11.4626 3.6478 11.467C3.95641 11.4696 4.3074 11.4373 4.69402 11.3707C4.45655 11.0439 4.23084 10.6971 4.02215 10.3303C3.81017 9.95774 3.62416 9.58004 3.46225 9.20206ZM10.0358 4.9423C9.42018 5.13611 8.75205 5.40184 8.05014 5.74698C6.61265 6.45384 5.33887 7.34291 4.37175 8.24373C4.55213 8.73829 4.78227 9.2401 5.06511 9.73722C5.33995 10.2202 5.64271 10.6614 5.96355 11.0575C6.5793 10.8637 7.24782 10.5987 7.94988 10.2535C9.38732 9.54667 10.6605 8.65688 11.6276 7.7561C11.4473 7.26173 11.2176 6.76019 10.9349 6.26326C10.66 5.78001 10.3568 5.33855 10.0358 4.9423ZM7.74675 3.00219C6.60759 2.41015 5.67698 2.42644 5.05405 2.79126C4.42548 3.15953 3.93866 3.98199 3.87371 5.29191C3.84822 5.80668 3.89236 6.36967 4.00978 6.95922C4.91091 6.19088 5.9923 5.45862 7.17905 4.84269L7.52084 4.67016C8.07056 4.39986 8.6096 4.17089 9.12957 3.98266C8.67568 3.57138 8.20757 3.24172 7.74675 3.00219ZM12.3522 4.53344C12.0433 4.53086 11.6918 4.56177 11.3047 4.6285C11.5425 4.95566 11.7689 5.3029 11.9779 5.67016C12.1897 6.04255 12.3753 6.41999 12.5371 6.79777C12.8481 6.41965 13.0818 6.06635 13.2376 5.75154C13.4564 5.30973 13.4804 5.03352 13.4616 4.90193C13.451 4.82831 13.4337 4.7859 13.3294 4.7242C13.1817 4.63684 12.8753 4.53787 12.3522 4.53344Z\"/>\n </symbol>\n <symbol id=\"flyIcon\" viewBox=\"-2 -2 20 20\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M12 1.33337C13.4727 1.33337 14.6666 2.52728 14.6666 4.00004C14.6666 5.4728 13.4727 6.66671 12 6.66671C11.4931 6.66671 11.0194 6.52491 10.6159 6.27934L10.2721 6.62374C10.0345 6.86142 9.90102 7.18411 9.90102 7.52022V8.47986C9.90102 8.81597 10.0345 9.13866 10.2721 9.37634L10.6159 9.72009C11.0193 9.47463 11.4932 9.33337 12 9.33337C13.4727 9.33337 14.6666 10.5273 14.6666 12C14.6666 13.4728 13.4727 14.6667 12 14.6667C10.5272 14.6667 9.33331 13.4728 9.33331 12C9.33331 11.4932 9.47457 11.0194 9.72003 10.6159L9.37628 10.2722C9.1386 10.0345 8.81591 9.90108 8.4798 9.90108H7.52016C7.18405 9.90108 6.86136 10.0345 6.62368 10.2722L6.27928 10.6159C6.52485 11.0195 6.66665 11.4931 6.66665 12C6.66665 13.4728 5.47274 14.6667 3.99998 14.6667C2.52722 14.6667 1.33331 13.4728 1.33331 12C1.33331 10.5273 2.52722 9.33337 3.99998 9.33337C4.50658 9.33337 4.98008 9.47481 5.38344 9.72009L5.72784 9.37634C5.9655 9.13866 6.09894 8.81597 6.09894 8.47986V7.52022C6.09894 7.18411 5.9655 6.86142 5.72784 6.62374L5.38344 6.27934C4.98001 6.52473 4.5067 6.66671 3.99998 6.66671C2.52722 6.66671 1.33331 5.4728 1.33331 4.00004C1.33331 2.52728 2.52722 1.33337 3.99998 1.33337C5.47274 1.33337 6.66665 2.52728 6.66665 4.00004C6.66665 4.50676 6.52467 4.98008 6.27928 5.3835L6.62368 5.72791C6.86136 5.96556 7.18405 6.099 7.52016 6.099H8.4798C8.81591 6.099 9.1386 5.96556 9.37628 5.72791L9.72003 5.3835C9.47475 4.98014 9.33331 4.50664 9.33331 4.00004C9.33331 2.52728 10.5272 1.33337 12 1.33337ZM3.99998 10.3998C3.11632 10.3998 2.39972 11.1164 2.39972 12C2.39972 12.8837 3.11632 13.6003 3.99998 13.6003C4.88364 13.6003 5.60024 12.8837 5.60024 12C5.60024 11.7911 5.55917 11.5919 5.48631 11.4089L4.41469 12.4812C4.16723 12.7284 3.76625 12.7286 3.51886 12.4812C3.27147 12.2338 3.27158 11.8328 3.51886 11.5853L4.59047 10.5131C4.40769 10.4404 4.20862 10.3998 3.99998 10.3998ZM12 10.3998C11.7911 10.3998 11.5918 10.4403 11.4088 10.5131L12.4811 11.5853C12.7284 11.8328 12.7285 12.2338 12.4811 12.4812C12.2337 12.7286 11.8327 12.7284 11.5853 12.4812L10.513 11.4089C10.4402 11.5918 10.3997 11.7912 10.3997 12C10.3997 12.8837 11.1163 13.6003 12 13.6003C12.8836 13.6003 13.6002 12.8837 13.6002 12C13.6002 11.1164 12.8836 10.3998 12 10.3998ZM3.99998 2.39978C3.11632 2.39978 2.39972 3.11639 2.39972 4.00004C2.39972 4.8837 3.11632 5.6003 3.99998 5.6003C4.20871 5.6003 4.40763 5.55909 4.59047 5.48637L3.51886 4.41475C3.27158 4.16729 3.27147 3.76631 3.51886 3.51892C3.76625 3.27153 4.16723 3.27164 4.41469 3.51892L5.48631 4.59054C5.55903 4.40769 5.60024 4.20877 5.60024 4.00004C5.60024 3.11639 4.88364 2.39978 3.99998 2.39978ZM12 2.39978C11.1163 2.39978 10.3997 3.11639 10.3997 4.00004C10.3997 4.20868 10.4403 4.40775 10.513 4.59054L11.5853 3.51892C11.8327 3.27164 12.2337 3.27153 12.4811 3.51892C12.7285 3.76631 12.7284 4.16729 12.4811 4.41475L11.4088 5.48637C11.5918 5.55923 11.791 5.6003 12 5.6003C12.8836 5.6003 13.6002 4.8837 13.6002 4.00004C13.6002 3.11639 12.8836 2.39978 12 2.39978Z\"/>\n </symbol>\n <symbol id=\"walkIcon\" viewBox=\"-2 -2 20 20\">\n <path d=\"M9.75 2a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0zM5.25 4.75h5.5v4.5h-.5v5h-2v-5h-.5v5h-2v-5h-.5z\"/>\n </symbol>\n <symbol id=\"arIcon\" viewBox=\"0 0 24 24\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M12 15.3994V6H16.9629C18.2109 6 19.1167 6.1089 19.6797 6.32715C20.248 6.53997 20.7021 6.92247 21.042 7.47363C21.3818 8.02476 21.5518 8.65491 21.5518 9.36426C21.5518 10.2645 21.2943 11.0093 20.7793 11.5986C20.2641 12.1825 19.4938 12.5513 18.4688 12.7041C18.9786 13.0097 19.3983 13.3453 19.7275 13.7109C20.0621 14.0766 20.5113 14.7261 21.0742 15.6592L22.5 18H19.6797L17.9746 15.3887C17.3694 14.4559 16.9555 13.8691 16.7324 13.6289C16.5095 13.3835 16.2729 13.2172 16.0234 13.1299C15.7738 13.0371 15.3777 12.9902 14.8359 12.9902H14.3584V18H10.4053L9.35742 15.2744H4.56055L3.57031 18H1L5.67383 6H8.23633L12 15.3994ZM5.30566 13.252H8.58008L6.92676 8.7998L5.30566 13.252ZM14.3584 11.0752H16.1025C17.2336 11.0752 17.9401 11.0259 18.2217 10.9277C18.5032 10.8295 18.7235 10.66 18.8828 10.4199C19.0421 10.1798 19.1221 9.87966 19.1221 9.51953C19.122 9.11597 19.016 8.79138 18.8037 8.5459C18.5966 8.29488 18.3013 8.13677 17.9189 8.07129C17.7276 8.04401 17.1541 8.03027 16.1982 8.03027H14.3584V11.0752Z\"/>\n </symbol>\n <symbol id=\"vrIcon\" viewBox=\"0 0 24 24\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.52148 10.7021L7.15918 15.1133L8.76465 10.7021L9.50195 8.7002L10.4834 6H16.9541C18.1903 6 19.0879 6.10801 19.6455 6.32422C20.2081 6.53499 20.6576 6.9134 20.9941 7.45898C21.3308 8.00484 21.499 8.62946 21.499 9.33203C21.499 10.2239 21.2446 10.9622 20.7344 11.5459C20.2241 12.1242 19.4606 12.4893 18.4453 12.6406C18.9503 12.9433 19.3662 13.2756 19.6924 13.6377C20.0238 13.9999 20.4688 14.6432 21.0264 15.5674L22.4385 17.8857H19.6455L17.9561 15.2998C17.3565 14.3758 16.9466 13.7946 16.7256 13.5566C16.5047 13.3135 16.2706 13.148 16.0234 13.0615C15.7762 12.9696 15.3841 12.9238 14.8477 12.9238H14.374V17.8857H12.0381V8.54395L8.39941 17.8857H5.86133L1.10254 6H3.71289L5.52148 10.7021ZM14.374 11.0264H16.1025C17.2228 11.0264 17.9224 10.9781 18.2012 10.8809C18.4799 10.7836 17.6986 10.6157 18.8564 10.3779C19.0141 10.1402 19.0928 9.84288 19.0928 9.48633C19.0928 9.08641 18.9877 8.7647 18.7773 8.52148C18.5722 8.27286 18.2801 8.11565 17.9014 8.05078C17.712 8.02376 17.1439 8.01075 16.1973 8.01074H14.374V11.0264Z\"/>\n </symbol>\n <symbol id=\"infoIcon\" viewBox=\"-2 -2 24 24\">\n <path d=\"M9.98633 7.58301C10.2598 7.58301 10.4854 7.67643 10.6631 7.86328C10.8408 8.05013 10.9297 8.3099 10.9297 8.64258V14.0361C10.9297 14.4098 10.8408 14.6924 10.6631 14.8838C10.4854 15.0752 10.2598 15.1709 9.98633 15.1709C9.71289 15.1709 9.48958 15.0729 9.31641 14.877C9.14779 14.681 9.06348 14.4007 9.06348 14.0361V8.69727C9.06348 8.32812 9.14779 8.05013 9.31641 7.86328C9.48958 7.67643 9.71289 7.58301 9.98633 7.58301Z\"/>\n <path d=\"M10.0068 4.88965C10.2484 4.88965 10.4626 4.96712 10.6494 5.12207C10.8363 5.27702 10.9297 5.5026 10.9297 5.79883C10.9297 6.08594 10.8385 6.31152 10.6562 6.47559C10.474 6.63509 10.2575 6.71484 10.0068 6.71484C9.74707 6.71484 9.52376 6.63509 9.33691 6.47559C9.15462 6.31608 9.06348 6.09049 9.06348 5.79883C9.06348 5.53451 9.1569 5.31803 9.34375 5.14941C9.53516 4.97624 9.75618 4.88965 10.0068 4.88965Z\"/>\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0ZM10 1.7998C5.47126 1.7998 1.7998 5.47126 1.7998 10C1.7998 14.5287 5.47126 18.2002 10 18.2002C14.5287 18.2002 18.2002 14.5287 18.2002 10C18.2002 5.47126 14.5287 1.7998 10 1.7998Z\"/>\n </symbol>\n <symbol id=\"settingsIcon\" viewBox=\"0 0 24 24\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M12 8C14.2091 8 16 9.79086 16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8ZM12 9.7998C10.785 9.7998 9.7998 10.785 9.7998 12C9.7998 13.215 10.785 14.2002 12 14.2002C13.215 14.2002 14.2002 13.215 14.2002 12C14.2002 10.785 13.215 9.7998 12 9.7998Z\"/>\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M12.7119 2.2002C13.1961 2.20028 13.6296 2.49055 13.8164 2.93066L13.8506 3.02051L14.3652 4.56641C14.7875 4.70091 15.1932 4.87075 15.5801 5.07129L17.042 4.3418L17.1299 4.30176C17.5436 4.13502 18.017 4.21245 18.3564 4.50195L18.4268 4.56641L19.4336 5.57324C19.7985 5.93836 19.8889 6.49621 19.6582 6.95801L18.9268 8.41895C19.1274 8.80592 19.2971 9.21159 19.4316 9.63379L20.9795 10.1504C21.4693 10.3138 21.7997 10.7717 21.7998 11.2881V12.7119C21.7997 13.2284 21.4695 13.6873 20.9795 13.8506L19.4316 14.3652C19.2971 14.7875 19.1274 15.1932 18.9268 15.5801L19.6582 17.042C19.8889 17.5038 19.7985 18.0616 19.4336 18.4268L18.4268 19.4336C18.0616 19.7985 17.5038 19.8889 17.042 19.6582L15.5801 18.9268C15.1932 19.1274 14.7875 19.2971 14.3652 19.4316L13.8506 20.9795C13.6873 21.4695 13.2284 21.7997 12.7119 21.7998H11.2881C10.7717 21.7997 10.3138 21.4693 10.1504 20.9795L9.63379 19.4316C9.21159 19.2971 8.80592 19.1274 8.41895 18.9268L6.95801 19.6582C6.49621 19.8889 5.93836 19.7985 5.57324 19.4336L4.56641 18.4268C4.20146 18.0617 4.1112 17.5038 4.3418 17.042L5.07129 15.5801C4.87075 15.1932 4.70091 14.7875 4.56641 14.3652L3.02051 13.8506C2.53057 13.6873 2.20029 13.2283 2.2002 12.7119V11.2881C2.20024 10.7718 2.53076 10.3139 3.02051 10.1504L4.56641 9.63379C4.70094 9.21149 4.86966 8.80498 5.07031 8.41797L4.3418 6.95801C4.11113 6.49617 4.20145 5.93834 4.56641 5.57324L5.57324 4.56641C5.93834 4.20145 6.49617 4.11113 6.95801 4.3418L8.41797 5.07031C8.80498 4.86966 9.21149 4.70094 9.63379 4.56641L10.1504 3.02051L10.1836 2.92969C10.3706 2.49001 10.8042 2.20023 11.2881 2.2002H12.7119ZM11.0186 5.4707L10.8809 5.88477L10.458 5.99316C9.88479 6.13982 9.34317 6.36768 8.84473 6.66309L8.46875 6.88477L8.0791 6.69043L6.50098 5.90137L5.90137 6.50098L6.69043 8.0791L6.88477 8.46875L6.66309 8.84473C6.36768 9.34317 6.13982 9.88479 5.99316 10.458L5.88477 10.8809L5.4707 11.0186L3.7998 11.5762V12.4229L5.4707 12.9805L5.88477 13.1182L5.99316 13.541C6.13969 14.1141 6.36763 14.6556 6.66309 15.1543L6.88477 15.5303L6.69043 15.9199L5.90137 17.498L6.50098 18.0977L8.0791 17.3086L8.46875 17.1133L8.84473 17.3359C9.34314 17.6314 9.88463 17.8591 10.458 18.0059L10.8809 18.1143L11.0186 18.5283L11.5771 20.2002H12.4229L13.1182 18.1143L13.541 18.0059C14.1143 17.8593 14.6556 17.6314 15.1543 17.3359L15.5303 17.1143L15.9199 17.3086L17.498 18.0977L18.0977 17.498L17.3086 15.9199L17.1143 15.5303L17.3359 15.1543C17.6314 14.6556 17.8593 14.1143 18.0059 13.541L18.1143 13.1182L20.2002 12.4229V11.5762L18.1143 10.8809L18.0059 10.458C17.8591 9.88463 17.6314 9.34314 17.3359 8.84473L17.1133 8.46875L17.3086 8.0791L18.0977 6.50098L17.498 5.90137L15.9199 6.69043L15.5303 6.88477L15.1543 6.66309C14.6556 6.36763 14.1141 6.13969 13.541 5.99316L13.1182 5.88477L12.9805 5.4707L12.4229 3.7998H11.5762L11.0186 5.4707Z\"/>\n </symbol>\n <symbol id=\"enterFullscreenIcon\" viewBox=\"0 0 24 24\">\n <path d=\"M3 14.7002C3.49693 14.7002 3.90018 15.1027 3.90039 15.5996V20C3.90039 20.0552 3.94477 20.0996 4 20.0996H8.40039C8.89727 20.0998 9.2998 20.5031 9.2998 21C9.2998 21.4969 8.89727 21.9002 8.40039 21.9004H4C2.95066 21.9004 2.09961 21.0493 2.09961 20V15.5996C2.09982 15.1027 2.50307 14.7002 3 14.7002Z\"/>\n <path d=\"M21 14.7002C21.4969 14.7002 21.9002 15.1027 21.9004 15.5996V20C21.9004 21.0493 21.0493 21.9004 20 21.9004H15.5996C15.1027 21.9002 14.7002 21.4969 14.7002 21C14.7002 20.5031 15.1027 20.0998 15.5996 20.0996H20C20.0552 20.0996 20.0996 20.0552 20.0996 20V15.5996C20.0998 15.1027 20.5031 14.7002 21 14.7002Z\"/>\n <path d=\"M8.40039 2.09961C8.89727 2.09982 9.2998 2.50307 9.2998 3C9.2998 3.49693 8.89727 3.90018 8.40039 3.90039H4C3.94477 3.90039 3.90039 3.94477 3.90039 4V8.40039C3.90018 8.89727 3.49693 9.2998 3 9.2998C2.50307 9.2998 2.09982 8.89727 2.09961 8.40039V4C2.09961 2.95066 2.95066 2.09961 4 2.09961H8.40039Z\"/>\n <path d=\"M20 2.09961C21.0493 2.09961 21.9004 2.95066 21.9004 4V8.40039C21.9002 8.89727 21.4969 9.2998 21 9.2998C20.5031 9.2998 20.0998 8.89727 20.0996 8.40039V4C20.0996 3.94477 20.0552 3.90039 20 3.90039H15.5996C15.1027 3.90018 14.7002 3.49693 14.7002 3C14.7002 2.50307 15.1027 2.09982 15.5996 2.09961H20Z\"/>\n </symbol>\n <symbol id=\"chevronLeftIcon\" viewBox=\"0 0 24 24\">\n <path d=\"M15 19l-7-7 7-7\"/>\n </symbol>\n <symbol id=\"chevronRightIcon\" viewBox=\"0 0 24 24\">\n <path d=\"M9 5l7 7-7 7\"/>\n </symbol>\n <symbol id=\"voxelIcon\" viewBox=\"0 0 16 16\">\n <path d=\"M8 1L2 4.5v7L8 15l6-3.5v-7L8 1zm0 1.15L12.85 5 8 7.85 3.15 5 8 2.15zM3 5.85l4.5 2.6v5.1L3 10.95V5.85zm5.5 7.7v-5.1L13 5.85v5.1l-4.5 2.6z\"/>\n </symbol>\n <symbol id=\"exitFullscreenIcon\" viewBox=\"0 0 24 24\">\n <path d=\"M8 15.0996C8.49706 15.0996 8.90039 15.5029 8.90039 16V21C8.90039 21.4971 8.49706 21.9004 8 21.9004C7.50294 21.9004 7.09961 21.4971 7.09961 21V16.9004H3C2.50294 16.9004 2.09961 16.4971 2.09961 16C2.09961 15.5029 2.50294 15.0996 3 15.0996H8Z\" />\n <path d=\"M21 15.0996C21.4971 15.0996 21.9004 15.5029 21.9004 16C21.9004 16.4971 21.4971 16.9004 21 16.9004H16.9004V21C16.9004 21.4971 16.4971 21.9004 16 21.9004C15.5029 21.9004 15.0996 21.4971 15.0996 21V16C15.0996 15.5029 15.5029 15.0996 16 15.0996H21Z\" />\n <path d=\"M8 2.09961C8.49706 2.09961 8.90039 2.50294 8.90039 3V8C8.90039 8.49706 8.49706 8.90039 8 8.90039H3C2.50294 8.90039 2.09961 8.49706 2.09961 8C2.09961 7.50294 2.50294 7.09961 3 7.09961H7.09961V3C7.09961 2.50294 7.50294 2.09961 8 2.09961Z\" />\n <path d=\"M16 2.09961C16.4971 2.09961 16.9004 2.50294 16.9004 3V7.09961H21C21.4971 7.09961 21.9004 7.50294 21.9004 8C21.9004 8.49706 21.4971 8.90039 21 8.90039H16C15.5029 8.90039 15.0996 8.49706 15.0996 8V3C15.0996 2.50294 15.5029 2.09961 16 2.09961Z\" />\n </symbol>\n </svg>\n\n <!-- Application Script -->\n <script type=\"module\">\n import { main } from './index.js';\n\n const { config, settings } = window.sse;\n const { poster } = config;\n\n // Show the poster image\n if (poster) {\n const element = document.getElementById('poster');\n element.style.setProperty('--poster-url', `url(${poster.src})`);\n element.style.display = 'block';\n element.style.filter = 'blur(40px)';\n\n // hide the canvas\n document.documentElement.style.setProperty('--canvas-opacity', '0');\n }\n\n document.addEventListener('DOMContentLoaded', async () => {\n const canvas = document.getElementById('application-canvas');\n const settingsJson = await settings;\n const viewer = await main(canvas, settingsJson, config);\n });\n </script>\n </body>\n</html>\n";
|
|
@@ -5195,90 +6336,6 @@ class ZipFileSystem {
|
|
|
5195
6336
|
}
|
|
5196
6337
|
}
|
|
5197
6338
|
|
|
5198
|
-
class KdTree {
|
|
5199
|
-
centroids;
|
|
5200
|
-
root;
|
|
5201
|
-
constructor(centroids) {
|
|
5202
|
-
const build = (indices, depth) => {
|
|
5203
|
-
const { centroids } = this;
|
|
5204
|
-
const values = centroids.columns[depth % centroids.numColumns].data;
|
|
5205
|
-
indices.sort((a, b) => values[a] - values[b]);
|
|
5206
|
-
if (indices.length === 1) {
|
|
5207
|
-
return {
|
|
5208
|
-
index: indices[0],
|
|
5209
|
-
count: 1
|
|
5210
|
-
};
|
|
5211
|
-
}
|
|
5212
|
-
else if (indices.length === 2) {
|
|
5213
|
-
return {
|
|
5214
|
-
index: indices[0],
|
|
5215
|
-
count: 2,
|
|
5216
|
-
right: {
|
|
5217
|
-
index: indices[1],
|
|
5218
|
-
count: 1
|
|
5219
|
-
}
|
|
5220
|
-
};
|
|
5221
|
-
}
|
|
5222
|
-
const mid = indices.length >> 1;
|
|
5223
|
-
const left = build(indices.subarray(0, mid), depth + 1);
|
|
5224
|
-
const right = build(indices.subarray(mid + 1), depth + 1);
|
|
5225
|
-
return {
|
|
5226
|
-
index: indices[mid],
|
|
5227
|
-
count: 1 + left.count + right.count,
|
|
5228
|
-
left,
|
|
5229
|
-
right
|
|
5230
|
-
};
|
|
5231
|
-
};
|
|
5232
|
-
const indices = new Uint32Array(centroids.numRows);
|
|
5233
|
-
for (let i = 0; i < indices.length; ++i) {
|
|
5234
|
-
indices[i] = i;
|
|
5235
|
-
}
|
|
5236
|
-
this.centroids = centroids;
|
|
5237
|
-
this.root = build(indices, 0);
|
|
5238
|
-
}
|
|
5239
|
-
findNearest(point, filterFunc) {
|
|
5240
|
-
const { centroids } = this;
|
|
5241
|
-
const { numColumns } = centroids;
|
|
5242
|
-
const calcDistance = (index) => {
|
|
5243
|
-
let l = 0;
|
|
5244
|
-
for (let i = 0; i < numColumns; ++i) {
|
|
5245
|
-
const v = centroids.columns[i].data[index] - point[i];
|
|
5246
|
-
l += v * v;
|
|
5247
|
-
}
|
|
5248
|
-
return l;
|
|
5249
|
-
};
|
|
5250
|
-
let mind = Infinity;
|
|
5251
|
-
let mini = -1;
|
|
5252
|
-
let cnt = 0;
|
|
5253
|
-
const recurse = (node, depth) => {
|
|
5254
|
-
const axis = depth % numColumns;
|
|
5255
|
-
const distance = point[axis] - centroids.columns[axis].data[node.index];
|
|
5256
|
-
const next = (distance > 0) ? node.right : node.left;
|
|
5257
|
-
cnt++;
|
|
5258
|
-
if (next) {
|
|
5259
|
-
recurse(next, depth + 1);
|
|
5260
|
-
}
|
|
5261
|
-
// check index
|
|
5262
|
-
if (!filterFunc || filterFunc(node.index)) {
|
|
5263
|
-
const thisd = calcDistance(node.index);
|
|
5264
|
-
if (thisd < mind) {
|
|
5265
|
-
mind = thisd;
|
|
5266
|
-
mini = node.index;
|
|
5267
|
-
}
|
|
5268
|
-
}
|
|
5269
|
-
// check the other side
|
|
5270
|
-
if (distance * distance < mind) {
|
|
5271
|
-
const other = next === node.right ? node.left : node.right;
|
|
5272
|
-
if (other) {
|
|
5273
|
-
recurse(other, depth + 1);
|
|
5274
|
-
}
|
|
5275
|
-
}
|
|
5276
|
-
};
|
|
5277
|
-
recurse(this.root, 0);
|
|
5278
|
-
return { index: mini, distanceSqr: mind, cnt };
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
|
|
5282
6339
|
const clusterWgsl = (numColumns, useF16) => {
|
|
5283
6340
|
const floatType = useF16 ? 'f16' : 'f32';
|
|
5284
6341
|
return /* wgsl */ `
|
|
@@ -7985,6 +9042,9 @@ const getOutputFormat = (filename, options) => {
|
|
|
7985
9042
|
else if (lowerFilename.endsWith('.ply')) {
|
|
7986
9043
|
return 'ply';
|
|
7987
9044
|
}
|
|
9045
|
+
else if (lowerFilename.endsWith('.glb')) {
|
|
9046
|
+
return 'glb';
|
|
9047
|
+
}
|
|
7988
9048
|
else if (lowerFilename.endsWith('.html')) {
|
|
7989
9049
|
return options.unbundled ? 'html' : 'html-bundle';
|
|
7990
9050
|
}
|
|
@@ -8055,6 +9115,9 @@ const writeFile = async (writeOptions, fs) => {
|
|
|
8055
9115
|
}
|
|
8056
9116
|
}, fs);
|
|
8057
9117
|
break;
|
|
9118
|
+
case 'glb':
|
|
9119
|
+
await writeGlb({ filename, dataTable }, fs);
|
|
9120
|
+
break;
|
|
8058
9121
|
case 'html':
|
|
8059
9122
|
case 'html-bundle':
|
|
8060
9123
|
await writeHtml({
|
|
@@ -8308,13 +9371,7 @@ const processDataTable = (dataTable, processActions) => {
|
|
|
8308
9371
|
result.permuteRowsInPlace(indices);
|
|
8309
9372
|
break;
|
|
8310
9373
|
}
|
|
8311
|
-
case '
|
|
8312
|
-
const indices = new Uint32Array(result.numRows);
|
|
8313
|
-
for (let i = 0; i < indices.length; i++) {
|
|
8314
|
-
indices[i] = i;
|
|
8315
|
-
}
|
|
8316
|
-
sortByVisibility(result, indices);
|
|
8317
|
-
// Determine how many to keep
|
|
9374
|
+
case 'decimate': {
|
|
8318
9375
|
let keepCount;
|
|
8319
9376
|
if (processAction.count !== null) {
|
|
8320
9377
|
keepCount = Math.min(processAction.count, result.numRows);
|
|
@@ -8323,7 +9380,7 @@ const processDataTable = (dataTable, processActions) => {
|
|
|
8323
9380
|
keepCount = Math.round(result.numRows * (processAction.percent ?? 100) / 100);
|
|
8324
9381
|
}
|
|
8325
9382
|
keepCount = Math.max(0, keepCount);
|
|
8326
|
-
result = result
|
|
9383
|
+
result = simplifyGaussians(result, keepCount);
|
|
8327
9384
|
break;
|
|
8328
9385
|
}
|
|
8329
9386
|
}
|
|
@@ -8356,12 +9413,14 @@ exports.readSog = readSog;
|
|
|
8356
9413
|
exports.readSplat = readSplat;
|
|
8357
9414
|
exports.readSpz = readSpz;
|
|
8358
9415
|
exports.readVoxel = readVoxel;
|
|
9416
|
+
exports.simplifyGaussians = simplifyGaussians;
|
|
8359
9417
|
exports.sortByVisibility = sortByVisibility;
|
|
8360
9418
|
exports.sortMortonOrder = sortMortonOrder;
|
|
8361
9419
|
exports.transform = transform;
|
|
8362
9420
|
exports.writeCompressedPly = writeCompressedPly;
|
|
8363
9421
|
exports.writeCsv = writeCsv;
|
|
8364
9422
|
exports.writeFile = writeFile;
|
|
9423
|
+
exports.writeGlb = writeGlb;
|
|
8365
9424
|
exports.writeHtml = writeHtml;
|
|
8366
9425
|
exports.writeLod = writeLod;
|
|
8367
9426
|
exports.writePly = writePly;
|