Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/core/docs/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
- Output: `Document`
- Uses: glTF Transform document graph
- Behavior: Strips materials, assignments, and newly unused textures.
- `CullUnusedBlock`
- Input: `Document`
- Output: the same `Document`
- Uses: glTF Transform `prune()`
- Behavior: Removes unused document resources with library-default behavior.
- `QuantizeBlock`
- Input: `Document`
- Output: the same `Document`
- Uses: glTF Transform `quantize()`; execution-scoped `PlatformIO`
- Behavior: Quantizes mesh attributes with library-default behavior.
- `ValidateBlock`
- Input: `Document`
- Output: the same `Document`
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/blocks/cullUnusedBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { prune } from "@gltf-transform/functions";

import { GltfDocumentType } from "../connectionPoints/gltfDocument";
import { Block, type BlockOptions } from "./block";
import { defineBlock } from "./blockDefinition";

const CullUnusedBlockDefinition = /* @__PURE__ */ defineBlock({
type: "transform.cull-unused",
input: GltfDocumentType,
output: GltfDocumentType,
runAsync: async (document) => {
await document.transform(prune());
return document;
},
});

/** Removes unused document resources with library-default behavior. */
export class CullUnusedBlock extends Block<typeof CullUnusedBlockDefinition> {
public constructor(options?: BlockOptions<typeof CullUnusedBlockDefinition>) {
super(CullUnusedBlockDefinition, options);
}
}
28 changes: 28 additions & 0 deletions packages/core/src/blocks/quantizeBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { KHRMeshQuantization } from "@gltf-transform/extensions";
import { quantize } from "@gltf-transform/functions";

import { GltfDocumentType } from "../connectionPoints/gltfDocument";
import { PlatformIOResource } from "../resources/platformIOResource";
import { Block, type BlockOptions } from "./block";
import { defineBlock } from "./blockDefinition";

const QuantizeBlockDefinition = /* @__PURE__ */ defineBlock({
type: "transform.quantize",
input: GltfDocumentType,
output: GltfDocumentType,
resources: {
io: PlatformIOResource,
},
runAsync: async (document, _config, { io }) => {
io.registerExtensions([KHRMeshQuantization]);
await document.transform(quantize());
return document;
},
});

/** Quantizes mesh attributes with library-default tuning. */
export class QuantizeBlock extends Block<typeof QuantizeBlockDefinition> {
public constructor(options?: BlockOptions<typeof QuantizeBlockDefinition>) {
super(QuantizeBlockDefinition, options);
}
}
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export { StripMaterialsBlock, type StripMaterialsBlockOptions } from "./blocks/stripMaterialsBlock";
export { CullUnusedBlock } from "./blocks/cullUnusedBlock";
export { EncodeDracoBlock } from "./blocks/encodeDracoBlock";
export { EncodeKTX2Block } from "./blocks/encodeKtx2Block";
export { EncodeMeshoptBlock } from "./blocks/encodeMeshoptBlock";
export { FbxInputBlock } from "./blocks/fbxInputBlock";
export { GltfInputBlock } from "./blocks/gltfInputBlock";
export { GltfOutputBlock, type GltfOutputBlockOptions } from "./blocks/gltfOutputBlock";
export { ObjInputBlock } from "./blocks/objInputBlock";
export { QuantizeBlock } from "./blocks/quantizeBlock";
export { StlInputBlock } from "./blocks/stlInputBlock";
export { ValidateBlock, type ValidateBlockOptions } from "./blocks/validateBlock";
export { NodeAsset } from "./nodeAsset";
Expand Down
43 changes: 43 additions & 0 deletions tests/integration/cullUnused.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it, vi } from "vitest";

import { CullUnusedBlock, GltfInputBlock, GltfOutputBlock, NodeAsset } from "../../packages/core/src/index";
import { parseGlbAsync } from "../helpers/glb";
import { generateGltfJson } from "../helpers/gltf";

describe("unused resource culling", () => {
it("removes unused resources from connected glTF output", async () => {
const url = "https://example.com/model.gltf";
vi.stubGlobal(
"fetch",
vi.fn(() => Promise.resolve(new Response(generateGltfWithUnusedMaterialJson())))
);

try {
const source = new GltfInputBlock({ input: url });
const cullUnused = new CullUnusedBlock();
const destination = new GltfOutputBlock();
source.output.connectTo(cullUnused.input);
cullUnused.output.connectTo(destination.input);

const parsed = await parseGlbAsync(await new NodeAsset({ name: "culled-glb", outputBlock: destination }).executeAsync());
const primitive = parsed.json.meshes?.[0]?.primitives[0];

expect(parsed.json.materials?.map(({ name }) => name)).toEqual(["used"]);
expect(primitive?.material).toBe(0);
expect(primitive?.attributes).toEqual({ NORMAL: 1, POSITION: 0 });
} finally {
vi.unstubAllGlobals();
}
});
});

function generateGltfWithUnusedMaterialJson(): string {
const gltf = JSON.parse(generateGltfJson()) as {
materials?: Array<{ name: string }>;
meshes: Array<{ primitives: Array<{ material?: number }> }>;
};

gltf.materials = [{ name: "used" }, { name: "unused" }];
gltf.meshes[0]!.primitives[0]!.material = 0;
return JSON.stringify(gltf);
}
41 changes: 41 additions & 0 deletions tests/integration/quantize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NodeIO } from "@gltf-transform/core";
import { KHRMeshQuantization } from "@gltf-transform/extensions";
import { describe, expect, it, vi } from "vitest";

import { GltfInputBlock, GltfOutputBlock, NodeAsset, QuantizeBlock } from "../../packages/core/src/index";
import { parseGlbAsync } from "../helpers/glb";
import { generateGltfJson } from "../helpers/gltf";

describe("mesh quantization", () => {
it("quantizes connected glTF output", async () => {
const url = "https://example.com/model.gltf";
vi.stubGlobal(
"fetch",
vi.fn(() => Promise.resolve(new Response(generateGltfJson())))
);

try {
const source = new GltfInputBlock({ input: url });
const quantizer = new QuantizeBlock();
const destination = new GltfOutputBlock();
source.output.connectTo(quantizer.input);
quantizer.output.connectTo(destination.input);

const output = await new NodeAsset({ name: "quantized-glb", outputBlock: destination }).executeAsync();
const parsed = await parseGlbAsync(output);
const document = await new NodeIO().registerExtensions([KHRMeshQuantization]).readBinary(new Uint8Array(await output.arrayBuffer()));
const primitive = document.getRoot().listMeshes()[0]!.listPrimitives()[0]!;
const position = primitive.getAttribute("POSITION")!;
const normal = primitive.getAttribute("NORMAL")!;

expect(parsed.json.extensionsUsed).toContain("KHR_mesh_quantization");
expect(parsed.json.extensionsRequired).toContain("KHR_mesh_quantization");
expect(position.getArray()).toBeInstanceOf(Int16Array);
expect(position.getNormalized()).toBe(true);
expect(normal.getArray()).toBeInstanceOf(Int16Array);
expect(normal.getNormalized()).toBe(true);
} finally {
vi.unstubAllGlobals();
}
});
});
Loading