diff --git a/Assets/PlayerControls.inputactions b/Assets/PlayerControls.inputactions index 3c0855a..ce85483 100644 --- a/Assets/PlayerControls.inputactions +++ b/Assets/PlayerControls.inputactions @@ -10,7 +10,7 @@ "name": "Move", "type": "Value", "id": "701d83ea-56bd-4dbb-86c8-ba6b2fea3af1", - "expectedControlType": "Vector2", + "expectedControlType": "Vector3", "processors": "", "interactions": "", "initialStateCheck": true @@ -49,7 +49,7 @@ { "name": "WASD", "id": "51492236-360c-4bbe-b388-38d0353c387a", - "path": "2DVector", + "path": "3DVector", "interactions": "", "processors": "", "groups": "", @@ -58,7 +58,7 @@ "isPartOfComposite": false }, { - "name": "up", + "name": "Up", "id": "0293c1d3-b1a3-454e-bc68-fc94cca3048b", "path": "/w", "interactions": "", @@ -69,7 +69,7 @@ "isPartOfComposite": true }, { - "name": "down", + "name": "Down", "id": "75b35661-ce4e-4133-afe7-636d5a48e463", "path": "/s", "interactions": "", @@ -80,7 +80,7 @@ "isPartOfComposite": true }, { - "name": "left", + "name": "Left", "id": "7b679c97-20c2-45d4-8480-6e333d9fb12a", "path": "/a", "interactions": "", @@ -91,7 +91,7 @@ "isPartOfComposite": true }, { - "name": "right", + "name": "Right", "id": "7fed79ba-720c-4f6d-a48d-e0f990cb4f34", "path": "/d", "interactions": "", @@ -101,6 +101,28 @@ "isComposite": false, "isPartOfComposite": true }, + { + "name": "Forward", + "id": "8f905a6e-4d1c-41dd-a38c-c0ac7eb2baa6", + "path": "/q", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Backward", + "id": "bbe2daf5-32ea-448d-b8a3-a48f5aacd1be", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, { "name": "", "id": "7136f4c4-0885-41a2-8ae2-4386402979ee", diff --git a/Assets/Scenes/Main.unity b/Assets/Scenes/Main.unity index 0e86036..52bfc6c 100644 --- a/Assets/Scenes/Main.unity +++ b/Assets/Scenes/Main.unity @@ -1412,8 +1412,11 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: '::' throttleAction: {fileID: -5533345291228272259, guid: c56ea0479fd0f296da411bf82c2c21a0, type: 3} + attitudeAction: {fileID: 7066772054865228084, guid: c56ea0479fd0f296da411bf82c2c21a0, type: 3} rampSpeed: 1 + attitudeResponseSpeed: 1 mainEngine: {fileID: 1437557825} + gimbalSystem: {fileID: 1437557827} throttleDisplay: {fileID: 348618513} --- !u!114 &986986473 MonoBehaviour: @@ -1607,10 +1610,15 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: '::' mainEngine: {fileID: 1437557825} + gimbalSystem: {fileID: 1437557827} rb: {fileID: 1172921199} altimeter: {fileID: 894314916} physicsTransitionAltitude: 1000 useUnityPhysics: 1 + isFrozen: 1 + showThrustArrow: 1 + thrustArrowScale: 0.00001 + thrustArrowMaxLength: 500 --- !u!54 &1172921199 Rigidbody: m_ObjectHideFlags: 0 @@ -1902,6 +1910,19 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a1205446b72eb1daeb13064fb0454b99, type: 3} m_Name: m_EditorClassIdentifier: '::' + mainEngine: {fileID: 1437557825} + rocketPhysics: {fileID: 1172921198} + rb: {fileID: 1172921199} + maxGimbalAngle: 10 + gimbalResponseRate: 30 + pitchInput: 0 + yawInput: 0 + rollInput: 0 + currentPitchAngle: 0 + currentYawAngle: 0 + appliedTorque: {x: 0, y: 0, z: 0} + gimbalLeverArm: 5 + showDebugInfo: 0 --- !u!4 &1437557828 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/GimbalSystem.cs b/Assets/Scripts/GimbalSystem.cs index 5bab546..39b4444 100644 --- a/Assets/Scripts/GimbalSystem.cs +++ b/Assets/Scripts/GimbalSystem.cs @@ -2,15 +2,130 @@ using UnityEngine; public class GimbalSystem : MonoBehaviour { - // Start is called once before the first execution of Update after the MonoBehaviour is created - void Start() + [Header("References")] + public MainEngine mainEngine; + public RocketPhysics rocketPhysics; + public Rigidbody rb; + + [Header("Gimbal Settings")] + [Range(0f, 20f)] + public float maxGimbalAngle = 10f; // degrees - maximum deflection angle + public float gimbalResponseRate = 30f; // degrees per second - how fast gimbal can move + + [Header("Input")] + public float pitchInput = 0f; // -1 to 1 (negative = pitch down, positive = pitch up) + public float yawInput = 0f; // -1 to 1 (negative = yaw left, positive = yaw right) + public float rollInput = 0f; // -1 to 1 (optional roll control) + + [Header("Current Gimbal State")] + public float currentPitchAngle = 0f; // degrees + public float currentYawAngle = 0f; // degrees + public Vector3 appliedTorque = Vector3.zero; // N⋅m + + [Header("Gimbal Physics")] + public float gimbalLeverArm = 5f; // meters - distance from CoM to thrust vector application point + + [Header("Debug")] + public bool showDebugInfo = false; + + void FixedUpdate() { - + UpdateGimbalAngles(); + CalculateGimbalTorque(); } - // Update is called once per frame - void Update() + /// + /// Updates the current gimbal angles based on input, respecting limits and response rates + /// + private void UpdateGimbalAngles() { - + // Calculate target angles from input + float targetPitchAngle = pitchInput * maxGimbalAngle; + float targetYawAngle = yawInput * maxGimbalAngle; + + // Smoothly move toward target angles at the response rate + float maxDelta = gimbalResponseRate * Time.fixedDeltaTime; + currentPitchAngle = Mathf.MoveTowards(currentPitchAngle, targetPitchAngle, maxDelta); + currentYawAngle = Mathf.MoveTowards(currentYawAngle, targetYawAngle, maxDelta); + + // Enforce gimbal limits (safety clamp) + currentPitchAngle = Mathf.Clamp(currentPitchAngle, -maxGimbalAngle, maxGimbalAngle); + currentYawAngle = Mathf.Clamp(currentYawAngle, -maxGimbalAngle, maxGimbalAngle); + } + + /// + /// Calculates torque based on gimbal angles and engine thrust (does not apply it) + /// RocketPhysics will read and apply this torque + /// + private void CalculateGimbalTorque() + { + // Only calculate torque if engine is producing thrust + if (mainEngine == null || !mainEngine.engineIgnited || mainEngine.thrust <= 0f) + { + appliedTorque = Vector3.zero; + return; + } + + // Convert gimbal angles to radians for calculation + float pitchRad = currentPitchAngle * Mathf.Deg2Rad; + float yawRad = currentYawAngle * Mathf.Deg2Rad; + + // Calculate perpendicular force components from gimbal deflection + // For small angles: F_perp ≈ F_thrust × sin(angle) ≈ F_thrust × angle_rad + // Using full sin for accuracy at larger gimbal angles + float pitchForce = mainEngine.thrust * 1000f * Mathf.Sin(pitchRad); // Convert kN to N + float yawForce = mainEngine.thrust * 1000f * Mathf.Sin(yawRad); + + // Calculate torque: τ = F × r (cross product, but simplified for perpendicular forces) + // Pitch gimbal creates torque around the local right axis (X) + // Yaw gimbal creates torque around the local forward axis (Z) + // Torque directions: positive pitch input → nose up, positive yaw input → nose right + Vector3 localTorque = new Vector3( + pitchForce * gimbalLeverArm, // Pitch torque around X axis + 0f, // No direct Y torque from gimbal + -yawForce * gimbalLeverArm // Yaw torque around Z axis (negative for correct direction) + ); + + // Convert local torque to world space + appliedTorque = rb.rotation * localTorque; + + if (showDebugInfo) + { + Debug.Log($"[GimbalSystem] Pitch: {currentPitchAngle:F2}°, Yaw: {currentYawAngle:F2}°, Torque: {appliedTorque.magnitude:F2} N⋅m"); + } + } + + /// + /// Sets the pitch input (-1 to 1) + /// + public void SetPitchInput(float value) + { + pitchInput = Mathf.Clamp(value, -1f, 1f); + } + + /// + /// Sets the yaw input (-1 to 1) + /// + public void SetYawInput(float value) + { + yawInput = Mathf.Clamp(value, -1f, 1f); + } + + /// + /// Sets the roll input (-1 to 1) - for future use with RCS or other control surfaces + /// + public void SetRollInput(float value) + { + rollInput = Mathf.Clamp(value, -1f, 1f); + } + + /// + /// Resets gimbal to neutral position + /// + public void ResetGimbal() + { + pitchInput = 0f; + yawInput = 0f; + rollInput = 0f; } } diff --git a/Assets/Scripts/InstrumentManager.cs b/Assets/Scripts/InstrumentManager.cs index 208b41e..47a80f9 100644 --- a/Assets/Scripts/InstrumentManager.cs +++ b/Assets/Scripts/InstrumentManager.cs @@ -6,12 +6,17 @@ public class InstrumentManager : MonoBehaviour { [Header("Input")] public InputActionReference throttleAction; // Your Input Action reference + public InputActionReference attitudeAction; // Vector3 input for pitch/yaw/roll control [Header("Throttle Settings")] public float rampSpeed = 1f; // Units per second + [Header("Attitude Control Settings")] + public float attitudeResponseSpeed = 1f; // Multiplier for attitude input sensitivity + [Header("References")] public MainEngine mainEngine; // Reference to your engine script + public GimbalSystem gimbalSystem; // Reference to gimbal control system // Referance to TextMeshPro Throttle slider public Slider throttleDisplay; // Reference to UI Slider component @@ -26,16 +31,30 @@ public class InstrumentManager : MonoBehaviour { Debug.LogWarning("Throttle action reference is not set!"); } + + if (attitudeAction != null) + { + attitudeAction.action.Enable(); + Debug.Log("Attitude action enabled."); + } + else + { + Debug.LogWarning("Attitude action reference is not set!"); + } } void OnDisable() { if (throttleAction != null) throttleAction.action.Disable(); + + if (attitudeAction != null) + attitudeAction.action.Disable(); } void Update() { + // Handle throttle input if (throttleAction == null || mainEngine == null) return; @@ -59,5 +78,23 @@ public class InstrumentManager : MonoBehaviour { throttleDisplay.value = mainEngine.throttleInput; } + + // Handle attitude control input + if (attitudeAction != null && gimbalSystem != null) + { + // Read Vector3 input + Vector3 attitudeInput = attitudeAction.action.ReadValue(); + + // Apply attitude response speed multiplier + attitudeInput *= attitudeResponseSpeed; + + // Map inputs to gimbal system: + // Y (up/down) → Pitch + // X (left/right) → Yaw + // Z (forward/backward) → Roll + gimbalSystem.SetPitchInput(attitudeInput.y); + gimbalSystem.SetYawInput(attitudeInput.x); + gimbalSystem.SetRollInput(attitudeInput.z); + } } } diff --git a/Assets/Scripts/MainEngine.cs b/Assets/Scripts/MainEngine.cs index 7e25aac..bed9578 100644 --- a/Assets/Scripts/MainEngine.cs +++ b/Assets/Scripts/MainEngine.cs @@ -20,7 +20,7 @@ public class MainEngine : MonoBehaviour public bool engineOnline = false; // true if engine is running public bool engineIgnited = false; // true if ignition completed public bool restartable = true; // if false, engine cannot restart once shut down - public float throttleInput = 0f; // 0-1 input from pilot + [Range(0f, 1f)]public float throttleInput = 0f; // 0-1 input from pilot public float thrust = 0f; // kN public float fuelFlowRate = 0f; // kg/s diff --git a/Assets/Scripts/RocketPhysics.cs b/Assets/Scripts/RocketPhysics.cs index 47ac535..7915e35 100644 --- a/Assets/Scripts/RocketPhysics.cs +++ b/Assets/Scripts/RocketPhysics.cs @@ -5,6 +5,7 @@ public class RocketPhysics : MonoBehaviour { [Header("References")] public MainEngine mainEngine; + public GimbalSystem gimbalSystem; public Rigidbody rb; public Altimeter altimeter; @@ -16,6 +17,12 @@ public class RocketPhysics : MonoBehaviour [Header("Physics Mode Settings")] public float physicsTransitionAltitude = 1000f; // meters - switch to Unity physics below this public bool useUnityPhysics = true; // current physics mode + public bool isFrozen = false; // when true, freezes rocket physics (thrust arrow still works) + + [Header("Visualization")] + public bool showThrustArrow = true; + public float thrustArrowScale = 0.0001f; // scales thrust magnitude for arrow length + public float thrustArrowMaxLength = 100f; // prevents arrow from being too long private Vector3 lastPhysicsPosition; @@ -34,12 +41,13 @@ public class RocketPhysics : MonoBehaviour // Determine which physics mode to use bool shouldUseUnityPhysics = altimeter.altitude < physicsTransitionAltitude; - // Handle mode transition - if (shouldUseUnityPhysics != useUnityPhysics) + // Handle mode transition (only if not frozen) + if (!isFrozen && shouldUseUnityPhysics != useUnityPhysics) { TransitionPhysicsMode(shouldUseUnityPhysics); } + // Always calculate physics, but only apply if not frozen if (useUnityPhysics) { UpdateWithUnityPhysics(); @@ -48,6 +56,12 @@ public class RocketPhysics : MonoBehaviour { UpdateWithDoublePrecision(); } + + // Draw thrust visualization (works even when frozen) + if (showThrustArrow) + { + DrawThrustArrow(); + } } void TransitionPhysicsMode(bool toUnityPhysics) @@ -78,11 +92,34 @@ public class RocketPhysics : MonoBehaviour double gravityAcceleration = altimeter.gh; Vector3 gravityForce = new Vector3(0, (float)(-gravityAcceleration * rb.mass), 0); + // Thrust force (converted from kN to N) Vector3 thrustDirection = rb.rotation * Vector3.up; - Vector3 thrustForce = thrustDirection * mainEngine.thrust; + Vector3 thrustForce = thrustDirection * mainEngine.thrust * 1000f; - rb.AddForce(gravityForce, ForceMode.Force); - rb.AddForce(thrustForce, ForceMode.Force); + if (!isFrozen) + { + // Apply forces only when not frozen + rb.AddForce(gravityForce, ForceMode.Force); + rb.AddForce(thrustForce, ForceMode.Force); + + // Apply gimbal torque if available + if (gimbalSystem != null) + { + rb.AddTorque(gimbalSystem.appliedTorque, ForceMode.Force); + } + } + else + { + // When frozen, manually calculate what velocity would be + Vector3 totalForce = gravityForce + thrustForce; + Vector3 acceleration = totalForce / rb.mass; + Vector3 newVelocity = rb.linearVelocity + acceleration * Time.fixedDeltaTime; + velocity = new DoublePrecisionVector3(newVelocity.x, newVelocity.y, newVelocity.z); + // Position stays the same when frozen + position = new DoublePrecisionVector3(rb.position.x, rb.position.y, rb.position.z); + rotation = new DoublePrecisionVector3(rb.rotation.eulerAngles.x, rb.rotation.eulerAngles.y, rb.rotation.eulerAngles.z); + return; + } // Update double precision tracking from Unity physics position = new DoublePrecisionVector3(rb.position.x, rb.position.y, rb.position.z); @@ -94,7 +131,7 @@ public class RocketPhysics : MonoBehaviour { // Manual double-precision integration - no ground interaction double gravityAcceleration = altimeter.gh; - double thrustAcceleration = mainEngine.thrust / rb.mass; + double thrustAcceleration = (mainEngine.thrust * 1000.0) / rb.mass; // Convert kN to N Vector3 thrustDirection = rb.rotation * Vector3.up; DoublePrecisionVector3 gravityAccelerationVector = new DoublePrecisionVector3(0, -gravityAcceleration, 0); @@ -105,15 +142,110 @@ public class RocketPhysics : MonoBehaviour ); DoublePrecisionVector3 totalAcceleration = gravityAccelerationVector + thrustAccelerationVector; + // Always calculate new velocity and position velocity = velocity + totalAcceleration * Time.fixedDeltaTime; position = position + velocity * Time.fixedDeltaTime; - // Update Unity transform (kinematic mode) - Vector3 newPosition = new Vector3((float)position.x, (float)position.y, (float)position.z); - rb.position = newPosition; - // Do not set linearVelocity for kinematic bodies - // rb.linearVelocity = new Vector3((float)velocity.x, (float)velocity.y, (float)velocity.z); - rb.rotation = Quaternion.Euler((float)rotation.x, (float)rotation.y, (float)rotation.z); + // Only apply to rigidbody if not frozen + if (!isFrozen) + { + // Apply gimbal torque if available + if (gimbalSystem != null && gimbalSystem.appliedTorque.magnitude > 0f) + { + ApplyManualTorque(gimbalSystem.appliedTorque); + } + + // Update Unity transform (kinematic mode) + Vector3 newPosition = new Vector3((float)position.x, (float)position.y, (float)position.z); + rb.position = newPosition; + // Do not set linearVelocity for kinematic bodies + // rb.linearVelocity = new Vector3((float)velocity.x, (float)velocity.y, (float)velocity.z); + rb.rotation = Quaternion.Euler((float)rotation.x, (float)rotation.y, (float)rotation.z); + } + } + + /// + /// Manually applies torque for double-precision physics mode + /// + private void ApplyManualTorque(Vector3 torque) + { + // Get moment of inertia tensor + Vector3 inertiaTensor = rb.inertiaTensor; + if (inertiaTensor == Vector3.zero) + { + // Fallback if inertia tensor not set - use a simplified calculation + float I = rb.mass * 25f; // Simplified inertia estimate + inertiaTensor = new Vector3(I, I, I); + } + + // Calculate angular acceleration: α = τ / I (in local space) + Vector3 localTorque = Quaternion.Inverse(rb.rotation) * torque; + Vector3 angularAcceleration = new Vector3( + inertiaTensor.x > 0 ? localTorque.x / inertiaTensor.x : 0, + inertiaTensor.y > 0 ? localTorque.y / inertiaTensor.y : 0, + inertiaTensor.z > 0 ? localTorque.z / inertiaTensor.z : 0 + ); + + // Integrate angular velocity (simplified Euler integration) + Vector3 angularDisplacement = angularAcceleration * Time.fixedDeltaTime * Time.fixedDeltaTime * Mathf.Rad2Deg; + + // Update rotation + Quaternion deltaRotation = Quaternion.Euler(angularDisplacement); + rb.rotation = rb.rotation * deltaRotation; + + // Update rotation tracking + rotation.x = rb.rotation.eulerAngles.x; + rotation.y = rb.rotation.eulerAngles.y; + rotation.z = rb.rotation.eulerAngles.z; + } + + /// + /// Draws a debug arrow showing the direction and magnitude of thrust + /// + private void DrawThrustArrow() + { + // Base thrust direction + Vector3 thrustDirection = Vector3.up; + + // Apply gimbal deflection if gimbal system is available + if (gimbalSystem != null) + { + // Apply pitch rotation around local X axis (right) + Quaternion pitchRotation = Quaternion.AngleAxis(gimbalSystem.currentPitchAngle, Vector3.right); + thrustDirection = pitchRotation * thrustDirection; + + // Apply yaw rotation around local Z axis (forward) + Quaternion yawRotation = Quaternion.AngleAxis(-gimbalSystem.currentYawAngle, Vector3.forward); + thrustDirection = yawRotation * thrustDirection; + } + + // Transform to world space + thrustDirection = rb.rotation * thrustDirection; + + float thrustMagnitude = mainEngine.thrust * 1000f; // Convert kN to N + + // Scale the arrow length proportionally but cap it + float arrowLength = Mathf.Min(thrustMagnitude * thrustArrowScale, thrustArrowMaxLength); + + if (arrowLength > 0.1f) // Only draw if there's meaningful thrust + { + Vector3 arrowEnd = rb.position + thrustDirection * arrowLength; + + // Draw main arrow line + Debug.DrawLine(rb.position, arrowEnd, Color.green, 0f); + + // Draw arrowhead (two lines forming a V) + Vector3 arrowHeadSize = thrustDirection * (arrowLength * 0.15f); + Vector3 perpendicular1 = Vector3.Cross(thrustDirection, Vector3.up).normalized * (arrowLength * 0.1f); + if (perpendicular1.magnitude < 0.01f) // Handle case where thrust is vertical + { + perpendicular1 = Vector3.Cross(thrustDirection, Vector3.right).normalized * (arrowLength * 0.1f); + } + Vector3 perpendicular2 = Vector3.Cross(thrustDirection, perpendicular1).normalized * (arrowLength * 0.1f); + + Debug.DrawLine(arrowEnd, arrowEnd - arrowHeadSize + perpendicular1, Color.green, 0f); + Debug.DrawLine(arrowEnd, arrowEnd - arrowHeadSize - perpendicular1, Color.green, 0f); + } } } diff --git a/UserSettings/Layouts/default-6000.dwlt b/UserSettings/Layouts/default-6000.dwlt index 39fdc26..7643e8d 100644 --- a/UserSettings/Layouts/default-6000.dwlt +++ b/UserSettings/Layouts/default-6000.dwlt @@ -1263,7 +1263,7 @@ MonoBehaviour: m_Position: m_Target: {x: 0, y: 0, z: 0} speed: 2 - m_Value: {x: 241.54735, y: 149.56917, z: 1.1022915} + m_Value: {x: 0, y: 0, z: 0} m_RenderMode: 0 m_CameraMode: drawMode: 0 @@ -1311,15 +1311,15 @@ MonoBehaviour: m_Rotation: m_Target: {x: -0.01953283, y: 0.97352827, z: -0.091104165, w: -0.20872095} speed: 2 - m_Value: {x: 0, y: 0, z: 0, w: 1} + m_Value: {x: -0.0195328, y: 0.9735268, z: -0.09110402, w: -0.20872062} m_Size: m_Target: 2.4383476 speed: 2 - m_Value: 192.62563 + m_Value: 2.4383476 m_Ortho: m_Target: 0 speed: 2 - m_Value: 1 + m_Value: 0 m_CameraSettings: m_Speed: 1 m_SpeedNormalized: 0.5