tengri/Content/Camera/Components/AC_Camera.ts

324 lines
10 KiB
TypeScript

// Camera/Components/AC_Camera.ts
import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
import { ActorComponent } from '#root/UE/ActorComponent.ts';
import type { Float } from '#root/UE/Float.ts';
import { MathLibrary } from '#root/UE/MathLibrary.ts';
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import { Vector } from '#root/UE/Vector.ts';
/**
* Camera System Component
* Deterministic camera control with smooth rotation and device-aware sensitivity
* Provides precise control over camera behavior for consistent experience
*/
export class AC_Camera extends ActorComponent {
// ════════════════════════════════════════════════════════════════════════════════════════
// FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Process look input and update camera rotation
* @param InputDelta - Input delta (X = Yaw, Y = Pitch)
* @param DeltaTime - Time since last frame
* @category Input Processing
*/
public ProcessLookInput(InputDelta: Vector, DeltaTime: Float): void {
if (this.IsInitialized) {
const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0;
let sensitivity: Float = 0;
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
sensitivity = this.InputDeviceComponent.IsGamepad()
? this.GamepadSensitivity
: this.MouseSensitivity;
} else {
sensitivity = this.MouseSensitivity;
}
const CalculateTargetPitch = (
targetPitch: Float,
inputDeltaY: Float,
deltaTime: Float
): Float =>
targetPitch - inputDeltaY * sensitivity * invertMultiplier * deltaTime;
const CalculateTargetYaw = (
targetYaw: Float,
inputDeltaX: Float,
deltaTime: Float
): Float => targetYaw + inputDeltaX * sensitivity * deltaTime;
this.TargetPitch = MathLibrary.ClampFloat(
CalculateTargetPitch(this.TargetPitch, InputDelta.Y, DeltaTime),
this.PitchMin,
this.PitchMax
);
this.TargetYaw = CalculateTargetYaw(
this.TargetYaw,
InputDelta.X,
DeltaTime
);
this.InputMagnitude = MathLibrary.VectorLength(InputDelta);
}
}
/**
* Update camera rotation with smooth interpolation
* @param DeltaTime - Time since last frame
* @category Camera Updates
*/
public UpdateCameraRotation(DeltaTime: Float): void {
if (this.IsInitialized) {
if (this.SmoothingSpeed > 0) {
// Smooth interpolation to target rotation
this.CurrentPitch = MathLibrary.FInterpTo(
this.CurrentPitch,
this.TargetPitch,
DeltaTime,
this.SmoothingSpeed
);
this.CurrentYaw = MathLibrary.FInterpTo(
this.CurrentYaw,
this.TargetYaw,
DeltaTime,
this.SmoothingSpeed
);
} else {
// Instant rotation (no smoothing)
this.CurrentPitch = this.TargetPitch;
this.CurrentYaw = this.TargetYaw;
}
}
}
/**
* Get current camera rotation for applying to SpringArm
* @returns Current camera rotation values
* @category Public Interface
* @pure true
*/
public GetCameraRotation(): { Pitch: Float; Yaw: Float } {
return {
Pitch: this.CurrentPitch,
Yaw: this.CurrentYaw,
};
}
/**
* Check if camera is currently rotating
* @returns True if there's active rotation input
* @category State Queries
* @pure true
*/
public IsCameraRotating(): boolean {
return this.InputMagnitude > 0.01;
}
/**
* Initialize camera system with default settings
* @category System Setup
*/
public InitializeCameraSystem(
InputDeviceRef: AC_InputDevice,
DebugComponentRef: AC_DebugHUD
): void {
this.InputDeviceComponent = InputDeviceRef;
this.DebugHUDComponent = DebugComponentRef;
this.IsInitialized = true;
if (SystemLibrary.IsValid(this.DebugHUDComponent)) {
this.DebugHUDComponent.AddDebugPage(
this.DebugPageID,
'Camera System',
60
);
}
}
/**
* Update debug HUD with current camera info
* @category Debug
*/
public UpdateDebugPage(): void {
if (SystemLibrary.IsValid(this.DebugHUDComponent)) {
if (
this.DebugHUDComponent.ShouldUpdatePage(
this.DebugPageID,
SystemLibrary.GetGameTimeInSeconds()
)
) {
this.DebugHUDComponent.UpdatePageContent(
this.DebugPageID,
`Current Device: ${SystemLibrary.IsValid(this.InputDeviceComponent) ? this.InputDeviceComponent.GetCurrentInputDevice() : 'Input Device Component Not Found'}\n` +
`Sensitivity: ${SystemLibrary.IsValid(this.InputDeviceComponent) && this.InputDeviceComponent.IsGamepad() ? this.GamepadSensitivity : this.MouseSensitivity}\n` +
`Pitch: ${this.GetCameraRotation().Pitch}°\n` +
`Yaw: ${this.GetCameraRotation().Yaw}°\n` +
`Is Rotating: ${this.IsCameraRotating() ? 'Yes' : 'No'}\n` +
`Smoothing: ${this.SmoothingSpeed}\n` +
`Invert Y: ${this.InvertYAxis ? 'Yes' : 'No'}`
);
}
}
}
/**
* Get camera configuration and state data for testing purposes
* Provides read-only access to private variables for automated tests
* Only includes essential data needed for test validation
* @category Debug
* @returns Object containing camera settings (sensitivity, pitch limits) for test assertions
*/
public GetTestData(): {
MouseSensitivity: Float;
GamepadSensitivity: Float;
PitchMin: Float;
PitchMax: Float;
} {
return {
MouseSensitivity: this.MouseSensitivity,
GamepadSensitivity: this.GamepadSensitivity,
PitchMin: this.PitchMin,
PitchMax: this.PitchMax,
};
}
// ════════════════════════════════════════════════════════════════════════════════════════
// VARIABLES
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Mouse sensitivity multiplier for camera rotation
* Higher values result in faster camera movement with mouse input
* Typical range: 50.0 (slow) - 200.0 (fast)
* @category Camera Config
* @instanceEditable true
* @default 100.0
*/
private readonly MouseSensitivity: Float = 100.0;
/**
* Gamepad sensitivity multiplier for camera rotation
* Higher than mouse sensitivity to compensate for analog stick precision
* Typical range: 100.0 (slow) - 300.0 (fast)
* @category Camera Config
* @instanceEditable true
* @default 150.0
*/
private readonly GamepadSensitivity: Float = 150.0;
/**
* Invert vertical axis for camera rotation
* When true, pushing up on input rotates camera down and vice versa
* Common preference for flight-sim style controls
* @category Camera Config
* @instanceEditable true
* @default false
*/
private readonly InvertYAxis: boolean = false;
/**
* Minimum pitch angle in degrees (looking down)
* Prevents camera from rotating beyond this angle
* Set to -89° to avoid gimbal lock at -90°
* @category Camera Config
* @instanceEditable true
* @default -89.0
*/
private readonly PitchMin: Float = -89.0;
/**
* Maximum pitch angle in degrees (looking up)
* Prevents camera from rotating beyond this angle
* Set to +89° to avoid gimbal lock at +90°
* @category Camera Config
* @instanceEditable true
* @default 89.0
*/
private readonly PitchMax: Float = 89.0;
/**
* Speed of smooth interpolation to target rotation
* Higher values make camera more responsive but less smooth
* Set to 0 for instant rotation without interpolation
* Typical range: 10.0 (smooth) - 30.0 (responsive)
* @category Camera Config
* @instanceEditable true
* @default 20.0
*/
private readonly SmoothingSpeed: Float = 20.0;
/**
* Current pitch angle for rendering
* Smoothly interpolates towards TargetPitch based on SmoothingSpeed
* Updated every frame by UpdateCameraRotation()
* @category Camera State
*/
private CurrentPitch: Float = 0;
/**
* Current yaw angle for rendering
* Smoothly interpolates towards TargetYaw based on SmoothingSpeed
* Updated every frame by UpdateCameraRotation()
* @category Camera State
*/
private CurrentYaw: Float = 0;
/**
* Target pitch angle from player input
* Updated by ProcessLookInput() based on input delta
* Clamped to PitchMin/PitchMax range
* @category Camera State
*/
private TargetPitch: Float = 0;
/**
* Target yaw angle from player input
* Updated by ProcessLookInput() based on input delta
* No clamping - can rotate freely beyond 360°
* @category Camera State
*/
private TargetYaw: Float = 0;
/**
* Magnitude of current input vector
* Used by IsCameraRotating() to detect active camera input
* Cached to avoid recalculating VectorLength every frame
* @category Camera State
* @default 0.0
*/
private InputMagnitude: Float = 0;
/**
* System initialization state flag
* @category Camera State
*/
private IsInitialized: boolean = false;
/**
* Reference to input device component for device detection
* Set externally, used for sensitivity adjustments
* @category Components
*/
public InputDeviceComponent: AC_InputDevice | null = null;
/**
* Reference to debug HUD component for displaying camera info
* Optional, used for debugging purposes
* @category Components
*/
public DebugHUDComponent: AC_DebugHUD | null = null;
/**
* Debug page identifier for organizing debug output
* Used by debug HUD to categorize information
* @category Debug
*/
public readonly DebugPageID: string = 'CameraInfo';
}