feat: build articulated crow flight v2
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
disposeCrow,
|
||||
disposeDog,
|
||||
disposeHumanoid,
|
||||
poseCrow,
|
||||
poseCrowFlight,
|
||||
poseDogAttention,
|
||||
poseDogWalk,
|
||||
@@ -100,6 +101,52 @@ describe("procedural actor assets", () => {
|
||||
disposeCrow(rig);
|
||||
});
|
||||
|
||||
it("builds a layered v2 feather rig with shoulder, elbow, wrist, tail and claw anatomy", () => {
|
||||
const rig = buildCrow();
|
||||
assert.equal(rig.root.userData.rigVersion, 2);
|
||||
assert.equal(rig.joints.elbowLeft.parent, rig.joints.shoulderLeft);
|
||||
assert.equal(rig.joints.wristLeft.parent, rig.joints.elbowLeft);
|
||||
assert.equal(rig.joints.elbowRight.parent, rig.joints.shoulderRight);
|
||||
assert.equal(rig.joints.wristRight.parent, rig.joints.elbowRight);
|
||||
|
||||
const allMeshes = meshes(rig.root);
|
||||
const featherCount = (prefix: string) => allMeshes
|
||||
.filter((mesh) => mesh.name.startsWith(prefix))
|
||||
.reduce((sum, mesh) => sum + Number(mesh.userData.featherCount ?? 0), 0);
|
||||
assert.equal(featherCount("crow.primaries."), 18);
|
||||
assert.equal(featherCount("crow.secondaries-"), 14);
|
||||
assert.equal(featherCount("crow.tail-feathers."), 9);
|
||||
assert.equal(allMeshes.filter((mesh) => mesh.name.startsWith("crow.toe.")).length, 8);
|
||||
assert.ok(allMeshes.length <= 36, `${allMeshes.length} meshes keeps one crow bounded for mobile/peers`);
|
||||
disposeCrow(rig);
|
||||
});
|
||||
|
||||
it("poses a materially different glide, bank, tuck and perch without rebuilding geometry", () => {
|
||||
const rig = buildCrow();
|
||||
const width = () => new THREE.Box3().setFromObject(rig.root).getSize(new THREE.Vector3()).x;
|
||||
|
||||
poseCrow(rig, { state: "glide", amount: 0.8, flight: 1 });
|
||||
const glideWidth = width();
|
||||
assert.equal(rig.root.userData.pose, "glide");
|
||||
assert.ok(Math.abs(glideWidth - CROW_METRICS.wingspan) < 0.12, `glide width ${glideWidth}`);
|
||||
|
||||
poseCrow(rig, { state: "bank", amount: 0.8, bank: 0.75, flight: 1 });
|
||||
assert.equal(rig.root.userData.pose, "bank");
|
||||
assert.notEqual(rig.joints.shoulderLeft.rotation.z, -rig.joints.shoulderRight.rotation.z,
|
||||
"bank makes the wing dihedral asymmetric");
|
||||
assert.ok(rig.joints.tail.rotation.z < 0, "tail counters a right bank");
|
||||
|
||||
poseCrow(rig, { state: "tuck", amount: 1, flight: 1 });
|
||||
const tuckWidth = width();
|
||||
poseCrow(rig, { state: "perch", amount: 1, flight: 0 });
|
||||
const perchWidth = width();
|
||||
assert.ok(tuckWidth < glideWidth, `${tuckWidth} tuck < ${glideWidth} glide`);
|
||||
assert.ok(perchWidth < tuckWidth, `${perchWidth} perch < ${tuckWidth} tuck`);
|
||||
assert.equal(rig.joints.legLeft.rotation.x, 0);
|
||||
assert.equal(rig.joints.legRight.rotation.x, 0);
|
||||
disposeCrow(rig);
|
||||
});
|
||||
|
||||
it("never disposes caller-owned materials unless explicitly requested", () => {
|
||||
const source = buildCrow();
|
||||
const shared = meshes(source.root)[0]!.material as THREE.Material;
|
||||
|
||||
Reference in New Issue
Block a user