Implement damped yaw rotation for smooth third-person camera target assist

This commit is contained in:
2026-04-25 17:10:37 +02:00
parent fa18467088
commit 688a2f3db6
2 changed files with 25 additions and 28 deletions

View File

@@ -37,6 +37,7 @@ const MIN_PITCH_RADIANS = -0.2;
const MAX_PITCH_RADIANS = Math.PI * 0.45;
export const THIRD_PERSON_CAMERA_COLLISION_RADIUS = 0.2;
const CAMERA_PIVOT_EYE_HEIGHT_FACTOR = 0.85;
const TARGET_ASSIST_YAW_SPEED = 2.2;
const TARGET_ASSIST_VERTICAL_LOOK_SPEED = 3.4;
const TARGET_ASSIST_VERTICAL_LOOK_LIMIT = 1.25;
const TARGET_ASSIST_VERTICAL_COLLISION_FADE_START_RATIO = 0.28;
@@ -62,6 +63,24 @@ function clampCameraDistance(distance: number): number {
);
}
function normalizeAngleRadians(angle: number): number {
return Math.atan2(Math.sin(angle), Math.cos(angle));
}
function dampAngleRadians(
current: number,
target: number,
strength: number,
dt: number
): number {
if (dt <= 0 || strength <= 0) {
return current;
}
const alpha = 1 - Math.exp(-strength * dt);
return current + normalizeAngleRadians(target - current) * alpha;
}
function dampScalar(
current: number,
target: number,
@@ -400,7 +419,12 @@ export class ThirdPersonNavigationController implements NavigationController {
targetAssist.targetPosition.x - this.feetPosition.x,
targetAssist.targetPosition.z - this.feetPosition.z
);
this.cameraYawRadians = targetYaw;
this.cameraYawRadians = dampAngleRadians(
this.cameraYawRadians,
targetYaw,
TARGET_ASSIST_YAW_SPEED * targetAssist.strength,
dt
);
const eyeHeight = getFirstPersonPlayerEyeHeight(this.activePlayerShape);
const pivotY =
this.smoothedFeetY + eyeHeight * CAMERA_PIVOT_EYE_HEIGHT_FACTOR;

View File

@@ -241,33 +241,6 @@ describe("ThirdPersonNavigationController", () => {
controller.deactivate(targetContext);
});
it("uses target direction as the lock-on base yaw immediately", () => {
const { context } = createRuntimeControllerContext();
const controller = new ThirdPersonNavigationController();
const targetContext = {
...context,
resolveThirdPersonTargetAssist: () => ({
targetPosition: {
x: 5,
y: 1,
z: 0
},
strength: 1
})
};
controller.activate(targetContext);
expect(Math.abs(targetContext.camera.position.x)).toBeLessThan(0.001);
controller.update(0);
expect(targetContext.camera.position.x).toBeLessThan(-3);
expect(Math.abs(targetContext.camera.position.z)).toBeLessThan(1);
controller.deactivate(targetContext);
});
it("fades vertical target assist when camera collision pushes the camera close", () => {
const { context } = createRuntimeControllerContext();
const controller = new ThirdPersonNavigationController();