Skip to content
Open
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
62 changes: 61 additions & 1 deletion src/movement/__tests__/MovementMath.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { Vector3 } from 'three';
import { accelerate, applyFriction, clipVelocity } from '../MovementMath';
import { accelerate, applyFriction, clipVelocity, clampHorizontalSpeed, projectDirectionOnPlane } from '../MovementMath';

describe('MovementMath', () => {
it('accelerate increases speed toward wishdir up to addSpeed', () => {
Expand All @@ -24,3 +24,63 @@ describe('MovementMath', () => {
expect(clipped.y).toBeGreaterThanOrEqual(-1e-5);
});
});

describe('clampHorizontalSpeed', () => {
it('does not modify velocity already under the limit', () => {
const vel = new Vector3(3, 5, 0); // horizontal speed = 3, under limit of 10
const result = clampHorizontalSpeed(vel, 10);
expect(result.x).toBeCloseTo(3);
expect(result.z).toBeCloseTo(0);
expect(result.y).toBeCloseTo(5); // vertical unchanged
});

it('clamps horizontal speed to the limit when over', () => {
const vel = new Vector3(6, 0, 8); // horizontal speed = 10, limit = 5
const result = clampHorizontalSpeed(vel, 5);
expect(Math.hypot(result.x, result.z)).toBeCloseTo(5);
expect(result.y).toBeCloseTo(0); // vertical unchanged
});

it('preserves direction when clamping', () => {
const vel = new Vector3(6, 2, 8); // horizontal speed = 10
const result = clampHorizontalSpeed(vel, 5);
// Direction ratio x/z should be preserved
expect(result.x / result.z).toBeCloseTo(vel.x / vel.z);
});

it('handles zero vector without division by zero', () => {
const vel = new Vector3(0, 0, 0);
const result = clampHorizontalSpeed(vel, 10);
expect(result.x).toBeCloseTo(0);
expect(result.z).toBeCloseTo(0);
});
});

describe('projectDirectionOnPlane', () => {
it('returns a normalized vector perpendicular to the normal', () => {
const dir = new Vector3(1, 1, 0).normalize();
const normal = new Vector3(0, 1, 0);
const result = projectDirectionOnPlane(dir, normal);
// Result should be unit length
expect(result.length()).toBeCloseTo(1);
// Result should be perpendicular to the normal (dot product ≈ 0)
expect(result.dot(normal)).toBeCloseTo(0);
});

it('returns zero vector when direction is parallel to normal', () => {
const dir = new Vector3(0, 1, 0); // same direction as normal
const normal = new Vector3(0, 1, 0);
const result = projectDirectionOnPlane(dir, normal);
expect(result.length()).toBeCloseTo(0);
});

it('does not mutate input vectors', () => {
const dir = new Vector3(1, 1, 0).normalize();
const normal = new Vector3(0, 1, 0);
const dirBefore = dir.clone();
const normalBefore = normal.clone();
projectDirectionOnPlane(dir, normal);
expect(dir.equals(dirBefore)).toBe(true);
expect(normal.equals(normalBefore)).toBe(true);
});
});