Skip to content

Commit eda7a3d

Browse files
Switching Between Arcade and Swerve Has Turning Issues [SYNTH-273] (#1451)
Co-authored-by: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com>
2 parents 8b32254 + ffab290 commit eda7a3d

3 files changed

Lines changed: 49 additions & 41 deletions

File tree

fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type Jolt from "@synthesis.adsk/jolt-physics"
21
import * as THREE from "three"
32
import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
43
import InputSystem from "@/systems/input/InputSystem"
@@ -11,7 +10,7 @@ import SkidSteerDriveBehavior from "@/systems/simulation/behavior/synthesis/driv
1110
import SwerveDriveBehavior from "@/systems/simulation/behavior/synthesis/drive/SwerveDriveBehavior.ts"
1211
import World from "@/systems/World"
1312
import JOLT from "@/util/loading/JoltSyncLoader"
14-
import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToJoltRVec3 } from "@/util/TypeConversions"
13+
import { convertJoltQuatToThreeQuaternion } from "@/util/TypeConversions"
1514
import Brain from "../Brain"
1615
import type Behavior from "../behavior/Behavior"
1716
import { DriveType } from "../behavior/Behavior"
@@ -29,6 +28,7 @@ import HingeStimulus from "../stimulus/HingeStimulus"
2928
import SliderStimulus from "../stimulus/SliderStimulus"
3029
import WheelRotationStimulus from "../stimulus/WheelStimulus"
3130
import { pairNearestHinges } from "./SwervePairing"
31+
import { globalAddToast } from "@/ui/components/GlobalUIControls"
3232

