void PositionCamera(ref CameraState curState, float deltaTime) { var targetPos = FollowTargetPosition; var prevTargetPos = deltaTime >= 0 ? PreviousFollowTargetPosition : targetPos; // Compute damped target pos (compute in camera space) var dampedTargetPos = Quaternion.Inverse(curState.RawOrientation) * (targetPos - prevTargetPos); if (deltaTime >= 0) { dampedTargetPos = VirtualCamera.DetachedFollowTargetDamp( dampedTargetPos, Damping, deltaTime); } dampedTargetPos = prevTargetPos + curState.RawOrientation * dampedTargetPos; // Get target rotation (worldspace) var fwd = Vector3.forward; var up = Vector3.up; var followTargetRotation = FollowTargetRotation; var followTargetForward = followTargetRotation * fwd; var angle = UnityVectorExtensions.SignedAngle( fwd, followTargetForward.ProjectOntoPlane(up), up); var previousHeadingAngle = deltaTime >= 0 ? PreviousHeadingAngle : angle; var deltaHeading = angle - previousHeadingAngle; PreviousHeadingAngle = angle; // Bypass user-sourced rotation dampedTargetPos = targetPos + Quaternion.AngleAxis(deltaHeading, up) * (dampedTargetPos - targetPos); PreviousFollowTargetPosition = dampedTargetPos; GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3 hand); // 1. Check if pivot itself is colliding with something, if yes, then move the pivot // closer to the player. The radius is bigger here than in step 2, to avoid problems // next to walls. Where the preferred distance would be pulled completely to the // player, using a bigger radius, this won't happen. hand = PullTowardsStartOnCollision(in root, in hand, in CameraCollisionFilter, CameraRadius * 1.05f); // 2. Try to place the camera to the preferred distance var camPos = hand - (followTargetForward * CameraDistance); camPos = PullTowardsStartOnCollision(in hand, in camPos, in CameraCollisionFilter, CameraRadius); curState.RawPosition = camPos; curState.RawOrientation = FollowTargetRotation; curState.ReferenceLookAt = camPos + 1000.0f * (FollowTargetRotation * Vector3.forward); curState.ReferenceUp = up; }
/// <summary>Applies the composer rules and orients the camera accordingly</summary> /// <param name="curState">The current camera state</param> /// <param name="deltaTime">Used for calculating damping. If less than /// zero, then target will snap to the center of the dead zone.</param> public override void MutateCameraState(ref CameraState curState, float deltaTime) { if (!IsValid) { return; } Vector3 dampedPos = FollowTargetPosition; if (deltaTime >= 0) { dampedPos = m_PreviousTargetPosition + VirtualCamera.DetachedFollowTargetDamp( dampedPos - m_PreviousTargetPosition, m_Damping, deltaTime); } m_PreviousTargetPosition = dampedPos; curState.RawPosition = dampedPos; }
/// <summary>Orients the camera to match the Follow target's orientation</summary> /// <param name="curState">The current camera state</param> /// <param name="deltaTime">Not used.</param> public override void MutateCameraState(ref CameraState curState, float deltaTime) { if (!IsValid) { return; } Quaternion dampedOrientation = FollowTargetRotation; if (deltaTime >= 0) { float t = VirtualCamera.DetachedFollowTargetDamp(1, m_Damping, deltaTime); dampedOrientation = Quaternion.Slerp( m_PreviousReferenceOrientation, FollowTargetRotation, t); } m_PreviousReferenceOrientation = dampedOrientation; curState.RawOrientation = dampedOrientation; }
void PositionCamera(ref CameraState curState, float deltaTime) { var up = curState.ReferenceUp; var targetPos = FollowTargetPosition; var targetRot = FollowTargetRotation; var targetForward = targetRot * Vector3.forward; var heading = GetHeading(targetRot, up); if (deltaTime < 0) { // No damping - reset damping state info m_DampingCorrection = Vector3.zero; #if CINEMACHINE_PHYSICS m_CamPosCollisionCorrection = 0; #endif } else { // Damping correction is applied to the shoulder offset - stretching the rig m_DampingCorrection += Quaternion.Inverse(heading) * (m_PreviousFollowTargetPosition - targetPos); m_DampingCorrection -= VirtualCamera.DetachedFollowTargetDamp(m_DampingCorrection, Damping, deltaTime); } m_PreviousFollowTargetPosition = targetPos; var root = targetPos; GetRawRigPositions(root, targetRot, heading, out _, out Vector3 hand); // Place the camera at the correct distance from the hand var camPos = hand - (targetForward * (CameraDistance - m_DampingCorrection.z)); #if CINEMACHINE_PHYSICS // Check if hand is colliding with something, if yes, then move the hand // closer to the player. The radius is slightly enlarged, to avoid problems // next to walls float dummy = 0; var collidedHand = ResolveCollisions(root, hand, -1, CameraRadius * 1.05f, ref dummy); camPos = ResolveCollisions( collidedHand, camPos, deltaTime, CameraRadius, ref m_CamPosCollisionCorrection); #endif // Set state curState.RawPosition = camPos; curState.RawOrientation = targetRot; // not necessary, but left in to avoid breaking scenes that depend on this }
void PositionCamera(ref CameraState curState, float deltaTime) { var up = curState.ReferenceUp; var targetPos = FollowTargetPosition; var targetRot = FollowTargetRotation; var targetForward = targetRot * Vector3.forward; var heading = GetHeading(targetForward, up); if (deltaTime < 0) { // No damping - reset damping state info m_DampingCorrection = Vector3.zero; } else { // Damping correction is applied to the shoulder offset - stretching the rig m_DampingCorrection += Quaternion.Inverse(heading) * (m_PreviousFollowTargetPosition - targetPos); m_DampingCorrection -= VirtualCamera.DetachedFollowTargetDamp(m_DampingCorrection, Damping, deltaTime); } m_PreviousFollowTargetPosition = targetPos; var root = targetPos; GetRawRigPositions(root, targetRot, heading, out _, out Vector3 hand); // Check if hand is colliding with something, if yes, then move the hand // closer to the player. The radius is slightly enlarged, to avoid problems // next to walls var collidedHand = ResolveCollisions(root, hand, CameraRadius * 1.05f); // Place the camera at the correct distance from the hand Vector3 camPos = hand - (targetForward * CameraDistance); camPos = ResolveCollisions(collidedHand, camPos, CameraRadius); // Set state curState.RawPosition = camPos; curState.RawOrientation = targetRot; curState.ReferenceUp = up; }
/// <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; }
/// <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) { LensSettings lens = curState.Lens; Vector3 followTargetPosition = FollowTargetPosition + (FollowTargetRotation * m_TrackedObjectOffset); bool previousStateIsValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid; if (!previousStateIsValid || VirtualCamera.FollowTargetChanged) { m_Predictor.Reset(); } if (!previousStateIsValid) { m_PreviousCameraPosition = curState.RawPosition; m_prevFOV = lens.Orthographic ? lens.OrthographicSize : lens.FieldOfView; m_prevRotation = curState.RawOrientation; if (!InheritingPosition && m_CenterOnActivate) { m_PreviousCameraPosition = FollowTargetPosition + (curState.RawOrientation * Vector3.back) * m_CameraDistance; } } if (!IsValid) { InheritingPosition = false; return; } var verticalFOV = lens.FieldOfView; // Compute group bounds and adjust follow target for group framing ICinemachineTargetGroup group = AbstractFollowTargetGroup; bool isGroupFraming = group != null && m_GroupFramingMode != FramingMode.None && !group.IsEmpty; if (isGroupFraming) { followTargetPosition = ComputeGroupBounds(group, ref curState); } TrackedPoint = followTargetPosition; if (m_LookaheadTime > Epsilon) { m_Predictor.Smoothing = m_LookaheadSmoothing; m_Predictor.AddPosition(followTargetPosition, deltaTime, m_LookaheadTime); var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime); if (m_LookaheadIgnoreY) { delta = delta.ProjectOntoPlane(curState.ReferenceUp); } var p = followTargetPosition + delta; if (isGroupFraming) { var b = LastBounds; b.center += LastBoundsMatrix.MultiplyPoint3x4(delta); LastBounds = b; } TrackedPoint = p; } if (!curState.HasLookAt) { curState.ReferenceLookAt = followTargetPosition; } // Adjust the desired depth for group framing float targetDistance = m_CameraDistance; bool isOrthographic = lens.Orthographic; float targetHeight = isGroupFraming ? GetTargetHeight(LastBounds.size / m_GroupFramingSize) : 0; targetHeight = Mathf.Max(targetHeight, kMinimumGroupSize); if (!isOrthographic && isGroupFraming) { // Adjust height for perspective - we want the height at the near surface float boundsDepth = LastBounds.extents.z; float z = LastBounds.center.z; if (z > boundsDepth) { targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z); } if (m_AdjustmentMode != AdjustmentMode.ZoomOnly) { // What distance from near edge would be needed to get the adjusted // target height, at the current FOV targetDistance = targetHeight / (2f * Mathf.Tan(verticalFOV * Mathf.Deg2Rad / 2f)); // Clamp to respect min/max distance settings to the near surface of the bounds targetDistance = Mathf.Clamp(targetDistance, m_MinimumDistance, m_MaximumDistance); // Clamp to respect min/max camera movement float targetDelta = targetDistance - m_CameraDistance; targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut); targetDistance = m_CameraDistance + targetDelta; } } // Optionally allow undamped camera orientation change Quaternion localToWorld = curState.RawOrientation; if (previousStateIsValid && m_TargetMovementOnly) { var q = localToWorld * Quaternion.Inverse(m_prevRotation); m_PreviousCameraPosition = TrackedPoint + q * (m_PreviousCameraPosition - TrackedPoint); } m_prevRotation = localToWorld; // Work in camera-local space Vector3 camPosWorld = m_PreviousCameraPosition; Quaternion worldToLocal = Quaternion.Inverse(localToWorld); Vector3 cameraPos = worldToLocal * camPosWorld; Vector3 targetPos = (worldToLocal * TrackedPoint) - cameraPos; Vector3 lookAtPos = targetPos; // Move along camera z Vector3 cameraOffset = Vector3.zero; float cameraMin = Mathf.Max(kMinimumCameraDistance, targetDistance - m_DeadZoneDepth / 2); float cameraMax = Mathf.Max(cameraMin, targetDistance + m_DeadZoneDepth / 2); float targetZ = Mathf.Min(targetPos.z, lookAtPos.z); if (targetZ < cameraMin) { cameraOffset.z = targetZ - cameraMin; } if (targetZ > cameraMax) { cameraOffset.z = targetZ - cameraMax; } // Move along the XY plane float screenSize = lens.Orthographic ? lens.OrthographicSize : Mathf.Tan(0.5f * verticalFOV * Mathf.Deg2Rad) * (targetZ - cameraOffset.z); Rect softGuideOrtho = ScreenToOrtho(SoftGuideRect, screenSize, lens.Aspect); if (!previousStateIsValid) { // No damping or hard bounds, just snap to central bounds, skipping the soft zone Rect rect = softGuideOrtho; if (m_CenterOnActivate && !InheritingPosition) { rect = new Rect(rect.center, Vector2.zero); // Force to center } cameraOffset += OrthoOffsetToScreenBounds(targetPos, rect); } else { // Move it through the soft zone, with damping cameraOffset += OrthoOffsetToScreenBounds(targetPos, softGuideOrtho); cameraOffset = VirtualCamera.DetachedFollowTargetDamp( cameraOffset, new Vector3(m_XDamping, m_YDamping, m_ZDamping), deltaTime); // Make sure the real target (not the lookahead one) is still in the frame if (!m_UnlimitedSoftZone && (deltaTime < 0 || VirtualCamera.FollowTargetAttachment > 1 - Epsilon)) { Rect hardGuideOrtho = ScreenToOrtho(HardGuideRect, screenSize, lens.Aspect); var realTargetPos = (worldToLocal * followTargetPosition) - cameraPos; cameraOffset += OrthoOffsetToScreenBounds( realTargetPos - cameraOffset, hardGuideOrtho); } } curState.RawPosition = localToWorld * (cameraPos + cameraOffset); m_PreviousCameraPosition = curState.RawPosition; // Adjust lens for group framing if (isGroupFraming) { if (isOrthographic) { targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize); // Apply Damping if (previousStateIsValid) { targetHeight = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp( targetHeight - m_prevFOV, m_ZDamping, deltaTime); } m_prevFOV = targetHeight; lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize); curState.Lens = lens; } else if (m_AdjustmentMode != AdjustmentMode.DollyOnly) { var localTarget = Quaternion.Inverse(curState.RawOrientation) * (followTargetPosition - curState.RawPosition); float nearBoundsDistance = localTarget.z; float targetFOV = 179; if (nearBoundsDistance > Epsilon) { targetFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg; } targetFOV = Mathf.Clamp(targetFOV, m_MinimumFOV, m_MaximumFOV); // ApplyDamping if (previousStateIsValid) { targetFOV = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp( targetFOV - m_prevFOV, m_ZDamping, deltaTime); } m_prevFOV = targetFOV; lens.FieldOfView = targetFOV; curState.Lens = lens; } } InheritingPosition = false; }