#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEngine
{
///
/// Represents camera-space light controls around a virtual pivot point.
///
[AddComponentMenu("Rendering/Light Anchor")]
[ExecuteInEditMode]
[DisallowMultipleComponent]
[CoreRPHelpURLAttribute("View-Lighting-Tool")]
public class LightAnchor : MonoBehaviour
{
const float k_ArcRadius = 5;
const float k_AxisLength = 10;
internal const float k_MaxDistance = 10000f;
[SerializeField, Min(0)]
float m_Distance = 0f;
[SerializeField]
UpDirection m_FrameSpace = UpDirection.World;
[SerializeField]
Transform m_AnchorPositionOverride;
[SerializeField]
Vector3 m_AnchorPositionOffset;
[SerializeField]
float m_Yaw;
[SerializeField]
float m_Pitch;
[SerializeField]
float m_Roll;
///
/// The camera-relative yaw.
///
///
/// The range is -180 through 180 inclusive. Values between 0 and 180 are to the right of the camera, and values between 0 and -180 to the left.
///
public float yaw
{
get { return m_Yaw; }
set { m_Yaw = NormalizeAngleDegree(value); }
}
///
/// The pitch relative to the horizon or camera depending on value of m_Space.
///
///
/// The range is -180 through 180 inclusive. Values between 0 and 180 are below the camera, and values between 0 and -180 are above the camera.
///
public float pitch
{
get { return m_Pitch; }
set { m_Pitch = NormalizeAngleDegree(value); }
}
///
/// The camera-relative roll.
///
///
/// The range is -180 through 180 inclusive. Values between 0 and 180 are to the right of the camera, and values between 0 and -180 are to the left of the camera.
///
public float roll
{
get { return m_Roll; }
set { m_Roll = NormalizeAngleDegree(value); }
}
///
/// The distance from the light's anchor point.
///
public float distance
{
get => m_Distance;
set => m_Distance = Mathf.Clamp(value, 0f, k_MaxDistance);
}
///
/// Enum to describes to up vector for the Light Anchor
///
public enum UpDirection
{
///
/// Up vector is world space Vector3.up
///
World = Space.World,
///
/// Up vector is the up of the main camera
///
Local = Space.Self
}
///
/// Indicates whether the up vector should be in world or camera space.
///
public UpDirection frameSpace
{
get { return m_FrameSpace; }
set { m_FrameSpace = value; }
}
///
/// The position of the light's anchor point.
///
public Vector3 anchorPosition
{
get
{
if (anchorPositionOverride != null)
return anchorPositionOverride.position + anchorPositionOverride.TransformDirection(anchorPositionOffset);
else
return transform.position + transform.forward * distance;
}
}
struct Axes
{
public Vector3 up;
public Vector3 right;
public Vector3 forward;
}
///
/// Overrides the pivot of used to compute the light position. This is useful to track an existing object in the scene.
/// The transform of the light will be automatically updated by the Update() method of the LightAnchor.
///
public Transform anchorPositionOverride
{
get => m_AnchorPositionOverride;
set => m_AnchorPositionOverride = value;
}
///
/// Offset relative to the position of the anchor position override transform in object space.
///
public Vector3 anchorPositionOffset
{
get => m_AnchorPositionOffset;
set => m_AnchorPositionOffset = value;
}
///
/// Normalizes the input angle to be in the range of -180 and 180.
///
/// Raw input angle or rotation.
/// Returns the angle of rotation between -180 and 180.
public static float NormalizeAngleDegree(float angle)
{
const float range = 360f;
const float startValue = -180f;
var offset = angle - startValue;
return offset - (Mathf.Floor(offset / range) * range) + startValue;
}
///
/// Updates Yaw, Pitch, Roll, and Distance based on the Transform.
///
/// The Camera to which light values are relative.
public void SynchronizeOnTransform(Camera camera)
{
Axes axes = GetWorldSpaceAxes(camera, anchorPosition);
Vector3 worldAnchorToLight = transform.position - anchorPosition;
// In case the distance is 0 or the anchor override is at the same position than the light anchor
if (worldAnchorToLight.magnitude == 0)
worldAnchorToLight = -transform.forward;
Vector3 projectOnGround = Vector3.ProjectOnPlane(worldAnchorToLight, axes.up);
if(projectOnGround.magnitude < 0.0001f)
projectOnGround = Vector3.ProjectOnPlane(worldAnchorToLight, axes.up + axes.right * 0.0001f);
projectOnGround.Normalize();
float extractedYaw = Vector3.SignedAngle(axes.forward, projectOnGround, axes.up);
Vector3 yawedRight = Quaternion.AngleAxis(extractedYaw, axes.up) * axes.right;
float extractedPitch = Vector3.SignedAngle(projectOnGround, worldAnchorToLight, yawedRight);
yaw = extractedYaw;
pitch = extractedPitch;
roll = transform.rotation.eulerAngles.z;
}
///
/// Updates the light's transform with respect to a given camera and anchor point
///
/// The camera to which values are relative.
/// The anchor position.
public void UpdateTransform(Camera camera, Vector3 anchor)
{
// It is important not to read the property anchorPosition here since the distance might have been updated
// (which is a part of the calculation), but the transform is yet to be set.
var axes = GetWorldSpaceAxes(camera, anchor);
UpdateTransform(axes.up, axes.right, axes.forward, anchor);
}
Axes GetWorldSpaceAxes(Camera camera, Vector3 anchor)
{
// Fallback when the light anchor object is child of the camera (bad setup)
if (transform.IsChildOf(camera.transform))
{
return new Axes
{
up = Vector3.up,
right = Vector3.right,
forward = Vector3.forward,
};
}
Matrix4x4 viewToWorld = camera.cameraToWorldMatrix;
if (m_FrameSpace == UpDirection.Local)
{
Vector3 localUp = Camera.main.transform.up;
viewToWorld = Matrix4x4.Scale(new Vector3(1, 1, -1)) * Matrix4x4.LookAt(camera.transform.position, anchor, localUp).inverse;
viewToWorld = viewToWorld.inverse;
}
// Correct view to world for perspective
else if (!camera.orthographic && camera.transform.position != anchor)
{
var d = (anchor - camera.transform.position).normalized;
var f = Quaternion.LookRotation(d);
viewToWorld = Matrix4x4.Scale(new Vector3(1, 1, -1)) * Matrix4x4.TRS(camera.transform.position, f, Vector3.one).inverse;
viewToWorld = viewToWorld.inverse;
}
Vector3 up = (viewToWorld * Vector3.up).normalized;
Vector3 right = (viewToWorld * Vector3.right).normalized;
Vector3 forward = (viewToWorld * Vector3.forward).normalized;
return new Axes
{
up = up,
right = right,
forward = forward
};
}
void Update()
{
if (anchorPositionOverride == null || Camera.main == null)
return;
if (anchorPositionOverride.hasChanged || Camera.main.transform.hasChanged)
UpdateTransform(Camera.main, anchorPosition);
}
void OnDrawGizmosSelected()
{
var camera = Camera.main;
if (camera == null)
{
return;
}
// TODO: fix light rotated when camera rotates
Vector3 anchor = anchorPosition;
Axes axes = GetWorldSpaceAxes(camera, anchor);
Vector3 d = transform.position - anchor;
Vector3 proj = Vector3.ProjectOnPlane(d, axes.up);
float arcRadius = Mathf.Min(distance * 0.25f, k_ArcRadius);
float axisLength = Mathf.Min(distance * 0.5f, k_AxisLength);
#if UNITY_EDITOR
const float alpha = 0.2f;
Handles.color = Color.grey;
Handles.DrawDottedLine(anchorPosition, anchorPosition + proj, 2);
Handles.DrawDottedLine(anchorPosition + proj, transform.position, 2);
Handles.DrawDottedLine(anchorPosition, transform.position, 2);
// forward
Color color = Color.green;
color.a = alpha;
Handles.color = color;
Handles.DrawLine(anchorPosition, anchorPosition + axes.forward * axisLength);
Handles.DrawSolidArc(anchor, axes.up, axes.forward, yaw, arcRadius);
// up
color = Color.blue;
color.a = alpha;
Handles.color = color;
Quaternion yawRot = Quaternion.AngleAxis(yaw, axes.up * k_AxisLength);
Handles.DrawSolidArc(anchor, yawRot * axes.right, yawRot * axes.forward, pitch, arcRadius);
Handles.DrawLine(anchorPosition, anchorPosition + (yawRot * axes.forward) * axisLength);
#endif
}
// arguments are passed in world space
void UpdateTransform(Vector3 up, Vector3 right, Vector3 forward, Vector3 anchor)
{
Quaternion worldYawRot = Quaternion.AngleAxis(m_Yaw, up);
Quaternion worldPitchRot = Quaternion.AngleAxis(m_Pitch, right);
Vector3 worldPosition = anchor + (worldYawRot * worldPitchRot) * forward * distance;
transform.position = worldPosition;
Vector3 lookAt = -((worldYawRot * worldPitchRot) * forward).normalized;
Vector3 angles = Quaternion.LookRotation(lookAt, up).eulerAngles;
angles.z = m_Roll;
transform.eulerAngles = angles;
}
}
}