Spaces:
Running
Running
import { Camera } from "../cameras/Camera"; | |
import type { Scene } from "../core/Scene"; | |
import { Matrix3 } from "../math/Matrix3"; | |
import { Quaternion } from "../math/Quaternion"; | |
import { Vector3 } from "../math/Vector3"; | |
import { Splatv } from "../splats/Splatv"; | |
import { SplatvData } from "../splats/SplatvData"; | |
import { initiateFetchRequest, loadRequestDataIntoBuffer } from "../utils/LoaderUtils"; | |
class SplatvLoader { | |
static async LoadAsync( | |
url: string, | |
scene: Scene, | |
camera: Camera | null, | |
onProgress?: (progress: number) => void, | |
useCache: boolean = false, | |
): Promise<Splatv> { | |
const res: Response = await initiateFetchRequest(url, useCache); | |
const buffer = await loadRequestDataIntoBuffer(res, onProgress); | |
return this._ParseSplatvBuffer(buffer.buffer, scene, camera); | |
} | |
static async LoadFromFileAsync( | |
file: File, | |
scene: Scene, | |
camera: Camera | null, | |
onProgress?: (progress: number) => void, | |
): Promise<Splatv> { | |
const reader = new FileReader(); | |
let splatv: Splatv | null = null; | |
reader.onload = (e) => { | |
splatv = this._ParseSplatvBuffer(e.target!.result as ArrayBuffer, scene, camera); | |
}; | |
reader.onprogress = (e) => { | |
onProgress?.(e.loaded / e.total); | |
}; | |
reader.readAsArrayBuffer(file); | |
await new Promise<void>((resolve) => { | |
reader.onloadend = () => { | |
resolve(); | |
}; | |
}); | |
if (!splatv) { | |
throw new Error("Failed to load splatv file"); | |
} | |
return splatv; | |
} | |
private static _ParseSplatvBuffer(inputBuffer: ArrayBuffer, scene: Scene, camera: Camera | null): Splatv { | |
let result: Splatv | null = null; | |
const handleChunk = ( | |
chunk: { size: number; type: string; texwidth: number; texheight: number }, | |
buffer: Uint8Array, | |
chunks: { size: number; type: string }[], | |
) => { | |
if (chunk.type === "magic") { | |
const intView = new Int32Array(buffer.buffer); | |
if (intView[0] !== 0x674b) { | |
throw new Error("Invalid splatv file"); | |
} | |
chunks.push({ size: intView[1], type: "chunks" }); | |
} else if (chunk.type === "chunks") { | |
const splatChunks = JSON.parse(new TextDecoder("utf-8").decode(buffer)); | |
if (splatChunks.length == 0) { | |
throw new Error("Invalid splatv file"); | |
} else if (splatChunks.length > 1) { | |
console.warn("Splatv file contains more than one chunk, only the first one will be loaded"); | |
} | |
const chunk = splatChunks[0]; | |
const cameras = chunk.cameras as { position: number[]; rotation: number[][] }[]; | |
if (camera && cameras && cameras.length) { | |
const cameraData = cameras[0]; | |
const position = new Vector3( | |
cameraData.position[0], | |
cameraData.position[1], | |
cameraData.position[2], | |
); | |
const rotation = Quaternion.FromMatrix3( | |
new Matrix3( | |
cameraData.rotation[0][0], | |
cameraData.rotation[0][1], | |
cameraData.rotation[0][2], | |
cameraData.rotation[1][0], | |
cameraData.rotation[1][1], | |
cameraData.rotation[1][2], | |
cameraData.rotation[2][0], | |
cameraData.rotation[2][1], | |
cameraData.rotation[2][2], | |
), | |
); | |
camera.position = position; | |
camera.rotation = rotation; | |
} | |
chunks.push(chunk); | |
} else if (chunk.type === "splat") { | |
const data = SplatvData.Deserialize(buffer, chunk.texwidth, chunk.texheight); | |
const splatv = new Splatv(data); | |
scene.addObject(splatv); | |
result = splatv; | |
} | |
}; | |
const ubuf = new Uint8Array(inputBuffer); | |
const chunks: { size: number; type: string; texwidth: number; texheight: number }[] = [ | |
{ size: 8, type: "magic", texwidth: 0, texheight: 0 }, | |
]; | |
let chunk: { size: number; type: string; texwidth: number; texheight: number } | undefined = chunks.shift(); | |
let buffer = new Uint8Array(chunk!.size); | |
let offset = 0; | |
let inputOffset = 0; | |
while (chunk) { | |
while (offset < chunk.size) { | |
const sizeToRead = Math.min(chunk.size - offset, ubuf.length - inputOffset); | |
buffer.set(ubuf.subarray(inputOffset, inputOffset + sizeToRead), offset); | |
offset += sizeToRead; | |
inputOffset += sizeToRead; | |
} | |
handleChunk(chunk, buffer, chunks); | |
if (result) { | |
return result; | |
} | |
chunk = chunks.shift(); | |
if (chunk) { | |
buffer = new Uint8Array(chunk.size); | |
offset = 0; | |
} | |
} | |
throw new Error("Invalid splatv file"); | |
} | |
} | |
export { SplatvLoader }; | |