From 01f033d0486b5980958e155984a2ee6aaf408766 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Thu, 9 Jan 2025 11:55:24 -0500 Subject: [PATCH 1/2] bugfix: decollider would sometimes cause camera to slip inside cracks between adjacent colliders --- com.unity.cinemachine/CHANGELOG.md | 1 + .../Behaviours/CinemachineDecollider.cs | 135 +++++++++--------- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index a35c8cd06..8d20d3e78 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - CameraDeactivated events were not sent consistently when a blend interrupted another blend before completion. - CameraActivated events were not sent consistently when activation was due to timeline blends. - Bugfix: FramingTransposer with a dead zone would sometimes drift. +- Decollider would sometimes cause camera to slip inside cracks between adjacent colliders. ### Changed - Added delayed processing to near and far clip plane inspector fields for the CinemachineCamera lens. diff --git a/com.unity.cinemachine/Runtime/Behaviours/CinemachineDecollider.cs b/com.unity.cinemachine/Runtime/Behaviours/CinemachineDecollider.cs index 804e07d0d..bdbc29862 100644 --- a/com.unity.cinemachine/Runtime/Behaviours/CinemachineDecollider.cs +++ b/com.unity.cinemachine/Runtime/Behaviours/CinemachineDecollider.cs @@ -1,5 +1,6 @@ #if CINEMACHINE_PHYSICS using System; +using System.Collections.Generic; using UnityEngine; namespace Unity.Cinemachine @@ -103,7 +104,18 @@ public struct TerrainSettings [FoldoutWithEnabledButton] public TerrainSettings TerrainResolution; - static Collider[] s_ColliderBuffer = new Collider[10]; + const int kColliderBufferSize = 10; + static Collider[] s_ColliderBuffer = new Collider[kColliderBufferSize]; + static float[] s_ColliderDistanceBuffer = new float[kColliderBufferSize]; + static int[] s_ColliderOrderBuffer = new int[kColliderBufferSize]; + + // Farthest stuff comes first + static readonly IComparer s_ColliderBufferSorter = Comparer.Create((a, b) => + { + if (s_ColliderDistanceBuffer[a] == s_ColliderDistanceBuffer[b]) + return 0; + return s_ColliderDistanceBuffer[a] > s_ColliderDistanceBuffer[b] ? -1 : 1; + }); void OnValidate() { @@ -143,28 +155,22 @@ class VcamExtraState : VcamExtraStateBase public Vector3 PreviousCorrectedCameraPosition; float m_SmoothedDistance; - float m_SmoothedTime; - public float ApplyDistanceSmoothing(float distance, float smoothingTime) - { - if (m_SmoothedTime != 0 && smoothingTime > Epsilon) - { - if (CinemachineCore.CurrentTime - m_SmoothedTime < smoothingTime) - return Mathf.Min(distance, m_SmoothedDistance); - } - return distance; - } - public void UpdateDistanceSmoothing(float distance) + float m_SmoothingStartTime; + public float UpdateDistanceSmoothing(float distance, float smoothingTime, bool haveDisplacement) { - if (m_SmoothedDistance == 0 || distance < m_SmoothedDistance) + if (haveDisplacement && (m_SmoothedDistance == 0 || distance <= m_SmoothedDistance)) { m_SmoothedDistance = distance; - m_SmoothedTime = CinemachineCore.CurrentTime; + m_SmoothingStartTime = CinemachineCore.CurrentTime; } - } - public void ResetDistanceSmoothing(float smoothingTime) - { - if (CinemachineCore.CurrentTime - m_SmoothedTime >= smoothingTime) - m_SmoothedDistance = m_SmoothedTime = 0; + + if (m_SmoothingStartTime != 0 && CinemachineCore.CurrentTime - m_SmoothingStartTime < smoothingTime) + distance = Mathf.Min(distance, m_SmoothedDistance); + + if (!haveDisplacement && CinemachineCore.CurrentTime - m_SmoothingStartTime >= smoothingTime) + m_SmoothedDistance = m_SmoothingStartTime = 0; + + return distance; } }; @@ -293,41 +299,47 @@ Vector3 DecollideCamera(Vector3 cameraPos, Vector3 lookAtPoint) var capsuleLength = dir.magnitude; if (capsuleLength < Epsilon) return Vector3.zero; - - dir /= capsuleLength; - capsuleLength = Mathf.Max(Epsilon, capsuleLength - CameraRadius * 2); - lookAtPoint = cameraPos - dir * capsuleLength; - - Vector3 newCamPos = cameraPos; int numObstacles = Physics.OverlapCapsuleNonAlloc( lookAtPoint, cameraPos, CameraRadius - Epsilon, s_ColliderBuffer, layers, QueryTriggerInteraction.Ignore); + if (numObstacles == 0) + return Vector3.zero; - // Find the one that the camera is intersecting that is closest to the target - if (numObstacles > 0) + dir /= capsuleLength; // normalize + + // Sort the colliders fartherst-to-nearest + for (int i = 0; i < numObstacles; ++i) { - var scratchCollider = RuntimeUtility.GetScratchCollider(); - scratchCollider.radius = CameraRadius - Epsilon; - float bestDistance = float.MaxValue; - for (int i = 0; i < numObstacles; ++i) + var c = s_ColliderBuffer[i]; + s_ColliderOrderBuffer[i] = i; + s_ColliderDistanceBuffer[i] = 0; // if raycast fails then target is inside collider - we will ignore those colliders + if (c.Raycast(new Ray(lookAtPoint, dir), out var hitInfo, capsuleLength + CameraRadius)) { - var c = s_ColliderBuffer[i]; - if (Physics.ComputePenetration( - scratchCollider, newCamPos, Quaternion.identity, - c, c.transform.position, c.transform.rotation, - out var _, out var _)) - { - // Camera is intersecting - decollide in direction of lookAtPoint - if (c.Raycast(new Ray(lookAtPoint, dir), out var hitInfo, capsuleLength)) - { - var distance = Mathf.Max(0, hitInfo.distance - CameraRadius); - if (distance < bestDistance) - { - bestDistance = distance; - newCamPos = lookAtPoint + dir * distance; - } - } - } + var distance = hitInfo.distance - CameraRadius; + if (distance < CameraRadius) + distance = Mathf.Max(0.01f, distance + (CameraRadius - distance) * 0.5f); + s_ColliderDistanceBuffer[i] = distance; + } + } + Array.Sort(s_ColliderOrderBuffer, 0, numObstacles, s_ColliderBufferSorter); + + // Move camera in front of any overlapping obstacles + var newCamPos = cameraPos; + var scratchCollider = RuntimeUtility.GetScratchCollider(); + scratchCollider.radius = CameraRadius - Epsilon; + for (int i = 0; i < numObstacles; ++i) + { + var index = s_ColliderOrderBuffer[i]; + if (s_ColliderDistanceBuffer[index] == 0) + continue; // ignore colliders that are on the target + var c = s_ColliderBuffer[index]; + if (Physics.ComputePenetration( + scratchCollider, newCamPos, Quaternion.identity, + c, c.transform.position, c.transform.rotation, + out var _, out var _)) + { + // Camera overlaps - move it in front + newCamPos = lookAtPoint + dir * s_ColliderDistanceBuffer[index]; } } return newCamPos - cameraPos; @@ -338,35 +350,26 @@ Vector3 ApplySmoothingAndDamping( Vector3 oldCamPos, VcamExtraState extra, float deltaTime) { var dir = oldCamPos + displacement - lookAtPoint; - var distance = float.MaxValue;; + var distance = float.MaxValue; if (deltaTime >= 0) { distance = dir.magnitude; - if (distance > Epsilon) + if (distance > CameraRadius) { // Apply smoothing dir /= distance; if (Decollision.SmoothingTime > Epsilon) { - if (!displacement.AlmostZero()) - extra.UpdateDistanceSmoothing(distance); - distance = extra.ApplyDistanceSmoothing(distance, Decollision.SmoothingTime); + distance = extra.UpdateDistanceSmoothing(distance, Decollision.SmoothingTime, !displacement.AlmostZero()); displacement = (lookAtPoint + dir * distance) - oldCamPos; } - if (displacement.AlmostZero()) - { - extra.ResetDistanceSmoothing(Decollision.SmoothingTime); - // Apply damping - if (Decollision.Damping > Epsilon) - { - if (distance > extra.PreviousObstacleDisplacement) - { - distance = extra.PreviousObstacleDisplacement - + Damper.Damp(distance - extra.PreviousObstacleDisplacement, Decollision.Damping, deltaTime); - displacement = (lookAtPoint + dir * distance) - oldCamPos; - } - } + // Apply damping + if (Decollision.Damping > Epsilon && distance > extra.PreviousObstacleDisplacement) + { + distance = extra.PreviousObstacleDisplacement + + Damper.Damp(distance - extra.PreviousObstacleDisplacement, Decollision.Damping, deltaTime); + displacement = (lookAtPoint + dir * distance) - oldCamPos; } } } From c6b10f0aebe45ef3109bf44ff7e1c73ec0217057 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Tue, 14 Jan 2025 10:15:40 -0500 Subject: [PATCH 2/2] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style Co-authored-by: Sébastien Duverne <55094336+sebastienduverne@users.noreply.github.com> --- com.unity.cinemachine/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index 8d20d3e78..390d7c63f 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Regression fix: CinemachinePanTilt recentering was ignoring axis Center setting. - CameraDeactivated events were not sent consistently when a blend interrupted another blend before completion. - CameraActivated events were not sent consistently when activation was due to timeline blends. -- Bugfix: FramingTransposer with a dead zone would sometimes drift. +- FramingTransposer with a dead zone would sometimes drift. - Decollider would sometimes cause camera to slip inside cracks between adjacent colliders. ### Changed