using System;
using UnityEngine;
namespace Unity.Cinemachine
{
///
/// Evaluates shot quality in the Finalize stage based on LookAt target occlusion and distance.
///
[AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Shot Quality Evaluator")]
[SaveDuringPlay]
[ExecuteAlways]
[DisallowMultipleComponent]
[HelpURL(Documentation.BaseURL + "manual/CinemachineShotQualityEvaluator.html")]
public class CinemachineShotQualityEvaluator : CinemachineExtension, IShotQualityEvaluator
{
/// Objects on these layers will be detected.
[Tooltip("Objects on these layers will be detected")]
public LayerMask OcclusionLayers = 1;
/// Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag
[TagField]
[Tooltip("Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag")]
public string IgnoreTag = string.Empty;
/// Obstacles closer to the target than this will be ignored
[Tooltip("Obstacles closer to the target than this will be ignored")]
public float MinimumDistanceFromTarget = 0.2f;
///
/// Radius of the spherecast that will be done to check for occlusions.
///
[Tooltip("Radius of the spherecast that will be done to check for occlusions.")]
public float CameraRadius;
/// Settings for shot quality evaluation
[Serializable]
public struct DistanceEvaluationSettings
{
/// If enabled, will evaluate shot quality based on target distance
[Tooltip("If enabled, will evaluate shot quality based on target distance")]
public bool Enabled;
/// If greater than zero, maximum quality boost will occur when target is this far from the camera
[Tooltip("If greater than zero, maximum quality boost will occur when target is this far from the camera")]
public float OptimalDistance;
/// Shots with targets closer to the camera than this will not get a quality boost
[Tooltip("Shots with targets closer to the camera than this will not get a quality boost")]
public float NearLimit;
/// Shots with targets farther from the camera than this will not get a quality boost
[Tooltip("Shots with targets farther from the camera than this will not get a quality boost")]
public float FarLimit;
/// High quality shots will be boosted by this fraction of their normal quality
[Tooltip("High quality shots will be boosted by this fraction of their normal quality")]
public float MaxQualityBoost;
internal static DistanceEvaluationSettings Default => new () { NearLimit = 5, FarLimit = 30, OptimalDistance = 10, MaxQualityBoost = 0.2f };
}
/// If enabled, will evaluate shot quality based on target distance and occlusion
[FoldoutWithEnabledButton]
public DistanceEvaluationSettings DistanceEvaluation = DistanceEvaluationSettings.Default;
void OnValidate()
{
CameraRadius = Mathf.Max(0, CameraRadius);
MinimumDistanceFromTarget = Mathf.Max(0.01f, MinimumDistanceFromTarget);
CameraRadius = Mathf.Max(0, CameraRadius);
DistanceEvaluation.NearLimit = Mathf.Max(0.1f, DistanceEvaluation.NearLimit);
DistanceEvaluation.FarLimit = Mathf.Max(DistanceEvaluation.NearLimit, DistanceEvaluation.FarLimit);
DistanceEvaluation.OptimalDistance = Mathf.Clamp(
DistanceEvaluation.OptimalDistance, DistanceEvaluation.NearLimit, DistanceEvaluation.FarLimit);
}
private void Reset()
{
OcclusionLayers = 1;
IgnoreTag = string.Empty;
MinimumDistanceFromTarget = 0.2f;
CameraRadius = 0;
DistanceEvaluation = DistanceEvaluationSettings.Default;
}
///
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
if (stage == CinemachineCore.Stage.Finalize && state.HasLookAt())
{
var targetObscured = state.IsTargetOffscreen() || IsTargetObscured(state);
if (targetObscured)
state.ShotQuality *= 0.2f;
if (DistanceEvaluation.Enabled)
{
float nearnessBoost = 0;
if (DistanceEvaluation.OptimalDistance > 0)
{
var distance = Vector3.Magnitude(state.ReferenceLookAt - state.GetFinalPosition());
if (distance <= DistanceEvaluation.OptimalDistance)
{
if (distance >= DistanceEvaluation.NearLimit)
nearnessBoost = DistanceEvaluation.MaxQualityBoost * (distance - DistanceEvaluation.NearLimit)
/ (DistanceEvaluation.OptimalDistance - DistanceEvaluation.NearLimit);
}
else
{
distance -= DistanceEvaluation.OptimalDistance;
if (distance < DistanceEvaluation.FarLimit)
nearnessBoost = DistanceEvaluation.MaxQualityBoost * (1f - (distance / DistanceEvaluation.FarLimit));
}
state.ShotQuality *= (1f + nearnessBoost);
}
}
}
}
bool IsTargetObscured(CameraState state)
{
#if CINEMACHINE_PHYSICS
var lookAtPos = state.ReferenceLookAt;
var pos = state.GetCorrectedPosition();
var dir = lookAtPos - pos;
var distance = dir.magnitude;
if (distance < Mathf.Max(MinimumDistanceFromTarget, Epsilon))
return true;
var ray = new Ray(pos, dir.normalized);
return RuntimeUtility.SphereCastIgnoreTag(
ray, CameraRadius, out _, distance - MinimumDistanceFromTarget, OcclusionLayers, IgnoreTag);
#else
return false;
#endif
}
}
}