//********************************** 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
}
/** @} */
}