// Copyright (C) 2023 Nicholas Maltbie
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace nickmaltbie.OpenKCC.Utils.ColliderCast
{
///
/// ColliderCast behaviour intended to work with any capsule collider shape.
///
public class CapsuleColliderCast : AbstractPrimitiveColliderCast
{
///
/// Default center for capsule collider.
///
///
private static readonly Vector3 DefaultCenter = new Vector3(0, 1, 0);
///
/// Radius of the capsule.
///
[SerializeField]
[Tooltip("Radius of the capsule.")]
public float radius = 0.5f;
///
/// Height of the capsule.
///
[SerializeField]
[Tooltip("Height of the capsule.")]
public float height = 2.0f;
///
/// Center of the capsule.
///
[SerializeField]
[Tooltip("Center of the capsule.")]
public Vector3 center = DefaultCenter;
///
/// Direction of the capsule.
///
[SerializeField]
[Tooltip("Direction of the capsule.")]
private CapsuleDirection capsuleDirection = CapsuleDirection.Y;
///
/// Capsule collider associated with this object.
///
[HideInInspector]
[SerializeField]
private CapsuleCollider capsuleCollider = null;
///
/// Mesh of capsule for debug drawing.
///
private Mesh _debugCapsuleMesh;
///
/// Debug mesh associated with capsule collider.
///
public Mesh DebugCapsuleMesh => _debugCapsuleMesh = _debugCapsuleMesh ??
CapsuleMaker.CapsuleData(radius: radius, depth: height - radius * 2);
///
/// Gets transformed parameters describing this capsule collider for a given position and rotation
///
/// Center of the capsule collider in relative position.
/// Radius of the capsule
/// Height of the capsule.
/// Direction of the capsule.
/// Position of the object.
/// Rotation of the object.
/// Modifier to add to radius when computing shape of collider.
/// The top, bottom, radius, and height of the capsule collider
public static (Vector3, Vector3, float, float) GetParams(Vector3 capsuleCenter, float capsuleRadius, float capsuleHeight, CapsuleDirection capsuleDirection, Vector3 position, Quaternion rotation, float radiusMod = 0.0f)
{
Vector3 center = rotation * capsuleCenter + position;
float radius = capsuleRadius + radiusMod;
float height = capsuleHeight + radiusMod * 2;
Vector3 up = GetCapsuleRelativeUp(capsuleDirection);
Vector3 bottom = center + rotation * -up * (height / 2 - radius);
Vector3 top = center + rotation * up * (height / 2 - radius);
return (top, bottom, radius, height);
}
///
/// Gets transformed parameters describing this capsule collider for a given position and rotation
///
/// Position of the object.
/// Rotation of the object.
/// Modifier to add to radius when computing shape of collider.
/// The top, bottom, radius, and height of the capsule collider
public (Vector3, Vector3, float, float) GetParams(Vector3 position, Quaternion rotation, float radiusMod = 0.0f)
{
return GetParams(center, radius, height, capsuleDirection, position, rotation, radiusMod);
}
///
public override IEnumerable GetOverlapping(
Vector3 position,
Quaternion rotation,
int layerMask = RaycastHelperConstants.DefaultLayerMask,
QueryTriggerInteraction queryTriggerInteraction = RaycastHelperConstants.DefaultQueryTriggerInteraction,
float skinWidth = 0.0f)
{
(Vector3 top, Vector3 bottom, float radius, float height) = GetParams(position, rotation, -skinWidth);
int overlap = Physics.OverlapCapsuleNonAlloc(top, bottom, radius, OverlapCache, layerMask, queryTriggerInteraction);
return Enumerable.Range(0, overlap).Select(i => OverlapCache[i]).Where(c => c.transform != transform);
}
///
public override IEnumerable GetHits(
Vector3 position,
Quaternion rotation,
Vector3 direction,
float distance,
int layerMask = RaycastHelperConstants.DefaultLayerMask,
QueryTriggerInteraction queryTriggerInteraction = RaycastHelperConstants.DefaultQueryTriggerInteraction,
float skinWidth = 0.01f)
{
(Vector3 top, Vector3 bottom, float radius, float height) = GetParams(position, rotation, -skinWidth);
int hits = Physics.CapsuleCastNonAlloc(top, bottom, radius, direction, HitCache, distance + skinWidth, layerMask, queryTriggerInteraction);
return Enumerable.Range(0, hits).Select(i => HitCache[i])
.Where(hit => hit.collider.transform != transform)
.Select(hit =>
{
hit.distance = Mathf.Max(hit.distance - skinWidth, 0);
return hit;
});
}
///
public override Vector3 GetBottom(Vector3 position, Quaternion rotation)
{
(_, Vector3 bottom, float radius, _) = GetParams(position, rotation);
return bottom + radius * (rotation * -GetCapsuleRelativeUp(capsuleDirection));
}
///
protected override Collider SetupColliderComponent()
{
#if UNITY_EDITOR
// Some magic to auto update the parameters from existing collider
CapsuleCollider existingCollider = GetComponent();
if (capsuleCollider == null && existingCollider != null)
{
capsuleCollider = existingCollider;
radius = existingCollider.radius;
center = existingCollider.center;
height = existingCollider.height;
capsuleDirection = GetCapsuleDirection(existingCollider.direction);
}
#endif
capsuleCollider = gameObject.GetComponent();
if (capsuleCollider == null)
{
capsuleCollider = gameObject.AddComponent();
}
return capsuleCollider;
}
///
public override void UpdateColliderParameters()
{
base.UpdateColliderParameters();
capsuleCollider.radius = radius;
capsuleCollider.height = height;
capsuleCollider.center = center;
capsuleCollider.direction = (int)capsuleDirection;
}
///
/// Get relative up direction for the capsule based on capsule direction.
///
/// Direction from a Capsule Collider.
///
public static Vector3 GetCapsuleRelativeUp(CapsuleDirection capsuleDirection)
{
switch (capsuleDirection)
{
default:
case CapsuleDirection.Y:
return Vector3.up;
case CapsuleDirection.X:
return Vector3.right;
case CapsuleDirection.Z:
return Vector3.forward;
}
}
///
/// See https://docs.unity3d.com/ScriptReference/CapsuleCollider-direction.html
///
/// Direction from a Capsule Collider.
/// CapsuleCollider direction from int to CapsuleDirection
private static CapsuleDirection GetCapsuleDirection(int dir)
{
return (CapsuleDirection)dir;
}
///
/// Debug function to reset the attached collider attribute.
///
internal void ResetConfigDebug()
{
capsuleCollider = null;
}
}
}