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