From c99942f1716357700dcee81fd95c2d58d983fda2 Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Thu, 5 Mar 2026 11:48:40 +0100 Subject: [PATCH 1/4] Update OpenPLX mapping for updated vehicle bundle --- AGXUnity/IO/OpenPLX/Errors.cs | 12 +- AGXUnity/IO/OpenPLX/MappingUtils.cs | 7 + AGXUnity/IO/OpenPLX/OpenPLXObject.cs | 67 +++++--- AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs | 18 +- AGXUnity/IO/OpenPLX/VehicleMapper.cs | 162 +++++++++++------- AGXUnity/Model/WheelJoint.cs | 16 +- Tests/Runtime/OpenPLXTests.cs | 4 +- .../Test Resources/elastic_wheel.openplx | 19 +- 8 files changed, 187 insertions(+), 118 deletions(-) diff --git a/AGXUnity/IO/OpenPLX/Errors.cs b/AGXUnity/IO/OpenPLX/Errors.cs index 7da82f74..bbe07508 100644 --- a/AGXUnity/IO/OpenPLX/Errors.cs +++ b/AGXUnity/IO/OpenPLX/Errors.cs @@ -212,9 +212,9 @@ public FileDoesNotExistError( string path ) public class InvalidWheelChassisError : BaseError { - private openplx.Vehicles.Suspensions.Interactions.Mate m_suspension; + private openplx.Vehicles.Suspensions.SingleMate.Base m_suspension; - public InvalidWheelChassisError( openplx.Vehicles.Suspensions.Interactions.Mate suspension ) + public InvalidWheelChassisError( openplx.Vehicles.Suspensions.SingleMate.Base suspension ) : base( suspension, AgxUnityOpenPLXErrors.InvalidWheelChassis ) { m_suspension = suspension; @@ -225,9 +225,9 @@ public InvalidWheelChassisError( openplx.Vehicles.Suspensions.Interactions.Mate public class MissingWheelBodyError : BaseError { - private openplx.Vehicles.Suspensions.Interactions.Mate m_suspension; + private openplx.Vehicles.Suspensions.SingleMate.Base m_suspension; - public MissingWheelBodyError( openplx.Vehicles.Suspensions.Interactions.Mate suspension ) + public MissingWheelBodyError( openplx.Vehicles.Suspensions.SingleMate.Base suspension ) : base( suspension, AgxUnityOpenPLXErrors.MissingWheelBody ) { m_suspension = suspension; @@ -238,9 +238,9 @@ public MissingWheelBodyError( openplx.Vehicles.Suspensions.Interactions.Mate sus public class UnmappedWheelError : BaseError { - private openplx.Vehicles.Suspensions.Suspension m_wheel; + private openplx.Vehicles.Suspensions.SingleMate.Base m_wheel; - public UnmappedWheelError( openplx.Vehicles.Suspensions.Suspension wheel ) + public UnmappedWheelError( openplx.Vehicles.Suspensions.SingleMate.Base wheel ) : base( wheel, AgxUnityOpenPLXErrors.UnmappedWheel ) { m_wheel = wheel; diff --git a/AGXUnity/IO/OpenPLX/MappingUtils.cs b/AGXUnity/IO/OpenPLX/MappingUtils.cs index 77ac2ea0..7df48d58 100644 --- a/AGXUnity/IO/OpenPLX/MappingUtils.cs +++ b/AGXUnity/IO/OpenPLX/MappingUtils.cs @@ -67,6 +67,13 @@ public static bool IsDefault( this openplx.Math.Quat quat ) } public static uint To32BitFnv1aHash( this openplx.Core.Object obj ) => obj.getName().To32BitFnv1aHash(); + + public static bool HasTrait( this openplx.Core.Object obj ) + where T : class + { + string traitName = typeof( T ).FullName.Substring( 8 ); + return obj.hasTrait( traitName ); + } } public static class Utils { diff --git a/AGXUnity/IO/OpenPLX/OpenPLXObject.cs b/AGXUnity/IO/OpenPLX/OpenPLXObject.cs index f127247d..85008185 100644 --- a/AGXUnity/IO/OpenPLX/OpenPLXObject.cs +++ b/AGXUnity/IO/OpenPLX/OpenPLXObject.cs @@ -39,32 +39,49 @@ bool NeedsNativeMapping( openplx.Core.Object obj ) }; } - internal agx.Referenced FindCorrespondingNative( OpenPLX.OpenPLXRoot root, openplx.Core.Object obj ) => obj switch + internal agx.Referenced FindCorrespondingNative( OpenPLX.OpenPLXRoot root, openplx.Core.Object obj ) { - Interactions.Lock => GetNativeConstraint().asLockJoint(), - Interactions.Hinge => GetNativeConstraint().asHinge(), - Interactions.Prismatic => GetNativeConstraint().asPrismatic(), - Interactions.Cylindrical => GetNativeConstraint().asCylindricalJoint(), - Interactions.Ball => GetNativeConstraint().asBallJoint(), - Interactions.Distance => GetNativeConstraint().asDistanceJoint(), - Interactions.RotationalRange => GetGeneric1DOFNative().getRange1D(), - Interactions.TorsionSpring => GetGeneric1DOFNative().getLock1D(), - Interactions.RotationalVelocityMotor => GetGeneric1DOFNative().getMotor1D(), - Interactions.TorqueMotor => GetGeneric1DOFNative().getMotor1D(), - Interactions.LinearRange => GetGeneric1DOFNative().getRange1D(), - Interactions.LinearSpring => GetGeneric1DOFNative().getLock1D(), - Interactions.LinearVelocityMotor => GetGeneric1DOFNative().getMotor1D(), - Interactions.ForceMotor => GetGeneric1DOFNative().getMotor1D(), - openplx.Physics.Geometries.ContactGeometry => gameObject.GetInitializedComponent().NativeGeometry, - Interactions.MateConnector => gameObject.GetInitializedComponent().Native, - openplx.Physics3D.Bodies.RigidBody => gameObject.GetInitializedComponent().Native, - openplx.Terrain.Terrain => gameObject.GetInitializedComponent().Native, - openplx.Terrain.Shovel => gameObject.GetInitializedComponent().Native, - openplx.Sensors.SensorLogic => gameObject.GetInitializedComponent().Native, - openplx.Vehicles.Steering.Interactions.DualSuspensionSteering => gameObject.GetInitializedComponent().Native, - openplx.Vehicles.Suspensions.Interactions.LinearSpringDamperMate => gameObject.GetInitializedComponent().Native, - _ => DefaultHandling( obj ) - }; + if ( obj.getOwner() is openplx.Vehicles.Suspensions.SingleMate.Base wj ) { + if ( obj == wj.range() ) + return gameObject.GetInitializedComponent().GetController( WheelJoint.WheelDimension.Suspension ).Native; + else if ( obj == wj.mate() ) + return gameObject.GetInitializedComponent().Native; + } + + if ( obj.getOwner() is openplx.Vehicles.Steering.Kinematic.Base steer ) { + if ( obj == steer.interaction() ) + return gameObject.GetInitializedComponent().Native; + } + + return obj switch + { + Interactions.Lock => GetNativeConstraint().asLockJoint(), + Interactions.Hinge => GetNativeConstraint().asHinge(), + Interactions.Prismatic => GetNativeConstraint().asPrismatic(), + Interactions.Cylindrical => GetNativeConstraint().asCylindricalJoint(), + Interactions.Ball => GetNativeConstraint().asBallJoint(), + Interactions.Distance => GetNativeConstraint().asDistanceJoint(), + Interactions.RotationalRange => GetGeneric1DOFNative().getRange1D(), + Interactions.TorsionSpring => GetGeneric1DOFNative().getLock1D(), + Interactions.RotationalVelocityMotor => GetGeneric1DOFNative().getMotor1D(), + Interactions.TorqueMotor => GetGeneric1DOFNative().getMotor1D(), + Interactions.LinearRange => GetGeneric1DOFNative().getRange1D(), + Interactions.LinearSpring => GetGeneric1DOFNative().getLock1D(), + Interactions.LinearVelocityMotor => GetGeneric1DOFNative().getMotor1D(), + Interactions.ForceMotor => GetGeneric1DOFNative().getMotor1D(), + openplx.Physics.Geometries.ContactGeometry => gameObject.GetInitializedComponent().NativeGeometry, + Interactions.MateConnector => gameObject.GetInitializedComponent().Native, + openplx.Physics3D.Bodies.RigidBody => gameObject.GetInitializedComponent().Native, + openplx.Terrain.Terrain => gameObject.GetInitializedComponent().Native, + openplx.Terrain.Shovel => gameObject.GetInitializedComponent().Native, + openplx.Sensors.SensorLogic => gameObject.GetInitializedComponent().Native, + openplx.Vehicles.Steering.Kinematic.Base => gameObject.GetInitializedComponent().Native, + openplx.Vehicles.Steering.Kinematic.Interactions.Base => gameObject.GetInitializedComponent().Native, + openplx.Vehicles.Suspensions.SingleMate.Base => gameObject.GetInitializedComponent().Native, + openplx.Vehicles.Suspensions.SingleMate.Interactions.Base => gameObject.GetInitializedComponent().Native, + _ => DefaultHandling( obj ) + }; + } [field: SerializeField] [DisableInRuntimeInspector] diff --git a/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs b/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs index a1a142dd..7a2d0c51 100644 --- a/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs +++ b/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs @@ -946,20 +946,18 @@ void MapSystemPass4( openplx.Physics3D.System system ) foreach ( var subSystem in system.getNonReferenceValues() ) MapSystemPass4( subSystem ); + if ( system is openplx.Vehicles.Suspensions.SingleMate.Base suspension ) + VehicleMapper.MapSingleMateSuspensionOnto( suspension, s ); + foreach ( var lidar in system.getNonReferenceValues() ) Utils.AddChild( s, SensorMapper.MapLidar( lidar ), Data.ErrorReporter, lidar ); foreach ( var kinematicLock in system.getNonReferenceValues() ) Utils.AddChild( s, MapKinematicLock( kinematicLock ), Data.ErrorReporter, kinematicLock ); - foreach ( var interaction in system.getNonReferenceValues() ) { - if ( !Utils.IsRuntimeMapped( interaction ) && interaction is not openplx.Vehicles.Steering.Interactions.DualSuspensionSteering ) { - if ( interaction is openplx.Vehicles.Suspensions.Interactions.Mate suspension ) - Utils.AddChild( s, VehicleMapper.MapSuspension( suspension ), Data.ErrorReporter, suspension ); - else - Utils.AddChild( s, InteractionMapper.MapInteraction( interaction, system ), Data.ErrorReporter, interaction ); - } - } + foreach ( var interaction in system.getNonReferenceValues() ) + if ( !Utils.IsRuntimeMapped( interaction ) && !VehicleMapper.HandledInteraction( interaction ) ) + Utils.AddChild( s, InteractionMapper.MapInteraction( interaction, system ), Data.ErrorReporter, interaction ); foreach ( var contactModel in system.getNonReferenceValues() ) InteractionMapper.MapContactModel( contactModel ); @@ -987,8 +985,8 @@ void MapSystemPass5( openplx.Physics3D.System system ) foreach ( var subSystem in system.getNonReferenceValues() ) MapSystemPass5( subSystem ); - foreach ( var steering in system.getNonReferenceValues() ) - Utils.AddChild( s, VehicleMapper.MapSteering( steering ), Data.ErrorReporter, steering ); + if ( system is openplx.Vehicles.Steering.Kinematic.Base steering ) + VehicleMapper.MapSteeringOnto( steering, s ); foreach ( var wheel in system.getNonReferenceValues() ) VehicleMapper.MapElasticWheel( wheel ); diff --git a/AGXUnity/IO/OpenPLX/VehicleMapper.cs b/AGXUnity/IO/OpenPLX/VehicleMapper.cs index 7f57b0c7..9eb3312f 100644 --- a/AGXUnity/IO/OpenPLX/VehicleMapper.cs +++ b/AGXUnity/IO/OpenPLX/VehicleMapper.cs @@ -2,6 +2,7 @@ using AGXUnity.Model; using AGXUnity.Rendering; using AGXUnity.Utils; +using openplx.Physics3D.Interactions; using openplx.Vehicles.Tracks; using System.Collections.Generic; using UnityEngine; @@ -14,13 +15,22 @@ public class VehicleMapper { private MapperData Data; - private Dictionary m_mappedWheels = new Dictionary (); + private Dictionary m_mappedWheels = new Dictionary (); public VehicleMapper( MapperData cache ) { Data = cache; } + public bool HandledInteraction( openplx.Physics.Interactions.Interaction interaction ) + { + if ( interaction is openplx.Vehicles.Steering.Kinematic.Interactions.Base ) + return true; + if ( interaction.getOwner() is openplx.Vehicles.Suspensions.SingleMate.Base susp && ( interaction == susp.mate() || interaction == susp.range() ) ) + return true; + return false; + } + public void MapElasticWheel( openplx.Vehicles.Wheels.ElasticWheel wheel ) { if ( !Data.BodyCache.TryGetValue( wheel.tire(), out RigidBody tireBody ) ) { @@ -378,97 +388,120 @@ private void MapTrackProperties( Tracks.System track_system, Track track ) Data.MappedTrackProperties.Add( track_props ); } - public GameObject MapSuspension( openplx.Vehicles.Suspensions.Interactions.Mate suspension ) - { - return suspension switch - { - openplx.Vehicles.Suspensions.Interactions.LinearSpringDamperMate linSpring => MapLinearSpringDamperMate( linSpring ), - _ => Utils.ReportUnimplemented( suspension, Data.ErrorReporter ) - }; - } - - public GameObject MapLinearSpringDamperMate( openplx.Vehicles.Suspensions.Interactions.LinearSpringDamperMate linearSpring ) + public bool MapSingleMateSuspensionOnto( openplx.Vehicles.Suspensions.SingleMate.Base suspension, GameObject onto ) { RigidBody chassis = null; RigidBody wheel = null; - var parent = linearSpring.chassis_connector().redirected_parent(); + var parent = suspension.chassis_connector().getOwner(); if ( parent == null ) - return null; + return false; + + openplx.Physics3D.Bodies.RigidBody chassisBody = null; + + if ( suspension.chassis_connector() is RedirectedMateConnector crmc ) + chassisBody = crmc.redirected_parent() as openplx.Physics3D.Bodies.RigidBody; + else if ( parent is openplx.Physics3D.System chassisSystem ) + chassisBody = suspension.attachment_connector().getOwner() as openplx.Physics3D.Bodies.RigidBody; + else + chassisBody = parent as openplx.Physics3D.Bodies.RigidBody; - if ( Data.BodyCache.ContainsKey( parent ) ) - chassis = Data.BodyCache[ parent ]; + if ( chassisBody != null && Data.BodyCache.ContainsKey( chassisBody ) ) + chassis = Data.BodyCache[ chassisBody ]; else { - Data.ErrorReporter.reportError( new InvalidWheelChassisError( linearSpring ) ); - return null; + Data.ErrorReporter.reportError( new InvalidWheelChassisError( suspension ) ); + return false; } - var wheelParent = linearSpring.attachment_connector().getOwner(); + var wheelParent = suspension.attachment_connector().getOwner(); + openplx.Physics3D.Bodies.RigidBody attachmentBody = null; - if ( wheelParent is openplx.Physics3D.Bodies.RigidBody wheelBody ) - wheel = Data.BodyCache[ wheelBody ]; - else if ( wheelParent is openplx.Vehicles.Wheels.Wheel wheelModel ) { - wheel = Data.BodyCache.GetValueOrDefault( wheelModel.rim() ); - } + if ( suspension.attachment_connector() is RedirectedMateConnector armc ) + attachmentBody = armc.redirected_parent() as openplx.Physics3D.Bodies.RigidBody; + else if ( wheelParent is openplx.Physics3D.Bodies.RigidBody ab ) + attachmentBody = ab; + else if ( wheelParent is openplx.Vehicles.Wheels.Base wheelModel ) + attachmentBody = wheelModel.rim(); - if ( wheel == null ) { - Data.ErrorReporter.reportError( new MissingWheelBodyError( linearSpring ) ); - return null; + if ( attachmentBody != null && Data.BodyCache.ContainsKey( attachmentBody ) ) + wheel = Data.BodyCache[ attachmentBody ]; + else { + Data.ErrorReporter.reportError( new MissingWheelBodyError( suspension ) ); + return false; } - // OpenPLX assumes axes N = Wheel and U = Steering, in agx N = Steering, V = Steering is used. Apply a local rotation to correct for this from the precalculated frame. - Quaternion frameCorrection = Quaternion.Euler( 0, -90, -90); - var chassisFrame = new ConstraintFrame( Data.MateConnectorCache[ linearSpring.chassis_connector() ], Vector3.zero, frameCorrection ); - var wheelFrame = new ConstraintFrame( Data.MateConnectorCache[ linearSpring.attachment_connector() ], Vector3.zero, frameCorrection ); + // OpenPLX assumes axes N = Wheel and U = Suspension, in agx N = Suspension, V = Wheel is used. Apply a local rotation to correct for this from the precalculated frame. + Quaternion frameCorrection = Quaternion.Euler( 0, -90, -90 ); + var chassisXForm = chassis.transform.worldToLocalMatrix * Data.MateConnectorCache[ suspension.chassis_connector() ].transform.localToWorldMatrix; + var wheelXForm = wheel.transform.worldToLocalMatrix * Data.MateConnectorCache[ suspension.attachment_connector() ].transform.localToWorldMatrix; + + var chassisFrame = new ConstraintFrame( chassis.gameObject, chassisXForm.GetTranslate(), chassisXForm.GetRotation() * frameCorrection); + var wheelFrame = new ConstraintFrame( wheel.gameObject, wheelXForm.GetTranslate(), wheelXForm.GetRotation() * frameCorrection ); - var wheelJoint = WheelJoint.Create(wheelFrame, chassisFrame); - if ( !linearSpring.getOwner().hasTrait( "Vehicles.Suspensions.Traits.Steering" ) ) + var wheelJoint = WheelJoint.Create(wheelFrame, chassisFrame, onto); + if ( !suspension.HasTrait() ) wheelJoint.GetController( WheelJoint.WheelDimension.Steering ).Enable = true; - if ( linearSpring.hasTrait( "Vehicles.Suspensions.Traits.SpringDamper" ) ) { - var damping = InteractionMapper.MapDissipation( linearSpring.spring_damping(), linearSpring.spring_constant() ); + if ( suspension is openplx.Vehicles.Suspensions.SingleMate.LinearSpringDamper lsd ) { + var damping = InteractionMapper.MapDissipation( lsd.mate().spring_damping(), lsd.mate().spring_constant() ); if ( damping.HasValue ) wheelJoint.GetController( WheelJoint.WheelDimension.Suspension ).Damping = damping.Value; - var compliance = InteractionMapper.MapFlexibility(linearSpring.spring_constant()); + var compliance = InteractionMapper.MapFlexibility(lsd.mate().spring_constant()); if ( compliance.HasValue ) wheelJoint.GetController( WheelJoint.WheelDimension.Suspension ).Compliance = compliance.Value; + + wheelJoint.enabled = lsd.mate().enabled(); } - wheelJoint.enabled = linearSpring.enabled(); + var range = wheelJoint.GetController( WheelJoint.WheelDimension.Suspension ); + range.Enable = suspension.range().enabled(); + var rangeCompliance = InteractionMapper.MapFlexibility(suspension.range().flexibility()); + if ( rangeCompliance.HasValue ) + range.Compliance = rangeCompliance.Value; + var rangeDamping = InteractionMapper.MapDissipation(suspension.range().dissipation(),suspension.range().flexibility()); + if ( rangeDamping.HasValue ) + range.Damping = rangeDamping.Value; + range.Range = new RangeReal( (float)suspension.range().start(), (float)suspension.range().end() ); + range.ForceRange = new RangeReal( (float)suspension.range().min_effort(), (float)suspension.range().max_effort() ); - Data.RegisterOpenPLXObject( linearSpring.getName(), wheelJoint.gameObject ); + Data.RegisterOpenPLXObject( suspension.getName(), wheelJoint.gameObject ); + Data.RegisterOpenPLXObject( suspension.range().getName(), wheelJoint.gameObject ); + Data.RegisterOpenPLXObject( suspension.mate().getName(), wheelJoint.gameObject ); - m_mappedWheels[ linearSpring ] = wheelJoint; + m_mappedWheels[ suspension ] = wheelJoint; - return wheelJoint.gameObject; + return true; } - public GameObject MapSteering( openplx.Vehicles.Steering.Interactions.DualSuspensionSteering steering ) + public bool MapSteeringOnto( openplx.Vehicles.Steering.Kinematic.Base steering, GameObject onto ) { Steering.SteeringMechanism? mechanism = steering switch { - openplx.Vehicles.Steering.Interactions.Ackermann => Steering.SteeringMechanism.Ackermann, - openplx.Vehicles.Steering.Interactions.BellCrank => Steering.SteeringMechanism.BellCrank, - openplx.Vehicles.Steering.Interactions.RackAndPinion => Steering.SteeringMechanism.RackPinion, + openplx.Vehicles.Steering.Kinematic.Ackermann => Steering.SteeringMechanism.Ackermann, + openplx.Vehicles.Steering.Kinematic.BellCrank => Steering.SteeringMechanism.BellCrank, + openplx.Vehicles.Steering.Kinematic.RackAndPinion => Steering.SteeringMechanism.RackPinion, _ => null }; - if ( !mechanism.HasValue ) - return Utils.ReportUnimplemented( steering, Data.ErrorReporter ); + if ( !mechanism.HasValue ) { + Data.ErrorReporter.reportError( new UnimplementedError( steering ) ); + return false; + } - var steeringGO = Data.CreateOpenPLXObject(steering.getName()); - var steeringComp = steeringGO.AddComponent(); + var steeringComp = onto.AddComponent(); + Data.RegisterOpenPLXObject( steering.getName(), onto ); + Data.RegisterOpenPLXObject( steering.interaction().getName(), onto ); WheelJoint rightWheel, leftWheel; - if ( !m_mappedWheels.TryGetValue( steering.right_suspension().mate(), out rightWheel ) ) { + if ( !m_mappedWheels.TryGetValue( steering.right_suspension(), out rightWheel ) ) { Data.ErrorReporter.reportError( new UnmappedWheelError( steering.right_suspension() ) ); - return null; + return false; } - if ( !m_mappedWheels.TryGetValue( steering.left_suspension().mate(), out leftWheel ) ) { + if ( !m_mappedWheels.TryGetValue( steering.left_suspension(), out leftWheel ) ) { Data.ErrorReporter.reportError( new UnmappedWheelError( steering.left_suspension() ) ); - return null; + return false; } steeringComp.RightWheel = rightWheel; @@ -476,22 +509,27 @@ public GameObject MapSteering( openplx.Vehicles.Steering.Interactions.DualSuspen steeringComp.Mechanism = mechanism.Value; - steeringComp.Phi0 = (float)steering.knuckle_angle(); - steeringComp.L = (float)steering.knuckle_length(); - - if ( steering is openplx.Vehicles.Steering.Interactions.BellCrank bellCrank ) { - steeringComp.Lc = (float)bellCrank.steering_column_distance(); - steeringComp.Gear = (float)bellCrank.gear(); + if ( steering is openplx.Vehicles.Steering.Kinematic.Ackermann ackermann ) { + steeringComp.Phi0 = (float)ackermann.knuckle_angle(); + steeringComp.L = (float)ackermann.knuckle_length(); + } + else if ( steering is openplx.Vehicles.Steering.Kinematic.BellCrank bellCrank ) { + steeringComp.Phi0 = (float)bellCrank.knuckle_angle(); + steeringComp.L = (float)bellCrank.knuckle_length(); + steeringComp.Lc = (float)bellCrank.steering_column_distance(); + steeringComp.Gear = (float)bellCrank.gear(); steeringComp.Alpha0 = (float)bellCrank.initial_angle_right_tie_rod(); } - else if ( steering is openplx.Vehicles.Steering.Interactions.RackAndPinion rackPinion ) { - steeringComp.Lc = (float)rackPinion.steering_column_distance(); - steeringComp.Lr = (float)rackPinion.rack_length(); - steeringComp.Gear = (float)rackPinion.gear(); + else if ( steering is openplx.Vehicles.Steering.Kinematic.RackAndPinion rackPinion ) { + steeringComp.Phi0 = (float)rackPinion.knuckle_angle(); + steeringComp.L = (float)rackPinion.knuckle_length(); + steeringComp.Lc = (float)rackPinion.steering_column_distance(); + steeringComp.Lr = (float)rackPinion.rack_length(); + steeringComp.Gear = (float)rackPinion.gear(); steeringComp.Alpha0 = (float)rackPinion.initial_angle_right_tie_rod(); } - return steeringGO; + return true; } } } diff --git a/AGXUnity/Model/WheelJoint.cs b/AGXUnity/Model/WheelJoint.cs index e5820f6a..cbe3486e 100644 --- a/AGXUnity/Model/WheelJoint.cs +++ b/AGXUnity/Model/WheelJoint.cs @@ -142,13 +142,18 @@ where ec is ElementaryConstraintController /// /// Reference frame. /// Connected frame. + /// Creates the constraint on the given gameobject, or a new if null /// WheelJoint component, added to a new game object - null if unsuccessful. public static WheelJoint Create( ConstraintFrame referenceFrame, - ConstraintFrame connectedFrame ) + ConstraintFrame connectedFrame, + GameObject createOn = null ) { - GameObject constraintGameObject = new GameObject( Factory.CreateName( "AGXUnity.WheelJoint" ) ); + bool createGameobject = createOn == null; + if ( createGameobject ) + createOn = new GameObject( Factory.CreateName( "AGXUnity.WheelJoint" ) ); + WheelJoint constraint = null; try { - WheelJoint constraint = constraintGameObject.AddComponent(); + constraint = createOn.AddComponent(); constraint.AttachmentPair.ReferenceFrame = referenceFrame ?? new ConstraintFrame(); constraint.AttachmentPair.ConnectedFrame = connectedFrame ?? new ConstraintFrame(); @@ -162,7 +167,10 @@ public static WheelJoint Create( ConstraintFrame referenceFrame, } catch ( System.Exception e ) { Debug.LogException( e ); - DestroyImmediate( constraintGameObject ); + if ( createGameobject ) + DestroyImmediate( createOn ); + else if ( constraint != null ) + DestroyImmediate( constraint ); return null; } } diff --git a/Tests/Runtime/OpenPLXTests.cs b/Tests/Runtime/OpenPLXTests.cs index 2f96f294..4585739b 100644 --- a/Tests/Runtime/OpenPLXTests.cs +++ b/Tests/Runtime/OpenPLXTests.cs @@ -555,8 +555,8 @@ public IEnumerator TestSteering() Assert.That( steeringComp.Mechanism, Is.EqualTo( Steering.SteeringMechanism.Ackermann ), "Specific steering mechanisms should map to proper enum flag" ); var signals = root.GetComponent(); - var steeringInput = signals.FindInputTarget( "WheelScene.steering.steering_angle_input" ); - var steeringOutput = signals.FindOutputSource( "WheelScene.steering.steering_angle_output" ); + var steeringInput = signals.FindInputTarget( "WheelScene.steering.interaction.steering_angle_input" ); + var steeringOutput = signals.FindOutputSource( "WheelScene.steering.interaction.steering_angle_output" ); yield return TestUtils.Step(); diff --git a/Tests/Runtime/Test Resources/elastic_wheel.openplx b/Tests/Runtime/Test Resources/elastic_wheel.openplx index 53e81dad..fa2ce2a6 100644 --- a/Tests/Runtime/Test Resources/elastic_wheel.openplx +++ b/Tests/Runtime/Test Resources/elastic_wheel.openplx @@ -9,27 +9,29 @@ Wheel is Vehicles.Wheels.ElasticWheel: radius: 0.03 width: 0.02 rotational_axis: Math.Vec3.X_AXIS() - geometry.local_transform.rotation: Math.Quat.from_to(Math.Vec3.Z_AXIS(), Math.Vec3.Y_AXIS()) tire becomes Vehicles.Wheels.Tire: outer_radius: 0.05 inner_radius: rim.radius width: 0.015 rotational_axis: Math.Vec3.X_AXIS() - geometry.local_transform.rotation: Math.Quat.from_to(Math.Vec3.Z_AXIS(), Math.Vec3.Y_AXIS()) + +WheelMC is Physics3D.Interactions.RedirectedMateConnector: + with Vehicles.Interfaces.UpForwardVectors + with Vehicles.Suspensions.Properties.WheelAxisFromUpForward WheelScene is Physics3D.System: chassis is Chassis: up is Math.Vec3: Math.Vec3.Z_AXIS() forward is Math.Vec3: Math.Vec3.Y_AXIS() - left_mc is Vehicles.Suspensions.Interactions.Connector: + left_mc is WheelMC: position: Math.Vec3.from_xyz(-0.06, 0.0, -0.05) redirected_parent: chassis up_vector: chassis.up forward_vector: chassis.forward - right_mc is Vehicles.Suspensions.Interactions.Connector: + right_mc is WheelMC: redirected_parent: chassis up_vector: chassis.up forward_vector: chassis.forward @@ -38,18 +40,17 @@ WheelScene is Physics3D.System: left_wheel is Wheel right_wheel is Wheel - suspension_left is Vehicles.Suspensions.LinearSpringDamper with Vehicles.Suspensions.Traits.Steering: + suspension_left is Vehicles.Suspensions.SingleMate.LinearSpringDamper with Vehicles.Suspensions.Properties.Steering: chassis_connector: left_mc attachment_connector: left_wheel.connector - suspension_right is Vehicles.Suspensions.LinearSpringDamper with Vehicles.Suspensions.Traits.Steering: + suspension_right is Vehicles.Suspensions.SingleMate.LinearSpringDamper with Vehicles.Suspensions.Properties.Steering: chassis_connector: right_mc attachment_connector: right_wheel.connector - steering is Vehicles.Steering.Interactions.Ackermann: + steering is Vehicles.Steering.Kinematic.Ackermann: left_suspension: suspension_left right_suspension: suspension_right - steering_angle_output: - enabled: true + interaction.steering_angle_output.enabled: true From 03ecf68941ad3905af444b8c2eac17307c029201 Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Thu, 5 Mar 2026 15:27:00 +0100 Subject: [PATCH 2/4] Fix issue with names of objects in nested bundles --- AGXUnity/IO/OpenPLX/OpenPLXRoot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGXUnity/IO/OpenPLX/OpenPLXRoot.cs b/AGXUnity/IO/OpenPLX/OpenPLXRoot.cs index 4eeedcba..27f475e1 100644 --- a/AGXUnity/IO/OpenPLX/OpenPLXRoot.cs +++ b/AGXUnity/IO/OpenPLX/OpenPLXRoot.cs @@ -31,7 +31,7 @@ public string PrunedNativeName throw new System.NullReferenceException( "Cannot get the pruned name before the OpenPLX root has been initialized" ); var prunedNativeName = Native.getName(); if ( prunedNativeName.Contains( "." ) ) - prunedNativeName = prunedNativeName[ ( prunedNativeName.IndexOf( "." ) + 1 ).. ]; + prunedNativeName = prunedNativeName[ ( prunedNativeName.LastIndexOf( "." ) + 1 ).. ]; return prunedNativeName; } } @@ -107,7 +107,7 @@ protected override bool Initialize() continue; var native = openPLXObj.FindCorrespondingNative( this, obj ); if ( native != null ) - m_nativeMap.Add( new( decl, native ) ); + m_nativeMap.Add( new( obj.getName(), native ) ); } } From 63858d9b2de0994afba37c00aabb29ad7f8a0b5f Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Thu, 5 Mar 2026 15:27:36 +0100 Subject: [PATCH 3/4] Separate game object and openplx declaration names for redirectedmateconnectors --- AGXUnity/IO/OpenPLX/InteractionMapper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AGXUnity/IO/OpenPLX/InteractionMapper.cs b/AGXUnity/IO/OpenPLX/InteractionMapper.cs index 9d4d362d..76507969 100644 --- a/AGXUnity/IO/OpenPLX/InteractionMapper.cs +++ b/AGXUnity/IO/OpenPLX/InteractionMapper.cs @@ -47,7 +47,9 @@ public void MapMateConnector( MateConnector mc ) // Since redirected MCs can be placed in other places than the unique location derived by their // names, we need to add some unique identifier to avoid name collisions in the hierarchy. - var mcObject = Data.CreateOpenPLXObject( mc.getName() + (mc is RedirectedMateConnector ? $"_rd_#{mc.To32BitFnv1aHash()}" : "") ); + var mcObject = Data.CreateOpenPLXObject( mc.getName() ); + if ( mc is RedirectedMateConnector ) + mcObject.name = mcObject.name + $"_rd_#{mc.To32BitFnv1aHash()}"; mcObject.AddComponent(); openplx.Core.Object owner = mc.getOwner(); if ( mc is RedirectedMateConnector redirected ) From 0a24209e312a0d431391ee1ecf8ad165bfec94f3 Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Thu, 5 Mar 2026 15:28:07 +0100 Subject: [PATCH 4/4] Fix naming of interface signals in nested bundles --- AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs b/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs index 7a2d0c51..7cffe0bd 100644 --- a/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs +++ b/AGXUnity/IO/OpenPLX/OpenPLXUnityMapper.cs @@ -127,10 +127,10 @@ private void MapSignals( Object obj, OpenPLXSignals signals, string prefix = "" sigInt.Enabled = sigInterface.enable(); sigInt.Inputs = new List(); foreach ( var (inpName, input) in sigInterface.getEntries() ) - sigInt.Inputs.Add( new InputTarget( sigInterface.getName() + "." + inpName, input ) ); + sigInt.Inputs.Add( new InputTarget( prefix + "." + name + "." + inpName, input ) ); sigInt.Outputs = new List(); foreach ( var (outName, output) in sigInterface.getEntries() ) - sigInt.Outputs.Add( new OutputSource( sigInterface.getName() + "." + outName, output ) ); + sigInt.Outputs.Add( new OutputSource( prefix + "." + name + "." + outName, output ) ); signals.RegisterInterface( sigInt ); }