using System;
using UnityEngine;
namespace Framework.Support {
/// Casts differen collider shapes into the world
public class ShapeCaster {
#region enum ColliderShape
/// Shape of a collider
private enum ColliderShape : uint {
/// The collider is a perfectly round sphere
Sphere = 0,
/// The collider is a cylinder with hemispheres at its ends
Capsule = 1,
/// The collider is a box with different side lengths
Box = 2
}
#endregion // enum ColliderShape
/// Delegate for a shape cast with a single result
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Receives informations about the collider that was hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// True if the shape cast hit another collider
private delegate bool ShapeCastDelegate(
Vector3 position, Vector3 direction,
out RaycastHit hitInfo,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
);
/// Delegate for a shape cast that returns all hit colliders
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// A list of all colliders hit by the shape cast
private delegate RaycastHit[] ShapeCastAllDelegate(
Vector3 position, Vector3 direction,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
);
/// Delegate for a shape cast that write all hit colliders into an array
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Array that will receive the colliders that were hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// The number of colliders that were hit by the shape cast
private delegate int ShapeCastNonAllocDelegate(
Vector3 position, Vector3 direction,
RaycastHit[] results,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
);
/// Initialized a new collider caster
/// Collider that will be cast
public ShapeCaster(Collider collider) {
this.shapeCastDelegates = new ShapeCastDelegate[] {
sphereCast, capsuleCast, boxCast
};
this.shapeCastAllDelegates = new ShapeCastAllDelegate[] {
sphereCastAll, capsuleCastAll, boxCastAll
};
this.shapeCastNonAllocDelegates = new ShapeCastNonAllocDelegate[] {
sphereCastNonAlloc, capsuleCastNonAlloc, boxCastNonAlloc
};
changeCollider(collider);
}
/// Collider the caster is casting
///
/// Thrown if the collider was not of a support type
///
public Collider Collider {
get {
switch(this.colliderShape) {
case ColliderShape.Sphere: { return this.sphereCollider; }
case ColliderShape.Capsule: { return this.capsuleCollider; }
case ColliderShape.Box: { return this.boxCollider; }
default: { throw new InvalidOperationException("Invalid collider"); }
}
}
set {
if(!ReferenceEquals(value, Collider)) {
changeCollider(value);
}
}
}
/// Casts the shape of the collider into the specified direction
/// Position of the game object carrying the collider
/// Direction the shape will be cast into
/// Receives informations about the collider that was hit
/// Maximum distance to consider for collisions
/// Layers the shape can hit colliders on
/// How to deal with trigger colliders
/// True if a collider was hit by the shape cast
public bool ShapeCast(
Vector3 position, Vector3 direction,
out RaycastHit hitInfo,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return this.shapeCastDelegates[(int)this.colliderShape](
position, direction, out hitInfo, maxDistance, layerMask, queryTriggerInteraction
);
}
/// Delegate for a shape cast that returns all hit colliders
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// A list of all colliders hit by the shape cast
public RaycastHit[] ShapeCastAll(
Vector3 position, Vector3 direction,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return this.shapeCastAllDelegates[(int)this.colliderShape](
position, direction, maxDistance, layerMask, queryTriggerInteraction
);
}
/// Delegate for a shape cast that write all hit colliders into an array
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Array that will receive the colliders that were hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// The number of colliders that were hit by the shape cast
public int ShapeCastNonAlloc(
Vector3 position, Vector3 direction,
RaycastHit[] results,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return this.shapeCastNonAllocDelegates[(int)this.colliderShape](
position, direction, results, maxDistance, layerMask, queryTriggerInteraction
);
}
/// Changes the collider that will be cast as a shape
/// New collider to cast
///
/// Thrown if the collider was not of a support type
///
private void changeCollider(Collider collider) {
this.sphereCollider = (collider as SphereCollider);
this.capsuleCollider = (collider as CapsuleCollider);
this.boxCollider = (collider as BoxCollider);
if(this.sphereCollider != null) {
this.colliderShape = ColliderShape.Sphere;
} else if(this.capsuleCollider != null) {
this.colliderShape = ColliderShape.Capsule;
} else if(this.boxCollider != null) {
this.colliderShape = ColliderShape.Box;
} else {
throw new ArgumentException("Unsupported collider type", "collider");
}
}
/// Returns informations for the first hit of a sphere cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Receives informations about the collider that was hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// True if the shape cast hit another collider
private bool sphereCast(
Vector3 position, Vector3 direction,
out RaycastHit hitInfo,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return Physics.SphereCast(
position + this.sphereCollider.center,
this.sphereCollider.radius,
direction,
out hitInfo,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Returns informations for the first hit of a capsule cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Receives informations about the collider that was hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// True if the shape cast hit another collider
private bool capsuleCast(
Vector3 position, Vector3 direction,
out RaycastHit hitInfo,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 point1 = position + this.capsuleCollider.center;
Vector3 point2 = point1;
float halfHeight = this.capsuleCollider.height / 2.0f;
switch(this.capsuleCollider.direction) {
case 0: {
point1.x -= halfHeight;
point2.x += halfHeight;
break;
}
case 1: {
point1.y -= halfHeight;
point2.y += halfHeight;
break;
}
case 2: {
point1.z -= halfHeight;
point2.z += halfHeight;
break;
}
}
return Physics.CapsuleCast(
point1, point2, this.capsuleCollider.radius,
direction,
out hitInfo,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Returns informations for the first hit of a box cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Receives informations about the collider that was hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// True if the shape cast hit another collider
private bool boxCast(
Vector3 position, Vector3 direction,
out RaycastHit hitInfo,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 extents = this.boxCollider.size / 2.0f;
return Physics.BoxCast(
position + this.boxCollider.center, extents,
direction,
out hitInfo,
this.boxCollider.transform.rotation,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Returns informations for all colliders hit by a sphere cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// A list of all colliders hit by the shape cast
private RaycastHit[] sphereCastAll(
Vector3 position, Vector3 direction,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return Physics.SphereCastAll(
position + this.sphereCollider.center,
this.sphereCollider.radius,
direction,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Returns informations for all colliders hit by a sphere cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// A list of all colliders hit by the shape cast
private RaycastHit[] capsuleCastAll(
Vector3 position, Vector3 direction,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 point1 = position + this.capsuleCollider.center;
Vector3 point2 = point1;
float halfHeight = this.capsuleCollider.height / 2.0f;
switch(this.capsuleCollider.direction) {
case 0: {
point1.x -= halfHeight;
point2.x += halfHeight;
break;
}
case 1: {
point1.y -= halfHeight;
point2.y += halfHeight;
break;
}
case 2: {
point1.z -= halfHeight;
point2.z += halfHeight;
break;
}
}
return Physics.CapsuleCastAll(
point1, point2, this.capsuleCollider.radius,
direction,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Returns informations for all colliders hit by a sphere cast
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// A list of all colliders hit by the shape cast
private RaycastHit[] boxCastAll(
Vector3 position, Vector3 direction,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 extents = this.boxCollider.size / 2.0f;
return Physics.BoxCastAll(
position + this.boxCollider.center, extents,
direction,
this.boxCollider.transform.rotation,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Write informations for all colliders hit by a sphere cast into an array
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Array that will receive the colliders that were hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// The number of colliders that were hit by the shape cast
private int sphereCastNonAlloc(
Vector3 position, Vector3 direction,
RaycastHit[] results,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
return Physics.SphereCastNonAlloc(
position + this.sphereCollider.center,
this.sphereCollider.radius,
direction,
results,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Write informations for all colliders hit by a capsule cast into an array
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Array that will receive the colliders that were hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// The number of colliders that were hit by the shape cast
private int capsuleCastNonAlloc(
Vector3 position, Vector3 direction,
RaycastHit[] results,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 point1 = position + this.capsuleCollider.center;
Vector3 point2 = point1;
float halfHeight = this.capsuleCollider.height / 2.0f;
switch(this.capsuleCollider.direction) {
case 0: {
point1.x -= halfHeight;
point2.x += halfHeight;
break;
}
case 1: {
point1.y -= halfHeight;
point2.y += halfHeight;
break;
}
case 2: {
point1.z -= halfHeight;
point2.z += halfHeight;
break;
}
}
return Physics.CapsuleCastNonAlloc(
point1, point2, this.capsuleCollider.radius,
direction,
results,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Write informations for all colliders hit by a box cast into an array
/// Position of the game object carrying the collider
/// Direction into which the shape will be cast
/// Array that will receive the colliders that were hit
/// Maximum distance to consider for the shape cast
/// Layers on which to check for collisions
/// How to deal with trigger colliders
/// The number of colliders that were hit by the shape cast
private int boxCastNonAlloc(
Vector3 position, Vector3 direction,
RaycastHit[] results,
float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction
) {
Vector3 extents = this.boxCollider.size / 2.0f;
return Physics.BoxCastNonAlloc(
position + this.boxCollider.center, extents,
direction,
results,
this.boxCollider.transform.rotation,
maxDistance, layerMask, queryTriggerInteraction
);
}
/// Delegates for single hit shape casting methods
private readonly ShapeCastDelegate[/*2*/] shapeCastDelegates;
/// Delegates for multi hit shape casting methods
private readonly ShapeCastAllDelegate[/*2*/] shapeCastAllDelegates;
/// Delegates for multi hit allocation-free shape casting methods
private readonly ShapeCastNonAllocDelegate[/*2*/] shapeCastNonAllocDelegates;
/// Shape of the collider that is cast by this caster
private ColliderShape colliderShape;
/// Sphere collider that will be cast if this is a sphere caster
private SphereCollider sphereCollider;
/// Sphere collider that will be cast if this is a capsule caster
private CapsuleCollider capsuleCollider;
/// Sphere collider that will be cast if this is a box caster
private BoxCollider boxCollider;
}
} // namespace Framework.Support