diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index 4cd12e9c1..f03e26323 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. - Fixed error when both HDRP and URP rendering pipelines are added to a project.\ - Fixed outdated warning on RotationComposerComponent. - Fixed GC Alloc in CinemachineInputAxisController.Update(). +- Fixed ForceCameraPosition regression in CinemachineFollow/CinemachineOrbitalFollow/CinemachineTransposer/CinemachineOrbitalTransposer to allow looking away from targets. ### Changed - Converted code using InstanceID references and API to EntityID. diff --git a/com.unity.cinemachine/Runtime/Core/TargetTracking.cs b/com.unity.cinemachine/Runtime/Core/TargetTracking.cs index d30aa7cca..ca012357a 100644 --- a/com.unity.cinemachine/Runtime/Core/TargetTracking.cs +++ b/com.unity.cinemachine/Runtime/Core/TargetTracking.cs @@ -179,7 +179,6 @@ struct Tracker public Quaternion PreviousReferenceOrientation { get; private set; } Vector3 m_PreviousOffset; - Vector3 m_PreviousTargetPositionDampingOffset; Quaternion m_TargetOrientationOnAssign; Transform m_PreviousTarget; @@ -331,7 +330,6 @@ public void TrackTarget( outTargetPosition = PreviousTargetPosition = currentPosition; outTargetOrient = dampedRot; - m_PreviousTargetPositionDampingOffset = currentPosition - targetPosition; } Vector3 GetTargetPositionWithOffset( @@ -405,20 +403,20 @@ public void OnForceCameraPosition( BindingMode bindingMode, Vector3 targetOffset, ref CameraState newState) { - var state = component.VcamState; // old state - var prevOrient = GetReferenceOrientation( - component, bindingMode, targetOffset, newState.ReferenceUp, ref state); - var targetPos = GetTargetPositionWithOffset(component, bindingMode, targetOffset, prevOrient); + var oldState = component.VcamState; - var orient = GetReferenceOrientation( - component, bindingMode, targetOffset, newState.ReferenceUp, ref newState); - m_PreviousOffset = orient * (Quaternion.Inverse(prevOrient) * m_PreviousOffset); - PreviousReferenceOrientation = orient; + var oldOrient = GetReferenceOrientation(component, bindingMode, targetOffset, newState.ReferenceUp, ref oldState); + var newOrient = GetReferenceOrientation(component, bindingMode, targetOffset, newState.ReferenceUp, ref newState); - // Rotate the damping data also, to preserve position damping integrity - var deltaRot = newState.GetFinalOrientation() * Quaternion.Inverse(state.GetFinalOrientation()); - m_PreviousTargetPositionDampingOffset = deltaRot * m_PreviousTargetPositionDampingOffset; - PreviousTargetPosition = targetPos + m_PreviousTargetPositionDampingOffset; + // Recompute the target position based on the camera state and target offset. This has the effect + // of snapping the camera to camera position since the camera posistion will be computed from the target and + // the target offset during the next frame. + var cameraOffset = Quaternion.Inverse(oldState.GetFinalOrientation()) * (PreviousTargetPosition - oldState.GetFinalPosition()); + PreviousTargetPosition = newState.GetFinalOrientation() * cameraOffset + newState.GetFinalPosition(); + PreviousReferenceOrientation = newOrient; + + m_PreviousOffset = newOrient * (Quaternion.Inverse(oldOrient) * m_PreviousOffset); + var deltaRot = newState.GetFinalOrientation() * Quaternion.Inverse(oldState.GetFinalOrientation()); if (bindingMode == BindingMode.WorldSpace) m_PreviousOffset = deltaRot * m_PreviousOffset; } diff --git a/com.unity.cinemachine/Tests/Runtime/CinemachineRuntimeFixtureBase.cs b/com.unity.cinemachine/Tests/Runtime/CinemachineRuntimeFixtureBase.cs index a5e56cc70..49c7c1e68 100644 --- a/com.unity.cinemachine/Tests/Runtime/CinemachineRuntimeFixtureBase.cs +++ b/com.unity.cinemachine/Tests/Runtime/CinemachineRuntimeFixtureBase.cs @@ -15,6 +15,7 @@ public class CinemachineRuntimeFixtureBase : CinemachineFixtureBase protected CinemachineBrain m_Brain; protected readonly FloatEqualityComparer m_FloatEqualityComparer = new(UnityVectorExtensions.Epsilon); protected readonly Vector3EqualityComparer m_Vector3EqualityComparer = new(UnityVectorExtensions.Epsilon); + protected readonly QuaternionEqualityComparer m_QuaternionComparer = new QuaternionEqualityComparer(1e-5f); [SetUp] public override void SetUp() diff --git a/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs b/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs new file mode 100644 index 000000000..29a24f190 --- /dev/null +++ b/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs @@ -0,0 +1,46 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Cinemachine; + +namespace Unity.Cinemachine.Tests +{ + [TestFixture] + public class FollowForcePositionTests : CinemachineRuntimeFixtureBase + { + CinemachineCamera m_Vcam; + GameObject m_FollowObject; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_Vcam = CreateGameObject("CM Vcam", typeof(CinemachineCamera)).GetComponent(); + m_FollowObject = CreateGameObject("Follow Object"); + } + + [UnityTest, Description("UUM-131870: ForceCameraPosition should allow looking away from the target.")] + public IEnumerator LookAwayFromTarget() + { + CinemachineCore.UniformDeltaTimeOverride = 0.0f; // we only test force position, not damping + var follow = m_Vcam.gameObject.AddComponent(); + follow.FollowOffset = new Vector3(0, 0, -10); + m_Vcam.Follow = m_FollowObject.transform; + + // Initial update to settle camera + m_FollowObject.transform.position = Vector3.zero; + yield return null; + Assume.That(m_Vcam.State.GetFinalPosition(), Is.EqualTo(follow.FollowOffset).Using(m_Vector3EqualityComparer)); + + // Move camera away from target + var forcedPos = new Vector3(0, 5, -5); + var forcedRot = Quaternion.identity; + m_Vcam.ForceCameraPosition(forcedPos, forcedRot); + yield return null; + Assert.That(m_Vcam.State.GetFinalPosition(), Is.EqualTo(forcedPos).Using(m_Vector3EqualityComparer)); + Assert.That(m_Vcam.State.GetFinalOrientation(), Is.EqualTo(forcedRot).Using(m_QuaternionComparer)); + } + } +} diff --git a/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs.meta b/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs.meta new file mode 100644 index 000000000..948472d7f --- /dev/null +++ b/com.unity.cinemachine/Tests/Runtime/FollowForcePositionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9106470f138959c43a0d029e42c4c217