/// <summary>Positions the virtual camera according to the transposer rules.</summary>
        /// <param name="curState">The current camera state</param>
        /// <param name="deltaTime">Used for damping.  If less than 0, no damping is done.</param>
        public override void MutateCameraState(ref CameraState curState, float deltaTime)
        {
            InitPrevFrameStateInfo(ref curState, deltaTime);

            // Update the heading
            if (FollowTarget != PreviousTarget)
            {
                PreviousTarget      = FollowTarget;
                mTargetRigidBody    = (PreviousTarget == null) ? null : PreviousTarget.GetComponent <Rigidbody>();
                mLastTargetPosition = (PreviousTarget == null) ? Vector3.zero : PreviousTarget.position;
                mHeadingTracker     = null;
            }
            LastHeading = HeadingUpdater(this, deltaTime, curState.ReferenceUp);
            float heading = LastHeading;

            if (IsValid)
            {
                // Calculate the heading
                if (m_BindingMode != BindingMode.SimpleFollowWithWorldUp)
                {
                    heading += m_Heading.m_Bias;
                }
                Quaternion headingRot = Quaternion.AngleAxis(heading, Vector3.up);

                Vector3 rawOffset = EffectiveOffset;
                Vector3 offset    = headingRot * rawOffset;

                // Track the target, with damping
                TrackTarget(deltaTime, curState.ReferenceUp, offset, out Vector3 pos, out Quaternion orient);

                // Place the camera
                offset = orient * offset;
                curState.ReferenceUp = orient * Vector3.up;

                // Respect minimum target distance on XZ plane
                var targetPosition = FollowTargetPosition;
                pos += GetOffsetForMinimumTargetDistance(
                    pos, offset, curState.RawOrientation * Vector3.forward,
                    curState.ReferenceUp, targetPosition);
                curState.RawPosition = pos + offset;

                if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
                {
                    var lookAt = targetPosition;
                    if (LookAtTarget != null)
                    {
                        lookAt = LookAtTargetPosition;
                    }
                    var dir0 = mLastCameraPosition - lookAt;
                    var dir1 = curState.RawPosition - lookAt;
                    if (dir0.sqrMagnitude > 0.01f && dir1.sqrMagnitude > 0.01f)
                    {
                        curState.PositionDampingBypass = UnityVectorExtensions.SafeFromToRotation(
                            dir0, dir1, curState.ReferenceUp).eulerAngles;
                    }
                }
                mLastTargetPosition = targetPosition;
                mLastCameraPosition = curState.RawPosition;
            }
        }
        /// <summary>Positions the virtual camera according to the transposer rules.</summary>
        /// <param name="deltaTime">Used for damping.  If less than 0, no damping is done.</param>
        /// <param name="up">Current camera up</param>
        /// <param name="desiredCameraOffset">Where we want to put the camera relative to the follow target</param>
        /// <param name="outTargetPosition">Resulting camera position</param>
        /// <param name="outTargetOrient">Damped target orientation</param>
        protected void TrackTarget(
            float deltaTime, Vector3 up, Vector3 desiredCameraOffset,
            out Vector3 outTargetPosition, out Quaternion outTargetOrient)
        {
            var  targetOrientation = GetReferenceOrientation(up);
            var  dampedOrientation = targetOrientation;
            bool prevStateValid    = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;

            if (prevStateValid)
            {
                if (m_AngularDampingMode == AngularDampingMode.Quaternion &&
                    m_BindingMode == BindingMode.LockToTarget)
                {
                    float t = VirtualCamera.DetachedFollowTargetDamp(1, m_AngularDamping, deltaTime);
                    dampedOrientation = Quaternion.Slerp(
                        m_PreviousReferenceOrientation, targetOrientation, t);
                }
                else
                {
                    var relative = (Quaternion.Inverse(m_PreviousReferenceOrientation)
                                    * targetOrientation).eulerAngles;
                    for (int i = 0; i < 3; ++i)
                    {
                        if (Mathf.Abs(relative[i]) < 0.01f) // correct for precision drift
                        {
                            relative[i] = 0;
                        }
                        else if (relative[i] > 180)
                        {
                            relative[i] -= 360;
                        }
                    }
                    relative          = VirtualCamera.DetachedFollowTargetDamp(relative, AngularDamping, deltaTime);
                    dampedOrientation = m_PreviousReferenceOrientation * Quaternion.Euler(relative);
                }
            }
            m_PreviousReferenceOrientation = dampedOrientation;

            var targetPosition  = FollowTargetPosition;
            var currentPosition = m_PreviousTargetPosition;
            var previousOffset  = prevStateValid ? m_PreviousOffset : desiredCameraOffset;
            var offsetDelta     = desiredCameraOffset - previousOffset;

            if (offsetDelta.sqrMagnitude > 0.01f)
            {
                var q = UnityVectorExtensions.SafeFromToRotation(
                    m_PreviousOffset.ProjectOntoPlane(up),
                    desiredCameraOffset.ProjectOntoPlane(up), up);
                currentPosition = targetPosition + q * (m_PreviousTargetPosition - targetPosition);
            }
            m_PreviousOffset = desiredCameraOffset;

            // Adjust for damping, which is done in camera-offset-local coords
            var positionDelta = targetPosition - currentPosition;

            if (prevStateValid)
            {
                Quaternion dampingSpace;
                if (desiredCameraOffset.AlmostZero())
                {
                    dampingSpace = VcamState.RawOrientation;
                }
                else
                {
                    dampingSpace = Quaternion.LookRotation(dampedOrientation * desiredCameraOffset, up);
                }
                var localDelta = Quaternion.Inverse(dampingSpace) * positionDelta;
                localDelta    = VirtualCamera.DetachedFollowTargetDamp(localDelta, Damping, deltaTime);
                positionDelta = dampingSpace * localDelta;
            }
            currentPosition += positionDelta;

            outTargetPosition = m_PreviousTargetPosition = currentPosition;
            outTargetOrient   = dampedOrientation;
        }