mirror of
https://github.com/storytold/spark.git
synced 2026-10-22 08:16:06 +00:00
Use DecompressionStream for decompressing .spz files (#181)
This commit is contained in:
+30
-18
@@ -18,21 +18,28 @@ export class SpzReader {
|
|||||||
fileBytes: Uint8Array;
|
fileBytes: Uint8Array;
|
||||||
reader: GunzipReader;
|
reader: GunzipReader;
|
||||||
|
|
||||||
version: number;
|
version = -1;
|
||||||
numSplats: number;
|
numSplats = 0;
|
||||||
shDegree: number;
|
shDegree = 0;
|
||||||
fractionalBits: number;
|
fractionalBits = 0;
|
||||||
flags: number;
|
flags = 0;
|
||||||
flagAntiAlias: boolean;
|
flagAntiAlias = false;
|
||||||
reserved: number;
|
reserved = 0;
|
||||||
parsed: boolean;
|
headerParsed = false;
|
||||||
|
parsed = false;
|
||||||
|
|
||||||
constructor({ fileBytes }: { fileBytes: Uint8Array | ArrayBuffer }) {
|
constructor({ fileBytes }: { fileBytes: Uint8Array | ArrayBuffer }) {
|
||||||
this.fileBytes =
|
this.fileBytes =
|
||||||
fileBytes instanceof ArrayBuffer ? new Uint8Array(fileBytes) : fileBytes;
|
fileBytes instanceof ArrayBuffer ? new Uint8Array(fileBytes) : fileBytes;
|
||||||
this.reader = new GunzipReader({ fileBytes: this.fileBytes });
|
this.reader = new GunzipReader({ fileBytes: this.fileBytes });
|
||||||
|
}
|
||||||
|
|
||||||
const header = new DataView(this.reader.read(16).buffer);
|
async parseHeader() {
|
||||||
|
if (this.headerParsed) {
|
||||||
|
throw new Error("SPZ file header already parsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = new DataView((await this.reader.read(16)).buffer);
|
||||||
if (header.getUint32(0, true) !== 0x5053474e) {
|
if (header.getUint32(0, true) !== 0x5053474e) {
|
||||||
throw new Error("Invalid SPZ file");
|
throw new Error("Invalid SPZ file");
|
||||||
}
|
}
|
||||||
@@ -47,10 +54,11 @@ export class SpzReader {
|
|||||||
this.flags = header.getUint8(14);
|
this.flags = header.getUint8(14);
|
||||||
this.flagAntiAlias = (this.flags & 0x01) !== 0;
|
this.flagAntiAlias = (this.flags & 0x01) !== 0;
|
||||||
this.reserved = header.getUint8(15);
|
this.reserved = header.getUint8(15);
|
||||||
|
this.headerParsed = true;
|
||||||
this.parsed = false;
|
this.parsed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSplats(
|
async parseSplats(
|
||||||
centerCallback?: (index: number, x: number, y: number, z: number) => void,
|
centerCallback?: (index: number, x: number, y: number, z: number) => void,
|
||||||
alphaCallback?: (index: number, alpha: number) => void,
|
alphaCallback?: (index: number, alpha: number) => void,
|
||||||
rgbCallback?: (index: number, r: number, g: number, b: number) => void,
|
rgbCallback?: (index: number, r: number, g: number, b: number) => void,
|
||||||
@@ -74,6 +82,9 @@ export class SpzReader {
|
|||||||
sh3?: Float32Array,
|
sh3?: Float32Array,
|
||||||
) => void,
|
) => void,
|
||||||
) {
|
) {
|
||||||
|
if (!this.headerParsed) {
|
||||||
|
throw new Error("SPZ file header must be parsed first");
|
||||||
|
}
|
||||||
if (this.parsed) {
|
if (this.parsed) {
|
||||||
throw new Error("SPZ file already parsed");
|
throw new Error("SPZ file already parsed");
|
||||||
}
|
}
|
||||||
@@ -81,7 +92,7 @@ export class SpzReader {
|
|||||||
|
|
||||||
if (this.version === 1) {
|
if (this.version === 1) {
|
||||||
// float16 centers
|
// float16 centers
|
||||||
const centerBytes = this.reader.read(this.numSplats * 3 * 2);
|
const centerBytes = await this.reader.read(this.numSplats * 3 * 2);
|
||||||
const centerUint16 = new Uint16Array(centerBytes.buffer);
|
const centerUint16 = new Uint16Array(centerBytes.buffer);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i3 = i * 3;
|
const i3 = i * 3;
|
||||||
@@ -93,7 +104,7 @@ export class SpzReader {
|
|||||||
} else if (this.version === 2 || this.version === 3) {
|
} else if (this.version === 2 || this.version === 3) {
|
||||||
// 24-bit fixed-point centers
|
// 24-bit fixed-point centers
|
||||||
const fixed = 1 << this.fractionalBits;
|
const fixed = 1 << this.fractionalBits;
|
||||||
const centerBytes = this.reader.read(this.numSplats * 3 * 3);
|
const centerBytes = await this.reader.read(this.numSplats * 3 * 3);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i9 = i * 9;
|
const i9 = i * 9;
|
||||||
const x =
|
const x =
|
||||||
@@ -121,13 +132,13 @@ export class SpzReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const bytes = this.reader.read(this.numSplats);
|
const bytes = await this.reader.read(this.numSplats);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
alphaCallback?.(i, bytes[i] / 255);
|
alphaCallback?.(i, bytes[i] / 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const rgbBytes = this.reader.read(this.numSplats * 3);
|
const rgbBytes = await this.reader.read(this.numSplats * 3);
|
||||||
const scale = SH_C0 / 0.15;
|
const scale = SH_C0 / 0.15;
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i3 = i * 3;
|
const i3 = i * 3;
|
||||||
@@ -138,7 +149,7 @@ export class SpzReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const scalesBytes = this.reader.read(this.numSplats * 3);
|
const scalesBytes = await this.reader.read(this.numSplats * 3);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i3 = i * 3;
|
const i3 = i * 3;
|
||||||
const scaleX = Math.exp(scalesBytes[i3] / 16 - 10);
|
const scaleX = Math.exp(scalesBytes[i3] / 16 - 10);
|
||||||
@@ -160,7 +171,7 @@ export class SpzReader {
|
|||||||
// v^2 + v^2 = 1
|
// v^2 + v^2 = 1
|
||||||
// v = 1 / sqrt(2);
|
// v = 1 / sqrt(2);
|
||||||
const maxValue = 1 / Math.sqrt(2); // 0.7071
|
const maxValue = 1 / Math.sqrt(2); // 0.7071
|
||||||
const quatBytes = this.reader.read(this.numSplats * 4);
|
const quatBytes = await this.reader.read(this.numSplats * 4);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i3 = i * 4;
|
const i3 = i * 4;
|
||||||
const quaternion = [0, 0, 0, 0];
|
const quaternion = [0, 0, 0, 0];
|
||||||
@@ -211,7 +222,7 @@ export class SpzReader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const quatBytes = this.reader.read(this.numSplats * 3);
|
const quatBytes = await this.reader.read(this.numSplats * 3);
|
||||||
for (let i = 0; i < this.numSplats; i++) {
|
for (let i = 0; i < this.numSplats; i++) {
|
||||||
const i3 = i * 3;
|
const i3 = i * 3;
|
||||||
const quatX = quatBytes[i3] / 127.5 - 1;
|
const quatX = quatBytes[i3] / 127.5 - 1;
|
||||||
@@ -228,7 +239,7 @@ export class SpzReader {
|
|||||||
const sh1 = new Float32Array(3 * 3);
|
const sh1 = new Float32Array(3 * 3);
|
||||||
const sh2 = this.shDegree >= 2 ? new Float32Array(5 * 3) : undefined;
|
const sh2 = this.shDegree >= 2 ? new Float32Array(5 * 3) : undefined;
|
||||||
const sh3 = this.shDegree >= 3 ? new Float32Array(7 * 3) : undefined;
|
const sh3 = this.shDegree >= 3 ? new Float32Array(7 * 3) : undefined;
|
||||||
const shBytes = this.reader.read(
|
const shBytes = await this.reader.read(
|
||||||
this.numSplats * SH_DEGREE_TO_VECS[this.shDegree] * 3,
|
this.numSplats * SH_DEGREE_TO_VECS[this.shDegree] * 3,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -592,6 +603,7 @@ export async function transcodeSpz(input: TranscodeSpzInput) {
|
|||||||
}
|
}
|
||||||
case SplatFileType.SPZ: {
|
case SplatFileType.SPZ: {
|
||||||
const spz = new SpzReader({ fileBytes: input.fileBytes });
|
const spz = new SpzReader({ fileBytes: input.fileBytes });
|
||||||
|
await spz.parseHeader();
|
||||||
const mapping = new Int32Array(spz.numSplats);
|
const mapping = new Int32Array(spz.numSplats);
|
||||||
mapping.fill(-1);
|
mapping.fill(-1);
|
||||||
const centers = new Float32Array(spz.numSplats * 3);
|
const centers = new Float32Array(spz.numSplats * 3);
|
||||||
|
|||||||
+12
-18
@@ -1344,10 +1344,9 @@ export class GunzipReader {
|
|||||||
fileBytes: Uint8Array;
|
fileBytes: Uint8Array;
|
||||||
chunkBytes: number;
|
chunkBytes: number;
|
||||||
|
|
||||||
offset: number;
|
|
||||||
chunks: Uint8Array[];
|
chunks: Uint8Array[];
|
||||||
totalBytes: number;
|
totalBytes: number;
|
||||||
gunzip: Gunzip;
|
reader: ReadableStreamDefaultReader;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
fileBytes,
|
fileBytes,
|
||||||
@@ -1355,28 +1354,23 @@ export class GunzipReader {
|
|||||||
}: { fileBytes: Uint8Array; chunkBytes?: number }) {
|
}: { fileBytes: Uint8Array; chunkBytes?: number }) {
|
||||||
this.fileBytes = fileBytes;
|
this.fileBytes = fileBytes;
|
||||||
this.chunkBytes = chunkBytes;
|
this.chunkBytes = chunkBytes;
|
||||||
this.offset = 0;
|
|
||||||
this.chunks = [];
|
this.chunks = [];
|
||||||
this.totalBytes = 0;
|
this.totalBytes = 0;
|
||||||
|
|
||||||
this.gunzip = new Gunzip((chunk, _final) => {
|
const ds = new DecompressionStream("gzip");
|
||||||
this.chunks.push(chunk);
|
const decompressionStream = new Blob([fileBytes]).stream().pipeThrough(ds);
|
||||||
this.totalBytes += chunk.length;
|
this.reader = decompressionStream.getReader();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
read(numBytes: number): Uint8Array {
|
async read(numBytes: number): Promise<Uint8Array> {
|
||||||
while (this.totalBytes < numBytes && this.offset < this.fileBytes.length) {
|
while (this.totalBytes < numBytes) {
|
||||||
const end = Math.min(
|
const { value: chunk, done: readerDone } = await this.reader.read();
|
||||||
this.offset + this.chunkBytes,
|
if (readerDone) {
|
||||||
this.fileBytes.length,
|
break;
|
||||||
);
|
}
|
||||||
this.gunzip.push(this.fileBytes.subarray(this.offset, end), false);
|
|
||||||
this.offset = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.totalBytes < numBytes && this.offset >= this.fileBytes.length) {
|
this.chunks.push(chunk);
|
||||||
this.gunzip.push(new Uint8Array(0), true);
|
this.totalBytes += chunk.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.totalBytes < numBytes) {
|
if (this.totalBytes < numBytes) {
|
||||||
|
|||||||
+6
-5
@@ -61,7 +61,7 @@ async function onMessage(event: MessageEvent) {
|
|||||||
fileBytes: Uint8Array;
|
fileBytes: Uint8Array;
|
||||||
splatEncoding: SplatEncoding;
|
splatEncoding: SplatEncoding;
|
||||||
};
|
};
|
||||||
const decoded = unpackSpz(fileBytes, splatEncoding);
|
const decoded = await unpackSpz(fileBytes, splatEncoding);
|
||||||
result = {
|
result = {
|
||||||
id,
|
id,
|
||||||
numSplats: decoded.numSplats,
|
numSplats: decoded.numSplats,
|
||||||
@@ -379,21 +379,22 @@ async function unpackPly({
|
|||||||
return { packedArray, numSplats, extra };
|
return { packedArray, numSplats, extra };
|
||||||
}
|
}
|
||||||
|
|
||||||
function unpackSpz(
|
async function unpackSpz(
|
||||||
fileBytes: Uint8Array,
|
fileBytes: Uint8Array,
|
||||||
splatEncoding: SplatEncoding,
|
splatEncoding: SplatEncoding,
|
||||||
): {
|
): Promise<{
|
||||||
packedArray: Uint32Array;
|
packedArray: Uint32Array;
|
||||||
numSplats: number;
|
numSplats: number;
|
||||||
extra: Record<string, unknown>;
|
extra: Record<string, unknown>;
|
||||||
} {
|
}> {
|
||||||
const spz = new SpzReader({ fileBytes });
|
const spz = new SpzReader({ fileBytes });
|
||||||
|
await spz.parseHeader();
|
||||||
const numSplats = spz.numSplats;
|
const numSplats = spz.numSplats;
|
||||||
const maxSplats = computeMaxSplats(numSplats);
|
const maxSplats = computeMaxSplats(numSplats);
|
||||||
const packedArray = new Uint32Array(maxSplats * 4);
|
const packedArray = new Uint32Array(maxSplats * 4);
|
||||||
const extra: Record<string, unknown> = {};
|
const extra: Record<string, unknown> = {};
|
||||||
|
|
||||||
spz.parseSplats(
|
await spz.parseSplats(
|
||||||
(index, x, y, z) => {
|
(index, x, y, z) => {
|
||||||
setPackedSplatCenter(packedArray, index, x, y, z);
|
setPackedSplatCenter(packedArray, index, x, y, z);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user