#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