3333
class SynthesisBrain extends Brain {
3434
public static brainIndexMap = new Map<number, SynthesisBrain>()
@@ -79,7 +79,11 @@ class SynthesisBrain extends Brain {
7979
return this._brainIndex
8080
}
8181

82-
public configureDriveBehavior(driveType: DriveType) {
82+
/**
83+
* Applies the requested drive type and returns the drive type actually in effect afterwards.
84+
* These can differ when the requested type is swerve but swerve detection fails.
85+
*/
86+
public configureDriveBehavior(driveType: DriveType): DriveType {
8387
const previousType = this.driveType
8488
this.driveType = driveType
8589

@@ -90,16 +94,17 @@ class SynthesisBrain extends Brain {
9094
const needsRebuild = (type: DriveType) => type === DriveType.SWERVE || type === DriveType.MECANUM
9195
if (needsRebuild(driveType) || needsRebuild(previousType)) {
9296
this.configure()
93-
return
97+
return this.driveType
9498
}
9599

96100
// Tank <-> Arcade is a lightweight toggle on the existing skid-steer behavior.
97101
const existing = this._behaviors.find((behavior: Behavior) => behavior instanceof SkidSteerDriveBehavior)
98102
if (existing == null) {
99103
console.error("Can't find drive behavior!")
100-
return
104+
return this.driveType
101105
}
102106
existing.isArcade = driveType == DriveType.ARCADE
107+
return this.driveType
103108
}
104109

105110
/** Toggles robot-centric mecanum drive without rebuilding the drivetrain. */
@@ -134,6 +139,11 @@ class SynthesisBrain extends Brain {
134139
const useSwerve = this.driveType === DriveType.SWERVE && swerveInfo.inSwerve
135140
if (this.driveType === DriveType.SWERVE && !swerveInfo.inSwerve) {
136141
console.warn("[Swerve] swerve detection failed for this robot; falling back to arcade drive.")
142+
globalAddToast(
143+
"warning",
144+
`Swerve detection failed for this ${this.assemblyName}; falling back to arcade drive.`
145+
)
146+
this.driveType = DriveType.ARCADE
137147
}
138148

139149
let driveBehavior: DriveBehavior
@@ -233,11 +243,6 @@ class SynthesisBrain extends Brain {
233243
stimulus => stimulus instanceof WheelRotationStimulus
234244
) as WheelRotationStimulus[]
235245

236-
// Two body constraints are part of wheels and are used to determine which way a wheel is facing
237-
const fixedConstraints: Jolt.TwoBodyConstraint[] = this._mechanism.constraints
238-
.filter(mechConstraint => mechConstraint.primaryConstraint instanceof JOLT.TwoBodyConstraint)
239-
.map(mechConstraint => mechConstraint.primaryConstraint as Jolt.TwoBodyConstraint)
240-
241246
const leftWheels: WheelDriver[] = []
242247
const leftStimuli: WheelRotationStimulus[] = []
243248

@@ -251,46 +256,49 @@ class SynthesisBrain extends Brain {
251256
? chassisBody.GetCenterOfMassPosition()
252257
: World.physicsSystem.getBody(this._mechanism.constraints[0].childBody)!.GetCenterOfMassPosition()
253258

254-
// Collect constraint positions to determine the correct lateral axis.
259+
// Get each wheel's position from its own vehicle constraint, expressed relative to the
260+
// chassis CoM in the chassis-local frame.
261+
const chassisRotation = chassisBody?.GetRotation()
262+
const wheelPositions: { x: number; z: number }[] = wheelDrivers.map(w => {
263+
const forward = new JOLT.Vec3(1, 0, 0)
264+
const up = new JOLT.Vec3(0, 1, 0)
265+
const transform = w.constraint.GetWheelWorldTransform(0, forward, up)
266+
const translation = transform.GetTranslation()
267+
const relWorld = new JOLT.Vec3(
268+
translation.GetX() - robotCOM.GetX(),
269+
translation.GetY() - robotCOM.GetY(),
270+
translation.GetZ() - robotCOM.GetZ()
271+
)
272+
// InverseRotate returns a reused temporary; read it out before freeing relWorld.
273+
const relLocal = chassisRotation ? chassisRotation.InverseRotate(relWorld) : relWorld
274+
const pos = { x: relLocal.GetX(), z: relLocal.GetZ() }
275+
JOLT.destroy(forward)
276+
JOLT.destroy(up)
277+
JOLT.destroy(relWorld)
278+
return pos
279+
})
280+
255281
// For skid-steer robots the lateral axis (left vs right) is the one that splits
256282
// wheels into two equal groups. Try X and Z; pick the more balanced split.
257-
const constraintPositions: { x: number; z: number }[] = []
258-
for (let i = 0; i < wheelDrivers.length; i++) {
259-
const m = fixedConstraints[i].GetConstraintToBody1Matrix() // STATIC_ALIAS
260-
const t = m.GetTranslation()
261-
constraintPositions.push({ x: t.GetX() - robotCOM.GetX(), z: t.GetZ() - robotCOM.GetZ() })
262-
}
263-
264283
const xImbalance = Math.abs(
265-
constraintPositions.filter(p => p.x >= 0).length - constraintPositions.filter(p => p.x < 0).length
284+
wheelPositions.filter(p => p.x >= 0).length - wheelPositions.filter(p => p.x < 0).length
266285
)
267286
const zImbalance = Math.abs(
268-
constraintPositions.filter(p => p.z >= 0).length - constraintPositions.filter(p => p.z < 0).length
287+
wheelPositions.filter(p => p.z >= 0).length - wheelPositions.filter(p => p.z < 0).length
269288
)
270289

271290
// Use Z axis when it gives a more balanced split (URDF robots); fall back to X (Fusion 360 robots).
272291
// URDF's usual +Y-left convention converts to -Z-left in Synthesis, so +Z is the right side.
273292
const useLateralZ = zImbalance < xImbalance
274-
const rightVector = useLateralZ ? new JOLT.RVec3(0, 0, -1) : new JOLT.RVec3(1, 0, 0)
275293

276294
for (let i = 0; i < wheelDrivers.length; i++) {
277-
// Jolt value returns (GetConstraintToBody1Matrix, GetTranslation, SubRVec3,
278-
// GetCenterOfMassPosition) point to reused static temporaries, not heap
279-
// allocations. Don't destroy them; freeing a non-heap address corrupts the heap.
280-
const constraintMatrix = fixedConstraints[i].GetConstraintToBody1Matrix()
281-
const translation = constraintMatrix.GetTranslation()
282-
const wheelPos = convertJoltVec3ToJoltRVec3(translation, false)
283-
284-
const dotProduct = rightVector.Dot(wheelPos.SubRVec3(robotCOM))
295+
// rightVector is (0,0,-1) for the Z axis and (1,0,0) for the X axis.
296+
const dotProduct = useLateralZ ? -wheelPositions[i].z : wheelPositions[i].x
285297
const [wheels, stimuli] = dotProduct < 0 ? [rightWheels, rightStimuli] : [leftWheels, leftStimuli]
286298

287299
wheels.push(wheelDrivers[i])
288300
stimuli.push(wheelStimuli[i])
289-
290-
// wheelPos is the only heap allocation in this loop.
291-
JOLT.destroy(wheelPos)
292301
}
293-
JOLT.destroy(rightVector)
294302

295303
return new SkidSteerDriveBehavior(
296304
leftWheels,

fission/src/ui/panels/configuring/assembly-config/interfaces/DrivetrainSelectionInterface.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ const DrivetrainSelectionInterface: ConfigurationSubpanelComponent = ({
4646
<Select // TODO: disable/hide when wpilib brain selected
4747
labelId="drivetrain-type-label"
4848
label="Drivetrain Type"
49-
defaultValue={driveType}
49+
value={driveType}
5050
onChange={e => {
5151
if (selectedAssembly.brain?.isSynthesis()) {
52-
const newDriveType = e.target.value as DriveType
53-
selectedAssembly.brain.configureDriveBehavior(newDriveType)
52+
const appliedDriveType = selectedAssembly.brain.configureDriveBehavior(
53+
e.target.value as DriveType
54+
)
55+
setDriveType(appliedDriveType)
56+
5457
InputSchemeManager.applyCompatibleScheme(selectedAssembly.brain.brainIndex)
5558
EventSystem.dispatch("InputSchemeChanged", {})
56-
setDriveType(newDriveType)
5759
}
5860
}}
5961
>

fission/src/ui/panels/configuring/initial-config/InputSchemeSelection.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,8 @@ export default function InputSchemeSelection({ brainIndex, onSelect, panelId }:
123123
onChange={e => {
124124
const newDriveType = e.target.value as DriveType
125125
const brain = SynthesisBrain.brainIndexMap.get(brainIndex)
126-
if (brain) {
127-
brain.configureDriveBehavior(newDriveType)
128-
}
129-
setRobotDriveType(newDriveType)
126+
const appliedDriveType = brain?.configureDriveBehavior(newDriveType) ?? newDriveType
127+
setRobotDriveType(appliedDriveType)
130128

131129
const scheme = InputSchemeManager.applyCompatibleScheme(brainIndex)
132130
if (scheme) setSelectedScheme(scheme)

0 commit comments

Comments
 (0)