/// <summary> /// Call this from InternalUpdateCameraState() to check for changed /// targets and update the target cache. This is needed for tracking /// when a target object changes. /// </summary> protected void UpdateTargetCache() { var target = ResolveFollow(Follow); FollowTargetChanged = target != m_CachedFollowTarget; m_CachedFollowTarget = target; m_CachedFollowTargetVcam = null; m_CachedFollowTargetGroup = null; if (m_CachedFollowTarget != null) { m_CachedFollowTargetVcam = target.GetComponent <CinemachineVirtualCameraBase>(); m_CachedFollowTargetGroup = target.GetComponent <ICinemachineTargetGroup>(); } target = ResolveLookAt(LookAt); LookAtTargetChanged = target != m_CachedLookAtTarget; m_CachedLookAtTarget = target; m_CachedLookAtTargetVcam = null; m_CachedLookAtTargetGroup = null; if (target != null) { m_CachedLookAtTargetVcam = target.GetComponent <CinemachineVirtualCameraBase>(); m_CachedLookAtTargetGroup = target.GetComponent <ICinemachineTargetGroup>(); } }
static Bounds GetScreenSpaceGroupBoundingBox( ICinemachineTargetGroup group, ref Vector3 pos, Quaternion orientation) { Matrix4x4 observer = Matrix4x4.TRS(pos, orientation, Vector3.one); Vector2 minAngles, maxAngles, zRange; group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange); Quaternion q = Quaternion.identity.ApplyCameraRotation((minAngles + maxAngles) / 2, Vector3.up); Vector3 localPosAdustment = q * new Vector3(0, 0, (zRange.y + zRange.x) / 2); localPosAdustment.z = 0; pos = observer.MultiplyPoint3x4(localPosAdustment); observer = Matrix4x4.TRS(pos, orientation, Vector3.one); group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange); float zSize = zRange.y - zRange.x; float z = zRange.x + (zSize / 2); Vector2 angles = new Vector2(89.5f, 89.5f); if (zRange.x > 0) { angles = Vector2.Max(maxAngles, UnityVectorExtensions.Abs(minAngles)); angles = Vector2.Min(angles, new Vector2(89.5f, 89.5f)); } angles *= Mathf.Deg2Rad; return(new Bounds(new Vector3(0, 0, z), new Vector3(Mathf.Tan(angles.y) * z * 2, Mathf.Tan(angles.x) * z * 2, zSize))); }
static Bounds GetScreenSpaceGroupBoundingBox( ICinemachineTargetGroup group, ref Vector3 pos, Quaternion orientation) { var observer = Matrix4x4.TRS(pos, orientation, Vector3.one); group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange); var shift = (minAngles + maxAngles) / 2; var q = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up); pos = q * new Vector3(0, 0, (zRange.y + zRange.x) / 2); pos.z = 0; pos = observer.MultiplyPoint3x4(pos); observer = Matrix4x4.TRS(pos, orientation, Vector3.one); group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange); // For width and height (in camera space) of the bounding box, we use the values at the center of the box. // This is an arbitrary choice. The gizmo drawer will take this into account when displaying // the frustum bounds of the group var d = zRange.y + zRange.x; Vector2 angles = new Vector2(89.5f, 89.5f); if (zRange.x > 0) { angles = Vector2.Max(maxAngles, UnityVectorExtensions.Abs(minAngles)); angles = Vector2.Min(angles, new Vector2(89.5f, 89.5f)); } angles *= Mathf.Deg2Rad; return(new Bounds( new Vector3(0, 0, d / 2), new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x))); }
Vector3 ComputeGroupBounds(ICinemachineTargetGroup group, ref CameraState curState) { Vector3 cameraPos = curState.RawPosition; Vector3 fwd = curState.RawOrientation * Vector3.forward; // Get the bounding box from camera's direction in view space LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one); Bounds b = group.GetViewSpaceBoundingBox(LastBoundsMatrix); Vector3 groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center); float boundsDepth = b.extents.z; if (!curState.Lens.Orthographic) { // Parallax might change bounds - refine float d = (Quaternion.Inverse(curState.RawOrientation) * (groupCenter - cameraPos)).z; cameraPos = groupCenter - fwd * (Mathf.Max(d, boundsDepth) + boundsDepth); // Will adjust cameraPos b = GetScreenSpaceGroupBoundingBox(group, ref cameraPos, curState.RawOrientation); LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one); groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center); } LastBounds = b; return(groupCenter - fwd * boundsDepth); }
void UpdateFollowTargetCache() { mCachedFollowTargetVcam = null; mCachedFollowTargetGroup = null; mCachedFollowTarget = FollowTarget; if (mCachedFollowTarget != null) { mCachedFollowTargetVcam = mCachedFollowTarget.GetComponent <CinemachineVirtualCameraBase>(); mCachedFollowTargetGroup = mCachedFollowTarget.GetComponent <ICinemachineTargetGroup>(); } }
/// <param name="observer">Point of view</param> /// <param name="newFwd">New forward direction to use when interpreting the return value</param> /// <returns>Bounding box in a slightly rotated version of observer, as specified by newFwd</returns> static Bounds GetScreenSpaceGroupBoundingBox( ICinemachineTargetGroup group, Matrix4x4 observer, out Vector3 newFwd) { Vector2 minAngles, maxAngles, zRange; group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange); Vector2 shift = (minAngles + maxAngles) / 2; newFwd = Quaternion.identity.ApplyCameraRotation(shift, Vector3.up) * Vector3.forward; newFwd = observer.MultiplyVector(newFwd); float d = (zRange.y + zRange.x); Vector2 angles = (maxAngles - shift) * Mathf.Deg2Rad; angles = Vector2.Min(angles, new Vector2(89.5f, 89.5f)); return(new Bounds( new Vector3(0, 0, d / 2), new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x))); }
/// <param name="observer">Point of view</param> /// <param name="newFwd">New forward direction to use when interpreting the return value</param> /// <returns>Bounding box in a slightly rotated version of observer, as specified by newFwd</returns> static Bounds GetScreenSpaceGroupBoundingBox( ICinemachineTargetGroup group, Matrix4x4 observer, out Vector3 newFwd) { group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange); var shift = (minAngles + maxAngles) / 2; newFwd = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up) * Vector3.forward; newFwd = observer.MultiplyVector(newFwd); // For width and height (in camera space) of the bounding box, we use the values at the center of the box. // This is an arbitrary choice. The gizmo drawer will take this into account when displaying // the frustum bounds of the group var d = zRange.y + zRange.x; var angles = Vector2.Min(maxAngles - shift, new Vector2(89.5f, 89.5f)) * Mathf.Deg2Rad; return(new Bounds( new Vector3(0, 0, d / 2), new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x))); }
/// <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) { // Can't do anything without a group to look at ICinemachineTargetGroup group = LookAtTargetGroup; if (group == null) { base.MutateCameraState(ref curState, deltaTime); return; } if (!IsValid || !curState.HasLookAt) { m_prevFramingDistance = 0; m_prevFOV = 0; return; } bool isOrthographic = curState.Lens.Orthographic; bool canMoveCamera = !isOrthographic && m_AdjustmentMode != AdjustmentMode.ZoomOnly; // Get the bounding box from camera's POV in view space Vector3 up = curState.ReferenceUp; var cameraPos = curState.RawPosition; BoundingSphere s = group.Sphere; Vector3 groupCenter = s.position; Vector3 fwd = groupCenter - cameraPos; float d = fwd.magnitude; if (d < Epsilon) { return; // navel-gazing, get outa here } // Approximate looking at the group center fwd /= d; LastBoundsMatrix = Matrix4x4.TRS( cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one); // Correction for the actual center Bounds b; if (isOrthographic) { b = group.GetViewSpaceBoundingBox(LastBoundsMatrix); groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center); fwd = (groupCenter - cameraPos).normalized; LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one); b = group.GetViewSpaceBoundingBox(LastBoundsMatrix); LastBounds = b; } else { b = GetScreenSpaceGroupBoundingBox(group, LastBoundsMatrix, out fwd); LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one); LastBounds = b; groupCenter = cameraPos + fwd * b.center.z; fwd = (groupCenter - cameraPos).normalized; } // Adjust bounds for framing size, and get target height float boundsDepth = b.extents.z; float targetHeight = GetTargetHeight(b.size / m_GroupFramingSize); if (isOrthographic) { targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize); // ApplyDamping if (deltaTime >= 0) { targetHeight = m_prevFOV + Damper.Damp(targetHeight - m_prevFOV, m_FrameDamping, deltaTime); } m_prevFOV = targetHeight; LensSettings lens = curState.Lens; lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize); curState.Lens = lens; } else { // Adjust height for perspective - we want the height at the near surface float z = b.center.z; if (z > boundsDepth) { targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z); } // Move the camera if (canMoveCamera) { // What distance from near edge would be needed to get the adjusted // target height, at the current FOV float targetDistance = boundsDepth + targetHeight / (2f * Mathf.Tan(curState.Lens.FieldOfView * Mathf.Deg2Rad / 2f)); // Clamp to respect min/max distance settings to the near surface of the bounds targetDistance = Mathf.Clamp( targetDistance, boundsDepth + m_MinimumDistance, boundsDepth + m_MaximumDistance); // Clamp to respect min/max camera movement float targetDelta = targetDistance - Vector3.Distance(curState.RawPosition, groupCenter); targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut); // ApplyDamping if (deltaTime >= 0) { float delta = targetDelta - m_prevFramingDistance; delta = Damper.Damp(delta, m_FrameDamping, deltaTime); targetDelta = m_prevFramingDistance + delta; } m_prevFramingDistance = targetDelta; curState.PositionCorrection -= fwd * targetDelta; cameraPos -= fwd * targetDelta; } // Apply zoom if (m_AdjustmentMode != AdjustmentMode.DollyOnly) { float nearBoundsDistance = (groupCenter - cameraPos).magnitude - boundsDepth; 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 (deltaTime >= 0 && m_prevFOV != 0) { targetFOV = m_prevFOV + Damper.Damp(targetFOV - m_prevFOV, m_FrameDamping, deltaTime); } m_prevFOV = targetFOV; LensSettings lens = curState.Lens; lens.FieldOfView = targetFOV; curState.Lens = lens; } } // Now compose normally curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint( groupCenter, curState.ReferenceUp, deltaTime); base.MutateCameraState(ref curState, deltaTime); }
/// <summary>Get the property names to exclude in the inspector.</summary> /// <param name="excluded">Add the names to this list</param> protected override void GetExcludedPropertiesInInspector(List <string> excluded) { base.GetExcludedPropertiesInInspector(excluded); if (Target.m_UnlimitedSoftZone) { excluded.Add(FieldPath(x => x.m_SoftZoneWidth)); excluded.Add(FieldPath(x => x.m_SoftZoneHeight)); excluded.Add(FieldPath(x => x.m_BiasX)); excluded.Add(FieldPath(x => x.m_BiasY)); } ICinemachineTargetGroup group = Target.AbstractFollowTargetGroup; if (group == null || Target.m_GroupFramingMode == CinemachineFramingTransposer.FramingMode.None) { excluded.Add(FieldPath(x => x.m_GroupFramingSize)); excluded.Add(FieldPath(x => x.m_AdjustmentMode)); excluded.Add(FieldPath(x => x.m_MaxDollyIn)); excluded.Add(FieldPath(x => x.m_MaxDollyOut)); excluded.Add(FieldPath(x => x.m_MinimumDistance)); excluded.Add(FieldPath(x => x.m_MaximumDistance)); excluded.Add(FieldPath(x => x.m_MinimumFOV)); excluded.Add(FieldPath(x => x.m_MaximumFOV)); excluded.Add(FieldPath(x => x.m_MinimumOrthoSize)); excluded.Add(FieldPath(x => x.m_MaximumOrthoSize)); if (group == null) { excluded.Add(FieldPath(x => x.m_GroupFramingMode)); } } else { CinemachineBrain brain = CinemachineCore.Instance.FindPotentialTargetBrain(Target.VirtualCamera); bool ortho = brain != null ? brain.OutputCamera.orthographic : false; if (ortho) { excluded.Add(FieldPath(x => x.m_AdjustmentMode)); excluded.Add(FieldPath(x => x.m_MaxDollyIn)); excluded.Add(FieldPath(x => x.m_MaxDollyOut)); excluded.Add(FieldPath(x => x.m_MinimumDistance)); excluded.Add(FieldPath(x => x.m_MaximumDistance)); excluded.Add(FieldPath(x => x.m_MinimumFOV)); excluded.Add(FieldPath(x => x.m_MaximumFOV)); } else { excluded.Add(FieldPath(x => x.m_MinimumOrthoSize)); excluded.Add(FieldPath(x => x.m_MaximumOrthoSize)); switch (Target.m_AdjustmentMode) { case CinemachineFramingTransposer.AdjustmentMode.DollyOnly: excluded.Add(FieldPath(x => x.m_MinimumFOV)); excluded.Add(FieldPath(x => x.m_MaximumFOV)); break; case CinemachineFramingTransposer.AdjustmentMode.ZoomOnly: excluded.Add(FieldPath(x => x.m_MaxDollyIn)); excluded.Add(FieldPath(x => x.m_MaxDollyOut)); excluded.Add(FieldPath(x => x.m_MinimumDistance)); excluded.Add(FieldPath(x => x.m_MaximumDistance)); break; default: break; } } } }
/// <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; }
/// <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) { Vector3 followTargetPosition = FollowTargetPosition; if (deltaTime < 0) { m_Predictor.Reset(); m_PreviousCameraPosition = curState.RawPosition; m_prevFOV = 0; if (!InheritingPosition && m_CenterOnActivate) { m_PreviousCameraPosition = FollowTargetPosition + (curState.RawOrientation * Vector3.back) * m_CameraDistance; } } if (!IsValid) { InheritingPosition = false; return; } // Compute group bounds and adjust follow target for group framing ICinemachineTargetGroup group = FollowTargetGroup; bool isGroupFraming = group != null && m_GroupFramingMode != FramingMode.None; 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 += p - followTargetPosition; LastBounds = b; } TrackedPoint = p; } if (!curState.HasLookAt) { curState.ReferenceLookAt = followTargetPosition; } // Adjust the desired depth for group framing float targetDistance = m_CameraDistance; bool isOrthographic = curState.Lens.Orthographic; float targetHeight = isGroupFraming ? GetTargetHeight(LastBounds.size / m_GroupFramingSize) : 0; 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(curState.Lens.FieldOfView * 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; } } // Work in camera-local space Vector3 camPosWorld = m_PreviousCameraPosition; Quaternion localToWorld = curState.RawOrientation; 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 = curState.Lens.Orthographic ? curState.Lens.OrthographicSize : Mathf.Tan(0.5f * curState.Lens.FieldOfView * Mathf.Deg2Rad) * (targetZ - cameraOffset.z); Rect softGuideOrtho = ScreenToOrtho(SoftGuideRect, screenSize, curState.Lens.Aspect); if (deltaTime < 0) { // 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 cameraOffset += OrthoOffsetToScreenBounds(targetPos, softGuideOrtho); // Find where it intersects the hard zone Vector3 hard = Vector3.zero; if (!m_UnlimitedSoftZone) { Rect hardGuideOrtho = ScreenToOrtho(HardGuideRect, screenSize, curState.Lens.Aspect); hard = OrthoOffsetToScreenBounds(targetPos, hardGuideOrtho); float t = Mathf.Max(hard.x / (cameraOffset.x + Epsilon), hard.y / (cameraOffset.y + Epsilon)); hard = cameraOffset * t; } // Apply damping, but only to the portion of the move that's inside the hard zone cameraOffset = hard + Damper.Damp( cameraOffset - hard, new Vector3(m_XDamping, m_YDamping, m_ZDamping), deltaTime); // If we have lookahead, make sure the real target is still in the frame if (!m_UnlimitedSoftZone && !(TrackedPoint - curState.ReferenceLookAt).AlmostZero()) { Rect hardGuideOrtho = ScreenToOrtho(HardGuideRect, screenSize, curState.Lens.Aspect); cameraOffset += OrthoOffsetToScreenBounds(lookAtPos - cameraOffset, hardGuideOrtho); } } curState.RawPosition = m_PreviousCameraPosition = localToWorld * (cameraPos + cameraOffset); // Adjust lens for group framing if (isGroupFraming) { if (isOrthographic) { targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize); // Apply Damping if (deltaTime >= 0) { targetHeight = m_prevFOV + Damper.Damp(targetHeight - m_prevFOV, m_ZDamping, deltaTime); } m_prevFOV = targetHeight; LensSettings lens = curState.Lens; 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 (deltaTime >= 0 && m_prevFOV != 0) { targetFOV = m_prevFOV + Damper.Damp(targetFOV - m_prevFOV, m_ZDamping, deltaTime); } m_prevFOV = targetFOV; LensSettings lens = curState.Lens; lens.FieldOfView = targetFOV; curState.Lens = lens; } } InheritingPosition = false; }
private void OnEnable() { _group = GetComponent <ICinemachineTargetGroup>(); }