1
0

feat(actors): build articulated dog v2

This commit is contained in:
2026-08-19 03:07:40 -07:00
parent 264daaab61
commit 20deae2a0f
9 changed files with 615 additions and 104 deletions
+36 -6
View File
@@ -11,9 +11,9 @@
import * as THREE from "three";
import {
buildDog,
dogPoseStateForSpeed,
disposeDog,
poseDogAttention,
poseDogWalk,
poseDog,
type DogRig,
} from "../assets/actors/dog.ts";
import {
@@ -118,6 +118,9 @@ export function createOfficeWalker(plan: Plan, options: OfficeWalkerOptions): Of
let enabled = options.active ?? false;
let desired: WalkerAction = { x: 0, z: 0 };
let gait = 0;
let presentationSeconds = 0;
let previousSpeed = 0;
const previousFacing = new THREE.Vector2(0, -1);
let faceTexture: THREE.Texture | null = null;
let disposed = false;
const root = new THREE.Group();
@@ -135,16 +138,40 @@ export function createOfficeWalker(plan: Plan, options: OfficeWalkerOptions): Of
view.position.copy(root.position);
root.rotation.y = Math.atan2(-state.facing.x, -state.facing.z);
const speed = elapsedSeconds > 0 ? travelled / elapsedSeconds : 0;
const speed = elapsedSeconds > 0 ? Math.max(0, travelled / elapsedSeconds) : 0;
const targetGait = enabled && speed > 1e-5 ? Math.min(1, speed / (options.speed ?? 1.6)) : 0;
gait += (targetGait - gait) * Math.min(1, elapsedSeconds / GAIT_EASE_SECONDS);
const phase = (state.distance / STRIDE_METRES) * Math.PI * 2;
presentationSeconds += elapsedSeconds;
const phase = speed > 1e-5
? (state.distance / STRIDE_METRES) * Math.PI * 2
: presentationSeconds * 2.2;
const facingDot = THREE.MathUtils.clamp(
previousFacing.x * state.facing.x + previousFacing.y * state.facing.z,
-1,
1,
);
const facingCross = previousFacing.x * state.facing.z - previousFacing.y * state.facing.x;
const turn = elapsedSeconds > 0
? THREE.MathUtils.clamp(Math.atan2(facingCross, facingDot) / elapsedSeconds / 4, -1, 1)
: 0;
const acceleration = elapsedSeconds > 0
? THREE.MathUtils.clamp((speed - previousSpeed) / elapsedSeconds / 5, -1, 1)
: 0;
if (actor.kind === "humanoid") {
poseHumanoid(actor.rig, { walkPhase: phase, stride: gait * 0.62 });
} else {
poseDogWalk(actor.rig, phase, gait * 0.62);
poseDogAttention(actor.rig, 0, state.distance * 5);
const dogState = dogPoseStateForSpeed(speed * Math.max(gait, 0.2));
poseDog(actor.rig, {
state: dogState,
phase,
amount: dogState === "idle" ? 1 : THREE.MathUtils.clamp(0.38 + gait * 0.62, 0, 1),
turn,
acceleration,
attentionYaw: -turn * 0.08,
});
}
previousSpeed = speed;
previousFacing.set(state.facing.x, state.facing.z);
}
function snapshot(): OfficeWalkerState {
@@ -202,6 +229,9 @@ export function createOfficeWalker(plan: Plan, options: OfficeWalkerOptions): Of
const state = controller.reset(spawn);
desired = { x: 0, z: 0 };
gait = 0;
presentationSeconds = 0;
previousSpeed = 0;
previousFacing.set(state.facing.x, state.facing.z);
sync(state);
return snapshot();
},