import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core"; import { GameConfig } from "./gameConfig"; export interface InputState { leftStick: Vector2; rightStick: Vector2; } export interface ForceApplicationResult { linearMagnitude: number; angularMagnitude: number; } /** * Handles physics force calculations and application for the ship * Reads physics parameters from GameConfig for runtime tuning */ export class ShipPhysics { /** * Apply forces to the ship based on input state * @param inputState - Current input state (stick positions) * @param physicsBody - Physics body to apply forces to * @param transformNode - Transform node for world space calculations * @returns Force magnitudes for audio feedback */ public applyForces( inputState: InputState, physicsBody: PhysicsBody, transformNode: TransformNode ): ForceApplicationResult { if (!physicsBody) { return { linearMagnitude: 0, angularMagnitude: 0 }; } const { leftStick, rightStick } = inputState; // Get physics config const config = GameConfig.getInstance().shipPhysics; // Get current velocities for velocity cap checks const currentLinearVelocity = physicsBody.getLinearVelocity(); const currentAngularVelocity = physicsBody.getAngularVelocity(); const currentSpeed = currentLinearVelocity.length(); let linearMagnitude = 0; let angularMagnitude = 0; // Apply linear force from left stick Y (forward/backward) if (Math.abs(leftStick.y) > 0.1) { linearMagnitude = Math.abs(leftStick.y); // Only apply force if we haven't reached max velocity if (currentSpeed < config.maxLinearVelocity) { // Get local direction (Z-axis for forward/backward thrust) const localDirection = new Vector3(0, 0, -leftStick.y); // Transform to world space const worldDirection = Vector3.TransformNormal( localDirection, transformNode.getWorldMatrix() ); const force = worldDirection.scale(config.linearForceMultiplier); // Calculate thrust point: center of mass + offset (0, 1, 0) in world space const thrustPoint = Vector3.TransformCoordinates( physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)), transformNode.getWorldMatrix() ); physicsBody.applyForce(force, thrustPoint); } } // Calculate rotation magnitude for torque angularMagnitude = Math.abs(rightStick.y) + Math.abs(rightStick.x) + Math.abs(leftStick.x); // Apply angular forces if any stick has significant rotation input if (angularMagnitude > 0.1) { const currentAngularSpeed = currentAngularVelocity.length(); // Only apply torque if we haven't reached max angular velocity if (currentAngularSpeed < config.maxAngularVelocity) { const yaw = -leftStick.x; const pitch = rightStick.y; const roll = rightStick.x; // Create torque in local space, then transform to world space const localTorque = new Vector3(pitch, yaw, roll).scale( config.angularForceMultiplier ); const worldTorque = Vector3.TransformNormal( localTorque, transformNode.getWorldMatrix() ); physicsBody.applyAngularImpulse(worldTorque); } } return { linearMagnitude, angularMagnitude, }; } }