.. include:: definitions.rstinc .. _scripting-ref: ========= Scripting ========= Many scripts in |AGXUnity| are an additional API layer on top of the AGX Dynamics API, managing data and additional features suitable for the Unity editor. The purpose of the additional data layer is mainly for serialization and transformation from/to Unity's **left** to AGX Dynamics **right** handed coordinate frames. The root namespace of runtime classes is :code:`AGXUnity` and :code:`AGXUnityEditor` for editor classes. The complete AGX Dynamics API is available in Unity and is accessed using namespaces :code:`agx`, :code:`agxCollide`, :code:`agxSDK`, :code:`agxModel`, :code:`agxTerrain`, :code:`agxVehicle`, :code:`agxHydraulics`, :code:`agxPowerLine`, :code:`agxDriveTrain`, to name a few. Instances of AGX Dynamics types are available during runtime. If instances are created within the editor loop (i.e., play isn't pressed), it's important that the lifetime of the instances are as short as possible and are properly disposed before the editor changes state. An example usage of an :code:`agx.RigidBody` instance within the editor loop: .. code:: c# using ( var rigidBody = new agx.RigidBody() ) { ... } // Or similarly: var rigidBody = new agx.RigidBody(); ... rigidBody.Dispose(); rigidBody = null; The same pattern should be applied in runtime scripts. E.g., .. code:: c# public class MyScript : UnityEngine.MonoBehaviour { private agx.RigidBody m_rigidBody = null; private void Start() { m_rigidBody = new agx.RigidBody(); } private void OnDestroy() { if ( m_rigidBody == null ) return; m_rigidBody.Dispose(); m_rigidBody = null; } } .. _script-component-ref: AGXUnity.ScriptComponent ------------------------ :code:`AGXUnity.ScriptComponent` is extending :code:`UnityEngine.MonoBehaviour` and implements additional features, such as :ref:`initialization ` and :ref:`propagation of data `. It's implicit that classes of :code:`AGXUnity.ScriptComponent` are dependent on and/or are managing native AGX Dynamics instances. They normally have a property :code:`Native` to access the native instance for full access of the AGX Dynamics API of that object. .. _script-component-initialization-of-native-instances-ref: Initialization of native instances ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The native instances are normally created when Unity calls :code:`Start` of the components of a :code:`GameObject` - if nothing else depends on the component. Instead of :code:`Start`, each :code:`AGXUnity.ScriptComponent` implements :code:`protected override bool Initialize()` which enables the native instances to be initialized in e.g., :code:`Awake` or :code:`Start` of another script. To trigger initialization or get an already initialized instance, call :code:`GetInitialized()`. If the initialization, for some reason, failed, :code:`null` is returned. The following example is assigning a tag to the native instance of a :ref:`rigid-body-ref` in the :code:`Awake` callback of the script: .. code:: c# using UnityEngine; using AGXUnity; namespace Scripts { public class MyScript : MonoBehaviour { private void Awake() { var rb = GetComponent()?.GetInitialized(); // Successfully initialized, implicit that rb.Native != null. if ( rb != null ) rb.Native.getPropertyContainer().addPropertyBool( "tag", true ); } } } .. _script-component-native-data-synchronization-ref: Native data synchronization ^^^^^^^^^^^^^^^^^^^^^^^^^^^ All data is synchronized to the native instance when the :code:`AGXUnity.ScriptComponent` has been initialized. The initial synchronization is performed by matching field names with property names of the type being initialized. Consider a custom script (inheriting from :ref:`script-component-ref`) implementing some functionality involving an :code:`agx.RigidBody`. The custom script includes properties :code:`Native`, :code:`Mass` and :code:`LinearVelocity`: .. code:: c# using UnityEngine; using AGXUnity; using AGXUnity.Utils; // For extension Vector3 -> agx.Vec3. namespace Scripts { public class MyRigidBody : ScriptComponent { public agx.RigidBody Native { get; private set; } = null; [SerializeField] private float m_mass = 1.0f; public float Mass { get { return m_mass; } set { m_mass = value; if ( Native != null ) { Debug.Log( "Setting mass: " + m_mass ); Native.getMassProperties().setMass( m_mass ); } } } [SerializeField] private Vector3 m_linearVelocity = Vector3.zero; public Vector3 LinearVelocity { get { return m_linearVelocity; } set { m_linearVelocity = value; if ( Native != null ) { Debug.Log( "Setting velocity: " + m_linearVelocity ); // Converts from left handed Vector3 velocity to right // handed agx.Vec3 velocity. Native.setVelocity( m_linearVelocity.ToHandedVec3() ); } } } protected override bool Initialize() { Native = new agx.RigidBody( name ); Native.add( new agxCollide.Geometry( new agxCollide.Sphere( 0.5 ) ) ); GetSimulation().add( Native ); Debug.Log( "Initialize success." ); return true; } protected override void OnDestroy() { if ( Simulation.HasInstance ) GetSimulation().remove( Native ); Native.Dispose(); Native = null; base.OnDestroy(); } } } In an empty scene, create a new empty :code:`GameObject`, add the :code:`MyRigidBody` component, change mass to 5 and linear velocity to (1, 2, 3), we get the following log when pressing play: .. image:: images/scripting_myrigidbody_inspector.png :align: center :width: 70% Our custom script has two matching fields and properties, namely :code:`m_mass <-> Mass` and :code:`m_linearVelocity <-> LinearVelocity`, being invoked after :code:`Initialize` returns :code:`true`. The post-initialize synchronization is basically performing: .. code:: c# // instance. = instance.m_ instance.Mass = instance.m_mass; instance.LinearVelocity = instance.m_linearVelocity; using reflection. .. note:: Unity isn't serializing private fields of components so the :code:`[SerializeField]` attribute has to be added to all private fields with matching properties for the value to be serialized. It's important and convenient to have the synchronization at one place, minimizing bugs related to data not being propagated to the simulation. Our current implementation fully supports scripts changing values of our component, e.g., .. code:: c# GetComponent().Mass = 500.0f; // Will write to Native IF created. It's also possible to change values in the Inspector when the editor isn't playing. The fields and properties synchronization takes place once, directly after initialization, and after that it's important that the public properties of our class are used for the data to be written to our native instance. Unity's default Inspector Editor is showing our private fields :code:`m_mass` and :code:`m_linearVelocity`, by removing :code:`m_`, capital first character and splitting words given camel case. E.g., adding private field: .. code:: c# [SerializeField] private int m_thisIsATestField = 0; without any public property will show up in the inspector as: .. image:: images/scripting_myrigidbody_field_inspector.png :align: center :width: 70% It's hence not possible to use Unity's default Inspector Editor if we require changes to propagate to our native instance while the editor is playing. Most components and scriptable objects in |AGXUnity| are instead using a custom Inspector Editor, rendering any public property or field in the Inspector. If enabled for our class, unlike Unity's default, our properties :code:`Mass` and :code:`LinearVelocity` will be rendered instead, and changes will show up as :code:`value` in our property set methods. Basically, our :code:`Mass` property is rendered in the Inspector as: .. code:: c# instance.Mass = UnityEditor.EditorGUILayout.FloatField( "Mass", instance.Mass ); To enable |AGXUnity| custom Inspector Editor, simply add a class inheriting from :code:`AGXUnityEditor.InspectorEditor`, with the :code:`[UnityEditor.CustomEditor( typeof( MyRigidBody ) )]` attribute. E.g., .. code:: c# #if UNITY_EDITOR namespace Scripts.Editor { [UnityEditor.CustomEditor( typeof( MyRigidBody ) )] public class MyRigidBodyEditor : AGXUnityEditor.InspectorEditor { } } #endif will result in the Inspector of :code:`MyRigidBody` to become: .. image:: images/scripting_myrigidbody_agxunity_inspector.png :align: center :width: 70% Making changes during runtime will print our logs within the :code:`if ( Native != null )` blocks and the private field :code:`This Is A Test Field` isn't shown anymore when only publicly accessible fields and properties are rendered. .. _script-asset-ref: AGXUnity.ScriptAsset -------------------- :code:`AGXUnity.ScriptAsset` is extending :code:`UnityEngine.ScriptableObject` and is assumed to be data on disk but adopts the behavior of :ref:`script-component-ref`, managing and synchronizing native instances. Similar to :ref:`script-component-ref`, any class inheriting from this class implements :code:`Initialize` and usage requires :code:`GetInitialized()` for access to its native instance. Fields and properties are also synchronized after a successful initialization. While in the editor, changes made to :code:`AGXUnity.ScriptAsset` references during runtime will *not* be reverted when the editor is stopped. This is the default behavior of Unity for :code:`UnityEngine.ScriptableObject`. .. _script-simulation-callbacks-ref: Simulation Callbacks -------------------- When the simulation is stepping, there are specific times when data should be read/written and specific data is available. E.g., collision detection has to be done for contact data to be available and the solver has to be done for constraint and contact forces to be available. The figure below summarizes the available callbacks and the order in which they are invoked during a step. .. figure:: images/events_order.png :align: center :width: 100% Callback names and order in which they are invoked. See :ref:`script-step-callbacks-ref` and :ref:`script-contact-callbacks-ref` for details of each individual callback. .. _script-step-callbacks-ref: Step Callbacks ^^^^^^^^^^^^^^ Step callbacks are callbacks made during the simulation step. In order: #. **PreStepForward**: First callback from within :code:`AGXUnity.Simulation`. This callback can, e.g., be used to change initial conditions for the current step. #. **PreSynchronizeTransforms**: Callback from within :code:`AGXUnity.Simulation`. This callback is used to write current transforms to native instances. #. **SimulationPreCollide**: Callback from within the native :code:`agxSDK.Simulation` step, before collision detection is performed. This callback can, e.g., be used to do final changes that collision detection depends on. #. **SimulationPre**: Callback from within the native :code:`agxSDK.Simulation` step, before the dynamics solvers are solving the system. This callback can, e.g., be used to check and modify contact data (more in :ref:`script-contact-callbacks-ref`) or change initial conditions for the solver, such as constraint compliance and damping. #. **SimulationPost**: Callback from within the native :code:`agxSDK.Simulation` step, after the dynamics has been solved and all moving objects has new transforms. Note that their Unity counterparts hasn't updated their transforms yet. This callback can, e.g., be used to monitor data, such as constraint/contact forces and torques, from the solve. #. **SimulationLast**: Callback from within the native :code:`agxSDK.Simulation` step, last thing that occurs before the native step is done. This callback can, e.g., be used to summarize simulation data gathered in many post callbacks. #. **PostSynchronizeTransforms**: Callback from within :code:`AGXUnity.Simulation`. This callback is used to read transforms from the native simulation, writing them to the corresponding Unity transforms. #. **PostStepForward**: Callback from within :code:`AGXUnity.Simulation`. Last callback before the simulation step is done where all data can be assumed to be up to date in Unity. All step callbacks signatures are :code:`void Callback()` and to assign to a callback, e.g., from a :code:`UnityEngine.MonoBehaviour` script, do: .. code:: c# private void OnEnable() { Simulation.Instance.StepCallbacks.PreStepForward += () => { Debug.Log( "Inline: " + Simulation.Instance.Native.getTimeStamp() ); }; Simulation.Instance.StepCallbacks.PreStepForward += OnPreStepForward; } private void OnPreStepForward() { Debug.Log( "OnPreStepForward: " + Simulation.Instance.Native.getTimeStamp() ); } Use :code:`-=` to remove a callback. .. _scripts-constraint-force-collector-ref: Constraint force collector @@@@@@@@@@@@@@@@@@@@@@@@@@ An example script, :code:`ConstraintForceCollector.cs`, collecting forces and torques applied to the one or two rigid bodies involved in a constraint. The script is collecting forces in **SimulationPost** so the fresh data is available in **SimulationLast** or **PostStepForward**. .. code:: c# using System.Collections.Generic; using UnityEngine; using AGXUnity; using AGXUnity.Utils; namespace Scripts { public class ConstraintForceCollector : MonoBehaviour { public struct ForceData { public float Time; public Vector3 RigidBody1Force; public Vector3 RigidBody2Force; public Vector3 RigidBody1Torque; public Vector3 RigidBody2Torque; } public Constraint Constraint = null; public ForceData[] Data => m_stepForceData?.ToArray() ?? new ForceData[] { }; private void Start() { if ( Constraint == null ) { Debug.LogError( "No constraint assigned.", this ); return; } // If initialization fails the error will be logged from // the constraint. if ( Constraint.GetInitialized() == null ) return; m_nativeConstraint = Constraint.Native; // The constraint forces in the constraint DOFs are always // available, this enables the constraint to compute the // resulting forces and torques applied to each body in the // constraint. This feature is disabled by default. m_nativeConstraint.setEnableComputeForces( true ); m_stepForceData = new List(); } private void OnEnable() { // Collect the data as soon as the solver has solved the system. Simulation.Instance.StepCallbacks.SimulationPost += OnSimulationPost; } private void OnDisable() { // Remove us from the callback when this component has been disabled. Simulation.Instance.StepCallbacks.SimulationPost -= OnSimulationPost; } /// /// Called after the dynamics solvers are done and the constraint forces /// has been computed by the constraint. /// private void OnSimulationPost() { if ( m_nativeConstraint == null ) return; // Collect forces and torques applied to the two bodies this current step. agx.Vec3 rb1Force = new agx.Vec3(), rb1Torque = new agx.Vec3(); agx.Vec3 rb2Force = new agx.Vec3(), rb2Torque = new agx.Vec3(); m_nativeConstraint.getLastForce( 0, ref rb1Force, ref rb1Torque ); m_nativeConstraint.getLastForce( 1, ref rb2Force, ref rb2Torque ); m_stepForceData.Add( new ForceData() { Time = (float)Simulation.Instance.Native.getTimeStamp(), RigidBody1Force = rb1Force.ToHandedVector3(), RigidBody1Torque = rb1Torque.ToHandedVector3(), RigidBody2Force = rb2Force.ToHandedVector3(), RigidBody2Torque = rb2Torque.ToHandedVector3() } ); } private agx.Constraint m_nativeConstraint = null; private List m_stepForceData = null; } } .. _script-contact-callbacks-ref: Contact Callbacks ^^^^^^^^^^^^^^^^^ There are three contact events signed :code:`OnContact`, :code:`OnForce` and :code:`OnSeparation`. The :code:`OnContact` and :code:`OnSeparation` callbacks are invoked after collision detection and before the step callback **SimulationPre** - see figure in :ref:`script-simulation-callbacks-ref`. At this point it's possible to monitor and/or manipulate the contact data before passed to the dynamics solvers, solving the contact. The contact data is accessible in all **SimulationPre** callbacks but will be cleared after that. :code:`OnForce` callbacks are invoked before **SimulationPost** when the contact force data is available. The contact and force data is available in :code:`OnForce` and in all **SimulationPost** callbacks but will be cleared after that. :code:`OnSeparation` callbacks, describing component pairs previously in contact (last step), are invoked before any :code:`OnContact` callbacks and has a different signature since there is no contact data. See :ref:`script-separation-data-ref` for more information on separations. **An important note**, in general, only a subset of all contacts in the simulation will have its representation as :ref:`script-contact-data-ref`. Only the contact data that the registered callbacks expects are generated where each callback acts as a filter for which contact data that should be generated. This means that it's not possible to only have a :ref:`script-step-callbacks-ref` :code:`SimulationPre` and/or :code:`SimulationPost` and expect the data to be available. You'll have to register a callback as well for the contact you're interested in. For details, see :ref:`script-listening-to-contact-data-ref`. .. _script-contact-data-ref: Contact Data @@@@@@@@@@@@ The :code:`AGXUnity.ContactData` struct is representing the :code:`agxCollide.GeometryContact` class in AGX Dynamics. The data is an array of :ref:`script-contact-point-data-ref`, enabled, two interacting components and the two geometries (:code:`agxCollide.Geometry`) matched to respective component. .. code:: c# // Complete struct and API documentation in AGXUnity/AGXUnity/ContactData.cs. public struct ContactData { public ScriptComponent Component1; public ScriptComponent Component2; public bool Enabled; public RefArraySegment Points; public agxCollide.Geometry Geometry1 { get; private set; } public agxCollide.Geometry Geometry2 { get; private set; } ... } .. csv-table:: :align: center :widths: 1, 5 :header: "Property", "Description" "**Component1**", "First interacting component, :code:`null` if not found." "**Component2**", "Second interacting component, :code:`null` if not found." "**Enabled**", "True if this contact is enabled and will be evaluated by the solver, false if disabled and about to be removed." "**Points**", ":ref:`Contact points ` in this contact." "**Geometry1**", "The :code:`agxCollide.Geometry` in contact - representing **Component1**." "**Geometry2**", "The :code:`agxCollide.Geometry` in contact - representing **Component2**." .. note:: Accessing :code:`Geometry1` and/or :code:`Geometry2` will create garbage and it's only safe to access the geometries during the lifetime of the contact data, i.e., :code:`OnContact`, :code:`OnSeparation` and :code:`SimulationPre`, or :code:`OnForce` and :code:`SimulationPost`. If a copy of the contact data is stored, make sure to invalidate the geometries before: :code:`contactData.InvalidateGeometries()`. .. _script-contact-point-data-ref: Contact Point Data @@@@@@@@@@@@@@@@@@ The :code:`AGXUnity.ContactPointData` struct is representing the :code:`agxCollide.ContactPoint` class in AGX Dynamics. Data such as contact normal and tangents (friction directions), contact depth, surface velocity and enabled state are available for each contact point. Manipulation of this data, affecting the system, is only possible in :code:`OnContact` callbacks. Contact forces in :ref:`script-contact-point-force-data-ref` are only available in :code:`OnForce` and :code:`SimulationPost`, i.e., the force data is :code:`null` in :code:`OnContact` and :code:`SimulationPre`. If you're unsure, verify :code:`point.HasForce == true` and/or :code:`contactData.HasContactPointForceData == true`. .. code:: c# // Complete struct and API documentation in AGXUnity/AGXUnity/ContactData.cs. public struct ContactPointData { public Vector3 Position; public Vector3 Normal; public Vector3 PrimaryTangent; public Vector3 SecondaryTangent; public Vector3 SurfaceVelocity; public float Depth; public bool Enabled; public bool HasForce; public ContactPointForceData Force; ... } .. |cp-force-desc| replace:: :ref:`Contact point forces ` available in :code:`OnForce` and/or :code:`SimulationPost`. This data is a :code:`Nullable` but a default instance (all zeros) is returned if this property is accessed when the data isn't available. The force data is always accessed by value and can only be modified locally. .. csv-table:: :align: center :widths: 1, 5 :header: "Property", "Description" "**Position**", "Position of this contact point in world coordinate frame." "**Normal**", "Contact normal of this contact point in world coordinate frame." "**PrimaryTangent**", "Primary tangent (friction) direction of this contact point in world coordinate frame." "**SecondaryTangent**", "Secondary tangent (friction) direction of this contact point in world coordinate frame." "**SurfaceVelocity**", "Target surface velocity (think conveyor belt) of this contact point in world coordinate frame." "**Depth**", "Penetration depth of this contact point." "**Enabled**", "True if this contact point is enabled and will be evaluated by the solver, false if disabled." "**HasForce**", "True if this contact point has force data, i.e., this contact point has been solved." "**Force**", "|cp-force-desc|" .. _script-contact-point-force-data-ref: Contact Point Force Data @@@@@@@@@@@@@@@@@@@@@@@@ The :code:`AGXUnity.ContactPointForceData` struct is representing the force data in the :code:`agxCollide.ContactPoint` class in AGX Dynamics. This data is `Nullable` in the :ref:`script-contact-point-data-ref` even though a default instance is returned if its property :code:`Force` is accessed without having read the force data from an :code:`agxCollide.ContactPoint`. This data is available in :code:`OnForce` and :code:`SimulationPost`. .. code:: c# // Complete struct and API documentation in AGXUnity/AGXUnity/ContactData.cs. public struct ContactPointForceData { public Vector3 Normal; public Vector3 PrimaryTangential; public Vector3 SecondaryTangential; public bool IsImpacting; public Vector3 Tangential => PrimaryTangential + SecondaryTangential; public Vector3 Total => Normal + Tangential; } The field names of :code:`ContactPointForceData` needs context to make sense. Each field/property could have the postfix :code:`Force` but will then be redundant given they are accessed from :ref:`script-contact-point-data-ref`, i.e., :code:`contactPoint.Force.Normal` vs. :code:`contactPoint.Force.NormalForce`. Think field/property postfix :code:`Force` in the following table: .. csv-table:: :align: center :widths: 1, 5 :header: "Property", "Description" "**Normal**", "Normal force given in world coordinate frame." "**PrimaryTangential**", "Primary tangential (friction) force given in world coordinate frame." "**SecondaryTangential**", "Secondary tangential (friction) force given in world coordinate frame." "**IsImpacting**", "True if the solver solved the contact point as impacting, where restitution of the :ref:`contact-material-ref` was used, otherwise false." "**Tangential**", "Total tangential (friction) force given in world coordinate frame." "**Total**", "Total (normal + friction) force given in world coordinate frame." .. _script-separation-data-ref: Separation Data @@@@@@@@@@@@@@@ The :code:`AGXUnity.SeparationData` struct in :code:`OnSeparation` represents two previously overlapping components that no longer are in contact. .. code:: c# // Complete struct and API documentation in AGXUnity/AGXUnity/ContactData.cs. public struct SeparationData { public ScriptComponent Component1; public ScriptComponent Component1; public agxCollide.Geometry Geometry1 { get; private set; } public agxCollide.Geometry Geometry2 { get; private set; } } .. csv-table:: :align: center :widths: 1, 5 :header: "Property", "Description" "**Component1**", "First separating component, :code:`null` if not found." "**Component2**", "Second separating component, :code:`null` if not found." "**Geometry1**", "The separating native :code:`agxCollide.Geometry` - representing **Component1**." "**Geometry2**", "The separating native :code:`agxCollide.Geometry` - representing **Component2**." .. note:: Accessing :code:`Geometry1` and/or :code:`Geometry2` will create garbage and it's only safe to access the geometries during the lifetime of the contact data, i.e., :code:`OnContact`, :code:`OnSeparation` and :code:`SimulationPre`, or :code:`OnForce` and :code:`SimulationPost`. If a copy of the contact data is stored, make sure to invalidate the geometries before: :code:`contactData.InvalidateGeometries()`. .. _script-listening-to-contact-data-ref: Listening to :ref:`Contact Data ` @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Given all contacts in the simulation, listeners (callbacks in this context) acts as filters matching contacts that should be processed. This is the behavior of AGX Dynamics :code:`agxSDK.ContactEventListener` with a given :code:`agxSDK.ExecuteFilter`. I.e., every registered listener only receives callbacks for contacts they are interested in. The filtering is applied for performance and convenience reasons when it's not effective in any context for each listener to do the filtering. Each implementation of an :code:`agxSDK.ExecuteFilter` is matching two instances of an `agxCollide.Geometry` present in a given `agxCollide.GeometryContact`, and if any implementation of the :code:`agxSDK.ExecuteFilter` matches the pair of geometries its corresponding listener is invoked. |AGXUnity| is using an :code:`agxSDK.ExecuteFilter` which is matching *corresponding* UUIDs of a geometry to a registered component. Only AGX Dynamics knows what a given :code:`agxCollide.Geometry` instance belongs to. E.g., a :ref:`wire-ref` or a :ref:`cable-ref` dynamically manages many native geometries that doesn't have a representation in |AGXUnity|. The filter enables match where a subject geometry corresponds to an UUID of a :ref:`wire-ref` or :ref:`cable-ref`. To register a contact callback/listener: .. code:: c# private bool MyContactCallback( ref AGXUnity.ContactData contactData ) { var hasModifications = false; ... return hasModifications; } private void MySeparationCallback( AGXUnity.SeparationData separationData ) { ... } - :code:`AGXUnity.Simulation.Instance.ContactCallbacks.OnContact( MyContactCallback, ... )` - :code:`AGXUnity.Simulation.Instance.ContactCallbacks.OnForce( MyContactCallback, ... )` - :code:`AGXUnity.Simulation.Instance.ContactCallbacks.OnContactAndForce( MyContactCallback, ... )` - :code:`AGXUnity.Simulation.Instance.ContactCallbacks.OnSeparation( MySeparationCallback, ... )` - :ref:`script-custom-contact-listener-ref`. where - **OnContact** is listening to contacts after collision detection and before the dynamics solvers. These callbacks may modify the contact data and the data will be propagated to the simulation if/when a callback returns :code:`true`, see :ref:`script-modifying-contact-data-ref` for more information. - **OnForce** is listening to contacts after the dynamics solvers has solved the contacts. :ref:`script-contact-point-force-data-ref` are available in these callbacks. Any modification to the contact data has zero effect in the simulation. - **OnContactAndForce** and the given callback will receive both **OnContact** and **OnForce**. :code:`contactData.HasContactPointForceData` is false while in **OnContact** and true in **OnForce**. - **OnSeparation** is listening to contacts from the previous step that was removed after the collision detection of the current step. Unlike the contact and force callbacks it is not possible to modify the contacts themselves in the **OnSeparation** callback. and :code:`...` is an arbitrary number of :code:`AGXUnity.ScriptComponent` instances. The behavior of the contact filtering depends on the number of given :code:`AGXUnity.ScriptComponent` instances, where - **0**: All contacts in the simulation will be passed to the callback. - **1**: All contacts interacting with the given component will be passed to the callback. - **>= 2**: All contacts **between** the given components will be passed to the callback. E.g., .. code:: c# namespace Scripts { public class MyContactListener : MonoBehaviour { public AGXUnity.Wire Wire = null; public AGXUnity.Collide.Box Box = null; public AGXUnity.RigidBody ARigidBody = null; public AGXUnity.RigidBody AnotherRigidBody = null; private void Start() { // 0 components: OnContactAll will receive all contacts in the simulation. AGXUnity.Simulation.Instance.ContactCallbacks.OnContact( OnContactAll ); // 1 component: OnContactBox will receive contacts where our Box is involved. AGXUnity.Simulation.Instance.ContactCallbacks.OnContact( OnContactBoxAnything, Box ); // 2 components: OnContactARigidBodyAnotherRigidBody will receive contacts // between ARigidBody and AnotherRigidBody only. AGXUnity.Simulation.Instance.ContactCallbacks.OnContact( OnContactARigidBodyAnotherRigidBody, ARigidBody, AnotherRigidBody ); // 4 components: OnContactInternalObjects will receive contacts between // our Wire, Box, ARigidBody and/or AnotherRigidBody. AGXUnity.Simulation.Instance.ContactCallbacks.OnContact( OnContactInternalObjects, Wire, Box, ARigidBody, AnotherRigidBody ); } private bool OnContactAll( ref AGXUnity.ContactData contactData ) { return false; } private bool OnContactBoxAnything( ref AGXUnity.ContactData contactData ) { return false; } private bool OnContactARigidBodyAnotherRigidBody( ref AGXUnity.ContactData contactData ) { return false; } private bool OnContactInternalObjects( ref AGXUnity.ContactData contactData ) { return false; } } } .. note:: When listening to a :ref:`rigid-body-ref`, the component of its interacting shape will be in the contact data :code:`Component1` or :code:`Component2` instead of the registered :ref:`rigid-body-ref`. .. _script-modifying-contact-data-ref: Modifying :ref:`Contact Data ` ??????????????????????????????????????????????????????? All available data supports modification with the exception of the components and geometries involved. The data will be synchronized with its native representation when an :code:`OnContact` callback returns :code:`true`. The following example is calculating and modifying the target surface velocity of each contact point in a contact. The surface velocity is calculated such that the subject will rotate about its up axis given that the shape is symmetric relative its center axis, e.g., :ref:`shape-box-ref`. .. code:: c# using UnityEngine; using AGXUnity; namespace Scripts { public class MyContactListener : MonoBehaviour { public float SurfaceSpeed = 1.0f; private void Start() { var rb = GetComponent(); if ( rb == null ) { Debug.LogWarning( "MyContactListener: Expecting a RigidBody component.", this ); return; } Debug.Log( "Modifying surface velocity of " + rb.name + "." ); Simulation.Instance.ContactCallbacks.OnContact( OnContact, rb ); } private bool OnContact( ref ContactData data ) { foreach ( ref var point in data.Points ) { var centerToPoint = ( point.Position - Vector3.Scale( data.Component1.transform.position, new Vector3( 1, 0, 1 ) ) ).normalized; point.SurfaceVelocity = SurfaceSpeed * Vector3.Cross( centerToPoint, Vector3.up ); } return true; } } } Note that :code:`ref var point` is used in the :code:`foreach` loop over the contact points. Without :code:`ref var` there, it's an error to write data to :code:`point` (*error CS1654: Cannot modify members of 'point' because it is a 'foreach iteration variable'*). .. _script-custom-contact-listener-ref: Custom AGXUnity.ContactListener [Advanced] ?????????????????????????????????????????? Registering callbacks results in an :code:`AGXUnity.ContactListener` being created and added to the :code:`AGXUnity.ContactEventHandler` of a simulation. It's possible to subclass :code:`AGXUnity.ContactListener` and change behavior of the contacts being matched and collected. The example below is configuring a listener that listens to objects in contact with :ref:`shape-sphere-ref` instances in a scene. Overridden methods are :code:`Initialize` and :code:`Remove`. As described in the comments, :code:`Initialize` is called every step from a :code:`PreStepForward` registered by the :code:`AGXUnity.ContactEventListener`. It's called all the time because a listener may wait for certain objects or implement other types of runtime functionality. What's important is that the filter is up to date before the collision detection is performed. The :code:`agxSDK.ExecuteFilter` of the default implementation of :code:`AGXUnity.ContactListener` is an :code:`agxSDK.UuidHashCollisionFilter`. The filter may be of any type of the :code:`agxSDK.ExecuteFilter` classes in AGX Dynamics but :code:`agxSDK.UuidHashCollisionFilter` is the preferred due to the connection of |AGXUnity| components with a :code:`Native` property and its UUID with the usage of :code:`uuidToComponentMap[ agxSDK.UuidHashCollisionFilter.findCorrespondingUuid( geometry ) ]` when determining :code:`Component1` and :code:`Component2` in the matched contact data. When a filter different from :code:`agxSDK.UuidHashCollisionFilter` is used, make sure to override both :code:`Initialize` and :code:`Remove` because the default implementations of these methods assumes the filter is of type :code:`agxSDK.UuidHashCollisionFilter`. Note that the main behavior change of this example listener is how the native filter is consistent in its matches given an arbitrarily number of subjects (spheres in this example). The *mode* of the :code:`agxSDK.UuidHashCollisionFilter` is always set to :code:`MATH_OR`. This means that only, at least one of the interacting geometries has to be a match in our UUID set. The *modes* of the filter are :code:`MATCH_ALL`, :code:`MATCH_OR` and :code:`MATCH_AND`, and it corresponds to the operator used to result in a match. Given two geometries in an :code:`agxCollide.GeometryContact`, the matching implementation basically is (C++): .. code:: c++ bool match( const agxCollide::Geometry* geometry1, const agxCollide::Geometry* geometry 2 ) const { return getMode() == MATCH_ALL || ( getMode() == MATCH_OR && ( match( geometry1 ) || match( geometry2 ) ) ) || ( getMode() == MATCH_AND && ( match( geometry1 ) && match( geometry2 ) ) ); } bool match( const agxCollide::Geometry* geometry ) const { const auto rbUuid = geometry->getRigidBody() != nullptr ? geometry->getRigidBody()->getUuid().hash() : 0u; return uuidSet.contains( findCorrespondingUuid( geometry ) ) || uuidSet.contains( rbUuid ); } Make sure to read all the comments in the following example, all comments are specific to this example. .. code:: c# using UnityEngine; using AGXUnity; namespace Scripts { public class SphereContactListener : ContactListener { public SphereContactListener() { // IMPORTANT: Callback has to be assigned before a listener // is added to the contact event handler. ContactCallback = OnContact; SeparationCallback = OnSeparation; // Default activation mask is IMPACT | CONTACT so SEPARATION has to be added or the separation callback will be ignored m_activationMask = agxSDK.ContactEventListener.ActivationMask.IMPACT | agxSDK.ContactEventListener.ActivationMask.CONTACT | agxSDK.ContactEventListener.ActivationMask.SEPARATION; } /// /// Called each step from a PreStepForward callback in the /// contact event handler this listener belongs to. /// /// Contact event handler this listener belongs to. public override void Initialize( ContactEventHandler handler ) { // Already initialized. if ( Filter != null ) return; var filter = new agxSDK.UuidHashCollisionFilter(); var spheres = Object.FindObjectsOfType(); Debug.Log( "Scripts.SphereContactListener: Found " + spheres.Length + " spheres to listen to." ); // Adding the UUIDs of the spheres to our filter. foreach ( var sphere in spheres ) { var uuid = handler.GetUuid( sphere ); if ( uuid == 0u ) { Debug.LogWarning( "Unable to find UUID for sphere " + sphere.name + "." ); continue; } filter.add( uuid ); } // Save the spheres in our Components array to keep track of the number // spheres left when objects are destroyed (see our Remove). Components = spheres; // MATCH_ALL: Any pair of geometries is a match (listen to all contacts). // MATCH_OR: At least one of the corresponding UUID must match our UUID set. // MATCH_AND: Both corresponding UUIDs must match our UUID set. // // We want to match if at least one of the colliding objects is a sphere. filter.setMode( agxSDK.UuidHashCollisionFilter.Mode.MATCH_OR ); // Add our filter given an activation mask for the agxSDK::ContactEventListener // being created as a result of this call. handler.GeometryContactHandler.Native.add( Filter, (int)m_activationMask ); // Our filter instance is used to fetch matched contact indices from // the AGXUnity.GeometryContactHandler when the callbacks are performed. Filter = filter; } /// /// Called from our ContactEventHandler when a component is about to be /// destroyed. If it is a sphere we remove it from our array of components. /// When all our spheres has been destroyed we return true, meaning this /// listener will also be removed from the ContactEventHandler. /// /// Native UUID being removed. /// Component about to be destroyed. /// /// True if we should print a message about our decision to be removed. /// /// True if this listener should be remove, otherwise false. public override bool Remove( uint uuid, ScriptComponent component, bool notifyOnRemove = true ) { var sphere = component as AGXUnity.Collide.Sphere; if ( sphere == null ) return false; var filter = GetFilter(); if ( filter != null ) { if ( !filter.contains( uuid ) ) return false; filter.remove( uuid ); } Components = System.Array.FindAll( Components, sphereComponent => sphereComponent != component ); var removeUs = filter == null || Components.Length == 0; if ( removeUs && notifyOnRemove ) { Debug.Log( "Scripts.SphereListener: Component " + component.name + " was our last sphere - removing us from listening to spheres." ); } return removeUs; } private bool OnContact( ref ContactData data ) { Debug.Assert( data.Component1 is AGXUnity.Collide.Sphere || data.Component2 is AGXUnity.Collide.Sphere ); return false; } private void OnSeparation( SeparationData data ) { Debug.Assert( data.Component1 is AGXUnity.Collide.Sphere || data.Component2 is AGXUnity.Collide.Sphere ); } } } To add our custom contact lister do, e.g., :code:`AGXUnity.Simulation.Instance.ContactCallbacks.Add( new SphereContactListener() )`. .. _script-contact-callbacks-performance-ref: Performance @@@@@@@@@@@ The filters and contact event listeners collecting matching contacts are implemented in AGX Dynamics, which in general is optimal performance. I.e., there is no interop between C++ and .NET when the contact event listeners are executed. The managed :ref:`script-contact-data-ref` is generated and updated without any garbage being waisted. Still, listening to contacts isn't free of CPU time, and it's important that the contact callbacks are configured such that the enabled filters and generated contacts both are as low as possible. Consider a simulation with many contacts. Generating matched :ref:`script-contact-data-ref` takes approximately 1 to 1.5 milliseconds per 1000 contact **points**. Filtering the contacts in AGX Dynamics is an :code:`O(N*M)` operation, where :code:`N` is the total number of geometry contacts in the simulation and :code:`M` the number of filters. Considering the total number of geometry contacts as a constant (not easy to affect), it's desired that :code:`M << N`, i.e., **having a low number of filters listening to many contacts is better for performance** than having many filters matching less contacts. The filtering performance explodes when :code:`N >> 1` and :code:`N ~= M`. .. _runtime-objects-ref: Runtime Objects --------------- A common problem that arises when scripting is the need to create and manage multiple gameobjects in a hierarchical way. In AGXUnity these objects are managed by a single instance object which is added to the scene called the :code:`RuntimeObjects` singleton. This class allows the user to create gameobjects under a single root object which makes it easy to localize runtime objects in the scene. This is used in various places throughout the AGXUnity plugin. A couple of examples include GameObject rendering of :ref:`Cables ` and :ref:`Terrain particles `, as well as debug rendering by the :ref:`debug-render-manager-ref`. When runtime objects are created, they are added to a child object under the *RuntimeObject* singleton and are given a unique name. .. figure:: images/runtime_objects_hierarchy.png :align: center :width: 80% An example hierarchy of runtime objects with :ref:`track-rendering-ref` and :ref:`debug-render-manager-ref`. .. _custom-target-tool-ref: Custom Target Tool [Editor] --------------------------- On top of having custom Inspector Editors it's possible to implement extended functionality for a class using *tools*. A Custom Target Tool is a top level tool for a given target type that's active when the given type is rendered in the Inspector with the |AGXUnity| custom Inspector Editor. Any active Custom Target Tool is the root of a tree of other tools related to functionality of the target type. E.g., :code:`AGXUnityEditor.Tools.ConstraintTool` is a Custom Target Tool for type :code:`AGXUnity.Constraint` with several other tools (in breadth and depth), e.g., selecting parent, edge, point, direction etc., of a constraint. Extending our :code:`MyRigidBody` script in :ref:`script-component-native-data-synchronization-ref` with a custom tool, simply add a class inheriting from :code:`AGXUnityEditor.Tools.CustomTargetTool` with the :code:`AGXUnityEditor.CustomTool( typeof( MyRigidBody ) )` attribute: .. code:: c# #if UNITY_EDITOR namespace Scripts.Editor { [UnityEditor.CustomEditor( typeof( MyRigidBody ) )] public class MyRigidBodyEditor : AGXUnityEditor.InspectorEditor { } [AGXUnityEditor.CustomTool( typeof( MyRigidBody ) )] public class MyRigidBodyTool : AGXUnityEditor.Tools.CustomTargetTool { /// /// Construction with the current selection of our target type. /// /// Selected targets of type MyRigidBody. public MyRigidBodyTool( Object[] targets ) : base( targets ) { } } } #endif Our tool is instantiated when our target type, :code:`MyRigidBody`, is selected or when the number of selected targets is changed. Overriding :code:`OnPreTargetMembersGUI`, called before members are being rendered in the Inspector, adding a tool button to select parent in the Scene View. :code:`OnPostTargetMembersGUI` is also overridden to show where we are in the Inspector when that method is called and :code:`OnSceneViewGUI` to render our rigid body sphere. .. code:: c# #if UNITY_EDITOR namespace Scripts.Editor { [UnityEditor.CustomEditor( typeof( MyRigidBody ) )] public class MyRigidBodyEditor : AGXUnityEditor.InspectorEditor { } [AGXUnityEditor.CustomTool( typeof( MyRigidBody ) )] public class MyRigidBodyTool : AGXUnityEditor.Tools.CustomTargetTool { /// /// Construction with the current selection of our target type. /// /// Selected targets of type MyRigidBody. public MyRigidBodyTool( Object[] targets ) : base( targets ) { } /// /// True if our select parent tool is active, otherwise false. /// public bool SelectParentActive { get { return GetChild() != null; } set { // Activate the tool and set parent of our targets // to the given game object if selected. if ( value && !SelectParentActive ) { var selectParentTool = new AGXUnityEditor.Tools.SelectGameObjectTool(); selectParentTool.OnSelect += parent => { using ( new AGXUnityEditor.Utils.UndoCollapseBlock( "Set parent" ) ) foreach ( var target in GetTargets() ) UnityEditor.Undo.SetTransformParent( target.transform, parent.transform, "New parent for " + target.name ); }; AddChild( selectParentTool ); } // Deactivate the tool. if ( !value && SelectParentActive ) RemoveChild( GetChild() ); } } public override void OnPreTargetMembersGUI() { // Called before the members of MyRigidBody is rendered in // the Inspector. var buttonIcon = AGXUnityEditor.ToolIcon.SelectParent; var buttonTooltip = "Select parent of our target(s) MyRigidBody component(s) transform."; // Button is pressed, toggle active state. System.Action onButtonPressed = () => SelectParentActive = !SelectParentActive; var buttonData = AGXUnityEditor.InspectorGUI.ToolButtonData.Create( buttonIcon, SelectParentActive, buttonTooltip, onButtonPressed ); AGXUnityEditor.InspectorGUI.ToolButtons( buttonData ); if ( SelectParentActive ) AGXUnityEditor.InspectorGUI.ToolDescription( "Select parent by clicking an " + "object in Scene View." ); UnityEditor.EditorGUILayout.TextField( "This is before members of " + Targets[ 0 ].GetType().FullName + " are being rendered.", AGXUnityEditor.InspectorEditor.Skin.TextField ); AGXUnityEditor.InspectorGUI.Separator( height: 1, space: 4 ); } public override void OnPostTargetMembersGUI() { // Called after the members of MyRigidBody has been rendered // in the Inspector. AGXUnityEditor.InspectorGUI.Separator( height: 1, space: 4 ); UnityEditor.EditorGUILayout.TextField( "This is after members of " + Targets[ 0 ].GetType().FullName + " are being rendered.", AGXUnityEditor.InspectorEditor.Skin.TextField ); } public override void OnSceneViewGUI( UnityEditor.SceneView sceneView ) { // Scene view update, from here we can render windows and/or // objects in the scene view. foreach ( var target in GetTargets() ) DebugRender( target.transform.position, 0.5f, Color.red ); } } } #endif The Inspector before the :code:`MyRigidBodyTool` is activated: .. image:: images/scripting_myrigidbody_agxunity_inspector.png :align: center :width: 70% The Inspector after the :code:`MyRigidBodyTool` is activated: .. figure:: images/scripting_myrigidbody_tool_inspector.png :align: center :width: 100%