@@ -14,6 +14,7 @@ import { decompressPartialGzip, getTextureSize } from "./utils";
1414export class SplatLoader extends Loader {
1515 fileLoader : FileLoader ;
1616 fileType ?: SplatFileType ;
17+ packedSplats ?: PackedSplats ;
1718
1819 constructor ( manager ?: LoadingManager ) {
1920 super ( manager ) ;
@@ -37,12 +38,61 @@ export class SplatLoader extends Loader {
3738 async ( response ) => {
3839 if ( onLoad ) {
3940 const input = response as ArrayBuffer ;
40- const decoded = await unpackSplats ( {
41- input,
42- fileType : this . fileType ,
43- pathOrUrl : url ,
44- } ) ;
45- onLoad ( new PackedSplats ( decoded ) ) ;
41+ const extraFiles : Record < string , ArrayBuffer > = { } ;
42+ const promises = [ ] ;
43+ let fileType = this . fileType ;
44+
45+ try {
46+ const pcSogsJson = tryPcSogs ( input ) ;
47+ if ( this . fileType === SplatFileType . PCSOGS ) {
48+ if ( pcSogsJson === undefined ) {
49+ throw new Error ( "Invalid PC SOGS file" ) ;
50+ }
51+ }
52+
53+ if ( pcSogsJson !== undefined ) {
54+ fileType = SplatFileType . PCSOGS ;
55+ for ( const key of [ "means" , "scales" , "quats" , "sh0" , "shN" ] ) {
56+ const prop = pcSogsJson [ key as keyof PcSogsJson ] ;
57+ if ( prop ) {
58+ const files = prop . files ;
59+ for ( const file of files ) {
60+ const fileUrl = new URL ( file , url ) . toString ( ) ;
61+ this . manager . itemStart ( fileUrl ) ;
62+ const promise = this . loadExtra ( fileUrl )
63+ . then ( ( data ) => {
64+ extraFiles [ file ] = data ;
65+ } )
66+ . catch ( ( error ) => {
67+ this . manager . itemError ( fileUrl ) ;
68+ throw error ;
69+ } )
70+ . finally ( ( ) => {
71+ this . manager . itemEnd ( fileUrl ) ;
72+ } ) ;
73+ promises . push ( promise ) ;
74+ }
75+ }
76+ }
77+ }
78+
79+ await Promise . all ( promises ) ;
80+ const decoded = await unpackSplats ( {
81+ input,
82+ extraFiles,
83+ fileType,
84+ pathOrUrl : url ,
85+ } ) ;
86+
87+ if ( this . packedSplats ) {
88+ this . packedSplats . initialize ( decoded ) ;
89+ onLoad ( this . packedSplats ) ;
90+ } else {
91+ onLoad ( new PackedSplats ( decoded ) ) ;
92+ }
93+ } catch ( error ) {
94+ onError ?.( error ) ;
95+ }
4696 }
4797 } ,
4898 onProgress ,
@@ -66,6 +116,17 @@ export class SplatLoader extends Loader {
66116 } ) ;
67117 }
68118
119+ async loadExtra ( url : string ) : Promise < ArrayBuffer > {
120+ return new Promise ( ( resolve , reject ) => {
121+ this . fileLoader . load (
122+ url ,
123+ ( response ) => resolve ( response as ArrayBuffer ) ,
124+ undefined ,
125+ ( error ) => reject ( error ) ,
126+ ) ;
127+ } ) ;
128+ }
129+
69130 parse ( packedSplats : PackedSplats ) : SplatMesh {
70131 return new SplatMesh ( { packedSplats } ) ;
71132 }
@@ -76,6 +137,7 @@ export enum SplatFileType {
76137 SPZ = "spz" ,
77138 SPLAT = "splat" ,
78139 KSPLAT = "ksplat" ,
140+ PCSOGS = "pcsogs" ,
79141}
80142
81143export function getSplatFileType (
@@ -133,12 +195,96 @@ export function getSplatFileTypeFromPath(
133195 return undefined ;
134196}
135197
198+ export type PcSogsJson = {
199+ means : {
200+ shape : number [ ] ;
201+ dtype : string ;
202+ mins : number [ ] ;
203+ maxs : number [ ] ;
204+ files : string [ ] ;
205+ } ;
206+ scales : {
207+ shape : number [ ] ;
208+ dtype : string ;
209+ mins : number [ ] ;
210+ maxs : number [ ] ;
211+ files : string [ ] ;
212+ } ;
213+ quats : { shape : number [ ] ; dtype : string ; encoding ?: string ; files : string [ ] } ;
214+ sh0 : {
215+ shape : number [ ] ;
216+ dtype : string ;
217+ mins : number [ ] ;
218+ maxs : number [ ] ;
219+ files : string [ ] ;
220+ } ;
221+ shN : {
222+ shape : number [ ] ;
223+ dtype : string ;
224+ mins : number ;
225+ maxs : number ;
226+ quantization : number ;
227+ files : string [ ] ;
228+ } ;
229+ } ;
230+
231+ export function isPcSogs ( input : ArrayBuffer | Uint8Array | string ) : boolean {
232+ // Returns true if the input seems to be a valid PC SOGS file
233+ return tryPcSogs ( input ) !== undefined ;
234+ }
235+
236+ export function tryPcSogs (
237+ input : ArrayBuffer | Uint8Array | string ,
238+ ) : PcSogsJson | undefined {
239+ // Try to parse input as SOGS JSON and see if it's valid
240+ try {
241+ let text : string ;
242+ if ( typeof input === "string" ) {
243+ text = input ;
244+ } else {
245+ const fileBytes =
246+ input instanceof ArrayBuffer ? new Uint8Array ( input ) : input ;
247+ if ( fileBytes . length > 65536 ) {
248+ // Should be only a few KB, definitely not a SOGS JSON file
249+ return undefined ;
250+ }
251+ text = new TextDecoder ( ) . decode ( fileBytes ) ;
252+ }
253+
254+ const json = JSON . parse ( text ) ;
255+ if ( ! json || typeof json !== "object" || Array . isArray ( json ) ) {
256+ return undefined ;
257+ }
258+ for ( const key of [ "means" , "scales" , "quats" , "sh0" ] ) {
259+ if (
260+ ! json [ key ] ||
261+ typeof json [ key ] !== "object" ||
262+ Array . isArray ( json [ key ] )
263+ ) {
264+ return undefined ;
265+ }
266+ if ( ! json [ key ] . shape || ! json [ key ] . files ) {
267+ return undefined ;
268+ }
269+ if ( key !== "quats" && ( ! json [ key ] . mins || ! json [ key ] . maxs ) ) {
270+ return undefined ;
271+ }
272+ }
273+ // This is probably a PC SOGS file
274+ return json as PcSogsJson ;
275+ } catch {
276+ return undefined ;
277+ }
278+ }
279+
136280export async function unpackSplats ( {
137281 input,
282+ extraFiles,
138283 fileType,
139284 pathOrUrl,
140285} : {
141286 input : Uint8Array | ArrayBuffer ;
287+ extraFiles ?: Record < string , ArrayBuffer > ;
142288 fileType ?: SplatFileType ;
143289 pathOrUrl ?: string ;
144290} ) : Promise < {
@@ -201,7 +347,7 @@ export async function unpackSplats({
201347 return { packedArray, numSplats } ;
202348 } ) ;
203349 }
204- case SplatFileType . KSPLAT :
350+ case SplatFileType . KSPLAT : {
205351 return await withWorker ( async ( worker ) => {
206352 const { packedArray, numSplats, extra } = ( await worker . call (
207353 "decodeKsplat" ,
@@ -213,6 +359,20 @@ export async function unpackSplats({
213359 } ;
214360 return { packedArray, numSplats, extra } ;
215361 } ) ;
362+ }
363+ case SplatFileType . PCSOGS : {
364+ return await withWorker ( async ( worker ) => {
365+ const { packedArray, numSplats, extra } = ( await worker . call (
366+ "decodePcSogs" ,
367+ { fileBytes, extraFiles } ,
368+ ) ) as {
369+ packedArray : Uint32Array ;
370+ numSplats : number ;
371+ extra : Record < string , unknown > ;
372+ } ;
373+ return { packedArray, numSplats, extra } ;
374+ } ) ;
375+ }
216376 default : {
217377 throw new Error ( `Unknown splat file type: ${ splatFileType } ` ) ;
218378 }
0 commit comments