Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
6.3 kB · 163 lines
TypeScript
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164import { pubsub } from "@thorium/.server/init/pubsub";import { clearAutopilotState } from "@thorium/utils/.server/ship/clearAutopilotState";import { getShipSystem } from "@thorium/utils/.server/ship/getShipSystem";import { type Entity, System } from "@thorium/utils/ecs";import { autopilotGetCoordinates } from "@thorium/utils/starmap/autopilotGetCoordinates";import { KM_TO_LY, lightYearToLightMinute } from "@thorium/utils/unitTypes";import { Quaternion, Vector3, Matrix4 } from "three";
const rotationQuat = new Quaternion();const desiredRotationQuat = new Quaternion();const up = new Vector3(0, 1, 0);const forward = new Vector3();const toWaypoint = new Vector3();const matrix = new Matrix4();const rotationMatrix = new Matrix4().makeRotationY(-Math.PI);
/* * How auto-rotation works: * - Bail if there is no destination, if the autopilot is turned off, * if the angle to the destination is 0, or if there is no desired rotation. * - Calculate the max angular acceleration and velocity the * thrusters can provide. * - Use a PID controller where the error input is the angle to the * destination and the output is the angular acceleration. * - Apply the PID output to the current angular velocity value. * - Use the Quaternion.rotateToward function with the angular * velocity value */
export class AutoRotateSystem extends System { static flightMode = ["nova"]; test(entity: Entity) { return !!( entity.components.isShip && entity.components.rotation && entity.components.autopilot ); }
update(entity: Entity, elapsed: number) { const { position, rotation, autopilot } = entity.components;
const thrusters = getShipSystem(this.ecs, { systemType: "thrusters", shipId: entity.id, }); if (!thrusters?.components?.isThrusters) return;
if ( !position || !rotation || !autopilot?.rotationAutopilot || (!autopilot?.desiredCoordinates && !autopilot.desiredRotation) ) { return; }
// Get the current system the ship is in and the autopilot desired system const entitySystem = entity.components.position?.parentId ? this.ecs.getEntityById(entity.components.position.parentId) : null; const destinationSystem = entity.components.autopilot?.desiredSolarSystemId ? this.ecs.getEntityById(entity.components.autopilot.desiredSolarSystemId) : null;
const { nextDestination, positionVec, isInInterstellar } = autopilotGetCoordinates( entity, entitySystem, destinationSystem, ); desiredRotationQuat.identity();
if (autopilot.desiredCoordinates) { let doneWithPath = false;
// Use the ship's current speed to determine when to advance to the // next waypoint, matching AutoThrustSystem's approach. This prevents // chaotic rotation at warp when the ship overshoots closely-spaced // intermediate waypoints. const currentSpeed = entity.components.velocity?.forwardVelocity || 0; const distanceToNext = positionVec.distanceTo(nextDestination); const distanceToNextInKM = distanceToNext * (isInInterstellar ? 1 / lightYearToLightMinute(KM_TO_LY) : 1); const distanceThreshold = currentSpeed * 2;
// Check if the ship has passed the waypoint by testing if it's behind // the ship's forward direction. This handles high-speed travel (e.g. warp) // where the ship can overshoot waypoints between frames. rotationQuat.set(rotation.x, rotation.y, rotation.z, rotation.w); forward.set(0, 0, 1).applyQuaternion(rotationQuat); toWaypoint.copy(nextDestination).sub(positionVec); const waypointIsBehind = distanceToNext > 0.001 && toWaypoint.dot(forward) < 0;
if (distanceToNextInKM < Math.max(1, distanceThreshold) || waypointIsBehind) { // Check if we're already at the final leg of the journey (no intermediate // waypoints left). If nextCoordinates was null and path is empty, // autopilotGetCoordinates used desiredDestination as nextDestination. const wasAtFinalLeg = !autopilot.nextCoordinates && autopilot.path.length === 0;
autopilot.nextCoordinates = autopilot.path.shift()!; if (!autopilot.nextCoordinates) { if (wasAtFinalLeg && !autopilot.forwardAutopilot && waypointIsBehind) { // Ship has blown past the final destination while using manual // engine controls. Deactivate autopilot and unlock the course, // similar to arriving normally via WaypointRemoveSystem. const waypointId = autopilot.destinationWaypointId; clearAutopilotState(entity); if (typeof waypointId === "number") { const waypoint = this.ecs.getEntityById(waypointId); waypoint?.updateComponent("isWaypoint", { isActive: false }); } pubsub.publish.pilot.autopilot.get({ shipId: entity.id }); pubsub.publish.waypoints.all({ shipId: entity.id }); return; } if (autopilot.desiredRotation) { doneWithPath = true; desiredRotationQuat.set( autopilot.desiredRotation.x, autopilot.desiredRotation.y, autopilot.desiredRotation.z, autopilot.desiredRotation.w, ); } // Otherwise, fall through — nextDestination is already set to // desiredDestination by autopilotGetCoordinates, so the lookAt // will smoothly point the ship toward the final destination. } else { nextDestination.set( autopilot.nextCoordinates.x, autopilot.nextCoordinates.y, autopilot.nextCoordinates.z, ); } } if (!doneWithPath) { rotationQuat.set(rotation.x, rotation.y, rotation.z, rotation.w); up.set(0, 1, 0).applyQuaternion(rotationQuat);
matrix.lookAt(positionVec, nextDestination, up).multiply(rotationMatrix); // Use the thrusters to adjust the rotation of the ship to point towards the desired destination. // First, determine the angle to the destination. desiredRotationQuat.setFromRotationMatrix(matrix); } } else if (autopilot.desiredRotation) { desiredRotationQuat.set( autopilot.desiredRotation.x, autopilot.desiredRotation.y, autopilot.desiredRotation.z, autopilot.desiredRotation.w, ); }
// Apply the rotation rotationQuat.slerp(desiredRotationQuat, elapsed / 1000); entity.updateComponent("rotation", { x: rotationQuat.x, y: rotationQuat.y, z: rotationQuat.z, w: rotationQuat.w, }); }}