Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 42 additions & 38 deletions src/spz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getSplatFileType,
getSplatFileTypeFromPath,
} from "./SplatLoader";
import { GunzipReader, fromHalf, unpackSplat } from "./utils";
import { GunzipReader, fromHalf, normalize, unpackSplat } from "./utils";

import { decodeAntiSplat } from "./antisplat";
import { decodeKsplat } from "./ksplat";
Expand Down Expand Up @@ -149,9 +149,7 @@ export class SpzReader {
}
if (this.version === 3) {
// Version 3 uses a trick called "smallest three" to compress the rotation quaternions
// achieving better precision.
// "Optimizing orientation" section at https://gafferongames.com/post/snapshot_compression/
// A quaternion length must be 1: x^2+y^2+z^2+w^2 = 1
// achieving better precision. "Optimizing orientation" section at https://gafferongames.com/post/snapshot_compression/ A quaternion length must be 1: x^2+y^2+z^2+w^2 = 1
// We can drop one component and reconstruct it with the identity above.
// Largest component is dropped for best numerical precision.
// Quaternion stored in 32 bits
Expand Down Expand Up @@ -262,7 +260,7 @@ const SH_DEGREE_TO_VECS: Record<number, number> = { 1: 3, 2: 8, 3: 15 };
const SH_C0 = 0.28209479177387814;

export const SPZ_MAGIC = 0x5053474e; // NGSP = Niantic gaussian splat
export const SPZ_VERSION = 2;
export const SPZ_VERSION = 3;
export const FLAG_ANTIALIASED = 0x1;

export class SpzWriter {
Expand All @@ -287,11 +285,11 @@ export class SpzWriter {
flagAntiAlias?: boolean;
}) {
const splatSize =
9 +
1 +
3 +
3 +
3 +
9 + // Position
1 + // Opacity
3 + // Scale
3 + // DC-rgb
4 + // Rotation
(shDegree >= 1 ? 9 : 0) +
(shDegree >= 2 ? 15 : 0) +
(shDegree >= 3 ? 21 : 0);
Expand Down Expand Up @@ -382,34 +380,40 @@ export class SpzWriter {

setQuat(
index: number,
quatX: number,
quatY: number,
quatZ: number,
quatW: number,
...q: [number, number, number, number] // x, y, z, w
) {
const base = 16 + this.numSplats * 16 + index * 3;
const quatNeg = quatW < 0;
this.view.setUint8(
base,
Math.max(
0,
Math.min(255, Math.round(((quatNeg ? -quatX : quatX) + 1) * 127.5)),
),
);
this.view.setUint8(
base + 1,
Math.max(
0,
Math.min(255, Math.round(((quatNeg ? -quatY : quatY) + 1) * 127.5)),
),
);
this.view.setUint8(
base + 2,
Math.max(
0,
Math.min(255, Math.round(((quatNeg ? -quatZ : quatZ) + 1) * 127.5)),
),
);
const base = 16 + this.numSplats * 16 + index * 4;

const quat = normalize(q);

// Find largest component
let iLargest = 0;
for (let i = 1; i < 4; ++i) {
if (Math.abs(quat[i]) > Math.abs(quat[iLargest])) {
iLargest = i;
}
}

// Since -quat represents the same rotation as quat, transform the quaternion so the largest element
// is positive. This avoids having to send its sign bit.
const negate = quat[iLargest] < 0 ? 1 : 0;

// Do compression using sign bit and 9-bit precision per element.
let comp = iLargest;
for (let i = 0; i < 4; ++i) {
if (i !== iLargest) {
const negbit = (quat[i] < 0 ? 1 : 0) ^ negate;
const mag = Math.floor(
((1 << 9) - 1) * (Math.abs(quat[i]) / Math.SQRT1_2) + 0.5,
);
comp = (comp << 10) | (negbit << 9) | mag;
}
}

this.view.setUint8(base, comp & 0xff);
this.view.setUint8(base + 1, (comp >> 8) & 0xff);
this.view.setUint8(base + 2, (comp >> 16) & 0xff);
this.view.setUint8(base + 3, (comp >>> 24) & 0xff);
}

static quantizeSh(sh: number, bits: number) {
Expand All @@ -427,7 +431,7 @@ export class SpzWriter {
sh3?: Float32Array,
) {
const shVecs = SH_DEGREE_TO_VECS[this.shDegree] || 0;
const base1 = 16 + this.numSplats * 19 + index * shVecs * 3;
const base1 = 16 + this.numSplats * 20 + index * shVecs * 3;
for (let j = 0; j < 9; ++j) {
this.view.setUint8(base1 + j, SpzWriter.quantizeSh(sh1[j], 5));
}
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const f16buffer = supportsFloat16Array
: null;
const u16buffer = new Uint16Array(f16buffer?.buffer);

// Returns a normalized array of numbers
export function normalize(vec: number[]) {
const norm = Math.sqrt(vec.reduce((acc, v) => acc + v * v, 0));
return vec.map((v) => v / norm);
}

// Reinterpret the bits of a float32 as a uint32
export function floatBitsToUint(f: number): number {
f32buffer[0] = f;
Expand Down
Loading