Add interactive tutorial playgrounds

Embed editable split-view examples throughout the guide, restore the full LFS-backed Sponza demo, batch glTF hydration, honor hierarchical transforms, and report transformed BVH picks with a live FPS counter.

Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-20 09:21:21 +00:00
co-authored by heaust
parent 9768765d74
commit ded70a3cb4
14 changed files with 1466 additions and 250 deletions
+39 -25
View File
@@ -4,11 +4,17 @@ import { PBRMaterial } from "../materials/PBRMaterial";
let worker: Worker | undefined;
let nextRequest = 1;
const pending = new Map<number, { resolve: (value: any) => void; reject: (error: Error) => void }>();
const pending = new Map<
number,
{ resolve: (value: any) => void; reject: (error: Error) => void }
>();
function importer() {
if (worker) return worker;
worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module", name: "yawn-importer" });
worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
name: "yawn-importer",
});
worker.addEventListener("message", ({ data }) => {
const request = pending.get(data.request);
if (!request) return;
@@ -17,7 +23,8 @@ function importer() {
else request.resolve(data.result);
});
worker.addEventListener("error", () => {
for (const request of pending.values()) request.reject(new Error("IMPORT_WORKER_ERROR"));
for (const request of pending.values())
request.reject(new Error("IMPORT_WORKER_ERROR"));
pending.clear();
});
return worker;
@@ -31,26 +38,33 @@ export async function importGltf(scene: Scene, url: string | URL) {
pending.set(request, { resolve, reject });
importer().postMessage({ request, url: String(url) });
});
const materials = result.materials.map((options: any) => new PBRMaterial(scene, options));
await Promise.all(materials.map((material: PBRMaterial) => material.ready));
const meshes: Mesh[] = [];
for (const primitive of result.primitives) {
const mesh = new Mesh(scene, {
position: primitive.position,
quaternion: primitive.quaternion,
scale: primitive.scale,
material: materials[primitive.material],
vertexData: {
positions: primitive.positions,
indices: primitive.indices,
...(primitive.normals ? { normals: primitive.normals } : {}),
...(primitive.tangents ? { tangents: primitive.tangents } : {}),
...(primitive.uvs ? { uvs: primitive.uvs } : {}),
...(primitive.colors ? { colors: primitive.colors } : {}),
},
});
await mesh.ready;
meshes.push(mesh);
}
return meshes;
await scene.reserve({
nodes: result.primitives.length,
materials: result.materials.length,
});
return scene.batchGraphUpdates(async () => {
const materials = result.materials.map(
(options: any) => new PBRMaterial(scene, options),
);
await Promise.all(materials.map((material: PBRMaterial) => material.ready));
const meshes: Mesh[] = result.primitives.map(
(primitive: any) =>
new Mesh(scene, {
position: primitive.position,
quaternion: primitive.quaternion,
scale: primitive.scale,
material: materials[primitive.material],
vertexData: {
positions: primitive.positions,
indices: primitive.indices,
...(primitive.normals ? { normals: primitive.normals } : {}),
...(primitive.tangents ? { tangents: primitive.tangents } : {}),
...(primitive.uvs ? { uvs: primitive.uvs } : {}),
...(primitive.colors ? { colors: primitive.colors } : {}),
},
}),
);
await Promise.all(meshes.map((mesh) => mesh.ready));
return meshes;
});
}