66 getSplatFileType ,
77 getSplatFileTypeFromPath ,
88} from "./SplatLoader" ;
9- import { GunzipReader , fromHalf , unpackSplat } from "./utils" ;
9+ import { GunzipReader , fromHalf , normalize , unpackSplat } from "./utils" ;
1010
1111import { decodeAntiSplat } from "./antisplat" ;
1212import { decodeKsplat } from "./ksplat" ;
@@ -149,9 +149,7 @@ export class SpzReader {
149149 }
150150 if ( this . version === 3 ) {
151151 // Version 3 uses a trick called "smallest three" to compress the rotation quaternions
152- // achieving better precision.
153- // "Optimizing orientation" section at https://gafferongames.com/post/snapshot_compression/
154- // A quaternion length must be 1: x^2+y^2+z^2+w^2 = 1
152+ // 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
155153 // We can drop one component and reconstruct it with the identity above.
156154 // Largest component is dropped for best numerical precision.
157155 // Quaternion stored in 32 bits
@@ -262,7 +260,7 @@ const SH_DEGREE_TO_VECS: Record<number, number> = { 1: 3, 2: 8, 3: 15 };
262260const SH_C0 = 0.28209479177387814 ;
263261
264262export const SPZ_MAGIC = 0x5053474e ; // NGSP = Niantic gaussian splat
265- export const SPZ_VERSION = 2 ;
263+ export const SPZ_VERSION = 3 ;
266264export const FLAG_ANTIALIASED = 0x1 ;
267265
268266export class SpzWriter {
@@ -287,11 +285,11 @@ export class SpzWriter {
287285 flagAntiAlias ?: boolean ;
288286 } ) {
289287 const splatSize =
290- 9 +
291- 1 +
292- 3 +
293- 3 +
294- 3 +
288+ 9 + // Position
289+ 1 + // Opacity
290+ 3 + // Scale
291+ 3 + // DC-rgb
292+ 4 + // Rotation
295293 ( shDegree >= 1 ? 9 : 0 ) +
296294 ( shDegree >= 2 ? 15 : 0 ) +
297295 ( shDegree >= 3 ? 21 : 0 ) ;
@@ -382,34 +380,40 @@ export class SpzWriter {
382380
383381 setQuat (
384382 index : number ,
385- quatX : number ,
386- quatY : number ,
387- quatZ : number ,
388- quatW : number ,
383+ ...q : [ number , number , number , number ] // x, y, z, w
389384 ) {
390- const base = 16 + this . numSplats * 16 + index * 3 ;
391- const quatNeg = quatW < 0 ;
392- this . view . setUint8 (
393- base ,
394- Math . max (
395- 0 ,
396- Math . min ( 255 , Math . round ( ( ( quatNeg ? - quatX : quatX ) + 1 ) * 127.5 ) ) ,
397- ) ,
398- ) ;
399- this . view . setUint8 (
400- base + 1 ,
401- Math . max (
402- 0 ,
403- Math . min ( 255 , Math . round ( ( ( quatNeg ? - quatY : quatY ) + 1 ) * 127.5 ) ) ,
404- ) ,
405- ) ;
406- this . view . setUint8 (
407- base + 2 ,
408- Math . max (
409- 0 ,
410- Math . min ( 255 , Math . round ( ( ( quatNeg ? - quatZ : quatZ ) + 1 ) * 127.5 ) ) ,
411- ) ,
412- ) ;
385+ const base = 16 + this . numSplats * 16 + index * 4 ;
386+
387+ const quat = normalize ( q ) ;
388+
389+ // Find largest component
390+ let iLargest = 0 ;
391+ for ( let i = 1 ; i < 4 ; ++ i ) {
392+ if ( Math . abs ( quat [ i ] ) > Math . abs ( quat [ iLargest ] ) ) {
393+ iLargest = i ;
394+ }
395+ }
396+
397+ // Since -quat represents the same rotation as quat, transform the quaternion so the largest element
398+ // is positive. This avoids having to send its sign bit.
399+ const negate = quat [ iLargest ] < 0 ? 1 : 0 ;
400+
401+ // Do compression using sign bit and 9-bit precision per element.
402+ let comp = iLargest ;
403+ for ( let i = 0 ; i < 4 ; ++ i ) {
404+ if ( i !== iLargest ) {
405+ const negbit = ( quat [ i ] < 0 ? 1 : 0 ) ^ negate ;
406+ const mag = Math . floor (
407+ ( ( 1 << 9 ) - 1 ) * ( Math . abs ( quat [ i ] ) / Math . SQRT1_2 ) + 0.5 ,
408+ ) ;
409+ comp = ( comp << 10 ) | ( negbit << 9 ) | mag ;
410+ }
411+ }
412+
413+ this . view . setUint8 ( base , comp & 0xff ) ;
414+ this . view . setUint8 ( base + 1 , ( comp >> 8 ) & 0xff ) ;
415+ this . view . setUint8 ( base + 2 , ( comp >> 16 ) & 0xff ) ;
416+ this . view . setUint8 ( base + 3 , ( comp >>> 24 ) & 0xff ) ;
413417 }
414418
415419 static quantizeSh ( sh : number , bits : number ) {
@@ -427,7 +431,7 @@ export class SpzWriter {
427431 sh3 ?: Float32Array ,
428432 ) {
429433 const shVecs = SH_DEGREE_TO_VECS [ this . shDegree ] || 0 ;
430- const base1 = 16 + this . numSplats * 19 + index * shVecs * 3 ;
434+ const base1 = 16 + this . numSplats * 20 + index * shVecs * 3 ;
431435 for ( let j = 0 ; j < 9 ; ++ j ) {
432436 this . view . setUint8 ( base1 + j , SpzWriter . quantizeSh ( sh1 [ j ] , 5 ) ) ;
433437 }
0 commit comments