Spaces:
Running
Running
| import { Scene } from "../core/Scene"; | |
| import { Vector3 } from "../math/Vector3"; | |
| import { Quaternion } from "../math/Quaternion"; | |
| import { SplatData } from "../splats/SplatData"; | |
| import { Splat } from "../splats/Splat"; | |
| import { Converter } from "../utils/Converter"; | |
| import { initiateFetchRequest, loadRequestDataIntoBuffer } from "../utils/LoaderUtils"; | |
| class PLYLoader { | |
| static async LoadAsync( | |
| url: string, | |
| scene: Scene, | |
| onProgress?: (progress: number) => void, | |
| format: string = "", | |
| useCache: boolean = false, | |
| ): Promise<Splat> { | |
| const res: Response = await initiateFetchRequest(url, useCache); | |
| const plyData = await loadRequestDataIntoBuffer(res, onProgress); | |
| if (plyData[0] !== 112 || plyData[1] !== 108 || plyData[2] !== 121 || plyData[3] !== 10) { | |
| throw new Error("Invalid PLY file"); | |
| } | |
| return this.LoadFromArrayBuffer(plyData.buffer, scene, format); | |
| } | |
| static async LoadFromFileAsync( | |
| file: File, | |
| scene: Scene, | |
| onProgress?: (progress: number) => void, | |
| format: string = "", | |
| ): Promise<Splat> { | |
| const reader = new FileReader(); | |
| let splat = new Splat(); | |
| reader.onload = (e) => { | |
| splat = this.LoadFromArrayBuffer(e.target!.result as ArrayBuffer, scene, format); | |
| }; | |
| reader.onprogress = (e) => { | |
| onProgress?.(e.loaded / e.total); | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| await new Promise<void>((resolve) => { | |
| reader.onloadend = () => { | |
| resolve(); | |
| }; | |
| }); | |
| return splat; | |
| } | |
| static LoadFromArrayBuffer(arrayBuffer: ArrayBufferLike, scene: Scene, format: string = ""): Splat { | |
| const buffer = new Uint8Array(this._ParsePLYBuffer(arrayBuffer, format)); | |
| const data = SplatData.Deserialize(buffer); | |
| const splat = new Splat(data); | |
| scene.addObject(splat); | |
| return splat; | |
| } | |
| private static _ParsePLYBuffer(inputBuffer: ArrayBuffer, format: string): ArrayBuffer { | |
| type PlyProperty = { | |
| name: string; | |
| type: string; | |
| offset: number; | |
| }; | |
| const ubuf = new Uint8Array(inputBuffer); | |
| const headerText = new TextDecoder().decode(ubuf.slice(0, 1024 * 10)); | |
| const header_end = "end_header\n"; | |
| const header_end_index = headerText.indexOf(header_end); | |
| if (header_end_index < 0) throw new Error("Unable to read .ply file header"); | |
| const vertexCount = parseInt(/element vertex (\d+)\n/.exec(headerText)![1]); | |
| let rowOffset = 0; | |
| const offsets: Record<string, number> = { | |
| double: 8, | |
| int: 4, | |
| uint: 4, | |
| float: 4, | |
| short: 2, | |
| ushort: 2, | |
| uchar: 1, | |
| }; | |
| const properties: PlyProperty[] = []; | |
| for (const prop of headerText | |
| .slice(0, header_end_index) | |
| .split("\n") | |
| .filter((k) => k.startsWith("property "))) { | |
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| const [_p, type, name] = prop.split(" "); | |
| properties.push({ name, type, offset: rowOffset }); | |
| if (!offsets[type]) throw new Error(`Unsupported property type: ${type}`); | |
| rowOffset += offsets[type]; | |
| } | |
| const dataView = new DataView(inputBuffer, header_end_index + header_end.length); | |
| const buffer = new ArrayBuffer(SplatData.RowLength * vertexCount); | |
| const q_polycam = Quaternion.FromEuler(new Vector3(Math.PI / 2, 0, 0)); | |
| for (let i = 0; i < vertexCount; i++) { | |
| const position = new Float32Array(buffer, i * SplatData.RowLength, 3); | |
| const scale = new Float32Array(buffer, i * SplatData.RowLength + 12, 3); | |
| const rgba = new Uint8ClampedArray(buffer, i * SplatData.RowLength + 24, 4); | |
| const rot = new Uint8ClampedArray(buffer, i * SplatData.RowLength + 28, 4); | |
| let r0: number = 255; | |
| let r1: number = 0; | |
| let r2: number = 0; | |
| let r3: number = 0; | |
| properties.forEach((property) => { | |
| let value; | |
| switch (property.type) { | |
| case "float": | |
| value = dataView.getFloat32(property.offset + i * rowOffset, true); | |
| break; | |
| case "int": | |
| value = dataView.getInt32(property.offset + i * rowOffset, true); | |
| break; | |
| default: | |
| throw new Error(`Unsupported property type: ${property.type}`); | |
| } | |
| switch (property.name) { | |
| case "x": | |
| position[0] = value; | |
| break; | |
| case "y": | |
| position[1] = value; | |
| break; | |
| case "z": | |
| position[2] = value; | |
| break; | |
| case "scale_0": | |
| case "scaling_0": | |
| scale[0] = Math.exp(value); | |
| break; | |
| case "scale_1": | |
| case "scaling_1": | |
| scale[1] = Math.exp(value); | |
| break; | |
| case "scale_2": | |
| case "scaling_2": | |
| scale[2] = Math.exp(value); | |
| break; | |
| case "red": | |
| rgba[0] = value; | |
| break; | |
| case "green": | |
| rgba[1] = value; | |
| break; | |
| case "blue": | |
| rgba[2] = value; | |
| break; | |
| case "f_dc_0": | |
| case "features_0": | |
| rgba[0] = (0.5 + Converter.SH_C0 * value) * 255; | |
| break; | |
| case "f_dc_1": | |
| case "features_1": | |
| rgba[1] = (0.5 + Converter.SH_C0 * value) * 255; | |
| break; | |
| case "f_dc_2": | |
| case "features_2": | |
| rgba[2] = (0.5 + Converter.SH_C0 * value) * 255; | |
| break; | |
| case "f_dc_3": | |
| rgba[3] = (0.5 + Converter.SH_C0 * value) * 255; | |
| break; | |
| case "opacity": | |
| case "opacity_0": | |
| rgba[3] = (1 / (1 + Math.exp(-value))) * 255; | |
| break; | |
| case "rot_0": | |
| case "rotation_0": | |
| r0 = value; | |
| break; | |
| case "rot_1": | |
| case "rotation_1": | |
| r1 = value; | |
| break; | |
| case "rot_2": | |
| case "rotation_2": | |
| r2 = value; | |
| break; | |
| case "rot_3": | |
| case "rotation_3": | |
| r3 = value; | |
| break; | |
| } | |
| }); | |
| let q = new Quaternion(r1, r2, r3, r0); | |
| switch (format) { | |
| case "polycam": { | |
| const temp = position[1]; | |
| position[1] = -position[2]; | |
| position[2] = temp; | |
| q = q_polycam.multiply(q); | |
| break; | |
| } | |
| case "": | |
| break; | |
| default: | |
| throw new Error(`Unsupported format: ${format}`); | |
| } | |
| q = q.normalize(); | |
| rot[0] = q.w * 128 + 128; | |
| rot[1] = q.x * 128 + 128; | |
| rot[2] = q.y * 128 + 128; | |
| rot[3] = q.z * 128 + 128; | |
| } | |
| return buffer; | |
| } | |
| } | |
| export { PLYLoader }; | |