//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
namespace BansheeEngine
{
/** @addtogroup Physics
* @{
*/
///
/// Collider represents physics geometry that can be in multiple states:
/// - Default: Static geometry that physics objects can collide with.
/// - Trigger: Static geometry that can't be collided with but will report touch events.
/// - Dynamic: Dynamic geometry that is a part of a Rigidbody.A set of colliders defines the shape of the parent
/// rigidbody.
///
public abstract class Collider : Component
{
internal NativeCollider native;
protected Rigidbody parent;
[SerializeField]
internal SerializableData serializableData = new SerializableData();
///
/// Triggered when some object starts interacting with the collider. Only triggered if proper collision report mode
/// is turned on.
///
public event Action OnCollisionBegin;
///
/// Triggered for every frame that an object remains interacting with a collider. Only triggered if proper collision
/// report mode is turned on.
///
public event Action OnCollisionStay;
///
/// Triggered when some object stops interacting with the collider. Only triggered if proper collision report mode
/// is turned on.
///
public event Action OnCollisionEnd;
///
/// Determines how the collider used. A trigger will not be used for collisions (objects will pass through it),
/// but collision events will still be reported.
///
public bool IsTrigger
{
get { return serializableData.isTrigger; }
set
{
if (serializableData.isTrigger == value)
return;
serializableData.isTrigger = value;
if (native != null)
{
native.IsTrigger = value;
UpdateParentRigidbody();
UpdateTransform();
}
}
}
///
/// Determines mass of the collider. Only relevant if the collider is part of a rigidbody. Ultimately this will
/// determine the total mass, center of mass and inertia tensors of the parent rigidbody(if they're being calculated
/// automatically).
///
public float Mass
{
get { return serializableData.mass; }
set
{
if (serializableData.mass == value)
return;
serializableData.mass = value;
if (native != null)
{
native.Mass = value;
if (parent != null)
parent.UpdateMassDistribution();
}
}
}
///
/// Physics material that determines how objects hitting the collider behave.
///
public PhysicsMaterial Material
{
get { return serializableData.material; }
set
{
serializableData.material = value;
if (native != null)
native.Material = value;
}
}
///
/// Determines how far apart do two shapes need to be away from each other before the physics runtime starts
/// generating repelling impulse for them.This distance will be the sum of contact offsets of the two interacting
/// objects.If objects are moving fast you can increase this value to start generating the impulse earlier and
/// potentially prevent the objects from interpenetrating. This value is in meters. Must be positive and larger
/// than .
///
public float ContactOffset
{
get { return serializableData.contactOffset; }
set
{
value = MathEx.Max(0, MathEx.Max(value, RestOffset));
serializableData.contactOffset = value;
if (native != null)
native.ContactOffset = value;
}
}
///
/// Determines at what distance should two objects resting on one another come to an equilibrium. The value used in the
/// runtime will be the sum of rest offsets for both interacting objects. This value is in meters. Cannot be larger
/// than
///
public float RestOffset
{
get { return serializableData.restOffset; }
set
{
value = MathEx.Min(value, ContactOffset);
serializableData.restOffset = value;
if (native != null)
native.RestOffset = value;
}
}
///
/// Determines with which objects will the collider collide. Objects that are allowed to collide with this
/// object must have the same bits set in their own layers.
///
public ulong Layer
{
get { return serializableData.layer; }
set
{
serializableData.layer = value;
if (native != null)
native.Layer = value;
}
}
///
/// Determines which (if any) collision events are reported.
///
public CollisionReportMode CollisionReportMode
{
get { return serializableData.collisionReportMode; }
set
{
serializableData.collisionReportMode = value;
if (native != null)
UpdateCollisionReportMode();
}
}
///
/// Checks does the ray hit this collider.
///
/// Ray to check.
/// Information about the hit. Valid only if the method returns true.
/// Maximum distance from the ray origin to search for hits.
/// True if the ray has hit the collider.
public bool Raycast(Ray ray, out PhysicsQueryHit hit, float maxDist)
{
return Raycast(ray.origin, ray.direction, out hit, maxDist);
}
///
/// Checks does the ray hit this collider.
///
/// Origin of the ray to check.
/// Unit direction of the ray to check.
/// Information about the hit. Valid only if the method returns true.
/// Maximum distance from the ray origin to search for hits.
/// True if the ray has hit the collider.
public bool Raycast(Vector3 origin, Vector3 unitDir, out PhysicsQueryHit hit, float maxDist = float.MaxValue)
{
hit = new PhysicsQueryHit();
if (native == null)
return false;
ScriptPhysicsQueryHit scriptHit;
bool wasHit = native.Raycast(origin, unitDir, out scriptHit, maxDist);
hit.collider = scriptHit.collider.Component;
hit.distance = scriptHit.distance;
hit.normal = scriptHit.normal;
hit.point = scriptHit.point;
hit.triangleIdx = scriptHit.triangleIdx;
hit.uv = scriptHit.uv;
return wasHit;
}
///
/// Creates an internal instance of the collider. Should be overriden by specific collider implementations.
///
/// An instance of a specific internal collider implementation.
internal abstract NativeCollider CreateCollider();
///
/// Changes the rigidbody parent of the collider. Meant to be called from the Rigidbody itself.
///
/// New rigidbody to assign as the parent to the collider.
/// If true the rigidbody will just be changed internally, but parent rigidbody will not be
/// notified.
internal void SetRigidbody(Rigidbody rigidbody, bool isInternal = false)
{
if (rigidbody == parent)
return;
if (native != null && !isInternal)
{
if (parent != null)
parent.RemoveCollider(this);
NativeRigidbody nativeRigidbody = null;
if (rigidbody != null)
nativeRigidbody = rigidbody.native;
native.Rigidbody = nativeRigidbody;
if (rigidbody != null)
rigidbody.AddCollider(this);
}
parent = rigidbody;
UpdateCollisionReportMode();
}
///
/// Triggered when the internal collider begins touching another object.
///
/// Data about the collision.
internal void DoOnCollisionBegin(CollisionData data)
{
if (OnCollisionBegin != null)
OnCollisionBegin(data);
}
///
/// Triggered when the internal collider ends touching another object.
///
/// Data about the collision.
internal void DoOnCollisionStay(CollisionData data)
{
if (OnCollisionStay != null)
OnCollisionStay(data);
}
///
/// Triggered when the internal collider ends touching another object.
///
/// Data about the collision.
internal void DoOnCollisionEnd(CollisionData data)
{
if (OnCollisionEnd != null)
OnCollisionEnd(data);
}
///
/// Checks is the provided rigidbody a valid parent for this collider.
///
/// This is required because certain colliders are limited in how they can be used.
///
/// Rigidbody that is the potential parent.
/// True if collider can be a part of the rigidbody.
protected internal virtual bool IsValidParent(Rigidbody parent)
{
return true;
}
///
/// Searches the parent scene object hierarchy to find a parent Rigidbody component.
///
protected void UpdateParentRigidbody()
{
if (serializableData.isTrigger)
{
SetRigidbody(null);
return;
}
SceneObject currentSO = SceneObject;
while (currentSO != null)
{
Rigidbody parent = currentSO.GetComponent();
if (parent != null)
{
if (currentSO.Active && IsValidParent(parent))
SetRigidbody(parent);
else
SetRigidbody(null);
return;
}
currentSO = currentSO.Parent;
}
// Not found
SetRigidbody(null);
}
///
/// Updates the transform of the internal Collider representation from the transform of the component's scene object.
///
protected void UpdateTransform()
{
Vector3 myScale = SceneObject.Scale;
if (parent != null)
{
Vector3 parentPos = parent.SceneObject.Position;
Quaternion parentRot = parent.SceneObject.Rotation;
Vector3 myPos = SceneObject.Position;
Quaternion myRot = SceneObject.Rotation;
Vector3 scale = parent.SceneObject.Scale;
Vector3 invScale = scale;
if (invScale.x != 0) invScale.x = 1.0f / invScale.x;
if (invScale.y != 0) invScale.y = 1.0f / invScale.y;
if (invScale.z != 0) invScale.z = 1.0f / invScale.z;
Quaternion invRotation = parentRot.Inverse;
Vector3 relativePos = invRotation.Rotate(myPos - parentPos) * invScale;
Quaternion relativeRot = invRotation * myRot;
relativePos = relativePos + myRot.Rotate(serializableData.localPosition * scale);
relativeRot = relativeRot * serializableData.localRotation;
native.Position = relativePos;
native.Rotation = relativeRot;
parent.UpdateMassDistribution();
}
else
{
Quaternion myRot = SceneObject.Rotation;
Vector3 myPos = SceneObject.Position + myRot.Rotate(serializableData.localPosition * myScale);
myRot = myRot * serializableData.localRotation;
native.Position = myPos;
native.Rotation = myRot;
}
native.Scale = myScale;
}
///
/// Applies the collision report mode to the internal collider depending on the current state.
///
internal void UpdateCollisionReportMode()
{
CollisionReportMode mode = serializableData.collisionReportMode;
if (parent != null)
mode = parent.CollisionReportMode;
if(native != null)
native.CollisionReportMode = mode;
}
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)
UpdateParentRigidbody();
// Don't update the transform if it's due to Physics update since then we can guarantee it will remain at the same
// relative transform to its parent
if (Physics.IsUpdateInProgress)
return;
if ((flags & (TransformChangedFlags.Parent | TransformChangedFlags.Transform)) != 0)
UpdateTransform();
}
///
/// Destroys the internal collider representation.
///
private void DestroyNative()
{
if (parent != null)
parent.RemoveCollider(this);
parent = null;
if (native != null)
{
native.Destroy();
native = null;
}
}
///
/// Creates the internal representation of the Collider and restores the values saved by the Component.
///
private void RestoreNative()
{
native = CreateCollider();
native.Component = this;
native.Position = serializableData.localPosition;
native.Rotation = serializableData.localRotation;
native.IsTrigger = serializableData.isTrigger;
native.Mass = serializableData.mass;
native.Material = serializableData.material;
native.ContactOffset = serializableData.contactOffset;
native.RestOffset = serializableData.restOffset;
native.Layer = serializableData.layer;
UpdateParentRigidbody();
UpdateTransform();
UpdateCollisionReportMode();
}
///
/// Holds all data the collider component needs to persist through serialization.
///
[SerializeObject]
internal class SerializableData
{
public Vector3 localPosition = Vector3.Zero;
public Quaternion localRotation = Quaternion.Identity;
public bool isTrigger = false;
public float mass = 1.0f;
public PhysicsMaterial material;
public float contactOffset = 0.02f;
public float restOffset = 0.0f;
public ulong layer = 1;
public CollisionReportMode collisionReportMode = CollisionReportMode.None;
}
}
/** @} */
}