// 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; } } }