236 lines
8.2 KiB
TypeScript
236 lines
8.2 KiB
TypeScript
// Content/Movement/Components/AC_Movement.ts
|
|
|
|
import type {S_MovementConstants} from "../Structs/S_MovementConstants.js";
|
|
import type {S_AngleThresholds} from "../Structs/S_AngleThresholds.js";
|
|
import type {Float, Vector} from "../../types.js";
|
|
import {acos, cos, D2R, Dot, Print, sin} from "../../functions.js";
|
|
import {E_SurfaceType} from "../Enums/E_SurfaceType.js";
|
|
import type {S_SurfaceTestCase} from "../Structs/S_SurfaceTestCase.js";
|
|
|
|
export class AC_Movement {
|
|
// === Movement configuration exposed to designers ===
|
|
// Category: "Movement Config"
|
|
// Instance editable: true
|
|
readonly MovementConstants: S_MovementConstants = {
|
|
MaxSpeed: 600.0,
|
|
Acceleration: 2000.0,
|
|
Friction: 8.0,
|
|
Gravity: 980.0,
|
|
}
|
|
|
|
// Category: "Movement Config"
|
|
// Instance editable: true
|
|
readonly AngleThresholdsDegrees: S_AngleThresholds = {
|
|
Walkable: 50.0, // degrees
|
|
SteepSlope: 85.0, // degrees
|
|
Wall: 95.0, // degrees
|
|
};
|
|
// =====================================================
|
|
|
|
// Category: "Debug"
|
|
IsInitialized: boolean = false;
|
|
|
|
// Category: "Debug"
|
|
// Instance editable: true
|
|
ShowDebugInfo: boolean = true;
|
|
|
|
// === Runtime cached values for performance (radians) ===
|
|
// Category: "InternalCache"
|
|
AngleThresholdsRads: S_AngleThresholds;
|
|
// =====================================================
|
|
|
|
// Category: "Math"
|
|
// Pure: true
|
|
/**
|
|
* Calculate angle between two normalized vectors
|
|
* @param Vector1 - First normalized vector
|
|
* @param Vector2 - Second normalized vector
|
|
* @returns Angle between vectors in radians (0 to π)
|
|
* @example
|
|
* // 90° angle between X and Z axes
|
|
* GetAngleBetweenVectors([1,0,0], [0,0,1]) // returns π/2
|
|
*/
|
|
GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector) {
|
|
return acos(Dot(Vector1, Vector2));
|
|
}
|
|
|
|
// Category: "Math"
|
|
// Pure: true
|
|
/**
|
|
* Calculate angle between surface normal and up vector
|
|
* @param SurfaceNormal - Normalized surface normal vector
|
|
* @returns Angle from horizontal plane in radians (0 = flat, π/2 = vertical)
|
|
* @example
|
|
* // Flat surface
|
|
* GetSurfaceAngle([0,0,1]) // returns 0
|
|
* // Vertical wall
|
|
* GetSurfaceAngle([1,0,0]) // returns π/2
|
|
*/
|
|
GetSurfaceAngle(SurfaceNormal: Vector): Float {
|
|
return this.GetAngleBetweenVectors(SurfaceNormal, [0.0, 0.0, 1.0]);
|
|
}
|
|
|
|
// Category: "Math"
|
|
// Pure: true
|
|
/**
|
|
* Generate surface normal vector from angle in degrees
|
|
* @param AngleDegrees - Angle from horizontal in degrees (0-180)
|
|
* @returns Normalized surface normal vector
|
|
* @example
|
|
* // Flat surface (0°)
|
|
* GenerateNormalFromAngle(0) // returns [0,0,1]
|
|
* // Vertical wall (90°)
|
|
* GenerateNormalFromAngle(90) // returns [1,0,0]
|
|
*/
|
|
GenerateNormalFromAngle(AngleDegrees: Float): Vector {
|
|
const AngleRads = D2R(AngleDegrees);
|
|
return [sin(AngleRads), 0.0, cos(AngleRads)];
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Classify surface type based on normal vector
|
|
* @param SurfaceNormal - Normalized surface normal vector
|
|
* @param AngleThresholds - Angle thresholds in radians
|
|
* @returns Surface type classification
|
|
* @example
|
|
* // Classify flat ground
|
|
* ClassifySurface([0,0,1], thresholds) // returns E_SurfaceType.Walkable
|
|
*/
|
|
ClassifySurface(SurfaceNormal: Vector, AngleThresholds: S_AngleThresholds): E_SurfaceType {
|
|
const SurfaceAngle = this.GetSurfaceAngle(SurfaceNormal);
|
|
|
|
if (SurfaceAngle <= AngleThresholds.Walkable) {
|
|
return E_SurfaceType.Walkable;
|
|
} else if (SurfaceAngle <= AngleThresholds.SteepSlope) {
|
|
return E_SurfaceType.SteepSlope;
|
|
} else if (SurfaceAngle <= AngleThresholds.Wall) {
|
|
return E_SurfaceType.Wall;
|
|
} else {
|
|
return E_SurfaceType.Ceiling;
|
|
}
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Check if surface allows normal walking movement
|
|
* @param SurfaceType - Surface type to check
|
|
* @returns True if surface is walkable
|
|
*/
|
|
IsSurfaceWalkable(SurfaceType: E_SurfaceType): boolean {
|
|
return SurfaceType === E_SurfaceType.Walkable;
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Check if surface causes sliding behavior
|
|
* @param SurfaceType - Surface type to check
|
|
* @returns True if surface is steep slope
|
|
*/
|
|
IsSurfaceSteep(SurfaceType: E_SurfaceType): boolean {
|
|
return SurfaceType === E_SurfaceType.SteepSlope;
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Check if surface blocks movement (collision)
|
|
* @param SurfaceType - Surface type to check
|
|
* @returns True if surface is a wall
|
|
*/
|
|
IsSurfaceWall(SurfaceType: E_SurfaceType): boolean {
|
|
return SurfaceType === E_SurfaceType.Wall;
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Check if surface is overhead (ceiling)
|
|
* @param SurfaceType - Surface type to check
|
|
* @returns True if surface is ceiling
|
|
*/
|
|
IsSurfaceCeiling(SurfaceType: E_SurfaceType): boolean {
|
|
return SurfaceType === E_SurfaceType.Ceiling;
|
|
}
|
|
|
|
// Category: "Surface Detection"
|
|
// Pure: true
|
|
/**
|
|
* Check if no surface detected (airborne state)
|
|
* @param SurfaceType - Surface type to check
|
|
* @returns True if no surface contact
|
|
*/
|
|
IsSurfaceNone(SurfaceType: E_SurfaceType): boolean {
|
|
return SurfaceType === E_SurfaceType.None;
|
|
}
|
|
|
|
// Category: "Debug"
|
|
PrintDebugInfo() {
|
|
Print('=== Movement System Initialized ===');
|
|
Print(`Walkable: ≤ ${this.AngleThresholdsDegrees.Walkable}°, Steep: ${this.AngleThresholdsDegrees.Walkable}°-${this.AngleThresholdsDegrees.SteepSlope}°, Wall: ${this.AngleThresholdsDegrees.SteepSlope}°-${this.AngleThresholdsDegrees.Wall}°, Ceiling: >${this.AngleThresholdsDegrees.Wall}°`);
|
|
Print(`Max Speed: ${this.MovementConstants.MaxSpeed}, Acceleration: ${this.MovementConstants.Acceleration}, Friction: ${this.MovementConstants.Friction}, Gravity: ${this.MovementConstants.Gravity}`);
|
|
Print('==================================');
|
|
}
|
|
|
|
// Category: "Debug"
|
|
/**
|
|
* Run comprehensive surface classification test suite
|
|
* @returns True if all tests pass
|
|
*/
|
|
TestSurfaceClassification() {
|
|
let AllTestsPassed: boolean = true;
|
|
|
|
const TestCases: S_SurfaceTestCase[] = [
|
|
{ AngleDegrees: 0.0, ExpectedType: E_SurfaceType.Walkable, Description: "Flat surface" },
|
|
{ AngleDegrees: 25.0, ExpectedType: E_SurfaceType.Walkable, Description: "Gentle slope" },
|
|
{ AngleDegrees: 49.0, ExpectedType: E_SurfaceType.Walkable, Description: "Max walkable" },
|
|
{ AngleDegrees: 51.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Steep slope" },
|
|
{ AngleDegrees: 70.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Very steep" },
|
|
{ AngleDegrees: 84.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Max steep" },
|
|
{ AngleDegrees: 90.0, ExpectedType: E_SurfaceType.Wall, Description: "Vertical wall" },
|
|
{ AngleDegrees: 94.0, ExpectedType: E_SurfaceType.Wall, Description: "Max wall" },
|
|
{ AngleDegrees: 120.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Overhang" },
|
|
{ AngleDegrees: 180.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Ceiling" }
|
|
];
|
|
|
|
for (let i = 0; i < TestCases.length; i++) {
|
|
const testCase = TestCases[i];
|
|
const normal = this.GenerateNormalFromAngle(testCase.AngleDegrees);
|
|
const result = this.ClassifySurface(normal, this.AngleThresholdsRads);
|
|
|
|
if (result === testCase.ExpectedType) {
|
|
Print(`✅ Test ${i+1} PASS: ${testCase.Description} (${testCase.AngleDegrees}°) = ${testCase.ExpectedType}`);
|
|
} else {
|
|
Print(`❌ Test ${i+1} FAIL: ${testCase.Description} (${testCase.AngleDegrees}°) expected ${testCase.ExpectedType}, got ${result}`);
|
|
AllTestsPassed = false;
|
|
}
|
|
}
|
|
|
|
return AllTestsPassed;
|
|
}
|
|
|
|
// Category: "System"
|
|
/**
|
|
* Initialize movement system with angle conversion and testing
|
|
* Converts degree thresholds to radians for runtime performance
|
|
* Runs automated tests if debug mode enabled
|
|
*/
|
|
InitializeMovementSystem() {
|
|
this.IsInitialized = true;
|
|
|
|
this.AngleThresholdsRads = {
|
|
Walkable: D2R(this.AngleThresholdsDegrees.Walkable),
|
|
SteepSlope: D2R(this.AngleThresholdsDegrees.SteepSlope),
|
|
Wall: D2R(this.AngleThresholdsDegrees.Wall),
|
|
};
|
|
|
|
if (this.ShowDebugInfo) {
|
|
this.PrintDebugInfo();
|
|
this.TestSurfaceClassification();
|
|
}
|
|
}
|
|
}
|