diff --git a/apps/control-station/src/components/simulation/SimulationUgvController.ts b/apps/control-station/src/components/simulation/SimulationUgvController.ts index d7b08e6..e3cf811 100644 --- a/apps/control-station/src/components/simulation/SimulationUgvController.ts +++ b/apps/control-station/src/components/simulation/SimulationUgvController.ts @@ -24,7 +24,9 @@ const PHYSICS_PROXY_ERROR = 0.0003; const PHYSICS_PROXY_MAX_TRIANGLES = 240_000; const SERVICE_BRAKE_DECELERATION_MPS2 = 1.8; const COAST_DECELERATION_MPS2 = 0.18; -const BRAKE_ATTITUDE_DAMPING = 6; +const PARKING_BRAKE_HOLD_DECELERATION_MPS2 = 6; +const PARKING_BRAKE_ENGAGE_SPEED_MPS = 0.08; +const TYRE_FRICTION_SLIP = 8.5; const DEFAULT_ORBIT_PITCH = 0.48; const CAMERA_RETURN_DELAY_SECONDS = 1.2; const CAMERA_RETURN_DURATION_SECONDS = 2; @@ -311,6 +313,7 @@ export class SimulationUgvController { const maxSpeed = this.settings.maxSpeedMetersPerSecond; const maxTurnRate = this.settings.maxTurnRateDegrees * Math.PI / 180; const pureTurn = !braking && forwardInput === 0 && turnInput !== 0; + const holding = !braking && forwardInput === 0 && turnInput === 0; const desiredSpeed = forwardInput * maxSpeed; const speedError = desiredSpeed - speedMetersPerSecond; const speedResponseRange = Math.max(0.35, maxSpeed * 0.2); @@ -325,13 +328,22 @@ export class SimulationUgvController { const leftCommand = clamp(forwardCommand - turnCommand, -1, 1); const rightCommand = clamp(forwardCommand + turnCommand, -1, 1); const engineForce = pureTurn ? pivotForcePerWheel : driveForcePerWheel; + const brakeDeceleration = braking + ? SERVICE_BRAKE_DECELERATION_MPS2 + : holding + ? Math.abs(speedMetersPerSecond) <= PARKING_BRAKE_ENGAGE_SPEED_MPS + ? PARKING_BRAKE_HOLD_DECELERATION_MPS2 + : COAST_DECELERATION_MPS2 + : 0; + const wheelBrakeForce = this.settings.massKg * brakeDeceleration + / Math.max(1, this.wheelDefinitions.length); for (let index = 0; index < this.wheelDefinitions.length; index += 1) { const definition = this.wheelDefinitions[index]; const command = definition.left ? leftCommand : rightCommand; this.vehicle.setSteeringValue(0, index); this.vehicle.applyEngineForce(command * engineForce, index); - this.vehicle.setBrake(0, index); + this.vehicle.setBrake(wheelBrakeForce, index); this.vehicle.updateWheelTransform(index, true); const transform = this.vehicle.getWheelTransformWS(index); const position = transform.getOrigin(); @@ -345,17 +357,6 @@ export class SimulationUgvController { let nextLinearX = linearVelocity.x; let nextLinearZ = linearVelocity.z; let horizontalSpeed = Math.hypot(nextLinearX, nextLinearZ); - const coasting = !braking && forwardInput === 0 && turnInput === 0; - if ((braking || coasting) && horizontalSpeed > 0.001) { - const deceleration = braking - ? SERVICE_BRAKE_DECELERATION_MPS2 - : COAST_DECELERATION_MPS2; - const nextSpeed = Math.max(0, horizontalSpeed - deceleration * Math.max(0, deltaSeconds)); - const scale = nextSpeed / horizontalSpeed; - nextLinearX *= scale; - nextLinearZ *= scale; - horizontalSpeed = nextSpeed; - } if (pureTurn && horizontalSpeed > 0.001) { const pivotDamping = Math.exp(-Math.max(0, deltaSeconds) * 8); nextLinearX *= pivotDamping; @@ -372,9 +373,6 @@ export class SimulationUgvController { rigidbody.linearVelocity = this.limitedLinearVelocity; } const angularVelocity = rigidbody.angularVelocity; - const attitudeDamping = braking - ? Math.exp(-Math.max(0, deltaSeconds) * BRAKE_ATTITUDE_DAMPING) - : 1; let nextAngularY = angularVelocity.y; if (pureTurn) { const desiredYawRate = -turnInput * maxTurnRate; @@ -387,11 +385,11 @@ export class SimulationUgvController { } else if (Math.abs(angularVelocity.y) > maxTurnRate) { nextAngularY = Math.sign(angularVelocity.y) * maxTurnRate; } - if (attitudeDamping !== 1 || nextAngularY !== angularVelocity.y) { + if (nextAngularY !== angularVelocity.y) { this.limitedAngularVelocity.set( - angularVelocity.x * attitudeDamping, + angularVelocity.x, nextAngularY, - angularVelocity.z * attitudeDamping, + angularVelocity.z, ); rigidbody.angularVelocity = this.limitedAngularVelocity; } @@ -464,8 +462,9 @@ export class SimulationUgvController { type: "dynamic", mass: this.settings.massKg, friction: 0.85, - linearDamping: 0.08, - angularDamping: 0.45, + rollingFriction: 0.12, + linearDamping: 0.12, + angularDamping: 0.6, }); this.chassisMaterial = createMaterial(readThemeAccent(), new Color(0.03, 0.04, 0.05)); @@ -559,7 +558,7 @@ export class SimulationUgvController { wheel.set_m_suspensionStiffness(24); wheel.set_m_wheelsDampingRelaxation(3.2); wheel.set_m_wheelsDampingCompression(4.8); - wheel.set_m_frictionSlip(5.5); + wheel.set_m_frictionSlip(TYRE_FRICTION_SLIP); wheel.set_m_rollInfluence(0.08); } this.ammo.destroy(axle); diff --git a/apps/control-station/test/simulationWorkspace.test.mjs b/apps/control-station/test/simulationWorkspace.test.mjs index 9553b8c..a671589 100644 --- a/apps/control-station/test/simulationWorkspace.test.mjs +++ b/apps/control-station/test/simulationWorkspace.test.mjs @@ -179,8 +179,16 @@ test("PlayCanvas owns the realtime scene graph without an iframe or React entity assert.match(ugv, /desiredSpeed = forwardInput \* maxSpeed/); assert.match(ugv, /maximumAcceleration = clamp\(1\.4 \+ maxSpeed \* 0\.35, 1\.8, 4\.2\)/); assert.match(ugv, /SERVICE_BRAKE_DECELERATION_MPS2 = 1\.8/); - assert.match(ugv, /horizontalSpeed - deceleration \* Math\.max\(0, deltaSeconds\)/); - assert.match(ugv, /this\.vehicle\.setBrake\(0, index\)/); + assert.match(ugv, /PARKING_BRAKE_HOLD_DECELERATION_MPS2 = 6/); + assert.match(ugv, /PARKING_BRAKE_ENGAGE_SPEED_MPS = 0\.08/); + assert.match(ugv, /TYRE_FRICTION_SLIP = 8\.5/); + assert.match(ugv, /holding = !braking && forwardInput === 0 && turnInput === 0/); + assert.match(ugv, /this\.settings\.massKg \* brakeDeceleration/); + assert.match(ugv, /this\.vehicle\.setBrake\(wheelBrakeForce, index\)/); + assert.doesNotMatch(ugv, /horizontalSpeed - deceleration \* Math\.max\(0, deltaSeconds\)/); + assert.match(ugv, /rollingFriction: 0\.12/); + assert.match(ugv, /angularDamping: 0\.6/); + assert.match(ugv, /wheel\.set_m_frictionSlip\(TYRE_FRICTION_SLIP\)/); assert.doesNotMatch(ugv, /massKg \* 3/); assert.match(ugv, /pureTurn = !braking && forwardInput === 0 && turnInput !== 0/); assert.match(ugv, /desiredYawRate = -turnInput \* maxTurnRate/);