#if CINEMACHINE_PHYSICS using UnityEngine; namespace Unity.Cinemachine { /// /// 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. /// [AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Confiner 3D")] [SaveDuringPlay] [ExecuteAlways] [DisallowMultipleComponent] [HelpURL(Documentation.BaseURL + "manual/CinemachineConfiner3D.html")] public class CinemachineConfiner3D : CinemachineExtension { /// The volume within which the camera is to be contained. [Tooltip("The volume within which the camera is to be contained")] public Collider BoundingVolume; /// Size of the slow-down zone at the edge of the bounding volume. [Tooltip("Size of the slow-down zone at the edge of the bounding volume.")] public float SlowingDistance = 0; /// See whether the virtual camera has been moved by the confiner /// 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 /// True if the virtual camera has been repositioned public bool CameraWasDisplaced(CinemachineVirtualCameraBase vcam) => GetCameraDisplacementDistance(vcam) > 0; /// See how far virtual camera has been moved by the confiner /// 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 /// True if the virtual camera has been repositioned public float GetCameraDisplacementDistance(CinemachineVirtualCameraBase vcam) => GetExtraState(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; }; /// Check if the bounding volume is defined public bool IsValid => BoundingVolume != null && BoundingVolume.enabled && BoundingVolume.gameObject.activeInHierarchy; /// /// Report maximum damping time needed for this component. /// /// Highest damping setting in this component public override float GetMaxDampTime() => SlowingDistance * 0.2f; // just an approximation - we don't know the time /// 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. /// The camera to warp /// The object that was warped /// The amount the target's position changed public override void OnTargetObjectWarped( CinemachineVirtualCameraBase vcam, Transform target, Vector3 positionDelta) { var extra = GetExtraState(vcam); if (extra.Vcam.Follow == target) extra.PreviousCameraPosition += positionDelta; } /// /// Callback to do the camera confining /// /// The virtual camera being processed /// The current pipeline stage /// The current virtual camera state /// The current applicable deltaTime protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { if (stage == CinemachineCore.Stage.Body && IsValid) { var extra = GetExtraState(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