1- import type Jolt from "@synthesis.adsk/jolt-physics"
21import * as THREE from "three"
32import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
43import InputSystem from "@/systems/input/InputSystem"
@@ -11,7 +10,7 @@ import SkidSteerDriveBehavior from "@/systems/simulation/behavior/synthesis/driv
1110import SwerveDriveBehavior from "@/systems/simulation/behavior/synthesis/drive/SwerveDriveBehavior.ts"
1211import World from "@/systems/World"
1312import JOLT from "@/util/loading/JoltSyncLoader"
14- import { convertJoltQuatToThreeQuaternion , convertJoltVec3ToJoltRVec3 } from "@/util/TypeConversions"
13+ import { convertJoltQuatToThreeQuaternion } from "@/util/TypeConversions"
1514import Brain from "../Brain"
1615import type Behavior from "../behavior/Behavior"
1716import { DriveType } from "../behavior/Behavior"
@@ -29,6 +28,7 @@ import HingeStimulus from "../stimulus/HingeStimulus"
2928import SliderStimulus from "../stimulus/SliderStimulus"
3029import WheelRotationStimulus from "../stimulus/WheelStimulus"
3130import { pairNearestHinges } from "./SwervePairing"
31+ import { globalAddToast } from "@/ui/components/GlobalUIControls"
3232
3333class 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 ,
0 commit comments