From c237b757e8665c1328b76378a83bca8f1f7ce869 Mon Sep 17 00:00:00 2001 From: Vaclav Elias <4528464+VaclavElias@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:36:18 +0000 Subject: [PATCH 1/3] Fix ShipComponent to manually apply drag instead of using non-existent damping properties --- .../Stride.BepuPhysics/ShipComponent.cs | 384 ++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs new file mode 100644 index 0000000000..6a597fa0cf --- /dev/null +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs @@ -0,0 +1,384 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using BepuPhysics; +using BepuPhysics.Collidables; +using Stride.BepuPhysics.Components; +using Stride.BepuPhysics.Definitions; +using Stride.Core; +using Stride.Core.Mathematics; +using Stride.Engine; +using NRigidPose = BepuPhysics.RigidPose; + +namespace Stride.BepuPhysics; + +/// +/// Component for controlling a ship with atmospheric and space flight modes. +/// Supports 2D flight with vertical thrust, horizontal acceleration, and mode-dependent physics. +/// +[ComponentCategory("Physics - Bepu")] +public class ShipComponent : BodyComponent, ISimulationUpdate +{ + private float _currentSpeed; + private bool _isHovering; + + /// + /// Flight mode of the ship affecting gravity and drag behavior. + /// + public FlightMode Mode { get; set; } = FlightMode.Atmospheric; + + /// + /// Maximum horizontal speed the ship can achieve in units per second. + /// + public float MaxSpeed { get; set; } = 50f; + + /// + /// Rate at which the ship accelerates horizontally in units per second squared. + /// + public float Acceleration { get; set; } = 20f; + + /// + /// Rate at which the ship decelerates when braking in units per second squared. + /// + public float BrakeDeceleration { get; set; } = 40f; + + /// + /// Force applied for vertical thrust (W/S keys) in Newtons. + /// + public float VerticalThrustForce { get; set; } = 100f; + + /// + /// Linear drag coefficient for atmospheric flight. Higher values = more drag. + /// Drag force = LinearDragCoefficient * velocity. + /// Only applied in Atmospheric mode. + /// + public float AtmosphericLinearDrag { get; set; } = 0.5f; + + /// + /// Angular drag coefficient for atmospheric flight. Higher values = more rotational drag. + /// Drag torque = AngularDragCoefficient * angularVelocity. + /// Only applied in Atmospheric mode. + /// + public float AtmosphericAngularDrag { get; set; } = 0.3f; + + /// + /// Linear drag coefficient for space flight. Lower values = less friction. + /// Only applied in Space mode. + /// + public float SpaceLinearDrag { get; set; } = 0.05f; + + /// + /// Angular drag coefficient for space flight. Lower values = less rotational friction. + /// Only applied in Space mode. + /// + public float SpaceAngularDrag { get; set; } = 0.02f; + + /// + /// When hovering in atmospheric mode, the ship will counteract gravity to maintain altitude. + /// + [DataMemberIgnore] + public bool IsHovering + { + get => _isHovering; + set + { + if (_isHovering == value) + return; + _isHovering = value; + UpdateHoverState(); + } + } + + /// + /// Current horizontal speed of the ship (can be negative for reverse). + /// + [DataMemberIgnore] + public float CurrentSpeed + { + get => _currentSpeed; + private set => _currentSpeed = MathUtil.Clamp(value, -MaxSpeed, MaxSpeed); + } + + /// + /// Intended thrust direction: 1 = forward (D), -1 = reverse (A), 0 = no thrust. + /// + [DataMemberIgnore] + public float ThrustDirection { get; set; } + + /// + /// Whether braking (Space) is currently active. + /// + [DataMemberIgnore] + public bool IsBraking { get; set; } + + /// + /// Vertical thrust input: 1 = up (W), -1 = down (S), 0 = none. + /// + [DataMemberIgnore] + public float VerticalThrust { get; set; } + + public ShipComponent() + { + InterpolationMode = InterpolationMode.Interpolated; + } + + /// + protected override void AttachInner(NRigidPose pose, BodyInertia shapeInertia, TypedIndex shapeIndex) + { + base.AttachInner(pose, shapeInertia, shapeIndex); + UpdateFlightMode(); + } + + /// + /// Applies forward thrust (D key). + /// + public void ThrustForward() + { + ThrustDirection = 1f; + IsBraking = false; + } + + /// + /// Applies reverse thrust (A key). + /// + public void ThrustReverse() + { + ThrustDirection = -1f; + IsBraking = false; + } + + /// + /// Stops horizontal thrust input. + /// + public void StopThrust() + { + ThrustDirection = 0f; + } + + /// + /// Activates braking (Space key). + /// + public void Brake() + { + IsBraking = true; + ThrustDirection = 0f; + } + + /// + /// Releases brake. + /// + public void ReleaseBrake() + { + IsBraking = false; + } + + /// + /// Applies upward thrust (W key). + /// + public void ThrustUp() + { + VerticalThrust = 1f; + } + + /// + /// Applies downward thrust (S key). + /// + public void ThrustDown() + { + VerticalThrust = -1f; + } + + /// + /// Stops vertical thrust input. + /// + public void StopVerticalThrust() + { + VerticalThrust = 0f; + } + + /// + /// Switches between Atmospheric and Space flight modes. + /// + public void ToggleFlightMode() + { + Mode = Mode == FlightMode.Atmospheric ? FlightMode.Space : FlightMode.Atmospheric; + UpdateFlightMode(); + } + + /// + /// Called before physics simulation tick. + /// + public virtual void SimulationUpdate(BepuSimulation sim, float simTimeStep) + { + Awake = true; // Keep ship active + + // Handle horizontal acceleration/deceleration + if (IsBraking) + { + ApplyBraking(simTimeStep); + } + else if (ThrustDirection != 0f) + { + CurrentSpeed += ThrustDirection * Acceleration * simTimeStep; + } + else + { + // Natural deceleration when no input + ApplyNaturalDeceleration(simTimeStep); + } + + // Apply horizontal velocity (assuming X is horizontal in 2D) + var velocity = LinearVelocity; + velocity.X = CurrentSpeed; + + // Handle vertical thrust + if (Mode == FlightMode.Atmospheric) + { + if (VerticalThrust != 0f) + { + // Active vertical thrust + ApplyLinearImpulse(new Vector3(0, VerticalThrust * VerticalThrustForce * simTimeStep, 0)); + IsHovering = false; + } + else if (IsHovering) + { + // Hover mode: counteract gravity + var gravity = Simulation!.PoseGravity; + var gravityMagnitude = gravity.Length(); + if (gravityMagnitude > 0f) + { + // Apply upward force equal to gravity + var mass = 1f / BodyInertia.InverseMass; + var counterForce = -Vector3.Normalize(gravity) * gravityMagnitude * mass; + ApplyLinearImpulse(counterForce * simTimeStep); + } + } + } + else // Space mode + { + if (VerticalThrust != 0f) + { + ApplyLinearImpulse(new Vector3(0, VerticalThrust * VerticalThrustForce * simTimeStep, 0)); + } + } + + // Apply drag to both linear and angular velocities + ApplyDrag(simTimeStep); + + LinearVelocity = velocity; + } + + /// + /// Called after physics simulation tick. + /// + public virtual void AfterSimulationUpdate(BepuSimulation sim, float simTimeStep) + { + // Update current speed from actual velocity + _currentSpeed = LinearVelocity.X; + } + + private void ApplyBraking(float deltaTime) + { + if (MathF.Abs(CurrentSpeed) < 0.01f) + { + CurrentSpeed = 0f; + return; + } + + var brakeAmount = BrakeDeceleration * deltaTime; + if (CurrentSpeed > 0f) + CurrentSpeed = MathF.Max(0f, CurrentSpeed - brakeAmount); + else + CurrentSpeed = MathF.Min(0f, CurrentSpeed + brakeAmount); + } + + private void ApplyNaturalDeceleration(float deltaTime) + { + // Slower natural deceleration compared to active braking + var linearDrag = Mode == FlightMode.Atmospheric ? AtmosphericLinearDrag : SpaceLinearDrag; + var deceleration = CurrentSpeed * linearDrag * deltaTime; + + if (MathF.Abs(CurrentSpeed) < 0.01f) + { + CurrentSpeed = 0f; + return; + } + + if (CurrentSpeed > 0f) + CurrentSpeed = MathF.Max(0f, CurrentSpeed - deceleration); + else + CurrentSpeed = MathF.Min(0f, CurrentSpeed + deceleration); + } + + private void ApplyDrag(float deltaTime) + { + // Get drag coefficients based on flight mode + var linearDrag = Mode == FlightMode.Atmospheric ? AtmosphericLinearDrag : SpaceLinearDrag; + var angularDrag = Mode == FlightMode.Atmospheric ? AtmosphericAngularDrag : SpaceAngularDrag; + + // Apply linear drag: F_drag = -coefficient * velocity + // Using exponential decay: v_new = v_old * (1 - drag * dt) + // This is more stable than force-based approach + var linearVelocity = LinearVelocity; + var dragFactor = 1f - MathUtil.Clamp(linearDrag * deltaTime, 0f, 1f); + + // Apply drag to Y (vertical) velocity only, X is controlled separately + linearVelocity.Y *= dragFactor; + linearVelocity.Z *= dragFactor; // Apply to Z for consistency + + LinearVelocity = linearVelocity; + + // Apply angular drag: T_drag = -coefficient * angularVelocity + var angularVelocity = AngularVelocity; + var angularDragFactor = 1f - MathUtil.Clamp(angularDrag * deltaTime, 0f, 1f); + angularVelocity *= angularDragFactor; + + AngularVelocity = angularVelocity; + } + + private void UpdateFlightMode() + { + if (Simulation == null) + return; + + switch (Mode) + { + case FlightMode.Atmospheric: + Gravity = true; + // Drag is now applied manually in ApplyDrag() using AtmosphericLinearDrag and AtmosphericAngularDrag + break; + + case FlightMode.Space: + Gravity = false; + // Drag is now applied manually in ApplyDrag() using SpaceLinearDrag and SpaceAngularDrag + IsHovering = false; // No hovering in space + break; + } + } + + private void UpdateHoverState() + { + if (Mode == FlightMode.Space) + { + _isHovering = false; // Cannot hover in space + } + } +} + +/// +/// Flight mode affecting physics behavior. +/// +public enum FlightMode +{ + /// + /// Atmospheric flight with gravity and higher drag. + /// Supports hovering to maintain altitude. + /// + Atmospheric, + + /// + /// Space flight with no gravity and minimal drag. + /// True Newtonian physics. + /// + Space +} From a31ea829bc77ef7838ad5fb6d4be47ca17392cd5 Mon Sep 17 00:00:00 2001 From: Vaclav Elias <4528464+VaclavElias@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:30:53 +0000 Subject: [PATCH 2/3] Delete sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs (#3008) --- .../Stride.BepuPhysics/ShipComponent.cs | 384 ------------------ 1 file changed, 384 deletions(-) delete mode 100644 sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs deleted file mode 100644 index 6a597fa0cf..0000000000 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/ShipComponent.cs +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using BepuPhysics; -using BepuPhysics.Collidables; -using Stride.BepuPhysics.Components; -using Stride.BepuPhysics.Definitions; -using Stride.Core; -using Stride.Core.Mathematics; -using Stride.Engine; -using NRigidPose = BepuPhysics.RigidPose; - -namespace Stride.BepuPhysics; - -/// -/// Component for controlling a ship with atmospheric and space flight modes. -/// Supports 2D flight with vertical thrust, horizontal acceleration, and mode-dependent physics. -/// -[ComponentCategory("Physics - Bepu")] -public class ShipComponent : BodyComponent, ISimulationUpdate -{ - private float _currentSpeed; - private bool _isHovering; - - /// - /// Flight mode of the ship affecting gravity and drag behavior. - /// - public FlightMode Mode { get; set; } = FlightMode.Atmospheric; - - /// - /// Maximum horizontal speed the ship can achieve in units per second. - /// - public float MaxSpeed { get; set; } = 50f; - - /// - /// Rate at which the ship accelerates horizontally in units per second squared. - /// - public float Acceleration { get; set; } = 20f; - - /// - /// Rate at which the ship decelerates when braking in units per second squared. - /// - public float BrakeDeceleration { get; set; } = 40f; - - /// - /// Force applied for vertical thrust (W/S keys) in Newtons. - /// - public float VerticalThrustForce { get; set; } = 100f; - - /// - /// Linear drag coefficient for atmospheric flight. Higher values = more drag. - /// Drag force = LinearDragCoefficient * velocity. - /// Only applied in Atmospheric mode. - /// - public float AtmosphericLinearDrag { get; set; } = 0.5f; - - /// - /// Angular drag coefficient for atmospheric flight. Higher values = more rotational drag. - /// Drag torque = AngularDragCoefficient * angularVelocity. - /// Only applied in Atmospheric mode. - /// - public float AtmosphericAngularDrag { get; set; } = 0.3f; - - /// - /// Linear drag coefficient for space flight. Lower values = less friction. - /// Only applied in Space mode. - /// - public float SpaceLinearDrag { get; set; } = 0.05f; - - /// - /// Angular drag coefficient for space flight. Lower values = less rotational friction. - /// Only applied in Space mode. - /// - public float SpaceAngularDrag { get; set; } = 0.02f; - - /// - /// When hovering in atmospheric mode, the ship will counteract gravity to maintain altitude. - /// - [DataMemberIgnore] - public bool IsHovering - { - get => _isHovering; - set - { - if (_isHovering == value) - return; - _isHovering = value; - UpdateHoverState(); - } - } - - /// - /// Current horizontal speed of the ship (can be negative for reverse). - /// - [DataMemberIgnore] - public float CurrentSpeed - { - get => _currentSpeed; - private set => _currentSpeed = MathUtil.Clamp(value, -MaxSpeed, MaxSpeed); - } - - /// - /// Intended thrust direction: 1 = forward (D), -1 = reverse (A), 0 = no thrust. - /// - [DataMemberIgnore] - public float ThrustDirection { get; set; } - - /// - /// Whether braking (Space) is currently active. - /// - [DataMemberIgnore] - public bool IsBraking { get; set; } - - /// - /// Vertical thrust input: 1 = up (W), -1 = down (S), 0 = none. - /// - [DataMemberIgnore] - public float VerticalThrust { get; set; } - - public ShipComponent() - { - InterpolationMode = InterpolationMode.Interpolated; - } - - /// - protected override void AttachInner(NRigidPose pose, BodyInertia shapeInertia, TypedIndex shapeIndex) - { - base.AttachInner(pose, shapeInertia, shapeIndex); - UpdateFlightMode(); - } - - /// - /// Applies forward thrust (D key). - /// - public void ThrustForward() - { - ThrustDirection = 1f; - IsBraking = false; - } - - /// - /// Applies reverse thrust (A key). - /// - public void ThrustReverse() - { - ThrustDirection = -1f; - IsBraking = false; - } - - /// - /// Stops horizontal thrust input. - /// - public void StopThrust() - { - ThrustDirection = 0f; - } - - /// - /// Activates braking (Space key). - /// - public void Brake() - { - IsBraking = true; - ThrustDirection = 0f; - } - - /// - /// Releases brake. - /// - public void ReleaseBrake() - { - IsBraking = false; - } - - /// - /// Applies upward thrust (W key). - /// - public void ThrustUp() - { - VerticalThrust = 1f; - } - - /// - /// Applies downward thrust (S key). - /// - public void ThrustDown() - { - VerticalThrust = -1f; - } - - /// - /// Stops vertical thrust input. - /// - public void StopVerticalThrust() - { - VerticalThrust = 0f; - } - - /// - /// Switches between Atmospheric and Space flight modes. - /// - public void ToggleFlightMode() - { - Mode = Mode == FlightMode.Atmospheric ? FlightMode.Space : FlightMode.Atmospheric; - UpdateFlightMode(); - } - - /// - /// Called before physics simulation tick. - /// - public virtual void SimulationUpdate(BepuSimulation sim, float simTimeStep) - { - Awake = true; // Keep ship active - - // Handle horizontal acceleration/deceleration - if (IsBraking) - { - ApplyBraking(simTimeStep); - } - else if (ThrustDirection != 0f) - { - CurrentSpeed += ThrustDirection * Acceleration * simTimeStep; - } - else - { - // Natural deceleration when no input - ApplyNaturalDeceleration(simTimeStep); - } - - // Apply horizontal velocity (assuming X is horizontal in 2D) - var velocity = LinearVelocity; - velocity.X = CurrentSpeed; - - // Handle vertical thrust - if (Mode == FlightMode.Atmospheric) - { - if (VerticalThrust != 0f) - { - // Active vertical thrust - ApplyLinearImpulse(new Vector3(0, VerticalThrust * VerticalThrustForce * simTimeStep, 0)); - IsHovering = false; - } - else if (IsHovering) - { - // Hover mode: counteract gravity - var gravity = Simulation!.PoseGravity; - var gravityMagnitude = gravity.Length(); - if (gravityMagnitude > 0f) - { - // Apply upward force equal to gravity - var mass = 1f / BodyInertia.InverseMass; - var counterForce = -Vector3.Normalize(gravity) * gravityMagnitude * mass; - ApplyLinearImpulse(counterForce * simTimeStep); - } - } - } - else // Space mode - { - if (VerticalThrust != 0f) - { - ApplyLinearImpulse(new Vector3(0, VerticalThrust * VerticalThrustForce * simTimeStep, 0)); - } - } - - // Apply drag to both linear and angular velocities - ApplyDrag(simTimeStep); - - LinearVelocity = velocity; - } - - /// - /// Called after physics simulation tick. - /// - public virtual void AfterSimulationUpdate(BepuSimulation sim, float simTimeStep) - { - // Update current speed from actual velocity - _currentSpeed = LinearVelocity.X; - } - - private void ApplyBraking(float deltaTime) - { - if (MathF.Abs(CurrentSpeed) < 0.01f) - { - CurrentSpeed = 0f; - return; - } - - var brakeAmount = BrakeDeceleration * deltaTime; - if (CurrentSpeed > 0f) - CurrentSpeed = MathF.Max(0f, CurrentSpeed - brakeAmount); - else - CurrentSpeed = MathF.Min(0f, CurrentSpeed + brakeAmount); - } - - private void ApplyNaturalDeceleration(float deltaTime) - { - // Slower natural deceleration compared to active braking - var linearDrag = Mode == FlightMode.Atmospheric ? AtmosphericLinearDrag : SpaceLinearDrag; - var deceleration = CurrentSpeed * linearDrag * deltaTime; - - if (MathF.Abs(CurrentSpeed) < 0.01f) - { - CurrentSpeed = 0f; - return; - } - - if (CurrentSpeed > 0f) - CurrentSpeed = MathF.Max(0f, CurrentSpeed - deceleration); - else - CurrentSpeed = MathF.Min(0f, CurrentSpeed + deceleration); - } - - private void ApplyDrag(float deltaTime) - { - // Get drag coefficients based on flight mode - var linearDrag = Mode == FlightMode.Atmospheric ? AtmosphericLinearDrag : SpaceLinearDrag; - var angularDrag = Mode == FlightMode.Atmospheric ? AtmosphericAngularDrag : SpaceAngularDrag; - - // Apply linear drag: F_drag = -coefficient * velocity - // Using exponential decay: v_new = v_old * (1 - drag * dt) - // This is more stable than force-based approach - var linearVelocity = LinearVelocity; - var dragFactor = 1f - MathUtil.Clamp(linearDrag * deltaTime, 0f, 1f); - - // Apply drag to Y (vertical) velocity only, X is controlled separately - linearVelocity.Y *= dragFactor; - linearVelocity.Z *= dragFactor; // Apply to Z for consistency - - LinearVelocity = linearVelocity; - - // Apply angular drag: T_drag = -coefficient * angularVelocity - var angularVelocity = AngularVelocity; - var angularDragFactor = 1f - MathUtil.Clamp(angularDrag * deltaTime, 0f, 1f); - angularVelocity *= angularDragFactor; - - AngularVelocity = angularVelocity; - } - - private void UpdateFlightMode() - { - if (Simulation == null) - return; - - switch (Mode) - { - case FlightMode.Atmospheric: - Gravity = true; - // Drag is now applied manually in ApplyDrag() using AtmosphericLinearDrag and AtmosphericAngularDrag - break; - - case FlightMode.Space: - Gravity = false; - // Drag is now applied manually in ApplyDrag() using SpaceLinearDrag and SpaceAngularDrag - IsHovering = false; // No hovering in space - break; - } - } - - private void UpdateHoverState() - { - if (Mode == FlightMode.Space) - { - _isHovering = false; // Cannot hover in space - } - } -} - -/// -/// Flight mode affecting physics behavior. -/// -public enum FlightMode -{ - /// - /// Atmospheric flight with gravity and higher drag. - /// Supports hovering to maintain altitude. - /// - Atmospheric, - - /// - /// Space flight with no gravity and minimal drag. - /// True Newtonian physics. - /// - Space -} From ea6ffa5594feb22b983884d314c4c17be2faf6b7 Mon Sep 17 00:00:00 2001 From: Vaclav Elias <4528464+VaclavElias@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:40:12 +0000 Subject: [PATCH 3/3] docs: copilot-instructions.md - Emphasize XML doc review - Instruct reviewers to always check for missing, incomplete, or incorrect XML documentation on public APIs. --- .github/copilot-instructions.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e5b1cfb03e..b2ab6746a6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -20,5 +20,13 @@ Stride is a game engine project that requires careful code reviews to maintain q - If you find a bug or performance issue, suggest a concrete fix in the PR. - For large PRs (20+ C#/*.cs files), do not attempt a full review, only highlight critical or blocking issues. - Always consider the context and established patterns in the Stride codebase before making suggestions. +- **Always check for missing, incomplete, or incorrect XML documentation comments** on public types, methods, properties, and fields. +- **Provide XML comment suggestions as individual, separate items** so they can be reviewed and approved independently. +- **Do not rewrite existing XML comments unless they contain errors** such as incorrect information, poor grammar, typos, or lack clarity. Preserve well-written documentation. +- When suggesting XML comments, ensure they are: + - Accurate and describe the actual functionality + - Consistent with existing documentation style in the codebase + - Include ``, ``, ``, `` and `` tags where appropriate + - Clear and helpful for API consumers The goal is to minimize noise and maximize helpful, actionable feedback.