mirror of
https://github.com/storytold/spark.git
synced 2026-10-14 23:52:59 +00:00
PC SOGS decoding from .Zip archive (#100)
* Implement PC SOGS decoding from .zip file archive. Update viewer and editor to accept .zip files. * Restore GL WEBP decoding.
This commit is contained in:
committed by
GitHub
parent
cd40e139d1
commit
4266db3259
+55
-1
@@ -1,3 +1,4 @@
|
||||
import { unzipSync } from "fflate";
|
||||
import { FileLoader, Loader, type LoadingManager } from "three";
|
||||
import { PackedSplats } from "./PackedSplats";
|
||||
import { SplatMesh } from "./SplatMesh";
|
||||
@@ -138,6 +139,7 @@ export enum SplatFileType {
|
||||
SPLAT = "splat",
|
||||
KSPLAT = "ksplat",
|
||||
PCSOGS = "pcsogs",
|
||||
PCSOGSZIP = "pcsogszip",
|
||||
}
|
||||
|
||||
export function getSplatFileType(
|
||||
@@ -157,6 +159,14 @@ export function getSplatFileType(
|
||||
// Unknown Gzipped file type
|
||||
return undefined;
|
||||
}
|
||||
if (view.getUint32(0, true) === 0x04034b50) {
|
||||
// PKZip file
|
||||
if (tryPcSogsZip(fileBytes)) {
|
||||
return SplatFileType.PCSOGSZIP;
|
||||
}
|
||||
// Unknown PKZip file type
|
||||
return undefined;
|
||||
}
|
||||
// Unknown file type
|
||||
return undefined;
|
||||
}
|
||||
@@ -218,7 +228,7 @@ export type PcSogsJson = {
|
||||
maxs: number[];
|
||||
files: string[];
|
||||
};
|
||||
shN: {
|
||||
shN?: {
|
||||
shape: number[];
|
||||
dtype: string;
|
||||
mins: number;
|
||||
@@ -277,6 +287,37 @@ export function tryPcSogs(
|
||||
}
|
||||
}
|
||||
|
||||
export function tryPcSogsZip(
|
||||
input: ArrayBuffer | Uint8Array,
|
||||
): { name: string; json: PcSogsJson } | undefined {
|
||||
try {
|
||||
const fileBytes =
|
||||
input instanceof ArrayBuffer ? new Uint8Array(input) : input;
|
||||
let metaFilename: string | null = null;
|
||||
|
||||
const unzipped = unzipSync(fileBytes, {
|
||||
filter: ({ name }) => {
|
||||
const filename = name.split(/[\\/]/).pop() as string;
|
||||
if (filename === "meta.json") {
|
||||
metaFilename = name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
if (!metaFilename) {
|
||||
return undefined;
|
||||
}
|
||||
const json = tryPcSogs(unzipped[metaFilename]);
|
||||
if (!json) {
|
||||
return undefined;
|
||||
}
|
||||
return { name: metaFilename, json };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function unpackSplats({
|
||||
input,
|
||||
extraFiles,
|
||||
@@ -373,6 +414,19 @@ export async function unpackSplats({
|
||||
return { packedArray, numSplats, extra };
|
||||
});
|
||||
}
|
||||
case SplatFileType.PCSOGSZIP: {
|
||||
return await withWorker(async (worker) => {
|
||||
const { packedArray, numSplats, extra } = (await worker.call(
|
||||
"decodePcSogsZip",
|
||||
{ fileBytes },
|
||||
)) as {
|
||||
packedArray: Uint32Array;
|
||||
numSplats: number;
|
||||
extra: Record<string, unknown>;
|
||||
};
|
||||
return { packedArray, numSplats, extra };
|
||||
});
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown splat file type: ${splatFileType}`);
|
||||
}
|
||||
|
||||
+58
-3
@@ -1,4 +1,5 @@
|
||||
import type { PcSogsJson } from "./SplatLoader";
|
||||
import { unzip } from "fflate";
|
||||
import { type PcSogsJson, tryPcSogsZip } from "./SplatLoader";
|
||||
import {
|
||||
computeMaxSplats,
|
||||
encodeSh1Rgb,
|
||||
@@ -11,14 +12,13 @@ import {
|
||||
} from "./utils";
|
||||
|
||||
export async function unpackPcSogs(
|
||||
fileBytes: Uint8Array,
|
||||
json: PcSogsJson,
|
||||
extraFiles: Record<string, ArrayBuffer>,
|
||||
): Promise<{
|
||||
packedArray: Uint32Array;
|
||||
numSplats: number;
|
||||
extra: Record<string, unknown>;
|
||||
}> {
|
||||
const json = JSON.parse(new TextDecoder().decode(fileBytes)) as PcSogsJson;
|
||||
if (json.quats.encoding !== "quaternion_packed") {
|
||||
throw new Error("Unsupported quaternion encoding");
|
||||
}
|
||||
@@ -204,3 +204,58 @@ async function decodeImageRgba(fileBytes: ArrayBuffer) {
|
||||
const { rgba } = await decodeImage(fileBytes);
|
||||
return rgba;
|
||||
}
|
||||
|
||||
export async function unpackPcSogsZip(fileBytes: Uint8Array): Promise<{
|
||||
packedArray: Uint32Array;
|
||||
numSplats: number;
|
||||
extra: Record<string, unknown>;
|
||||
}> {
|
||||
const nameJson = tryPcSogsZip(fileBytes);
|
||||
if (!nameJson) {
|
||||
throw new Error("Invalid PC SOGS zip file");
|
||||
}
|
||||
const { name, json } = nameJson;
|
||||
// Find path prefix, will be -1 if no / or \
|
||||
const lastSlash = name.lastIndexOf("/");
|
||||
const lastBackslash = name.lastIndexOf("\\");
|
||||
const prefix = name.slice(0, Math.max(lastSlash, lastBackslash) + 1);
|
||||
|
||||
const fileMap = new Map<string, string>();
|
||||
const refFiles = [
|
||||
...json.means.files,
|
||||
...json.scales.files,
|
||||
...json.quats.files,
|
||||
...json.sh0.files,
|
||||
...(json.shN?.files ?? []),
|
||||
];
|
||||
for (const file of refFiles) {
|
||||
fileMap.set(prefix + file, file);
|
||||
}
|
||||
|
||||
const unzipped = await new Promise<Record<string, ArrayBuffer>>(
|
||||
(resolve, reject) => {
|
||||
unzip(
|
||||
fileBytes,
|
||||
{
|
||||
filter: ({ name }) => {
|
||||
return fileMap.has(name);
|
||||
},
|
||||
},
|
||||
(err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(files);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const extraFiles: Record<string, ArrayBuffer> = {};
|
||||
for (const [full, name] of fileMap.entries()) {
|
||||
extraFiles[name] = unzipped[full];
|
||||
}
|
||||
|
||||
return await unpackPcSogs(json, extraFiles);
|
||||
}
|
||||
|
||||
+17
-3
@@ -1,9 +1,9 @@
|
||||
import init_wasm, { sort_splats } from "spark-internal-rs";
|
||||
import type { TranscodeSpzInput } from "./SplatLoader";
|
||||
import type { PcSogsJson, TranscodeSpzInput } from "./SplatLoader";
|
||||
import { unpackAntiSplat } from "./antisplat";
|
||||
import { SCALE_MIN, WASM_SPLAT_SORT } from "./defines";
|
||||
import { unpackKsplat } from "./ksplat";
|
||||
import { unpackPcSogs } from "./pcsogs";
|
||||
import { unpackPcSogs, unpackPcSogsZip } from "./pcsogs";
|
||||
import { PlyReader } from "./ply";
|
||||
import { SpzReader, transcodeSpz } from "./spz";
|
||||
import {
|
||||
@@ -86,7 +86,21 @@ async function onMessage(event: MessageEvent) {
|
||||
fileBytes: Uint8Array;
|
||||
extraFiles: Record<string, ArrayBuffer>;
|
||||
};
|
||||
const decoded = await unpackPcSogs(fileBytes, extraFiles);
|
||||
const json = JSON.parse(
|
||||
new TextDecoder().decode(fileBytes),
|
||||
) as PcSogsJson;
|
||||
const decoded = await unpackPcSogs(json, extraFiles);
|
||||
result = {
|
||||
id,
|
||||
numSplats: decoded.numSplats,
|
||||
packedArray: decoded.packedArray,
|
||||
extra: decoded.extra,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "decodePcSogsZip": {
|
||||
const { fileBytes } = args as { fileBytes: Uint8Array };
|
||||
const decoded = await unpackPcSogsZip(fileBytes);
|
||||
result = {
|
||||
id,
|
||||
numSplats: decoded.numSplats,
|
||||
|
||||
Reference in New Issue
Block a user