#if CINEMACHINE_PHYSICS using UnityEngine; namespace Unity.Cinemachine { /// <summary> /// An add-on module for CinemachineCamera that post-processes /// the final position of the camera. It will confine the camera's position /// to the volume specified in the Bounding Volume field. /// </summary> [AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Confiner 3D")] [SaveDuringPlay] [ExecuteAlways] [DisallowMultipleComponent] [HelpURL(Documentation.BaseURL + "manual/CinemachineConfiner3D.html")] public class CinemachineConfiner3D : CinemachineExtension { /// <summary>The volume within which the camera is to be contained.</summary> [Tooltip("The volume within which the camera is to be contained")] public Collider BoundingVolume; /// <summary>Size of the slow-down zone at the edge of the bounding volume.</summary> [Tooltip("Size of the slow-down zone at the edge of the bounding volume.")] public float SlowingDistance = 0; /// <summary>See whether the virtual camera has been moved by the confiner</summary> /// <param name="vcam">The virtual camera in question. This might be different from the /// virtual camera that owns the confiner, in the event that the camera has children</param> /// <returns>True if the virtual camera has been repositioned</returns> public bool CameraWasDisplaced(CinemachineVirtualCameraBase vcam) => GetCameraDisplacementDistance(vcam) > 0; /// <summary>See how far virtual camera has been moved by the confiner</summary> /// <param name="vcam">The virtual camera in question. This might be different from the /// virtual camera that owns the confiner, in the event that the camera has children</param> /// <returns>True if the virtual camera has been repositioned</returns> public float GetCameraDisplacementDistance(CinemachineVirtualCameraBase vcam) => GetExtraState<VcamExtraState>(vcam).PreviousDisplacement.magnitude; void Reset() { BoundingVolume = null; SlowingDistance = 0; } void OnValidate() { SlowingDistance = Mathf.Max(0, SlowingDistance); } class VcamExtraState : VcamExtraStateBase { public Vector3 PreviousDisplacement; public Vector3 PreviousCameraPosition; }; /// <summary>Check if the bounding volume is defined</summary> public bool IsValid => BoundingVolume != null && BoundingVolume.enabled && BoundingVolume.gameObject.activeInHierarchy; /// <summary> /// Report maximum damping time needed for this component. /// </summary> /// <returns>Highest damping setting in this component</returns> public override float GetMaxDampTime() => SlowingDistance * 0.2f; // just an approximation - we don't know the time /// <summary>This is called to notify the extension that a target got warped, /// so that the extension can update its internal state to make the camera /// also warp seamlessly. Base class implementation does nothing.</summary> /// <param name="vcam">The camera to warp</param> /// <param name="target">The object that was warped</param> /// <param name="positionDelta">The amount the target's position changed</param> public override void OnTargetObjectWarped( CinemachineVirtualCameraBase vcam, Transform target, Vector3 positionDelta) { var extra = GetExtraState<VcamExtraState>(vcam); if (extra.Vcam.Follow == target) extra.PreviousCameraPosition += positionDelta; } /// <summary> /// Callback to do the camera confining /// </summary> /// <param name="vcam">The virtual camera being processed</param> /// <param name="stage">The current pipeline stage</param> /// <param name="state">The current virtual camera state</param> /// <param name="deltaTime">The current applicable deltaTime</param> protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { if (stage == CinemachineCore.Stage.Body && IsValid) { var extra = GetExtraState<VcamExtraState>(vcam); var camPos = state.GetCorrectedPosition(); // Snap the point inside the bounds var newPos = ConfinePoint(camPos); if (SlowingDistance > Epsilon && deltaTime >= 0 && vcam.PreviousStateIsValid) { // Reduce speed if moving towards the edge and close enough to it var prevPos = extra.PreviousCameraPosition; var dir = newPos - prevPos; var speed = dir.magnitude; if (speed > Epsilon) { var t = GetDistanceFromEdge(prevPos, dir / speed, SlowingDistance) / SlowingDistance; // This formula is found to give a smooth slowing curve while ensuring // that it comes to a full stop in a reasonable time newPos = Vector3.Lerp(prevPos, newPos, t * t * t + 0.05f); } } var displacement = newPos - camPos; state.PositionCorrection += displacement; extra.PreviousCameraPosition = state.GetCorrectedPosition(); extra.PreviousDisplacement = displacement; } } Vector3 ConfinePoint(Vector3 p) { var mesh = BoundingVolume as MeshCollider; if (mesh != null && !mesh.convex) return p; return BoundingVolume.ClosestPoint(p); } // Returns distance from edge in direction of motion, or max if distance is greater than max. // dirUnit must be unit length. float GetDistanceFromEdge(Vector3 p, Vector3 dirUnit, float max) { p += dirUnit * max; return max - (ConfinePoint(p) - p).magnitude; } } } #endif