using System;
using UnityEngine;
namespace Cinemachine.Utility
{
/// Extensions to the Vector3 class, used by Cinemachine
public static class UnityVectorExtensions
{
/// A useful Epsilon
public const float Epsilon = 0.0001f;
///
/// Checks if the Vector2 contains NaN for x or y.
///
/// Vector2 to check for NaN
/// True, if any components of the vector are NaN
public static bool IsNaN(this Vector2 v)
{
return float.IsNaN(v.x) || float.IsNaN(v.y);
}
///
/// Checks if the Vector2 contains NaN for x or y.
///
/// Vector2 to check for NaN
/// True, if any components of the vector are NaN
public static bool IsNaN(this Vector3 v)
{
return float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z);
}
///
/// Get the closest point on a line segment.
///
/// A point in space
/// Start of line segment
/// End of line segment
/// The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1
public static float ClosestPointOnSegment(this Vector3 p, Vector3 s0, Vector3 s1)
{
Vector3 s = s1 - s0;
float len2 = Vector3.SqrMagnitude(s);
if (len2 < Epsilon)
return 0; // degenrate segment
return Mathf.Clamp01(Vector3.Dot(p - s0, s) / len2);
}
///
/// Get the closest point on a line segment.
///
/// A point in space
/// Start of line segment
/// End of line segment
/// The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1
public static float ClosestPointOnSegment(this Vector2 p, Vector2 s0, Vector2 s1)
{
Vector2 s = s1 - s0;
float len2 = Vector2.SqrMagnitude(s);
if (len2 < Epsilon)
return 0; // degenrate segment
return Mathf.Clamp01(Vector2.Dot(p - s0, s) / len2);
}
///
/// Returns a non-normalized projection of the supplied vector onto a plane
/// as described by its normal
///
///
/// The normal that defines the plane. Must have a length of 1.
/// The component of the vector that lies in the plane
public static Vector3 ProjectOntoPlane(this Vector3 vector, Vector3 planeNormal)
{
return (vector - Vector3.Dot(vector, planeNormal) * planeNormal);
}
///
/// Normalized the vector onto the unit square instead of the unit circle
///
/// The vector to normalize
/// The normalized vector, or the zero vector if its magnitude
/// was too small to normalize
public static Vector2 SquareNormalize(this Vector2 v)
{
var d = Mathf.Max(Mathf.Abs(v.x), Mathf.Abs(v.y));
return d < Epsilon ? Vector2.zero : v / d;
}
///
/// Calculates the intersection point defined by line_1 [p1, p2], and line_2 [q1, q2].
///
/// line_1 is defined by (p1, p2)
/// line_1 is defined by (p1, p2)
/// line_2 is defined by (q1, q2)
/// line_2 is defined by (q1, q2)
/// If lines intersect at a single point,
/// then this will hold the intersection point.
/// Otherwise, it will be Vector2.positiveInfinity.
///
/// 0 = no intersection,
/// 1 = lines intersect,
/// 2 = segments intersect,
/// 3 = lines are colinear, segments do not touch,
/// 4 = lines are colinear, segments touch (at one or at multiple points)
///
public static int FindIntersection(
in Vector2 p1, in Vector2 p2, in Vector2 q1, in Vector2 q2,
out Vector2 intersection)
{
var p = p2 - p1;
var q = q2 - q1;
var pq = q1 - p1;
var pXq = p.Cross(q);
if (Mathf.Abs(pXq) < 0.00001f)
{
// The lines are parallel (or close enough to it)
intersection = Vector2.positiveInfinity;
if (Mathf.Abs(pq.Cross(p)) < 0.00001f)
{
// The lines are colinear. Do the segments touch?
var dotPQ = Vector2.Dot(q, p);
if (dotPQ > 0 && (p1 - q2).sqrMagnitude < 0.001f)
{
// q points to start of p
intersection = q2;
return 4;
}
if (dotPQ < 0 && (p2 - q2).sqrMagnitude < 0.001f)
{
// p and q point at the same point
intersection = p2;
return 4;
}
var dot = Vector2.Dot(pq, p);
if (0 <= dot && dot <= Vector2.Dot(p, p))
{
if (dot < 0.0001f)
{
if (dotPQ <= 0 && (p1 - q1).sqrMagnitude < 0.001f)
intersection = p1; // p and q start at the same point and point away
}
else if (dotPQ > 0 && (p2 - q1).sqrMagnitude < 0.001f)
intersection = p2; // p points at start of q
return 4; // colinear segments touch
}
dot = Vector2.Dot(p1 - q1, q);
if (0 <= dot && dot <= Vector2.Dot(q, q))
return 4; // colinear segments overlap
return 3; // colinear segments don't touch
}
return 0; // the lines are parallel and not colinear
}
var t = pq.Cross(q) / pXq;
intersection = p1 + t * p;
var u = pq.Cross(p) / pXq;
if (0 <= t && t <= 1 && 0 <= u && u <= 1)
return 2; // segments touch
return 1; // segments don't touch but lines intersect
}
private static float Cross(this Vector2 v1, Vector2 v2) { return (v1.x * v2.y) - (v1.y * v2.x); }
///
/// Component-wise absolute value
///
/// Input vector
/// Component-wise absolute value of the input vector
public static Vector2 Abs(this Vector2 v)
{
return new Vector2(Mathf.Abs(v.x), Mathf.Abs(v.y));
}
///
/// Component-wise absolute value
///
/// Input vector
/// Component-wise absolute value of the input vector
public static Vector3 Abs(this Vector3 v)
{
return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
}
///
/// Checks whether the vector components are the same value.
///
/// Vector to check
/// True, if the vector elements are the same. False, otherwise.
public static bool IsUniform(this Vector2 v)
{
return Math.Abs(v.x - v.y) < Epsilon;
}
///
/// Checks whether the vector components are the same value.
///
/// Vector to check
/// True, if the vector elements are the same. False, otherwise.
public static bool IsUniform(this Vector3 v)
{
return Math.Abs(v.x - v.y) < Epsilon && Math.Abs(v.x - v.z) < Epsilon;
}
/// Is the vector within Epsilon of zero length?
///
/// True if the square magnitude of the vector is within Epsilon of zero
public static bool AlmostZero(this Vector3 v)
{
return v.sqrMagnitude < (Epsilon * Epsilon);
}
/// Much more stable for small angles than Unity's native implementation
/// The first vector
/// The second vector
/// Angle between the vectors, in degrees
public static float Angle(Vector3 v1, Vector3 v2)
{
#if false // Maybe this version is better? to test....
float a = v1.magnitude;
v1 *= v2.magnitude;
v2 *= a;
return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
#else
v1.Normalize();
v2.Normalize();
return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
#endif
}
/// Much more stable for small angles than Unity's native implementation
/// The first vector
/// The second vector
/// Definition of up (used to determine the sign)
/// Signed angle between the vectors, in degrees
public static float SignedAngle(Vector3 v1, Vector3 v2, Vector3 up)
{
float angle = Angle(v1, v2);
if (Mathf.Sign(Vector3.Dot(up, Vector3.Cross(v1, v2))) < 0)
return -angle;
return angle;
}
/// Much more stable for small angles than Unity's native implementation
/// The first vector
/// The second vector
/// Definition of up (used to determine the sign)
/// Rotation between the vectors
public static Quaternion SafeFromToRotation(Vector3 v1, Vector3 v2, Vector3 up)
{
var axis = Vector3.Cross(v1, v2);
if (axis.AlmostZero())
axis = up; // in case they are pointing in opposite directions
return Quaternion.AngleAxis(Angle(v1, v2), axis);
}
/// This is a slerp that mimics a camera operator's movement in that
/// it chooses a path that avoids the lower hemisphere, as defined by
/// the up param
/// First direction
/// Second direction
/// Interpolation amoun t
/// Defines the up direction
/// Interpolated vector
public static Vector3 SlerpWithReferenceUp(
Vector3 vA, Vector3 vB, float t, Vector3 up)
{
float dA = vA.magnitude;
float dB = vB.magnitude;
if (dA < Epsilon || dB < Epsilon)
return Vector3.Lerp(vA, vB, t);
Vector3 dirA = vA / dA;
Vector3 dirB = vB / dB;
Quaternion qA = Quaternion.LookRotation(dirA, up);
Quaternion qB = Quaternion.LookRotation(dirB, up);
Quaternion q = UnityQuaternionExtensions.SlerpWithReferenceUp(qA, qB, t, up);
Vector3 dir = q * Vector3.forward;
return dir * Mathf.Lerp(dA, dB, t);
}
}
/// Extensions to the Quaternion class, usen in various places by Cinemachine
public static class UnityQuaternionExtensions
{
/// This is a slerp that mimics a camera operator's movement in that
/// it chooses a path that avoids the lower hemisphere, as defined by
/// the up param
/// First direction
/// Second direction
/// Interpolation amount
/// Defines the up direction. Must have a length of 1.
/// Interpolated quaternion
public static Quaternion SlerpWithReferenceUp(
Quaternion qA, Quaternion qB, float t, Vector3 up)
{
var dirA = (qA * Vector3.forward).ProjectOntoPlane(up);
var dirB = (qB * Vector3.forward).ProjectOntoPlane(up);
if (dirA.AlmostZero() || dirB.AlmostZero())
return Quaternion.Slerp(qA, qB, t);
// Work on the plane, in eulers
var qBase = Quaternion.LookRotation(dirA, up);
var qBaseInv = Quaternion.Inverse(qBase);
Quaternion qA1 = qBaseInv * qA;
Quaternion qB1 = qBaseInv * qB;
var eA = qA1.eulerAngles;
var eB = qB1.eulerAngles;
return qBase * Quaternion.Euler(
Mathf.LerpAngle(eA.x, eB.x, t),
Mathf.LerpAngle(eA.y, eB.y, t),
Mathf.LerpAngle(eA.z, eB.z, t));
}
/// Normalize a quaternion
///
/// The normalized quaternion. Unit length is 1.
public static Quaternion Normalized(this Quaternion q)
{
Vector4 v = new Vector4(q.x, q.y, q.z, q.w).normalized;
return new Quaternion(v.x, v.y, v.z, v.w);
}
///
/// Get the rotations, first about world up, then about (travelling) local right,
/// necessary to align the quaternion's forward with the target direction.
/// This represents the tripod head movement needed to look at the target.
/// This formulation makes it easy to interpolate without introducing spurious roll.
///
///
/// The worldspace target direction in which we want to look
/// Which way is up. Must have a length of 1.
/// Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
/// about local right.
public static Vector2 GetCameraRotationToTarget(
this Quaternion orient, Vector3 lookAtDir, Vector3 worldUp)
{
if (lookAtDir.AlmostZero())
return Vector2.zero; // degenerate
// Work in local space
Quaternion toLocal = Quaternion.Inverse(orient);
Vector3 up = toLocal * worldUp;
lookAtDir = toLocal * lookAtDir;
// Align yaw based on world up
float angleH = 0;
{
Vector3 targetDirH = lookAtDir.ProjectOntoPlane(up);
if (!targetDirH.AlmostZero())
{
Vector3 currentDirH = Vector3.forward.ProjectOntoPlane(up);
if (currentDirH.AlmostZero())
{
// We're looking at the north or south pole
if (Vector3.Dot(currentDirH, up) > 0)
currentDirH = Vector3.down.ProjectOntoPlane(up);
else
currentDirH = Vector3.up.ProjectOntoPlane(up);
}
angleH = UnityVectorExtensions.SignedAngle(currentDirH, targetDirH, up);
}
}
Quaternion q = Quaternion.AngleAxis(angleH, up);
// Get local vertical angle
float angleV = UnityVectorExtensions.SignedAngle(
q * Vector3.forward, lookAtDir, q * Vector3.right);
return new Vector2(angleV, angleH);
}
///
/// Apply rotations, first about world up, then about (travelling) local right.
/// rot.y is rotation about worldUp, and rot.x is second rotation, about local right.
///
///
/// Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
/// about local right.
/// Which way is up
/// Result rotation after the input is applied to the input quaternion
public static Quaternion ApplyCameraRotation(
this Quaternion orient, Vector2 rot, Vector3 worldUp)
{
if (rot.sqrMagnitude < 0.0001f)
return orient;
Quaternion q = Quaternion.AngleAxis(rot.x, Vector3.right);
return (Quaternion.AngleAxis(rot.y, worldUp) * orient) * q;
}
}
/// Ad-hoc xxtentions to the Rect structure, used by Cinemachine
public static class UnityRectExtensions
{
/// Inflate a rect
///
/// x and y are added/subtracted fto/from the edges of
/// the rect, inflating it in all directions
/// The inflated rect
public static Rect Inflated(this Rect r, Vector2 delta)
{
return new Rect(
r.xMin - delta.x, r.yMin - delta.y,
r.width + delta.x * 2, r.height + delta.y * 2);
}
}
}