1
0

California gets roads, traffic, and a car to follow

This commit is contained in:
2026-08-11 18:24:53 -07:00
parent 9c9e78f6f9
commit fe58290728
43 changed files with 5593 additions and 68 deletions
+70
View File
@@ -0,0 +1,70 @@
import * as THREE from "three";
export interface ActorRigBase {
/** Floor-centred root. Every actor faces -Z at yaw zero. */
root: THREE.Group;
/** True only when the builder created the material set. */
readonly ownsMaterials: boolean;
}
export function actorMesh(
name: string,
geometry: THREE.BufferGeometry,
material: THREE.Material,
options: {
position?: readonly [number, number, number];
rotation?: readonly [number, number, number];
scale?: readonly [number, number, number];
receiveShadow?: boolean;
} = {},
): THREE.Mesh {
const mesh = new THREE.Mesh(geometry, material);
mesh.name = name;
if (options.position) mesh.position.set(...options.position);
if (options.rotation) mesh.rotation.set(...options.rotation);
if (options.scale) mesh.scale.set(...options.scale);
mesh.castShadow = true;
mesh.receiveShadow = options.receiveShadow ?? true;
return mesh;
}
export function namedGroup(name: string, position?: readonly [number, number, number]): THREE.Group {
const group = new THREE.Group();
group.name = name;
if (position) group.position.set(...position);
return group;
}
export function requireGroup(root: THREE.Object3D, name: string): THREE.Group {
const object = root.getObjectByName(name);
if (!(object instanceof THREE.Group)) throw new Error(`actor: missing joint "${name}"`);
return object;
}
/** Release only resources owned by a prototype. Clones share those resources. */
export function disposeActor(
rig: ActorRigBase,
options: { disposeMaterials?: boolean } = {},
): void {
const geometries = new Set<THREE.BufferGeometry>();
const materials = new Set<THREE.Material>();
rig.root.traverse((object) => {
if (!(object instanceof THREE.Mesh)) return;
geometries.add(object.geometry);
if (Array.isArray(object.material)) {
for (const material of object.material) materials.add(material);
} else materials.add(object.material);
});
for (const geometry of geometries) geometry.dispose();
if (options.disposeMaterials ?? rig.ownsMaterials) {
for (const material of materials) material.dispose();
}
}
export function actorMeshes(root: THREE.Object3D): THREE.Mesh[] {
const meshes: THREE.Mesh[] = [];
root.traverse((object) => {
if (object instanceof THREE.Mesh) meshes.push(object);
});
return meshes;
}
+161
View File
@@ -0,0 +1,161 @@
/** A low-cost anonymous Tera crow, ready to perch or flap in flight. */
import * as THREE from "three";
import {
actorMesh,
disposeActor,
namedGroup,
requireGroup,
type ActorRigBase,
} from "./common.ts";
export const CROW_METRICS = { bodyLength: 0.42, perchedHeight: 0.34, wingspan: 0.84 } as const;
export interface CrowMaterials {
feather: THREE.Material;
sheen: THREE.Material;
beak: THREE.Material;
eye: THREE.Material;
foot: THREE.Material;
}
export interface CrowBuildOptions {
materials?: CrowMaterials;
featherColor?: THREE.ColorRepresentation;
sheenColor?: THREE.ColorRepresentation;
}
export interface CrowJoints {
body: THREE.Group;
head: THREE.Group;
wingLeft: THREE.Group;
wingRight: THREE.Group;
tail: THREE.Group;
}
export interface CrowRig extends ActorRigBase {
joints: CrowJoints;
}
export function createCrowMaterials(options: CrowBuildOptions = {}): CrowMaterials {
return {
feather: new THREE.MeshStandardMaterial({ name: "crow.feather", color: options.featherColor ?? 0x111519, roughness: 0.62, metalness: 0.12 }),
sheen: new THREE.MeshStandardMaterial({ name: "crow.sheen", color: options.sheenColor ?? 0x1f2c36, roughness: 0.4, metalness: 0.28 }),
beak: new THREE.MeshStandardMaterial({ name: "crow.beak", color: 0x202327, roughness: 0.78 }),
eye: new THREE.MeshBasicMaterial({ name: "crow.eye", color: 0xd4b168, toneMapped: false }),
foot: new THREE.MeshStandardMaterial({ name: "crow.foot", color: 0x24272a, roughness: 0.9 }),
};
}
function wingGeometry(side: -1 | 1): THREE.BufferGeometry {
const s = side;
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute([
0, 0, 0.04,
s * 0.32, -0.015, 0.1,
s * 0.4, -0.03, 0.2,
s * 0.18, -0.015, -0.14,
0, 0, -0.16,
], 3),
);
geometry.setIndex([0, 1, 3, 1, 2, 3, 0, 3, 4]);
geometry.computeVertexNormals();
geometry.name = `crow.wing.${side < 0 ? "left" : "right"}`;
return geometry;
}
function resolveCrow(root: THREE.Group, ownsMaterials: boolean): CrowRig {
return {
root,
ownsMaterials,
joints: {
body: requireGroup(root, "crow.body"),
head: requireGroup(root, "crow.head"),
wingLeft: requireGroup(root, "crow.wing.left"),
wingRight: requireGroup(root, "crow.wing.right"),
tail: requireGroup(root, "crow.tail"),
},
};
}
export function buildCrow(options: CrowBuildOptions = {}): CrowRig {
const m = options.materials ?? createCrowMaterials(options);
const root = namedGroup("crow");
root.userData.kind = "actor";
root.userData.actorType = "anonymous-crow";
root.userData.forwardAxis = "-Z";
const body = namedGroup("crow.body", [0, 0.2, 0]);
root.add(body);
body.add(
actorMesh("crow.torso", new THREE.SphereGeometry(0.13, 12, 9), m.feather, {
rotation: [-0.16, 0, 0],
scale: [0.82, 1.08, 1.34],
}),
actorMesh("crow.breast", new THREE.SphereGeometry(0.105, 10, 8), m.sheen, {
position: [0, 0.012, -0.105],
scale: [0.72, 1, 0.58],
}),
);
const head = namedGroup("crow.head", [0, 0.145, -0.105]);
body.add(head);
head.add(
actorMesh("crow.skull", new THREE.SphereGeometry(0.093, 12, 9), m.feather, { scale: [0.92, 1, 0.95] }),
actorMesh("crow.beak", new THREE.ConeGeometry(0.055, 0.18, 6), m.beak, {
position: [0, -0.018, -0.145],
rotation: [-Math.PI / 2, 0, 0],
scale: [0.72, 1, 0.68],
}),
);
for (const side of [-1, 1] as const) {
const word = side < 0 ? "left" : "right";
head.add(
actorMesh(`crow.eye.${word}`, new THREE.SphereGeometry(0.011, 7, 5), m.eye, {
position: [side * 0.068, 0.02, -0.06],
}),
);
const wing = namedGroup(`crow.wing.${word}`, [side * 0.075, 0.035, 0]);
body.add(wing);
wing.add(actorMesh(`crow.wing-mesh.${word}`, wingGeometry(side), m.feather, { receiveShadow: false }));
}
const tail = namedGroup("crow.tail", [0, -0.01, 0.13]);
body.add(tail);
for (const side of [-1, 0, 1] as const) {
tail.add(
actorMesh(`crow.tail-feather.${side}`, new THREE.ConeGeometry(0.045, 0.25, 4), m.feather, {
position: [side * 0.04, -0.015, 0.12],
rotation: [Math.PI / 2, 0, 0],
scale: [0.72, 1, 0.35],
}),
);
}
for (const side of [-1, 1] as const) {
body.add(
actorMesh(`crow.foot.${side < 0 ? "left" : "right"}`, new THREE.CylinderGeometry(0.012, 0.009, 0.14, 6), m.foot, {
position: [side * 0.048, -0.14, -0.012],
}),
);
}
return resolveCrow(root, !options.materials);
}
export function cloneCrow(source: CrowRig): CrowRig {
return resolveCrow(source.root.clone(true), false);
}
/** Pose a flap. `amount=0` folds the wings; `amount=1` is a broad flight stroke. */
export function poseCrowFlight(rig: CrowRig, phase: number, amount = 1): void {
const strength = THREE.MathUtils.clamp(amount, 0, 1);
const stroke = Math.sin(phase) * 0.72 * strength;
const spread = 0.2 + strength * 0.74;
rig.joints.wingLeft.rotation.z = spread + stroke;
rig.joints.wingRight.rotation.z = -spread - stroke;
rig.joints.wingLeft.rotation.x = -0.12 * strength;
rig.joints.wingRight.rotation.x = -0.12 * strength;
rig.joints.body.rotation.x = 0.08 * Math.cos(phase) * strength;
rig.joints.tail.rotation.x = -0.12 * Math.cos(phase) * strength;
}
export function disposeCrow(rig: CrowRig, options: { disposeMaterials?: boolean } = {}): void {
disposeActor(rig, options);
}
+171
View File
@@ -0,0 +1,171 @@
/** A compact, friendly anonymous office dog with a poseable head, legs and tail. */
import * as THREE from "three";
import {
actorMesh,
disposeActor,
namedGroup,
requireGroup,
type ActorRigBase,
} from "./common.ts";
export const DOG_METRICS = { length: 0.76, shoulderHeight: 0.46, height: 0.74 } as const;
export interface DogMaterials {
coat: THREE.Material;
markings: THREE.Material;
nose: THREE.Material;
collar: THREE.Material;
}
export interface DogBuildOptions {
materials?: DogMaterials;
coatColor?: THREE.ColorRepresentation;
markingsColor?: THREE.ColorRepresentation;
collarColor?: THREE.ColorRepresentation;
}
export interface DogJoints {
body: THREE.Group;
head: THREE.Group;
earLeft: THREE.Group;
earRight: THREE.Group;
tail: THREE.Group;
legFrontLeft: THREE.Group;
legFrontRight: THREE.Group;
legRearLeft: THREE.Group;
legRearRight: THREE.Group;
}
export interface DogRig extends ActorRigBase {
joints: DogJoints;
}
export function createDogMaterials(options: DogBuildOptions = {}): DogMaterials {
return {
coat: new THREE.MeshStandardMaterial({ name: "dog.coat", color: options.coatColor ?? 0x9a673d, roughness: 0.92 }),
markings: new THREE.MeshStandardMaterial({ name: "dog.markings", color: options.markingsColor ?? 0xe4c9a2, roughness: 0.95 }),
nose: new THREE.MeshStandardMaterial({ name: "dog.nose", color: 0x151719, roughness: 0.72 }),
collar: new THREE.MeshStandardMaterial({ name: "dog.collar", color: options.collarColor ?? 0x2d92a7, roughness: 0.55 }),
};
}
function resolveDog(root: THREE.Group, ownsMaterials: boolean): DogRig {
return {
root,
ownsMaterials,
joints: {
body: requireGroup(root, "dog.body"),
head: requireGroup(root, "dog.head"),
earLeft: requireGroup(root, "dog.ear.left"),
earRight: requireGroup(root, "dog.ear.right"),
tail: requireGroup(root, "dog.tail"),
legFrontLeft: requireGroup(root, "dog.leg.front.left"),
legFrontRight: requireGroup(root, "dog.leg.front.right"),
legRearLeft: requireGroup(root, "dog.leg.rear.left"),
legRearRight: requireGroup(root, "dog.leg.rear.right"),
},
};
}
export function buildDog(options: DogBuildOptions = {}): DogRig {
const m = options.materials ?? createDogMaterials(options);
const root = namedGroup("dog");
root.userData.kind = "actor";
root.userData.actorType = "anonymous-dog";
root.userData.forwardAxis = "-Z";
const body = namedGroup("dog.body", [0, 0.39, 0.04]);
root.add(body);
body.add(
actorMesh("dog.torso", new THREE.CapsuleGeometry(0.18, 0.34, 5, 10), m.coat, {
rotation: [Math.PI / 2, 0, 0],
scale: [0.82, 1, 0.92],
}),
actorMesh("dog.chest", new THREE.SphereGeometry(0.185, 12, 9), m.markings, {
position: [0, 0.01, -0.17],
scale: [0.72, 1, 0.56],
}),
);
const head = namedGroup("dog.head", [0, 0.16, -0.32]);
body.add(head);
head.add(
actorMesh("dog.skull", new THREE.SphereGeometry(0.16, 12, 10), m.coat, { scale: [0.82, 0.92, 0.88] }),
actorMesh("dog.muzzle", new THREE.SphereGeometry(0.105, 12, 8), m.markings, {
position: [0, -0.045, -0.13],
scale: [0.8, 0.62, 1],
}),
actorMesh("dog.nose", new THREE.SphereGeometry(0.045, 10, 7), m.nose, {
position: [0, -0.035, -0.224],
scale: [1.15, 0.72, 0.7],
}),
actorMesh("dog.collar", new THREE.TorusGeometry(0.118, 0.016, 6, 18), m.collar, {
position: [0, -0.11, 0.1],
rotation: [Math.PI / 2, 0, 0],
scale: [1, 0.82, 1],
}),
);
for (const side of [-1, 1] as const) {
const word = side < 0 ? "left" : "right";
const ear = namedGroup(`dog.ear.${word}`, [side * 0.1, 0.105, -0.015]);
head.add(ear);
ear.add(
actorMesh(`dog.ear-flap.${word}`, new THREE.ConeGeometry(0.075, 0.19, 5), m.coat, {
position: [side * 0.015, -0.065, 0.02],
rotation: [0.18, 0, side * 0.28],
}),
);
}
for (const z of [-0.2, 0.22] as const) {
for (const side of [-1, 1] as const) {
const fore = z < 0 ? "front" : "rear";
const word = side < 0 ? "left" : "right";
const leg = namedGroup(`dog.leg.${fore}.${word}`, [side * 0.125, -0.1, z]);
body.add(leg);
leg.add(
actorMesh(`dog.leg-mesh.${fore}.${word}`, new THREE.CapsuleGeometry(0.046, 0.19, 3, 7), m.coat, {
position: [0, -0.135, 0],
}),
actorMesh(`dog.paw.${fore}.${word}`, new THREE.SphereGeometry(0.06, 8, 6), m.markings, {
position: [0, -0.29, -0.022],
scale: [0.84, 0.48, 1.18],
}),
);
}
}
const tail = namedGroup("dog.tail", [0, 0.03, 0.31]);
body.add(tail);
tail.rotation.x = 0.68;
tail.add(
actorMesh("dog.tail-mesh", new THREE.CapsuleGeometry(0.04, 0.25, 4, 8), m.coat, {
position: [0, 0.15, 0],
}),
);
return resolveDog(root, !options.materials);
}
export function cloneDog(source: DogRig): DogRig {
return resolveDog(source.root.clone(true), false);
}
export function poseDogWalk(rig: DogRig, phase: number, amount = 0.55): void {
const swing = Math.sin(phase) * THREE.MathUtils.clamp(amount, 0, 0.8);
rig.joints.legFrontLeft.rotation.x = swing;
rig.joints.legRearRight.rotation.x = swing;
rig.joints.legFrontRight.rotation.x = -swing;
rig.joints.legRearLeft.rotation.x = -swing;
rig.joints.body.position.y = 0.39 + Math.abs(Math.cos(phase)) * Math.abs(swing) * 0.018;
}
export function poseDogAttention(rig: DogRig, lookYaw: number, tailPhase: number): void {
rig.joints.head.rotation.y = THREE.MathUtils.clamp(lookYaw, -0.9, 0.9);
rig.joints.head.rotation.x = -0.08;
rig.joints.earLeft.rotation.z = 0.08;
rig.joints.earRight.rotation.z = -0.08;
rig.joints.tail.rotation.z = Math.sin(tailPhase) * 0.72;
}
export function disposeDog(rig: DogRig, options: { disposeMaterials?: boolean } = {}): void {
disposeActor(rig, options);
}
+261
View File
@@ -0,0 +1,261 @@
/**
* A friendly, original humanoid avatar built entirely from Three.js primitives.
*
* Dimensions are metres, the origin is between the soles, and -Z is forward.
* The face is a slightly curved dark display with an optional caller-owned
* texture. A deployment can build one prototype per appearance/webcam stream,
* then cheaply clone that prototype for repeated views: clones share immutable
* geometry and materials while all animation joints remain independent.
*/
import * as THREE from "three";
import {
actorMesh,
disposeActor,
namedGroup,
requireGroup,
type ActorRigBase,
} from "./common.ts";
export const HUMANOID_METRICS = {
height: 1.76,
shoulderWidth: 0.46,
hipY: 0.94,
eyeY: 1.65,
} as const;
export interface HumanoidMaterials {
skin: THREE.Material;
outfit: THREE.Material;
accent: THREE.Material;
sole: THREE.Material;
hair: THREE.Material;
face: THREE.Material;
}
export interface HumanoidBuildOptions {
materials?: HumanoidMaterials;
skinTone?: THREE.ColorRepresentation;
outfitColor?: THREE.ColorRepresentation;
accentColor?: THREE.ColorRepresentation;
hairColor?: THREE.ColorRepresentation;
/** Caller-owned and never disposed by this asset. */
faceTexture?: THREE.Texture;
bodyShape?: "slim" | "average" | "broad";
}
export interface HumanoidJoints {
pelvis: THREE.Group;
torso: THREE.Group;
head: THREE.Group;
shoulderLeft: THREE.Group;
shoulderRight: THREE.Group;
elbowLeft: THREE.Group;
elbowRight: THREE.Group;
hipLeft: THREE.Group;
hipRight: THREE.Group;
kneeLeft: THREE.Group;
kneeRight: THREE.Group;
}
export interface HumanoidRig extends ActorRigBase {
joints: HumanoidJoints;
/** Front-facing surface for a webcam or generated profile face. */
face: THREE.Mesh;
}
export interface HumanoidPose {
walkPhase?: number;
stride?: number;
armSwing?: number;
headYaw?: number;
headPitch?: number;
}
export function createHumanoidMaterials(options: HumanoidBuildOptions = {}): HumanoidMaterials {
return {
skin: new THREE.MeshStandardMaterial({
name: "humanoid.skin",
color: options.skinTone ?? 0x9b6246,
roughness: 0.68,
}),
outfit: new THREE.MeshStandardMaterial({
name: "humanoid.outfit",
color: options.outfitColor ?? 0x26364b,
roughness: 0.72,
}),
accent: new THREE.MeshStandardMaterial({
name: "humanoid.accent",
color: options.accentColor ?? 0x4fa9c8,
roughness: 0.55,
}),
sole: new THREE.MeshStandardMaterial({
name: "humanoid.sole",
color: 0x181b1e,
roughness: 0.9,
}),
hair: new THREE.MeshStandardMaterial({
name: "humanoid.hair",
color: options.hairColor ?? 0x241b18,
roughness: 0.86,
}),
face: new THREE.MeshBasicMaterial({
name: "humanoid.face",
color: options.faceTexture ? 0xffffff : 0x18242b,
map: options.faceTexture ?? null,
toneMapped: false,
}),
};
}
function limb(
joint: THREE.Group,
name: string,
material: THREE.Material,
radius: number,
length: number,
): void {
joint.add(
actorMesh(name, new THREE.CapsuleGeometry(radius, Math.max(0.01, length - radius * 2), 4, 8), material, {
position: [0, -length / 2, 0],
}),
);
}
function resolveHumanoid(root: THREE.Group, ownsMaterials: boolean): HumanoidRig {
const face = root.getObjectByName("humanoid.face");
if (!(face instanceof THREE.Mesh)) throw new Error("humanoid: missing face surface");
return {
root,
ownsMaterials,
face,
joints: {
pelvis: requireGroup(root, "humanoid.pelvis"),
torso: requireGroup(root, "humanoid.torso"),
head: requireGroup(root, "humanoid.head"),
shoulderLeft: requireGroup(root, "humanoid.shoulder.left"),
shoulderRight: requireGroup(root, "humanoid.shoulder.right"),
elbowLeft: requireGroup(root, "humanoid.elbow.left"),
elbowRight: requireGroup(root, "humanoid.elbow.right"),
hipLeft: requireGroup(root, "humanoid.hip.left"),
hipRight: requireGroup(root, "humanoid.hip.right"),
kneeLeft: requireGroup(root, "humanoid.knee.left"),
kneeRight: requireGroup(root, "humanoid.knee.right"),
},
};
}
export function buildHumanoid(options: HumanoidBuildOptions = {}): HumanoidRig {
const materials = options.materials ?? createHumanoidMaterials(options);
const shape = options.bodyShape ?? "average";
const widthScale = shape === "slim" ? 0.86 : shape === "broad" ? 1.14 : 1;
const root = namedGroup("humanoid");
root.userData.kind = "actor";
root.userData.actorType = "humanoid";
root.userData.forwardAxis = "-Z";
const pelvis = namedGroup("humanoid.pelvis", [0, HUMANOID_METRICS.hipY, 0]);
root.add(pelvis);
pelvis.add(
actorMesh("humanoid.pelvis.shell", new THREE.CapsuleGeometry(0.12, 0.12, 4, 8), materials.outfit, {
scale: [1.25 * widthScale, 0.75, 0.9],
position: [0, 0.02, 0],
}),
);
const torso = namedGroup("humanoid.torso", [0, 0.11, 0]);
pelvis.add(torso);
torso.add(
actorMesh("humanoid.chest", new THREE.CapsuleGeometry(0.18, 0.25, 5, 10), materials.outfit, {
position: [0, 0.24, 0],
scale: [1.08 * widthScale, 1, 0.72],
}),
actorMesh("humanoid.chest.accent", new THREE.BoxGeometry(0.2 * widthScale, 0.065, 0.018), materials.accent, {
position: [0, 0.32, -0.134],
}),
);
const head = namedGroup("humanoid.head", [0, 0.58, 0]);
torso.add(head);
head.add(
actorMesh("humanoid.head.shell", new THREE.SphereGeometry(0.12, 16, 12), materials.skin, {
position: [0, 0.1, 0],
scale: [0.86, 1.05, 0.86],
}),
actorMesh("humanoid.hair", new THREE.SphereGeometry(0.122, 14, 8, 0, Math.PI * 2, 0, Math.PI * 0.47), materials.hair, {
position: [0, 0.115, 0.004],
scale: [0.88, 1.06, 0.88],
}),
);
const face = actorMesh("humanoid.face", new THREE.PlaneGeometry(0.125, 0.105, 2, 2), materials.face, {
position: [0, 0.09, -0.105],
rotation: [0, Math.PI, 0],
receiveShadow: false,
});
head.add(face);
const shoulderY = 0.47;
for (const side of [-1, 1] as const) {
const word = side < 0 ? "left" : "right";
const shoulder = namedGroup(`humanoid.shoulder.${word}`, [side * 0.23 * widthScale, shoulderY, 0]);
torso.add(shoulder);
limb(shoulder, `humanoid.upper-arm.${word}`, materials.outfit, 0.066, 0.3);
const elbow = namedGroup(`humanoid.elbow.${word}`, [0, -0.3, 0]);
shoulder.add(elbow);
limb(elbow, `humanoid.forearm.${word}`, materials.skin, 0.055, 0.27);
elbow.add(
actorMesh(`humanoid.hand.${word}`, new THREE.SphereGeometry(0.065, 10, 8), materials.skin, {
position: [0, -0.295, -0.012],
scale: [0.72, 1.08, 0.55],
}),
);
}
for (const side of [-1, 1] as const) {
const word = side < 0 ? "left" : "right";
const hip = namedGroup(`humanoid.hip.${word}`, [side * 0.105 * widthScale, -0.02, 0]);
pelvis.add(hip);
limb(hip, `humanoid.thigh.${word}`, materials.outfit, 0.085, 0.43);
const knee = namedGroup(`humanoid.knee.${word}`, [0, -0.43, 0]);
hip.add(knee);
limb(knee, `humanoid.shin.${word}`, materials.outfit, 0.072, 0.4);
knee.add(
actorMesh(`humanoid.shoe.${word}`, new THREE.BoxGeometry(0.15, 0.09, 0.25), materials.sole, {
position: [0, -0.405, -0.055],
}),
);
}
return resolveHumanoid(root, !options.materials);
}
/** Clone the hierarchy while sharing geometry and materials. */
export function cloneHumanoid(source: HumanoidRig): HumanoidRig {
return resolveHumanoid(source.root.clone(true), false);
}
export function poseHumanoid(rig: HumanoidRig, pose: HumanoidPose = {}): void {
const phase = pose.walkPhase ?? 0;
const stride = THREE.MathUtils.clamp(pose.stride ?? 0, 0, 0.75);
const arm = THREE.MathUtils.clamp(pose.armSwing ?? stride * 0.85, 0, 0.7);
const swing = Math.sin(phase);
const bendLeft = Math.max(0, -swing) * stride * 0.7;
const bendRight = Math.max(0, swing) * stride * 0.7;
rig.joints.hipLeft.rotation.x = swing * stride;
rig.joints.hipRight.rotation.x = -swing * stride;
rig.joints.kneeLeft.rotation.x = -bendLeft;
rig.joints.kneeRight.rotation.x = -bendRight;
rig.joints.shoulderLeft.rotation.x = -swing * arm;
rig.joints.shoulderRight.rotation.x = swing * arm;
rig.joints.elbowLeft.rotation.x = 0.1 + Math.max(0, swing) * 0.18;
rig.joints.elbowRight.rotation.x = 0.1 + Math.max(0, -swing) * 0.18;
rig.joints.pelvis.position.y = HUMANOID_METRICS.hipY + Math.abs(Math.cos(phase)) * stride * 0.018;
rig.joints.head.rotation.y = THREE.MathUtils.clamp(pose.headYaw ?? 0, -0.9, 0.9);
rig.joints.head.rotation.x = THREE.MathUtils.clamp(pose.headPitch ?? 0, -0.45, 0.45);
}
export function disposeHumanoid(
rig: HumanoidRig,
options: { disposeMaterials?: boolean } = {},
): void {
disposeActor(rig, options);
}
+40
View File
@@ -0,0 +1,40 @@
export {
HUMANOID_METRICS,
buildHumanoid,
cloneHumanoid,
createHumanoidMaterials,
disposeHumanoid,
poseHumanoid,
type HumanoidBuildOptions,
type HumanoidJoints,
type HumanoidMaterials,
type HumanoidPose,
type HumanoidRig,
} from "./humanoid.ts";
export {
DOG_METRICS,
buildDog,
cloneDog,
createDogMaterials,
disposeDog,
poseDogAttention,
poseDogWalk,
type DogBuildOptions,
type DogJoints,
type DogMaterials,
type DogRig,
} from "./dog.ts";
export {
CROW_METRICS,
buildCrow,
cloneCrow,
createCrowMaterials,
disposeCrow,
poseCrowFlight,
type CrowBuildOptions,
type CrowJoints,
type CrowMaterials,
type CrowRig,
} from "./crow.ts";