/// <summary> /// Adjust the rigOrientation to put the camera within the screen bounds. /// If deltaTime >= 0 then damping will be applied. /// Assumes that currentOrientation fwd is such that input rigOrientation's /// local up is NEVER NEVER NEVER pointing downwards, relative to /// state.ReferenceUp. If this condition is violated /// then you will see crazy spinning. That's the symptom. /// </summary> private void RotateToScreenBounds( ref CameraState state, Rect screenRect, Vector3 trackedPoint, ref Quaternion rigOrientation, float fov, float fovH, float deltaTime) { Vector3 targetDir = trackedPoint - state.CorrectedPosition; Vector2 rotToRect = rigOrientation.GetCameraRotationToTarget(targetDir, state.ReferenceUp); // Bring it to the edge of screenRect, if outside. Leave it alone if inside. ClampVerticalBounds(ref screenRect, targetDir, state.ReferenceUp, fov); float min = (screenRect.yMin - 0.5f) * fov; float max = (screenRect.yMax - 0.5f) * fov; if (rotToRect.x < min) { rotToRect.x -= min; } else if (rotToRect.x > max) { rotToRect.x -= max; } else { rotToRect.x = 0; } min = (screenRect.xMin - 0.5f) * fovH; max = (screenRect.xMax - 0.5f) * fovH; if (rotToRect.y < min) { rotToRect.y -= min; } else if (rotToRect.y > max) { rotToRect.y -= max; } else { rotToRect.y = 0; } // Apply damping if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid) { rotToRect.x = VirtualCamera.DetachedLookAtTargetDamp( rotToRect.x, m_VerticalDamping, deltaTime); rotToRect.y = VirtualCamera.DetachedLookAtTargetDamp( rotToRect.y, m_HorizontalDamping, deltaTime); } // Rotate rigOrientation = rigOrientation.ApplyCameraRotation(rotToRect, state.ReferenceUp); }
/// <summary>Callback to preform the zoom adjustment</summary> protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { VcamExtraState extra = GetExtraState <VcamExtraState>(vcam); if (deltaTime < 0 || !VirtualCamera.PreviousStateIsValid) { extra.m_previousFrameZoom = state.Lens.FieldOfView; } // Set the zoom after the body has been positioned, but before the aim, // so that composer can compose using the updated fov. if (stage == CinemachineCore.Stage.Body) { // Try to reproduce the target width float targetWidth = Mathf.Max(m_Width, 0); float fov = 179f; float d = Vector3.Distance(state.CorrectedPosition, state.ReferenceLookAt); if (d > UnityVectorExtensions.Epsilon) { // Clamp targetWidth to FOV min/max float minW = d * 2f * Mathf.Tan(m_MinFOV * Mathf.Deg2Rad / 2f); float maxW = d * 2f * Mathf.Tan(m_MaxFOV * Mathf.Deg2Rad / 2f); targetWidth = Mathf.Clamp(targetWidth, minW, maxW); // Apply damping if (deltaTime >= 0 && m_Damping > 0 && VirtualCamera.PreviousStateIsValid) { float currentWidth = d * 2f * Mathf.Tan(extra.m_previousFrameZoom * Mathf.Deg2Rad / 2f); float delta = targetWidth - currentWidth; delta = VirtualCamera.DetachedLookAtTargetDamp(delta, m_Damping, deltaTime); targetWidth = currentWidth + delta; } fov = 2f * Mathf.Atan(targetWidth / (2 * d)) * Mathf.Rad2Deg; } LensSettings lens = state.Lens; lens.FieldOfView = extra.m_previousFrameZoom = Mathf.Clamp(fov, m_MinFOV, m_MaxFOV); state.Lens = lens; } }
/// <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 = AbstractLookAtTargetGroup; 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; } // 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 && VirtualCamera.PreviousStateIsValid) { targetHeight = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp( 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 && VirtualCamera.PreviousStateIsValid) { float delta = targetDelta - m_prevFramingDistance; delta = VirtualCamera.DetachedLookAtTargetDamp(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 && VirtualCamera.PreviousStateIsValid) { targetFOV = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp( 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); }