Skip to content
Draft
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
15 changes: 15 additions & 0 deletions lab/lite/babylon-ref-scene231.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>BJS Reference — Scene 231: StandardMaterial Deform Features</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #000; }
canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="renderCanvas" width="1280" height="720"></canvas>
<script type="module" src="/lite/src/bjs/scene231.ts"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions lab/lite/bundle-scene231.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Babylon Lite — Scene 231: StandardMaterial Deform Features (Bundle)</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #000; }
canvas { width: 100%; height: 100%; display: block; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script src="/loader.js"></script>
<script type="module" src="/lite/bundle/scene231.js"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions lab/lite/scene231.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Babylon Lite — Scene 231: StandardMaterial Deform Features</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #000; }
canvas { width: 100%; height: 100%; display: block; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script src="/loader.js"></script>
<script type="module" src="/lite/src/lite/scene231.ts"></script>
</body>
</html>
122 changes: 122 additions & 0 deletions lab/lite/src/bjs/scene231.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// BJS reference for Scene 231 — StandardMaterial deform features (per-vertex
// color + skeletal skinning + UV offset) on an in-code beam with programmatically
// animated bones. Mirrors lab/lite/src/lite/scene231.ts exactly: the geometry,
// vertex colors, skin weights, checker texture, and per-frame bone matrices all
// come from the shared engine-agnostic module so the two engines stay in lockstep.

import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { WebGPUEngine } from "@babylonjs/core/Engines/webgpuEngine";
import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";
import "@babylonjs/core/Materials/standardMaterial";
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
import { RawTexture } from "@babylonjs/core/Materials/Textures/rawTexture";
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
import { Matrix, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { Mesh } from "@babylonjs/core/Meshes/mesh";
import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";
import { Bone } from "@babylonjs/core/Bones/bone";
import { Skeleton } from "@babylonjs/core/Bones/skeleton";
import { Scene } from "@babylonjs/core/scene";
import { boneMatrixData, buildBeamData, buildCheckerPixels, CHECKER_SIZE, UV_OFFSET } from "../shared/scene231-skin.js";

(async function () {
const __initStart = performance.now();
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
const engine = new WebGPUEngine(canvas, { antialias: true, adaptToDeviceRatio: true });
await engine.initAsync();
engine.displayLoadingUI = function () {};

const scene = new Scene(engine);
scene.clearColor = new Color4(0.145, 0.165, 0.21, 1);

const camera = new ArcRotateCamera("camera", -Math.PI / 2, 1.1, 5.5, new Vector3(0, 0, 0), scene);
camera.fov = 0.72;
camera.minZ = 0.1;
camera.maxZ = 100;
camera.attachControl(canvas, true);
scene.activeCamera = camera;

const light = new HemisphericLight("hemi", new Vector3(0, 1, 0), scene);
light.intensity = 0.9;

const beam = buildBeamData();

// Build the mesh from the shared geometry, including per-vertex color and skin attributes.
const mesh = new Mesh("scene231-beam", scene);
const vd = new VertexData();
vd.positions = beam.positions;
vd.normals = beam.normals;
vd.uvs = beam.uvs;
vd.colors = beam.colors;
vd.indices = beam.indices;
vd.matricesIndices = Array.from(beam.joints);
vd.matricesWeights = Array.from(beam.weights);
vd.applyToMesh(mesh);
mesh.numBoneInfluencers = 4;
mesh.hasVertexAlpha = false;
mesh.useVertexColors = true;

// Two independent bones (root + upper), bound at identity so the per-frame
// local matrices become the skin palette directly (matching Lite's bone texture).
const skeleton = new Skeleton("scene231-skeleton", "scene231-skeleton", scene);
const root = new Bone("root", skeleton, null, Matrix.Identity());
const upper = new Bone("upper", skeleton, null, Matrix.Identity());
mesh.skeleton = skeleton;

const checker = RawTexture.CreateRGBATexture(buildCheckerPixels(), CHECKER_SIZE, CHECKER_SIZE, scene, false, false, Texture.NEAREST_SAMPLINGMODE);
checker.wrapU = Texture.WRAP_ADDRESSMODE;
checker.wrapV = Texture.WRAP_ADDRESSMODE;
checker.uOffset = UV_OFFSET[0];
checker.vOffset = UV_OFFSET[1];

const material = new StandardMaterial("scene231-mat", scene);
material.diffuseColor = new Color3(1, 1, 1);
material.diffuseTexture = checker;
mesh.material = material;

// Per-frame programmatic bone animation: recompute the bone matrices as a pure
// function of the frame index and push them as the bones' local transforms.
const boneData = boneMatrixData(0);
const m0 = Matrix.Identity();
const m1 = Matrix.Identity();
const params = new URLSearchParams(window.location.search);
const seek = parseFloat(params.get("seekTime") || "");
const freezeFrame = isNaN(seek) ? -1 : Math.round(seek * 60);
let frame = 0;
let frozen = false;

const applyBones = (): void => {
Matrix.FromArrayToRef(boneData, 0, m0);
Matrix.FromArrayToRef(boneData, 16, m1);
root.updateMatrix(m0, false, true);
upper.updateMatrix(m1, false, true);
};
applyBones();

scene.onBeforeRenderObservable.add(() => {
if (frozen) {
return;
}
frame++;
boneMatrixData(frame, boneData);
applyBones();
if (freezeFrame >= 0 && frame >= freezeFrame) {
frozen = true;
canvas.dataset.animationFrozen = "true";
}
});

engine.getDeltaTime = function () {
return 16;
};
scene.useConstantAnimationDeltaTime = true;

await scene.whenReadyAsync();
engine.runRenderLoop(() => scene.render());
window.addEventListener("resize", () => engine.resize());

await new Promise<void>((resolve) => scene.onAfterRenderObservable.addOnce(() => resolve()));
canvas.dataset.initMs = String(performance.now() - __initStart);
canvas.dataset.ready = "true";
})().catch(console.error);
102 changes: 102 additions & 0 deletions lab/lite/src/lite/scene231.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Scene 231 — StandardMaterial deform features (in-code, no glTF):
// per-vertex color + skeletal skinning + UV offset on a procedurally built beam,
// with bones animated programmatically (per-frame updates, no animation system).
//
// The beam is a tall box (4 vertically-subdivided side faces + caps) skinned to a
// 2-bone skeleton: a fixed root bone and an upper bone that swings about the beam
// origin. The pose is a pure function of the frame index, so freezing at a known
// frame (via ?seekTime) is deterministic and matches the Babylon.js reference.

import {
addToScene,
attachControl,
createArcRotateCamera,
createEngine,
createHemisphericLight,
createSceneContext,
createStandardMaterial,
createTexture2DFromPixels,
onBeforeRender,
registerScene,
startEngine,
} from "babylon-lite";
import type { ArcRotateCamera } from "babylon-lite";
import { enableStandardSkeleton, enableStandardUvOffset, enableStandardVertexColor } from "babylon-lite/material/standard/enable-standard-mesh-features.js";
import { createMeshFromData } from "babylon-lite/mesh/mesh-factories.js";
import { createSkeleton } from "babylon-lite/skeleton/create-skeleton.js";
import { boneMatrixData, buildBeamData, buildCheckerPixels, CHECKER_SIZE, SKELETON_BONE_COUNT, UV_OFFSET } from "../shared/scene231-skin.js";

async function main(): Promise<void> {
const __initStart = performance.now();
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
const engine = await createEngine(canvas);
const scene = createSceneContext(engine);
scene.clearColor = { r: 0.145, g: 0.165, b: 0.21, a: 1 };

// Opt in to the Standard deform/vertex features used by this scene. Plain
// Standard scenes that never call these stay byte-identical to upstream.
enableStandardVertexColor();
enableStandardSkeleton();
enableStandardUvOffset();

const camera = createArcRotateCamera(-Math.PI / 2, 1.1, 5.5, { x: 0, y: 0, z: 0 });
camera.fov = 0.72;
camera.nearPlane = 0.1;
camera.farPlane = 100;
scene.camera = camera;
attachControl(camera as ArcRotateCamera, canvas, scene);

addToScene(scene, createHemisphericLight([0, 1, 0], 0.9));

const beam = buildBeamData();
const mesh = createMeshFromData(engine, "scene231-beam", beam.positions, beam.normals, beam.indices, beam.uvs, undefined, undefined, beam.colors);

const material = createStandardMaterial();
material.diffuseColor = [1, 1, 1];
material.diffuseTexture = createTexture2DFromPixels(engine, buildCheckerPixels(), CHECKER_SIZE, CHECKER_SIZE, { addressModeU: "repeat", addressModeV: "repeat" });
material.uvOffset = UV_OFFSET;
mesh.material = material;

const boneData = boneMatrixData(0);
mesh.skeleton = createSkeleton(engine, beam.joints, beam.weights, SKELETON_BONE_COUNT, boneData);
addToScene(scene, mesh);

// Per-frame programmatic bone animation: recompute the bone matrices as a pure
// function of the frame index and re-upload them to the skeleton's bone texture.
const boneTexWidth = SKELETON_BONE_COUNT * 4;
const boneTexture = mesh.skeleton.boneTexture;
scene.fixedDeltaMs = 16.0;

const params = new URLSearchParams(window.location.search);
const seek = parseFloat(params.get("seekTime") || "");
const freezeFrame = isNaN(seek) ? -1 : Math.round(seek * 60);
let frame = 0;
let frozen = false;

onBeforeRender(scene, () => {
if (frozen) {
return;
}
frame++;
boneMatrixData(frame, boneData);
engine._device.queue.writeTexture({ texture: boneTexture }, boneData.buffer as ArrayBuffer, { bytesPerRow: boneTexWidth * 16 }, { width: boneTexWidth, height: 1 });
if (freezeFrame >= 0 && frame >= freezeFrame) {
frozen = true;
canvas.dataset.animationFrozen = "true";
}
});

await registerScene(scene);
await startEngine(engine);
canvas.dataset.drawCalls = String(engine.drawCallCount);
canvas.dataset.initMs = String(performance.now() - __initStart);
canvas.dataset.ready = "true";
}

main().catch((error: unknown) => {
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement | null;
if (canvas) {
canvas.dataset.error = error instanceof Error ? error.message : String(error);
}
console.error(error);
});
Loading
Loading