Skip to content

Commit

Permalink
Decode meshopt in buffer structure
Browse files Browse the repository at this point in the history
  • Loading branch information
javagl committed Nov 27, 2024
1 parent 0e8f5e0 commit b41affd
Showing 1 changed file with 135 additions and 17 deletions.
152 changes: 135 additions & 17 deletions src/base/binary/BinaryBufferDataResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { BinaryBufferData } from "./BinaryBufferData";
import { BinaryBufferStructure } from "./BinaryBufferStructure";
import { BinaryDataError } from "./BinaryDataError";

import { MeshoptDecoder } from "meshoptimizer";

/**
* A class for resolving binary buffer data.
*
Expand All @@ -23,7 +25,7 @@ export class BinaryBufferDataResolver {
*
* The given `binaryBuffer` will be used as the buffer data
* for any buffer that does not have a URI (intended for
* binary subtree files))
* binary subtree files)
*
* @param binaryBufferStructure - The `BinaryBufferStructure`
* @param binaryBuffer - The optional binary buffer
Expand All @@ -41,22 +43,42 @@ export class BinaryBufferDataResolver {
const buffersData: Buffer[] = [];
const buffers = binaryBufferStructure.buffers;
if (buffers) {
for (const buffer of buffers) {
if (!defined(buffer.uri)) {
if (!binaryBuffer) {
throw new BinaryDataError(
"Expected a binary buffer, but got undefined"
);
}
buffersData.push(binaryBuffer);
} else {
for (let b = 0; b < buffers.length; b++) {
const buffer = buffers[b];

// If the buffer defines a URI, it will be resolved
if (defined(buffer.uri)) {
//console.log("Obtaining buffer data from " + buffer.uri);
const bufferData = await resourceResolver.resolveData(buffer.uri);
if (!bufferData) {
const message = `Could not resolve buffer ${buffer.uri}`;
throw new BinaryDataError(message);
}
buffersData.push(bufferData);
} else {
// If the buffer does not define a URI, then it might
// be a "fallback" buffer from `EXT_meshopt_compression`.
// In this case, the `EXT_meshopt_compression` extension
// is required, and a dummy buffer with the appropriate
// size will be returned.
const isFallbackBuffer =
BinaryBufferDataResolver.isMeshoptFallbackBuffer(
binaryBufferStructure,
b
);
if (isFallbackBuffer) {
const fallbackBuffer = Buffer.alloc(buffer.byteLength);
buffersData.push(fallbackBuffer);
} else {
// When the buffer does not have a URI and is not a fallback
// buffer, then it must be the binary buffer
if (!binaryBuffer) {
throw new BinaryDataError(
"Expected a binary buffer, but got undefined"
);
}
buffersData.push(binaryBuffer);
}
}
}
}
Expand All @@ -67,18 +89,114 @@ export class BinaryBufferDataResolver {
const bufferViews = binaryBufferStructure.bufferViews;
if (bufferViews) {
for (const bufferView of bufferViews) {
const bufferData = buffersData[bufferView.buffer];
const start = bufferView.byteOffset;
const end = start + bufferView.byteLength;
const bufferViewData = bufferData.subarray(start, end);
bufferViewsData.push(bufferViewData);
// If the buffer view defines the `EXT_meshopt_compression`
// extension, then decode the meshopt buffer
const extensions = bufferView.extensions ?? {};
const meshopt = extensions["EXT_meshopt_compression"];
if (meshopt) {
const compressedBufferData = buffersData[meshopt.buffer];
const bufferViewData = await BinaryBufferDataResolver.decodeMeshopt(
compressedBufferData,
meshopt
);
bufferViewsData.push(bufferViewData);
} else {
// Otherwise, just slice out the required part of
// the buffer that the buffer view refers to
const bufferData = buffersData[bufferView.buffer];
const start = bufferView.byteOffset ?? 0;
const end = start + bufferView.byteLength;
const bufferViewData = bufferData.subarray(start, end);
bufferViewsData.push(bufferViewData);
}
}
}

const binarybBufferData: BinaryBufferData = {
const binaryBufferData: BinaryBufferData = {
buffersData: buffersData,
bufferViewsData: bufferViewsData,
};
return binarybBufferData;
return binaryBufferData;
}

/**
* Decode the meshopt-compressed data for a buffer view that contained
* the given `EXT_meshopt_compression` extension object.
*
* @param compressedBufferData - The buffer containing the compressed
* data that the extension object refers to
* @param meshopt The extension object
* @returns The decoded buffer (view) data
*/
private static async decodeMeshopt(
compressedBufferData: Buffer,
meshopt: any
): Promise<Buffer> {
const byteOffset = meshopt.byteOffset ?? 0;
const byteLength = meshopt.byteLength;
const byteStride = meshopt.byteStride;
const count = meshopt.count;
const mode = meshopt.mode;
const filter = meshopt.filter ?? "NONE";
const compressedBufferViewData = compressedBufferData.subarray(
byteOffset,
byteOffset + byteLength
);

const uncompressedByteLength = byteStride * count;
const uncompressedBufferViewData = new Uint8Array(uncompressedByteLength);

await MeshoptDecoder.ready;
MeshoptDecoder.decodeGltfBuffer(
uncompressedBufferViewData,
count,
byteStride,
compressedBufferViewData,
mode,
filter
);
return Buffer.from(uncompressedBufferViewData);
}

/**
* Returns whether the given buffer is a "fallback" buffer for the
* `EXT_meshopt_compression` extension.
*
* This is the case when it is either itself marked with
* an `EXT_meshopt_compression` extension object that
* defines `fallback: true`, or when it is referred to by
* a buffer view that defines the `EXT_meshopt_compression`
* extension.
*
* @param binaryBufferStructure - The BinaryBufferStructure
* @param bufferIndex - The buffer index
* @returns Whether the given buffer is a fallback buffer
*/
private static isMeshoptFallbackBuffer(
binaryBufferStructure: BinaryBufferStructure,
bufferIndex: number
) {
const buffers = binaryBufferStructure.buffers;
const buffer = buffers[bufferIndex];
if (buffer.extensions) {
const meshopt = buffer.extensions["EXT_meshopt_compression"];
if (meshopt) {
if (meshopt["fallback"] === true) {
return true;
}
}
}
const bufferViews = binaryBufferStructure.bufferViews;
for (const bufferView of bufferViews) {
if (bufferView.extensions) {
const meshopt = bufferView.extensions["EXT_meshopt_compression"];
if (meshopt) {
if (bufferView.buffer === bufferIndex) {
return true;
}
}
}
}
return false;
}
}

0 comments on commit b41affd

Please sign in to comment.