namespace Fusion.Addons.KCC { using System; using System.Collections.Generic; using UnityEngine; // This file contains implementation related to physics. public partial class KCC { // PUBLIC METHODS /// /// Sphere overlap using same filtering as for KCC physics query. /// /// Contains results of the overlap. /// Center position of the sphere. /// Radius of the sphere. /// Use to enable/disable trigger hits. public bool SphereOverlap(KCCOverlapInfo overlapInfo, Vector3 position, float radius, QueryTriggerInteraction triggerInteraction) { return SphereOverlap(overlapInfo, Data, position, radius, default, _settings.CollisionLayerMask, triggerInteraction); } /// /// Capsule overlap using same filtering as for KCC physics query. /// /// Contains results of the overlap. /// Bottom position of the capsule. /// Radius of the capsule. /// Height of the capsule. /// Use to enable/disable trigger hits. public bool CapsuleOverlap(KCCOverlapInfo overlapInfo, Vector3 position, float radius, float height, QueryTriggerInteraction triggerInteraction) { return CapsuleOverlap(overlapInfo, Data, position, radius, height, default, _settings.CollisionLayerMask, triggerInteraction); } /// /// Ray cast using same filtering as for KCC physics query. /// /// Contains results of the cast sorted by distance. /// Origin position of the cast. /// Direction of the cast. /// Distance of the cast. /// Use to enable/disable trigger hits. public bool RayCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction) { return RayCast(shapeCastInfo, Data, position, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction); } /// /// Sphere cast using same filtering as for KCC physics query. /// /// Contains results of the cast sorted by distance. /// Center position of the sphere. /// Radius of the sphere. /// Direction of the cast. /// Distance of the cast. /// Use to enable/disable trigger hits. /// Set to true for the result to contain initially overlapping colliders. public bool SphereCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, float radius, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps = true) { return SphereCast(shapeCastInfo, Data, position, radius, default, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction, trackInitialOverlaps); } /// /// Capsule cast using same filtering as for KCC physics query. /// /// Contains results of the cast sorted by distance. /// Bottom position of the capsule. /// Radius of the capsule. /// Height of the capsule. /// Direction of the cast. /// Distance of the cast. /// Use to enable/disable trigger hits. /// Set to true for the result to contain initially overlapping colliders. public bool CapsuleCast(KCCShapeCastInfo shapeCastInfo, Vector3 position, float radius, float height, Vector3 direction, float maxDistance, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps = true) { return CapsuleCast(shapeCastInfo, Data, position, radius, height, default, direction, maxDistance, _settings.CollisionLayerMask, triggerInteraction, trackInitialOverlaps); } /// /// Force refresh KCCData.Hits based on current position. If an existing overlap info is provided, the method takes as much information as possible from it. /// Metadata (collision type, penetration, ...) for other (new) hits will be missing. Use with caution. /// /// Base overlap query results. Only matching colliders metadata is taken from this info if new overlap query is executed. /// Controls reuse of base query results / execution of a new overlap query. /// /// Default - Hits from base overlap query will be reused only if all colliders are within extent, otherwise new overlap query will be executed. /// Reuse - Force reuse hits from base overlap query, even if colliders are not within extent. /// New - Force execute new overlap query. /// /// public void UpdateHits(KCCOverlapInfo baseOverlapInfo, EKCCHitsOverlapQuery overlapQuery) { UpdateHits(Data, baseOverlapInfo, overlapQuery); } /// /// Check if the KCC potentially collides with a collider, using same filtering as physics query. /// Returning true doesn't mean the collider overlaps. Can be used as a filter after custom overlap/shapecast query. /// /// Collider instance. public bool IsValidHitCollider(Collider hitCollider) { if (hitCollider == null) return false; return IsValidHitCollider(Data, hitCollider); } // PRIVATE METHODS private bool SphereOverlap(KCCOverlapInfo overlapInfo, KCCData data, Vector3 position, float radius, float extent, LayerMask layerMask, QueryTriggerInteraction triggerInteraction) { overlapInfo.Reset(false); overlapInfo.Position = position; overlapInfo.Radius = radius; overlapInfo.Height = 0.0f; overlapInfo.Extent = extent; overlapInfo.LayerMask = layerMask; overlapInfo.TriggerInteraction = triggerInteraction; Collider hitCollider; Collider[] hitColliders = _hitColliders; int hitColliderCount = Runner.GetPhysicsScene().OverlapSphere(position, radius + extent, hitColliders, layerMask, triggerInteraction); for (int i = 0; i < hitColliderCount; ++i) { hitCollider = hitColliders[i]; if (IsValidHitColliderUnsafe(data, hitCollider) == true) { overlapInfo.AddHit(hitCollider); } } return overlapInfo.AllHitCount > 0; } private bool CapsuleOverlap(KCCOverlapInfo overlapInfo, KCCData data, Vector3 position, float radius, float height, float extent, LayerMask layerMask, QueryTriggerInteraction triggerInteraction) { overlapInfo.Reset(false); overlapInfo.Position = position; overlapInfo.Radius = radius; overlapInfo.Height = height; overlapInfo.Extent = extent; overlapInfo.LayerMask = layerMask; overlapInfo.TriggerInteraction = triggerInteraction; Vector3 positionUp = position + new Vector3(0.0f, height - radius, 0.0f); Vector3 positionDown = position + new Vector3(0.0f, radius, 0.0f); Collider hitCollider; Collider[] hitColliders = _hitColliders; int hitColliderCount = Runner.GetPhysicsScene().OverlapCapsule(positionDown, positionUp, radius + extent, hitColliders, layerMask, triggerInteraction); for (int i = 0; i < hitColliderCount; ++i) { hitCollider = hitColliders[i]; if (IsValidHitColliderUnsafe(data, hitCollider) == true) { overlapInfo.AddHit(hitCollider); } } return overlapInfo.AllHitCount > 0; } private bool RayCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction) { shapeCastInfo.Reset(false); shapeCastInfo.Position = position; shapeCastInfo.Direction = direction; shapeCastInfo.MaxDistance = maxDistance; shapeCastInfo.LayerMask = layerMask; shapeCastInfo.TriggerInteraction = triggerInteraction; RaycastHit raycastHit; RaycastHit[] raycastHits = _raycastHits; int raycastHitCount = Runner.GetPhysicsScene().Raycast(position, direction, raycastHits, maxDistance, layerMask, triggerInteraction); for (int i = 0; i < raycastHitCount; ++i) { raycastHit = raycastHits[i]; if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true) { shapeCastInfo.AddHit(raycastHit); } } shapeCastInfo.Sort(); return shapeCastInfo.AllHitCount > 0; } private bool SphereCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, float radius, float extent, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps) { shapeCastInfo.Reset(false); shapeCastInfo.Position = position; shapeCastInfo.Radius = radius; shapeCastInfo.Extent = extent; shapeCastInfo.Direction = direction; shapeCastInfo.MaxDistance = maxDistance; shapeCastInfo.LayerMask = layerMask; shapeCastInfo.TriggerInteraction = triggerInteraction; RaycastHit raycastHit; RaycastHit[] raycastHits = _raycastHits; int raycastHitCount = Runner.GetPhysicsScene().SphereCast(position, radius + extent, direction, raycastHits, maxDistance, layerMask, triggerInteraction); for (int i = 0; i < raycastHitCount; ++i) { raycastHit = raycastHits[i]; if (trackInitialOverlaps == false && raycastHit.distance <= 0.0f && raycastHit.point.Equals(default) == true) continue; if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true) { shapeCastInfo.AddHit(raycastHit); } } shapeCastInfo.Sort(); return shapeCastInfo.AllHitCount > 0; } private bool CapsuleCast(KCCShapeCastInfo shapeCastInfo, KCCData data, Vector3 position, float radius, float height, float extent, Vector3 direction, float maxDistance, LayerMask layerMask, QueryTriggerInteraction triggerInteraction, bool trackInitialOverlaps) { shapeCastInfo.Reset(false); shapeCastInfo.Position = position; shapeCastInfo.Radius = radius; shapeCastInfo.Height = height; shapeCastInfo.Extent = extent; shapeCastInfo.Position = position; shapeCastInfo.Direction = direction; shapeCastInfo.MaxDistance = maxDistance; shapeCastInfo.LayerMask = layerMask; shapeCastInfo.TriggerInteraction = triggerInteraction; Vector3 positionUp = position + new Vector3(0.0f, height - radius, 0.0f); Vector3 positionDown = position + new Vector3(0.0f, radius, 0.0f); RaycastHit raycastHit; RaycastHit[] raycastHits = _raycastHits; int raycastHitCount = Runner.GetPhysicsScene().CapsuleCast(positionDown, positionUp, radius + extent, direction, raycastHits, maxDistance, layerMask, triggerInteraction); for (int i = 0; i < raycastHitCount; ++i) { raycastHit = raycastHits[i]; if (trackInitialOverlaps == false && raycastHit.distance <= 0.0f && raycastHit.point.Equals(default) == true) continue; if (IsValidHitColliderUnsafe(data, raycastHit.collider) == true) { shapeCastInfo.AddHit(raycastHit); } } shapeCastInfo.Sort(); return shapeCastInfo.AllHitCount > 0; } private void UpdateHits(KCCData data, KCCOverlapInfo baseOverlapInfo, EKCCHitsOverlapQuery overlapQuery) { bool reuseOverlapInfo; switch (overlapQuery) { case EKCCHitsOverlapQuery.Default: { reuseOverlapInfo = baseOverlapInfo != null && baseOverlapInfo.AllHitsWithinExtent() == true; break; } case EKCCHitsOverlapQuery.Reuse: { reuseOverlapInfo = baseOverlapInfo != null; break; } case EKCCHitsOverlapQuery.New: { reuseOverlapInfo = false; break; } default: throw new NotImplementedException(nameof(overlapQuery)); } if (reuseOverlapInfo == true) { _trackOverlapInfo.CopyFromOther(baseOverlapInfo); } else { CapsuleOverlap(_trackOverlapInfo, data, data.TargetPosition, _settings.Radius, _settings.Height, _settings.Extent, _settings.CollisionLayerMask, QueryTriggerInteraction.Collide); if (baseOverlapInfo != null) { for (int i = 0; i < _trackOverlapInfo.AllHitCount; ++i) { KCCOverlapHit trackedHit = _trackOverlapInfo.AllHits[i]; for (int j = 0; j < baseOverlapInfo.AllHitCount; ++j) { KCCOverlapHit extendedHit = baseOverlapInfo.AllHits[j]; if (object.ReferenceEquals(trackedHit.Collider, extendedHit.Collider) == true) { trackedHit.CopyFromOther(extendedHit); } } } } } data.Hits.Clear(); for (int i = 0, count = _trackOverlapInfo.AllHitCount; i < count; ++i) { data.Hits.Add(_trackOverlapInfo.AllHits[i]); } } private void ForceRemoveAllHits(KCCData data) { _trackOverlapInfo.Reset(false); _extendedOverlapInfo.Reset(false); data.Hits.Clear(); } private bool IsValidHitCollider(KCCData data, Collider hitCollider) { if (hitCollider == _collider.Collider) return false; for (int i = 0, count = _childColliders.Count; i < count; ++i) { if (hitCollider == _childColliders[i]) return false; } int colliderLayerMask = 1 << hitCollider.gameObject.layer; if ((_settings.CollisionLayerMask & colliderLayerMask) != colliderLayerMask) return false; List ignores = data.Ignores.All; for (int i = 0, count = ignores.Count; i < count; ++i) { if (hitCollider == ignores[i].Collider) return false; } if (ResolveCollision != null) { try { return ResolveCollision(this, hitCollider); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return true; } private bool IsValidHitColliderUnsafe(KCCData data, Collider overlapCollider) { if (object.ReferenceEquals(overlapCollider, _collider.Collider) == true) return false; for (int i = 0, count = _childColliders.Count; i < count; ++i) { if (object.ReferenceEquals(overlapCollider, _childColliders[i]) == true) return false; } List ignores = data.Ignores.All; for (int i = 0, count = ignores.Count; i < count; ++i) { if (object.ReferenceEquals(overlapCollider, ignores[i].Collider) == true) return false; } if (ResolveCollision != null) { try { return ResolveCollision(this, overlapCollider); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return true; } } }