//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Collections.Generic; namespace BansheeEngine { /** @addtogroup Physics * @{ */ /// /// Rigidbody is a dynamic physics object that can be moved using forces (or directly). It will interact with other /// static and dynamic physics objects in the scene accordingly (it will push other non-kinematic rigidbodies, /// and collide with static objects). /// /// The shape and mass of a rigidbody is governed by its colliders. You must attach at least one collider for the /// rigidbody to be valid. Colliders that are on the same scene object as the rigidbody, or on child scene objects /// are automatically considered as part of the rigidbody. /// public sealed class Rigidbody : Component { internal NativeRigidbody native; private List children = new List(); private Joint parentJoint; [SerializeField] internal SerializableData serializableData = new SerializableData(); /// /// Triggered when some object starts interacting with one of the child colliders. Only triggered if proper /// collision report mode is turned on. /// public event Action OnCollisionBegin; /// /// Triggered for every frame that an object remains interacting with one of the child colliders. Only triggered if /// proper collision report mode is turned on. /// public event Action OnCollisionStay; /// /// Triggered when some object stops interacting with one of the child colliders. Only triggered if proper collision /// report mode is turned on. /// public event Action OnCollisionEnd; /// /// Determines the mass of the object and all of its collider shapes. Only relevant if RigidbodyFlag.AutoMass or /// RigidbodyFlag.AutoTensors is turned off. Value of zero means the object is immovable (but can be rotated). /// public float Mass { get { return serializableData.mass; } set { serializableData.mass = value; if (native != null) native.Mass = value; } } /// /// Determines if the body is kinematic. Kinematic body will not move in response to external forces (for example /// gravity, or another object pushing it), essentially behaving like collider. Unlike a collider though, you can /// still move the object and have other dynamic objects respond correctly (that is, it will push other objects). /// public bool Kinematic { get { return serializableData.isKinematic; } set { if (serializableData.isKinematic == value) return; serializableData.isKinematic = value; if (native != null) { native.Kinematic = value; ClearColliders(); UpdateColliders(); } } } /// /// Determines if the body is sleeping. Objects that aren't moved/rotated for a while are put to sleep to reduce /// load on the physics system. You may also manually force objects to sleep or wake up. /// public bool Sleeping { get { if (native != null) return native.Sleeping; return true; } set { if (native != null) native.Sleeping = value; } } /// /// Threshold of force and torque under which the object will be considered to be put to sleep. /// public float SleepThreshold { get { return serializableData.sleepThreshold; } set { serializableData.sleepThreshold = value; if (native != null) native.SleepThreshold = value; } } /// /// Determines whether or not the rigidbody will have the global gravity force applied to it. /// public bool UseGravity { get { return serializableData.useGravity; } set { serializableData.useGravity = value; if (native != null) native.UseGravity = value; } } /// /// Determines current linear velocity of the body. /// public Vector3 Velocity { get { if (native != null) return native.Velocity; return Vector3.Zero; } set { if (native != null) native.Velocity = value; } } /// /// Determines current angular velocity of the body. /// public Vector3 AngularVelocity { get { if (native != null) return native.AngularVelocity; return Vector3.Zero; } set { if (native != null) native.AngularVelocity = value; } } /// /// Determines linear drag of the body. Higher drag values means the object resists linear movement more. /// public float Drag { get { return serializableData.linearDrag; } set { serializableData.linearDrag = value; if (native != null) native.Drag = value; } } /// /// Determines angular drag of the body. Higher drag values means the object resists angular movement more. /// public float AngularDrag { get { return serializableData.angularDrag; } set { serializableData.angularDrag = value; if (native != null) native.AngularDrag = value; } } /// /// Sets the inertia tensor in local mass space. Inertia tensor determines how difficult is to rotate the object. /// Values of zero in the inertia tensor mean the object will be unable to rotate around a specific axis. Changing /// this value is only relevant if RigidbodyFlag.AutoTensors is turned off. /// public Vector3 InertiaTensor { get { return serializableData.inertiaTensor; } set { serializableData.inertiaTensor = value; if (native != null) native.InertiaTensor = value; } } /// /// Determines position of the center of the mass. Changing this value is only relevant if RigidbodyFlag.AutoTensors /// is turned off. /// public Vector3 CenterOfMassPosition { get { return serializableData.centerMassPosition; } set { serializableData.centerMassPosition = value; if (native != null) native.CenterOfMassPosition = value; } } /// /// Determines rotation of the inertia tensor (rotation of the center of mass frame). Changing this value is only /// relevant if RigidbodyFlag.AutoTensors is turned off. /// public Quaternion CenterOfMassRotation { get { return serializableData.centerMassRotation; } set { serializableData.centerMassRotation = value; if (native != null) native.CenterOfMassRotation = value; } } /// /// Determines maximum angular velocity of the rigidbody. Velocity will be clamped to this value. /// public float MaxAngularVelocity { get { return serializableData.maxAngularVelocity; } set { serializableData.maxAngularVelocity = value; if (native != null) native.MaxAngularVelocity = value; } } /// /// Determines number of iterations to use when solving for position. Higher values can improve precision and /// numerical stability of the simulation. /// public int PositionSolverCount { get { return serializableData.positionSolverCount; } set { serializableData.positionSolverCount = value; if (native != null) serializableData.positionSolverCount = value; } } /// /// Determines number of iterations to use when solving for velocity. Higher values can improve precision and /// numerical stability of the simulation. /// public int VelocitySolverCount { get { return serializableData.velocitySolverCount; } set { serializableData.velocitySolverCount = value; if (native != null) serializableData.velocitySolverCount = value; } } /// /// Determines which (if any) collision events are reported. /// public CollisionReportMode CollisionReportMode { get { return serializableData.collisionReportMode; } set { if (serializableData.collisionReportMode == value) return; serializableData.collisionReportMode = value; foreach (var entry in children) entry.UpdateCollisionReportMode(); } } /// /// Various flags that control the behaviour of the rigidbody. /// public RigidbodyFlag Flags { get { return serializableData.flags; } set { if (serializableData.flags == value) return; serializableData.flags = value; if (native != null) { native.Flags = value; native.UpdateMassDistribution(); } } } /// /// Moves the rigidbody to a specific position. This method will ensure physically correct movement, meaning the /// body will collide with other objects along the way. /// /// New position for the body, in world space. public void Move(Vector3 position) { if (native != null) native.Move(position); NotifyFlags = TransformChangedFlags.None; SceneObject.Position = position; NotifyFlags = TransformChangedFlags.Transform | TransformChangedFlags.Parent; } /// /// Rotates the rigidbody. This method will ensure physically correct rotation, meaning the body will collide with /// other objects along the way. /// /// New orientation of the body, in world space. public void Rotate(Quaternion rotation) { if (native != null) native.Rotate(rotation); NotifyFlags = TransformChangedFlags.None; SceneObject.Rotation = rotation; NotifyFlags = TransformChangedFlags.Transform | TransformChangedFlags.Parent; } /// /// Applies a force to the center of the mass of the rigidbody. This will produce linear momentum. /// /// Force to apply. /// Determines what type of force was applied. public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force) { if (native != null) native.AddForce(force, mode); } /// /// Applies a torque to the rigidbody. This will produce angular momentum. /// /// Torque to apply. /// Determines what type of torque was applied. public void AddTorque(Vector3 torque, ForceMode mode = ForceMode.Force) { if (native != null) native.AddTorque(torque, mode); } /// /// Applies a force to a specific point on the rigidbody. This will in most cases produce both linear and angular /// momentum. /// /// Force to apply. /// World space point to apply the force at. /// Determines what type of force was applied. public void AddForceAtPoint(Vector3 force, Vector3 position, PointForceMode mode = PointForceMode.Force) { if (native != null) native.AddForceAtPoint(force, position, mode); } /// /// Returns the total (linear + angular) velocity at a specific point. /// /// Point in world space. /// Total velocity of the point. public Vector3 GetVelocityAtPoint(Vector3 position) { if (native != null) return native.GetVelocityAtPoint(position); return position; } /// /// Triggered when one of the child colliders begins touching another object. /// /// Data about the collision. internal void DoOnCollisionBegin(CollisionData data) { if (OnCollisionBegin != null) OnCollisionBegin(data); } /// /// Triggered when one of the child colliders ends touching another object. /// /// Data about the collision. internal void DoOnCollisionStay(CollisionData data) { if (OnCollisionStay != null) OnCollisionStay(data); } /// /// Triggered when one of the child colliders ends touching another object. /// /// Data about the collision. internal void DoOnCollisionEnd(CollisionData data) { if (OnCollisionEnd != null) OnCollisionEnd(data); } /// /// Sets that joint that this rigidbody is attached to. Allows the rigidbody to notify the joint when it moves. /// /// Joint the rigidbody is attached to, or null if none. internal void SetJoint(Joint joint) { parentJoint = joint; } /// /// Recalculates rigidbody's mass, inertia tensors and center of mass depending on the currently set child /// colliders. This should be called whenever relevant child collider properties change(like mass or shape). /// /// If automatic tensor calculation is turned off then this will do nothing. If automatic mass calculation is turned /// off then this will use the mass set directly on the body using . /// internal void UpdateMassDistribution() { if (native != null) native.UpdateMassDistribution(); } /// /// Unregisters all child colliders from the Rigidbody. /// internal void ClearColliders() { foreach (var collider in children) collider.SetRigidbody(null, true); children.Clear(); if (native != null) native.RemoveColliders(); } /// /// Registers a new collider with the Rigidbody. This collider will then be used to calculate Rigidbody's geometry /// used for collisions, and optionally (depending on set flags) total mass, inertia tensors and center of mass. /// /// Collider to register. internal void AddCollider(Collider collider) { if (native == null) return; children.Add(collider); native.AddCollider(collider); } /// /// Unregisters the collider from the Rigidbody. /// /// Collider to unregister. internal void RemoveCollider(Collider collider) { if (native == null) return; if (children.Exists(x => x == collider)) { native.RemoveCollider(collider); children.Remove(collider); } } private void OnInitialize() { NotifyFlags = TransformChangedFlags.Transform | TransformChangedFlags.Parent; } private void OnEnable() { RestoreNative(); } private void OnDisable() { DestroyNative(); } private void OnDestroy() { DestroyNative(); } private void OnTransformChanged(TransformChangedFlags flags) { if (!SceneObject.Active) return; if ((flags & TransformChangedFlags.Parent) != 0) { ClearColliders(); UpdateColliders(); if ((serializableData.flags & RigidbodyFlag.AutoTensors) != 0) native.UpdateMassDistribution(); #if DEBUG CheckForNestedRigibody(); #endif } native.Position = SceneObject.Position; native.Rotation = SceneObject.Rotation; if (parentJoint != null) parentJoint.NotifyRigidbodyMoved(this); } /// /// Searches child scene objects for Collider components and attaches them to the rigidbody. Make sure to call /// if you need to clear old colliders first. /// private void UpdateColliders() { Stack todo = new Stack(); todo.Push(SceneObject); while (todo.Count > 0) { SceneObject currentSO = todo.Pop(); if (currentSO.GetComponent() != null) { Collider[] colliders = currentSO.GetComponents(); foreach (var entry in colliders) { if (!entry.IsValidParent(this)) continue; if (entry.native == null) continue; entry.SetRigidbody(this, true); entry.native.Rigidbody = native; children.Add(entry); native.AddCollider(entry); } } int childCount = currentSO.GetNumChildren(); for (int i = 0; i < childCount; i++) { SceneObject child = currentSO.GetChild(i); if (child.GetComponent() != null) continue; todo.Push(child); } } } /// /// Checks if the rigidbody is nested under another rigidbody, and throws out a warning if so. /// private void CheckForNestedRigibody() { SceneObject currentSO = SceneObject.Parent; while (currentSO != null) { if (currentSO.GetComponent() != null) { Debug.LogWarning("Nested Rigidbodies detected. This will result in inconsistent transformations. " + "To parent one Rigidbody to another move its colliders to the new parent, but remove the " + "Rigidbody component."); return; } currentSO = currentSO.Parent; } } /// /// Destroys the internal rigidbody representation. /// private void DestroyNative() { ClearColliders(); if (native != null) { native.Destroy(); native = null; } } /// /// Restores internal rigidbody representation and assigns it the properties stored by the component. /// private void RestoreNative() { native = new NativeRigidbody(SceneObject); native.Component = this; UpdateColliders(); #if DEBUG CheckForNestedRigibody(); #endif native.Position = SceneObject.Position; native.Rotation = SceneObject.Rotation; // Note: Merge into one call to avoid many virtual function calls native.PositionSolverCount = serializableData.positionSolverCount; native.VelocitySolverCount = serializableData.velocitySolverCount; native.MaxAngularVelocity = serializableData.maxAngularVelocity; native.Drag = serializableData.linearDrag;; native.AngularDrag = serializableData.angularDrag; native.SleepThreshold = serializableData.sleepThreshold; native.UseGravity = serializableData.useGravity; native.Kinematic = serializableData.isKinematic; native.Flags = serializableData.flags; if ((serializableData.flags & RigidbodyFlag.AutoTensors) == 0) { native.CenterOfMassPosition = serializableData.centerMassPosition; native.CenterOfMassRotation = serializableData.centerMassRotation; native.InertiaTensor = serializableData.inertiaTensor; native.Mass = serializableData.mass; } else { if ((serializableData.flags & RigidbodyFlag.AutoMass) == 0) native.Mass = serializableData.mass; native.UpdateMassDistribution(); } } /// /// Holds all data the rigidbody component needs to persist through serialization. /// [SerializeObject] internal class SerializableData { public int positionSolverCount = 4; public int velocitySolverCount = 1; public RigidbodyFlag flags = RigidbodyFlag.AutoTensors | RigidbodyFlag.AutoMass; public CollisionReportMode collisionReportMode = CollisionReportMode.None; public Vector3 centerMassPosition = Vector3.Zero; public Quaternion centerMassRotation = Quaternion.Identity; public Vector3 inertiaTensor = Vector3.Zero; public float mass = 0.0f; public float maxAngularVelocity = 1.0f; public float linearDrag = 0.0f; public float angularDrag = 0.0f; public float sleepThreshold = 0.0f; public bool useGravity = true; public bool isKinematic = false; } } /// /// Type of force or torque that can be applied to a rigidbody. /// public enum ForceMode { /// /// Value applied is a force. /// Force, /// /// Value applied is an impulse (a direct change in its linear or angular momentum). /// Impulse, /// /// Value applied is velocity. /// Velocity, /// /// Value applied is accelearation. /// Acceleration } /// /// Type of force that can be applied to a rigidbody at an arbitrary point. /// public enum PointForceMode { /// /// Value applied is a force. /// Force, /// /// Value applied is an impulse (a direct change in its linear or angular momentum). /// Impulse, } /// /// Flags that control options of a Rigidbody object. /// [Flags] public enum RigidbodyFlag { /// /// No options. /// None = 0x00, /// /// Automatically calculate center of mass transform and inertia tensors from child shapes (colliders) /// AutoTensors = 0x01, /// /// Calculate mass distribution from child shapes (colliders). Only relevant when auto-tensors is on. /// AutoMass = 0x02, /// /// Enables continous collision detection. This can prevent fast moving bodies from tunneling through each other. /// This must also be enabled globally in Physics otherwise the flag will be ignored. /// CCD = 0x04 } /** @} */ }