//********************************** 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 { /// /// Base class for all Joint types. Joints constrain how two rigidbodies move relative to one another (e.g. a door /// hinge). One of the bodies in the joint must always be movable (i.e. non-kinematic). /// public abstract class Joint : Component { internal NativeJoint native; [SerializeField] internal SerializableData serializableData = 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 serializableData.breakForce; } set { if (serializableData.breakForce == value) return; serializableData.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 serializableData.breakTorque; } set { if (serializableData.breakTorque == value) return; serializableData.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 serializableData.enableCollision; } set { if (serializableData.enableCollision == value) return; serializableData.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 serializableData.bodies[(int) body]; } /// /// Sets a body managed by the joint. One of the bodies must be movable (i.e. 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 (serializableData.bodies[(int)body] == rigidbody) return; if (serializableData.bodies[(int)body] != null) serializableData.bodies[(int)body].SetJoint(null); serializableData.bodies[(int)body] = rigidbody; if (rigidbody != null) serializableData.bodies[(int)body].SetJoint(this); if (native != null) { native.SetRigidbody(body, rigidbody); UpdateTransform(body); } } /// /// 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 serializableData.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 (serializableData.positions[(int)body] == position) return; serializableData.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 serializableData.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 (serializableData.rotations[(int)body] == rotation) return; serializableData.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 physics update is in progress do nothing, as its the joint itself that's probably moving the body if (Physics.IsUpdateInProgress) return; if (serializableData.bodies[0] == body) UpdateTransform(JointBody.A); else if (serializableData.bodies[1] == body) UpdateTransform(JointBody.B); } /// /// 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 OnReset() { RestoreNative(); } private void OnEnable() { if (native == null) RestoreNative(); } private void OnDisable() { DestroyNative(); } private void OnDestroy() { if (serializableData.bodies[0] != null) serializableData.bodies[0].SetJoint(null); if (serializableData.bodies[1] != null) serializableData.bodies[1].SetJoint(null); DestroyNative(); } private void OnTransformChanged(TransformChangedFlags flags) { if (!SceneObject.Active) 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.A); UpdateTransform(JointBody.B); } /// /// Creates the internal representation of the Joint and restores the values saved by the Component. /// private void RestoreNative() { native = CreateNative(); // Note: Merge into one call to avoid many virtual function calls Rigidbody[] bodies = new Rigidbody[2]; if (serializableData.bodies[0] != null) bodies[0] = serializableData.bodies[0]; else bodies[0] = null; if (serializableData.bodies[1] != null) bodies[1] = serializableData.bodies[1]; else bodies[1] = null; native.SetRigidbody(JointBody.A, bodies[0]); native.SetRigidbody(JointBody.B, bodies[1]); native.BreakForce = serializableData.breakForce; native.BreakTorque = serializableData.breakTorque; native.EnableCollision = serializableData.enableCollision; native.BreakTorque = serializableData.breakTorque; native.EnableCollision = serializableData.enableCollision; UpdateTransform(JointBody.A); UpdateTransform(JointBody.B); } /// /// Destroys the internal joint representation. /// private void DestroyNative() { if (native != null) { native.Destroy(); native = null; } } /// /// Updates the local transform for the specified body attached to the joint. /// /// Body to update. private void UpdateTransform(JointBody body) { Vector3 localPos; Quaternion localRot; localPos = serializableData.positions[(int)body]; localRot = serializableData.rotations[(int)body]; // Transform to world space of the related body Rigidbody rigidbody = serializableData.bodies[(int)body]; if (rigidbody != null) { localRot = rigidbody.SceneObject.Rotation * localRot; localPos = localRot.Rotate(localPos) + rigidbody.SceneObject.Position; } // Transform to space local to the joint Quaternion invRotation = SceneObject.Rotation.Inverse; localPos = invRotation.Rotate(localPos - SceneObject.Position); localRot = invRotation * 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 Rigidbody[] bodies = new Rigidbody[2]; public Vector3[] positions = new Vector3[2]; public Quaternion[] rotations = new Quaternion[2]; public float breakForce = float.MaxValue; public float breakTorque = float.MaxValue; public bool enableCollision = false; } } /// /// Controls spring parameters for a physics joint limits. If a limit is soft (body bounces back due to restition 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; } /// /// 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 { A, B }; /// /// 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 DriveType { /// /// 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. /// public class D6JointDrive { /// /// Spring strength. Force proportional to the position error. /// public float stiffness = 0.0f; /// /// Damping strength. Force propertional to the velocity error. /// public float damping = 0.0f; /// /// Maximum force the drive can apply. /// public float forceLimit = float.MaxValue; /// /// 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 = false; /// /// Used for accessing drive data from native code. /// /// Native readable drive structure. private void Internal_GetNative(ref ScriptD6JointDrive output) { output.stiffness = stiffness; output.damping = damping; output.forceLimit = forceLimit; output.acceleration = acceleration; } } /// /// Properties of a drive that drives the hinge joint's angular velocity towards a paricular value. /// public class HingeJointDrive { /// /// Target speed of the joint. /// public float speed = 0.0f; /// /// Maximum torque the drive is allowed to apply. /// public float forceLimit = float.MaxValue; /// /// Scales the velocity of the first body, and its response to drive torque is scaled down. /// public float gearRatio = 1.0f; /// /// 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 = false; /// /// Used for accessing drive data from native code. /// /// Native readable drive structure. private void Internal_GetNative(ref ScriptHingeJointDrive output) { output.speed = speed; output.forceLimit = forceLimit; output.gearRatio = gearRatio; output.freeSpin = freeSpin; } }; /// /// Contains common values used by all Joint limit types. /// [SerializeObject] public class LimitCommon { public LimitCommon(float contactDist = -1.0f) { this.contactDist = contactDist; this.restitution = 0.0f; this.spring = new Spring(); } public LimitCommon(Spring spring, float restitution = 0.0f) { this.contactDist = -1.0f; this.restitution = restitution; this.spring = spring; } /// /// 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; /// /// 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; /// /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// public Spring spring; } /// /// Represents a joint limit between two distance values. Lower value must be less than the upper value. /// [SerializeObject] public class LimitLinearRange : LimitCommon { /// /// 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. /// /// Lower distance of the limit.Must be less than . /// Upper distance of the limit.Must be more than . /// 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.Specify -1 for the /// default. public LimitLinearRange(float lower, float upper, float contactDist = -1.0f) :base(contactDist) { this.lower = lower; this.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. /// /// Lower distance of the limit. Must be less than . /// Upper distance of the limit. Must be more than . /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// /// 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 LimitLinearRange(float lower, float upper, Spring spring, float restitution = 0.0f) :base(spring, restitution) { this.lower = lower; this.upper = upper; } /// /// Lower distance of the limit. Must be less than #upper. /// public float lower; /// /// Upper distance of the limit. Must be more than #lower. /// public float upper; /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private void Internal_GetNative(ref ScriptLimitLinearRange output) { output.contactDist = contactDist; output.resitution = restitution; output.spring = spring; output.lower = lower; output.upper = upper; } } /// /// Represents a joint limit between zero a single distance value. /// [SerializeObject] public class LimitLinear : LimitCommon { /// /// 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. /// /// Distance at which the limit becomes active. /// 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.Specify -1 for the /// default. public LimitLinear(float extent, float contactDist = -1.0f) :base(contactDist) { this.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. /// /// Distance at which the limit becomes active. /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// /// 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 LimitLinear(float extent, Spring spring, float restitution = 0.0f) :base(spring, restitution) { this.extent = extent; } /// /// Distance at which the limit becomes active. /// public float extent = 0.0f; /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private void Internal_GetNative(ref ScriptLimitLinear output) { output.contactDist = contactDist; output.resitution = restitution; output.spring = spring; output.extent = extent; } } /// /// Represents a joint limit between two angles. /// [SerializeObject] public class LimitAngularRange : LimitCommon { /// /// 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. /// /// Lower angle of the limit. Must be less than . /// Upper angle of the limit. Must be more than . /// 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.Specify -1 for the /// default. public LimitAngularRange(Radian lower, Radian upper, float contactDist = -1.0f) : base(contactDist) { this.lower = lower; this.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. /// /// Lower angle of the limit. Must be less than . /// Upper angle of the limit. Must be more than . /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// /// 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 LimitAngularRange(Radian lower, Radian upper, Spring spring, float restitution = 0.0f) : base(spring, restitution) { this.lower = lower; this.upper = upper; } /// /// Lower angle of the limit. Must be less than #upper. /// public Radian lower = new Radian(0.0f); /// /// Upper angle of the limit. Must be less than #lower. /// public Radian upper = new Radian(0.0f); /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private void Internal_GetNative(ref ScriptLimitAngularRange output) { output.contactDist = contactDist; output.resitution = restitution; output.spring = spring; output.lower = lower; output.upper = upper; } } /// /// Represents a joint limit that contraints movement to within an elliptical cone. /// [SerializeObject] public class LimitConeRange : LimitCommon { /// /// Constructs a limit with a 45 degree cone. /// public LimitConeRange() { } /// /// Constructs a hard limit. Once the limit is reached the movement of the attached bodies will come to a stop. /// /// Y angle of the cone. Movement is constrainted between 0 and this angle on the Y axis. /// /// Z angle of the cone. Movement is constrainted between 0 and this angle on the Z axis. /// /// 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.Specify -1 for the /// default. public LimitConeRange(Radian yLimitAngle, Radian zLimitAngle, float contactDist = -1.0f) : base(contactDist) { this.yLimitAngle = yLimitAngle; this.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. /// /// Y angle of the cone. Movement is constrainted between 0 and this angle on the Y axis. /// /// Z angle of the cone. Movement is constrainted between 0 and this angle on the Z axis. /// /// Spring that controls how are the bodies pulled back towards the limit when they breach it. /// /// 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 LimitConeRange(Radian yLimitAngle, Radian zLimitAngle, Spring spring, float restitution = 0.0f) : base(spring, restitution) { this.yLimitAngle = yLimitAngle; this.zLimitAngle = zLimitAngle; } /// /// Y angle of the cone. Movement is constrainted between 0 and this angle on the Y axis. /// public Radian yLimitAngle = new Radian(MathEx.Pi * 0.5f); /// /// Z angle of the cone. Movement is constrainted between 0 and this angle on the Z axis. /// public Radian zLimitAngle = new Radian(MathEx.Pi * 0.5f); /// /// Used for accessing limit data from native code. /// /// Native readable limit structure. private void Internal_GetNative(ref ScriptLimitConeRange output) { output.contactDist = contactDist; output.resitution = restitution; output.spring = spring; output.yLimitAngle = yLimitAngle; output.zLimitAngle = zLimitAngle; } } /// /// Used for passing HingeJointDrive data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptHingeJointDrive // Note: Must match C++ struct HingeJoint::Drive { public float speed; public float forceLimit; public float gearRatio; public bool freeSpin; } /// /// Used for passing D6JointDrive data between native and managed code. /// [StructLayout(LayoutKind.Sequential)] internal struct ScriptD6JointDrive // Note: Must match C++ struct D6Joint::Drive { public float stiffness; public float damping; public float forceLimit; public bool acceleration; } /// /// 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 resitution; 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 resitution; 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 resitution; 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 resitution; public Spring spring; public Radian yLimitAngle; public Radian zLimitAngle; } }