//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Runtime.InteropServices; namespace BansheeEngine { /** @addtogroup Physics * @{ */ /// /// Base class for all Joint types. Joints constrain how two rigidbodies move relative to one another (for example a /// door hinge). One of the bodies in the joint must always be movable (that is non-kinematic). /// public abstract class Joint : Component { internal NativeJoint native; [SerializeField] internal SerializableData commonData = new SerializableData(); /// /// Triggered when the joint's break force or torque is exceeded. /// public event Action OnJointBreak; /// /// Maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// public float BreakForce { get { return commonData.@internal.breakForce; } set { if (commonData.@internal.breakForce == value) return; commonData.@internal.breakForce = value; if (native != null) native.BreakForce = value; } } /// /// Sets the maximum force the joint can apply before breaking. Broken joints no longer participate in physics /// simulation. /// public float BreakTorque { get { return commonData.@internal.breakTorque; } set { if (commonData.@internal.breakTorque == value) return; commonData.@internal.breakTorque = value; if (native != null) native.BreakTorque = value; } } /// /// Determines whether collisions between the two bodies managed by the joint are enabled. /// public bool EnableCollision { get { return commonData.@internal.enableCollision; } set { if (commonData.@internal.enableCollision == value) return; commonData.@internal.enableCollision = value; if (native != null) native.EnableCollision = value; } } /// /// Returns one of the bodies managed by the joint. /// /// Which of the rigidbodies to return. /// Rigidbody managed by the joint, or null if none. public Rigidbody GetRigidbody(JointBody body) { return commonData.bodies[(int) body]; } /// /// Sets a body managed by the joint. One of the bodies must be movable (non-kinematic). /// /// Which of the rigidbodies to set. /// Rigidbody to managed by the joint, or null. If one of the bodies is null the other /// one will be anchored globally to the position/rotation set by /// and . public void SetRigidbody(JointBody body, Rigidbody rigidbody) { if (commonData.bodies[(int)body] == rigidbody) return; if (commonData.bodies[(int)body] != null) commonData.bodies[(int)body].SetJoint(null); commonData.bodies[(int)body] = rigidbody; if (rigidbody != null) commonData.bodies[(int)body].SetJoint(this); // If joint already exists, destroy it if we removed all bodies, otherwise update its transform if (native != null) { if (!IsBodyValid(commonData.bodies[0]) && !IsBodyValid(commonData.bodies[0])) DestroyNative(); else { native.SetRigidbody(body, rigidbody); UpdateTransform(body); } } else // If joint doesn't exist, check if we can create it { // Must be an active component and at least one of the bodies must be non-null if (SceneObject.Active && (IsBodyValid(commonData.bodies[0]) || IsBodyValid(commonData.bodies[0]))) { RestoreNative(); } } } /// /// Returns the position at which the body is anchored to the joint. /// /// Which body to retrieve position for. /// Position relative to the body. public Vector3 GetPosition(JointBody body) { return commonData.positions[(int)body]; } /// /// Sets the position at which the body is anchored to the joint. /// /// Which body set the position for. /// Position relative to the body. public void SetPosition(JointBody body, Vector3 position) { if (commonData.positions[(int)body] == position) return; commonData.positions[(int) body] = position; if (native != null) UpdateTransform(body); } /// /// Returns the rotation at which the body is anchored to the joint. /// /// Which body to retrieve rotation for. /// Rotation relative to the body. public Quaternion GetRotation(JointBody body) { return commonData.rotations[(int)body]; } /// /// Sets the rotation at which the body is anchored to the joint. /// /// Which body set the rotation for. /// Rotation relative to the body. public void SetRotation(JointBody body, Quaternion rotation) { if (commonData.rotations[(int)body] == rotation) return; commonData.rotations[(int)body] = rotation; if (native != null) UpdateTransform(body); } /// /// Triggered when the joint breaks. /// internal void DoOnJointBreak() { if (OnJointBreak != null) OnJointBreak(); } /// /// Notifies the joint that one of the attached rigidbodies moved and that its transform needs updating. /// /// Rigidbody that moved. internal void NotifyRigidbodyMoved(Rigidbody body) { if (native == null) return; // If physics update is in progress do nothing, as its the joint itself that's probably moving the body if (Physics.IsUpdateInProgress) return; if (commonData.bodies[0] == body) UpdateTransform(JointBody.Target); else if (commonData.bodies[1] == body) UpdateTransform(JointBody.Anchor); } /// /// Creates the internal representation of the Joint for use by the component. /// /// New native joint object. internal abstract NativeJoint CreateNative(); private void OnInitialize() { NotifyFlags = TransformChangedFlags.Transform | TransformChangedFlags.Parent; } private void OnEnable() { if(IsBodyValid(commonData.bodies[0]) || IsBodyValid(commonData.bodies[1])) RestoreNative(); } private void OnDisable() { DestroyNative(); } private void OnDestroy() { if (commonData.bodies[0] != null) commonData.bodies[0].SetJoint(null); if (commonData.bodies[1] != null) commonData.bodies[1].SetJoint(null); DestroyNative(); } private void OnTransformChanged(TransformChangedFlags flags) { if (native == null) return; // We're ignoring this during physics update because it would cause problems if the joint itself was moved by physics // Note: This isn't particularily correct because if the joint is being moved by physics but the rigidbodies // themselves are not parented to the joint, the transform will need updating. However I'm leaving it up to the // user to ensure rigidbodies are always parented to the joint in such a case (It's an unlikely situation that // I can't think of an use for - joint transform will almost always be set as an initialization step and not a // physics response). if (Physics.IsUpdateInProgress) return; UpdateTransform(JointBody.Target); UpdateTransform(JointBody.Anchor); } /// /// Creates the internal representation of the Joint and restores the values saved by the Component. /// private void RestoreNative() { // Make sure to always create a new instance of this array, as IntPtrs don't get serialized commonData.@internal.bodies = new []{ IntPtr.Zero, IntPtr.Zero }; if (commonData.bodies[0] != null) { NativeRigidbody nativeBody = commonData.bodies[0].native; if (nativeBody != null) commonData.@internal.bodies[0] = nativeBody.GetCachedPtr(); } if (commonData.bodies[1] != null) { NativeRigidbody nativeBody = commonData.bodies[1].native; if (nativeBody != null) commonData.@internal.bodies[1] = nativeBody.GetCachedPtr(); } GetLocalTransform(JointBody.Target, out commonData.@internal.positions[0], out commonData.@internal.rotations[0]); GetLocalTransform(JointBody.Anchor, out commonData.@internal.positions[1], out commonData.@internal.rotations[1]); native = CreateNative(); native.Component = this; } /// /// Destroys the internal joint representation. /// private void DestroyNative() { if (native != null) { native.Destroy(); native = null; } } /// /// Checks can the provided rigidbody be used for initializing the joint. /// /// Body to check. /// True if the body can be used for initializing the joint, false otherwise. private bool IsBodyValid(Rigidbody body) { if (body == null) return false; if (body.native == null) return false; return true; } /// /// Calculates the local position/rotation that needs to be applied to the particular joint body. /// /// Body to calculate the transform for. /// Output position for the body. /// Output rotation for the body. protected virtual void GetLocalTransform(JointBody body, out Vector3 position, out Quaternion rotation) { position = commonData.positions[(int)body]; rotation = commonData.rotations[(int)body]; Rigidbody rigidbody = commonData.bodies[(int)body]; if (rigidbody == null) // Get world space transform if not relative to any body { Quaternion worldRot = SceneObject.Rotation; rotation = worldRot * rotation; position = worldRot.Rotate(position) + SceneObject.Position; } else { position = rotation.Rotate(position); } } /// /// Updates the local transform for the specified body attached to the joint. /// /// Body to update. private void UpdateTransform(JointBody body) { Vector3 localPos; Quaternion localRot; GetLocalTransform(body, out localPos, out localRot); native.SetPosition(body, localPos); native.SetRotation(body, localRot); } /// /// Holds all data the joint component needs to persist through serialization. /// [SerializeObject] internal class SerializableData { public ScriptCommonJointData @internal; public SerializableData() { @internal.positions = new Vector3[2] { Vector3.Zero, Vector3.Zero }; @internal.rotations = new Quaternion[2] { Quaternion.Identity, Quaternion.Identity }; @internal.breakForce = float.MaxValue; @internal.breakTorque = float.MaxValue; @internal.enableCollision = false; } public Rigidbody[] bodies = new Rigidbody[2]; public Vector3[] positions = new Vector3[2] { Vector3.Zero, Vector3.Zero }; public Quaternion[] rotations = new Quaternion[2] { Quaternion.Identity, Quaternion.Identity }; } } /// /// Controls spring parameters for a physics joint limits. If a limit is soft (body bounces back due to restitution when /// the limit is reached) the spring will pull the body back towards the limit using the specified parameters. /// [StructLayout(LayoutKind.Sequential), SerializeObject] public struct Spring // Note: Must match C++ struct Spring { /// /// Constructs a spring. /// /// Spring strength.Force proportional to the position error. /// Damping strength. Force propertional to the velocity error. public Spring(float stiffness, float damping) { this.stiffness = stiffness; this.damping = damping; } /// public override bool Equals(object rhs) { if (rhs is Spring) { Spring other = (Spring)rhs; return stiffness == other.stiffness && damping == other.damping; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(Spring a, Spring b) { return a.Equals(b); } public static bool operator !=(Spring a, Spring b) { return !(a == b); } /// /// Spring strength. Force proportional to the position error. /// public float stiffness; /// /// Damping strength. Force propertional to the velocity error. /// public float damping; } /// /// Specifies first or second body referenced by a Joint. /// public enum JointBody { /// /// Body the joint is influencing. /// Target, /// /// Body to which the joint is attached to (if any). /// Anchor }; /// /// Specifies axes that the D6 joint can constrain motion on. /// public enum D6JointAxis { /// /// Movement on the X axis. /// X, /// /// Movement on the Y axis. /// Y, /// /// Movement on the Z axis. /// Z, /// /// Rotation around the X axis. /// Twist, /// /// Rotation around the Y axis. /// SwingY, /// /// Rotation around the Z axis. /// SwingZ, Count } /// /// Specifies type of constraint placed on a specific axis of a D6 joint. /// public enum D6JointMotion { /// /// Axis is immovable. /// Locked, /// /// Axis will be constrained by the specified limits. /// Limited, /// /// Axis will not be constrained. /// Free, Count } /// /// Type of drives that can be used for moving or rotating bodies attached to the D6 joint. /// public enum D6JointDriveType { /// /// Linear movement on the X axis using the linear drive model. /// X, /// /// Linear movement on the Y axis using the linear drive model. /// Y, /// /// Linear movement on the Z axis using the linear drive model. /// Z, /// /// Rotation around the Y axis using the twist/swing angular drive model. Should not be used together with /// SLERP mode. /// Swing, /// /// Rotation around the Z axis using the twist/swing angular drive model. Should not be used together with /// SLERP mode. /// Twist, /// /// Rotation using spherical linear interpolation. Uses the SLERP angular drive mode which performs rotation /// by interpolating the quaternion values directly over the shortest path (applies to all three axes, which /// they all must be unlocked). /// SLERP, Count } /// /// Specifies parameters for a drive that will attempt to move the D6 joint bodies to the specified drive position and /// velocity. /// [SerializeObject] public class D6JointDrive { [SerializeField] private D6JointDriveData data; /// /// Spring strength. Force proportional to the position error. /// public float Stiffness { get { return data.stiffness; } } /// /// Damping strength. Force propertional to the velocity error. /// public float Damping { get { return data.damping; } } /// /// Maximum force the drive can apply. /// public float ForceLimit { get { return data.forceLimit; } } /// /// If true the drive will generate acceleration instead of forces. Acceleration drives are easier to tune as /// they account for the masses of the actors to which the joint is attached. /// public bool Acceleration { get { return data.acceleration; } } /// /// Gets drive properties. /// public D6JointDriveData Data { get { return data; } } /// /// Constructor for deserialization only. /// private D6JointDrive() { } /// /// Constructs a new D6 joint drive. /// /// /// /// /// public D6JointDrive(float stiffness = 0.0f, float damping = 0.0f, float forceLimit = float.MaxValue, bool acceleration = false) { data.stiffness = stiffness; data.damping = damping; data.forceLimit = forceLimit; data.acceleration = acceleration; } /// /// Constructs a new D6 joint drive. /// /// Properties to initialize the drive with. public D6JointDrive(D6JointDriveData data) { this.data = data; } /// public override bool Equals(object rhs) { if (rhs is D6JointDrive) { D6JointDrive other = (D6JointDrive)rhs; return Stiffness == other.Stiffness && Damping == other.Damping && ForceLimit == other.ForceLimit && Acceleration == other.Acceleration; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(D6JointDrive a, D6JointDrive b) { return a.Equals(b); } public static bool operator !=(D6JointDrive a, D6JointDrive b) { return !(a == b); } /// /// Used for accessing drive data from native code. /// /// Native readable drive structure. private D6JointDriveData Internal_GetNative() { return data; } } /// /// Properties of a drive that drives the hinge joint's angular velocity towards a paricular value. /// [SerializeObject] public class HingeJointDrive { [SerializeField] private HingeJointDriveData data; /// /// Target speed of the joint. /// public float Speed { get { return data.speed; } } /// /// Maximum torque the drive is allowed to apply. /// public float ForceLimit { get { return data.forceLimit; } } /// /// Scales the velocity of the first body, and its response to drive torque is scaled down. /// public float GearRatio { get { return data.gearRatio; } } /// /// If the joint is moving faster than the drive's target speed, the drive will try to break. If you don't want /// the breaking to happen set this to true. /// public bool FreeSpin { get { return data.freeSpin; } } /// /// Gets drive properties. /// public HingeJointDriveData Data { get { return data; } } /// /// Constructor for deserialization only. /// private HingeJointDrive() { } /// /// Constructs a new hinge joint drive. /// /// /// /// /// public HingeJointDrive(float speed = 0.0f, float forceLimit = float.MaxValue, float gearRatio = 1.0f, bool freeSpin = false) { data.speed = speed; data.forceLimit = forceLimit; data.gearRatio = gearRatio; data.freeSpin = freeSpin; } /// /// Constructs a new hinge joint drive. /// /// Properties to initialize the drive with. public HingeJointDrive(HingeJointDriveData data) { this.data = data; } /// public override bool Equals(object rhs) { if (rhs is HingeJointDrive) { HingeJointDrive other = (HingeJointDrive)rhs; return data.speed == other.data.speed && data.gearRatio == other.data.gearRatio && data.forceLimit == other.data.forceLimit && data.freeSpin == other.data.freeSpin; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(HingeJointDrive a, HingeJointDrive b) { return a.Equals(b); } public static bool operator !=(HingeJointDrive a, HingeJointDrive b) { return !(a == b); } /// /// Used for accessing drive data from native code. /// /// Native readable drive structure. private HingeJointDriveData Internal_GetNative() { return data; } }; /// /// Contains common values used by all Joint limit types. /// [SerializeObject] public class LimitCommon { [SerializeField] private LimitCommonData data; /// /// Distance from the limit at which it becomes active. Allows the solver to activate earlier than the limit is /// reached to avoid breaking the limit. /// public float ContactDist { get { return data.contactDist; } } /// /// Controls how do objects react when the limit is reached, values closer to zero specify non-ellastic collision, /// while those closer to one specify more ellastic(i.e bouncy) collision.Must be in [0, 1] range. /// public float Restitution { get { return data.restitution; } } /// /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// public Spring Spring { get { return data.spring; } } /// /// Gets properties common to all limit types. /// public LimitCommonData CommonData { get { return data; } } protected LimitCommon(float contactDist = -1.0f) { data.contactDist = contactDist; data.restitution = 0.0f; data.spring = new Spring(); } protected LimitCommon(Spring spring, float restitution = 0.0f) { data.contactDist = -1.0f; data.restitution = restitution; data.spring = spring; } protected LimitCommon(LimitCommonData data) { this.data = data; } /// public override bool Equals(object rhs) { if (rhs is LimitCommon) { LimitCommon other = (LimitCommon)rhs; return ContactDist == other.ContactDist && Restitution == other.Restitution && Spring == other.Spring; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(LimitCommon a, LimitCommon b) { return a.Equals(b); } public static bool operator !=(LimitCommon a, LimitCommon b) { return !(a == b); } } /// /// Represents a joint limit between two distance values. Lower value must be less than the upper value. /// [SerializeObject] public class LimitLinearRange : LimitCommon { [SerializeField] private LimitLinearRangeData data; /// /// Lower distance of the limit. Must be less than . /// public float Lower { get { return data.lower; } } /// /// Upper distance of the limit. Must be greater than . /// public float Upper { get { return data.upper; } } /// /// Gets properties of the linear limit range. /// public LimitLinearRangeData Data { get { return data; } } /// /// Constructs an empty limit. /// public LimitLinearRange() { } /// /// Constructs a hard limit. Once the limit is reached the movement of the attached bodies will come to a stop. /// /// /// /// public LimitLinearRange(float lower, float upper, float contactDist = -1.0f) :base(contactDist) { data.lower = lower; data.upper = upper; } /// /// Constructs a soft limit. Once the limit is reached the bodies will bounce back according to the resitution /// parameter and will be pulled back towards the limit by the provided spring. /// /// /// /// /// public LimitLinearRange(float lower, float upper, Spring spring, float restitution = 0.0f) :base(spring, restitution) { data.lower = lower; data.upper = upper; } /// /// Constructs a new limit from the provided properties. /// /// Linear range specific properties. /// Properties common to all limit types. public LimitLinearRange(LimitLinearRangeData limitData, LimitCommonData commonData) :base(commonData) { this.data = limitData; } /// public override bool Equals(object rhs) { if (rhs is LimitLinearRange) { LimitLinearRange other = (LimitLinearRange)rhs; return base.Equals(rhs) && Lower == other.Lower && Upper == other.Upper; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(LimitLinearRange a, LimitLinearRange b) { return a.Equals(b); } public static bool operator !=(LimitLinearRange a, LimitLinearRange b) { return !(a == b); } /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private ScriptLimitLinearRange Internal_GetNative() { ScriptLimitLinearRange output; output.contactDist = ContactDist; output.restitution = Restitution; output.spring = Spring; output.lower = Lower; output.upper = Upper; return output; } } /// /// Represents a joint limit between zero a single distance value. /// [SerializeObject] public class LimitLinear : LimitCommon { [SerializeField] private LimitLinearData data; /// /// Distance at which the limit becomes active. /// public float Extent { get { return data.extent; } } /// /// Gets properties of the linear limit. /// public LimitLinearData Data { get { return data; } } /// /// Constructs an empty limit. /// public LimitLinear() { } /// /// Constructs a hard limit.Once the limit is reached the movement of the attached bodies will come to a stop. /// /// /// public LimitLinear(float extent, float contactDist = -1.0f) :base(contactDist) { data.extent = extent; } /// /// Constructs a soft limit.Once the limit is reached the bodies will bounce back according to the resitution /// parameter and will be pulled back towards the limit by the provided spring. /// /// /// /// public LimitLinear(float extent, Spring spring, float restitution = 0.0f) :base(spring, restitution) { data.extent = extent; } /// /// Constructs a new limit from the provided properties. /// /// Linear limit specific properties. /// Properties common to all limit types. public LimitLinear(LimitLinearData limitData, LimitCommonData commonData) :base(commonData) { this.data = limitData; } /// public override bool Equals(object rhs) { if (rhs is LimitLinear) { LimitLinear other = (LimitLinear)rhs; return base.Equals(rhs) && Extent == other.Extent; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(LimitLinear a, LimitLinear b) { return a.Equals(b); } public static bool operator !=(LimitLinear a, LimitLinear b) { return !(a == b); } /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private ScriptLimitLinear Internal_GetNative() { ScriptLimitLinear output; output.contactDist = ContactDist; output.restitution = Restitution; output.spring = Spring; output.extent = Extent; return output; } } /// /// Represents a joint limit between two angles. /// [SerializeObject] public class LimitAngularRange : LimitCommon { [SerializeField] private LimitAngularRangeData data; /// /// Lower angle of the limit. Must be less than . /// public Radian Lower { get { return data.lower; } } /// /// Upper angle of the limit. Must be greater than . /// public Radian Upper { get { return data.upper; } } /// /// Gets properties of the angular limit range. /// public LimitAngularRangeData Data { get { return data; } } /// /// Constructs an empty limit. /// public LimitAngularRange() { } /// /// Constructs a hard limit. Once the limit is reached the movement of the attached bodies will come to a stop. /// /// /// /// public LimitAngularRange(Radian lower, Radian upper, float contactDist = -1.0f) : base(contactDist) { data.lower = lower; data.upper = upper; } /// /// Constructs a soft limit. Once the limit is reached the bodies will bounce back according to the resitution /// parameter and will be pulled back towards the limit by the provided spring. /// /// /// /// /// public LimitAngularRange(Radian lower, Radian upper, Spring spring, float restitution = 0.0f) : base(spring, restitution) { data.lower = lower; data.upper = upper; } /// /// Constructs a new limit from the provided properties. /// /// Angular limit range specific properties. /// Properties common to all limit types. public LimitAngularRange(LimitAngularRangeData limitData, LimitCommonData commonData) :base(commonData) { this.data = limitData; } /// public override bool Equals(object rhs) { if (rhs is LimitAngularRange) { LimitAngularRange other = (LimitAngularRange)rhs; return base.Equals(rhs) && Lower == other.Lower && Upper == other.Upper; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(LimitAngularRange a, LimitAngularRange b) { return a.Equals(b); } public static bool operator !=(LimitAngularRange a, LimitAngularRange b) { return !(a == b); } /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private ScriptLimitAngularRange Internal_GetNative() { ScriptLimitAngularRange output; output.contactDist = ContactDist; output.restitution = Restitution; output.spring = Spring; output.lower = Lower; output.upper = Upper; return output; } } /// /// Represents a joint limit that contraints movement to within an elliptical cone. /// [SerializeObject] public class LimitConeRange : LimitCommon { [SerializeField] private LimitConeRangeData data; /// /// Y angle of the cone. Movement is constrainted between 0 and this angle on the Y axis. /// public Radian YLimitAngle { get { return data.yLimitAngle; } } /// /// Z angle of the cone. Movement is constrainted between 0 and this angle on the Z axis. /// public Radian ZLimitAngle { get { return data.zLimitAngle; } } /// /// Gets properties of the cone limit range. /// public LimitConeRangeData Data { get { return data; } } /// /// Constructs a limit with a 45 degree cone. /// public LimitConeRange() { data.yLimitAngle = new Radian(MathEx.Pi * 0.5f); data.zLimitAngle = new Radian(MathEx.Pi * 0.5f); } /// /// Constructs a hard limit. Once the limit is reached the movement of the attached bodies will come to a stop. /// /// /// /// public LimitConeRange(Radian yLimitAngle, Radian zLimitAngle, float contactDist = -1.0f) : base(contactDist) { data.yLimitAngle = yLimitAngle; data.zLimitAngle = zLimitAngle; } /// /// Constructs a soft limit. Once the limit is reached the bodies will bounce back according to the resitution /// parameter and will be pulled back towards the limit by the provided spring. /// /// /// /// /// public LimitConeRange(Radian yLimitAngle, Radian zLimitAngle, Spring spring, float restitution = 0.0f) : base(spring, restitution) { data.yLimitAngle = yLimitAngle; data.zLimitAngle = zLimitAngle; } /// /// Constructs a new limit from the provided properties. /// /// Cone limit range specific properties. /// Properties common to all limit types. public LimitConeRange(LimitConeRangeData limitData, LimitCommonData commonData) :base(commonData) { this.data = limitData; } /// public override bool Equals(object rhs) { if (rhs is LimitConeRange) { LimitConeRange other = (LimitConeRange)rhs; return base.Equals(rhs) && YLimitAngle == other.YLimitAngle && ZLimitAngle == other.ZLimitAngle; } return false; } /// public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(LimitConeRange a, LimitConeRange b) { return a.Equals(b); } public static bool operator !=(LimitConeRange a, LimitConeRange b) { return !(a == b); } /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private ScriptLimitConeRange Internal_GetNative() { ScriptLimitConeRange output; output.contactDist = ContactDist; output.restitution = Restitution; output.spring = Spring; output.yLimitAngle = YLimitAngle; output.zLimitAngle = ZLimitAngle; return output; } } /// /// Contains data used by HingeJointDrive. /// [StructLayout(LayoutKind.Sequential), SerializeObject] public struct HingeJointDriveData // Note: Must match C++ struct HingeJoint::Drive { /// /// /// public float speed; /// /// /// public float forceLimit; /// /// /// public float gearRatio; /// /// /// public bool freeSpin; } /// /// Contains data used by D6JointDrive. /// [StructLayout(LayoutKind.Sequential), SerializeObject] public struct D6JointDriveData // Note: Must match C++ struct D6Joint::Drive { /// /// /// public float stiffness; /// /// /// public float damping; /// /// /// public float forceLimit; /// /// /// public bool acceleration; } /// /// Contains data used by LimitCommon. /// [SerializeObject] public struct LimitCommonData { /// /// /// public float contactDist; /// /// /// public float restitution; /// /// /// public Spring spring; } /// /// Contains data used by LimitLinearRange. /// [SerializeObject] public struct LimitLinearRangeData { /// /// /// public float lower; /// /// /// public float upper; } /// /// Contains data used by LimitLinear. /// [SerializeObject] public struct LimitLinearData { /// /// /// public float extent; } /// /// Contains data used by LimitAngularRange. /// [SerializeObject] public struct LimitAngularRangeData { /// /// /// public Radian lower; /// /// /// public Radian upper; } /// /// Contains data used by LimitConeRange. /// [SerializeObject] public struct LimitConeRangeData { /// /// /// public Radian yLimitAngle; /// /// /// public Radian zLimitAngle; } /// /// Used for passing LimitLinearRange data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptLimitLinearRange // Note: Must match C++ struct LimitLinearRange { public float contactDist; public float restitution; public Spring spring; public float lower; public float upper; } /// /// Used for passing LimitLinear data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptLimitLinear // Note: Must match C++ struct LimitLinear { public float contactDist; public float restitution; public Spring spring; public float extent; } /// /// Used for passing LimitAngularRange data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptLimitAngularRange // Note: Must match C++ struct LimitAngularRange { public float contactDist; public float restitution; public Spring spring; public Radian lower; public Radian upper; } /// /// Used for passing LimitConeRange data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptLimitConeRange // Note: Must match C++ struct LimitConeRange { public float contactDist; public float restitution; public Spring spring; public Radian yLimitAngle; public Radian zLimitAngle; } /** @} */ }