Rasagar/Library/PackageCache/com.unity.cinemachine/Runtime/Core/RuntimeUtility.cs
2024-08-26 23:07:20 +03:00

277 lines
11 KiB
C#

#if !UNITY_2019_3_OR_NEWER
#define CINEMACHINE_PHYSICS
#define CINEMACHINE_PHYSICS_2D
#endif
using UnityEngine;
namespace Cinemachine
{
/// <summary>An ad-hoc collection of helpers, used by Cinemachine
/// or its editor tools in various places</summary>
[DocumentationSorting(DocumentationSortingAttribute.Level.Undoc)]
public static class RuntimeUtility
{
/// <summary>Convenience to destroy an object, using the appropriate method depending
/// on whether the game is playing</summary>
/// <param name="obj">The object to destroy</param>
public static void DestroyObject(UnityEngine.Object obj)
{
if (obj != null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
UnityEngine.Object.Destroy(obj);
else
UnityEngine.Object.DestroyImmediate(obj);
#else
UnityEngine.Object.Destroy(obj);
#endif
}
}
/// <summary>
/// Check whether a GameObject is a prefab.
/// For editor only - some things are disallowed if prefab. In runtime, will always return false.
/// </summary>
/// <param name="gameObject">the object to check</param>
/// <returns>If editor, checks if object is a prefab or prefab instance.
/// In runtime, returns false always</returns>
public static bool IsPrefab(GameObject gameObject)
{
#if UNITY_EDITOR
return UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject)
!= UnityEditor.PrefabInstanceStatus.NotAPrefab;
#else
return false;
#endif
}
#if CINEMACHINE_PHYSICS
private static RaycastHit[] s_HitBuffer = new RaycastHit[16];
private static int[] s_PenetrationIndexBuffer = new int[16];
/// <summary>
/// Perform a raycast, but pass through any objects that have a given tag
/// </summary>
/// <param name="ray">The ray to cast</param>
/// <param name="hitInfo">The returned results</param>
/// <param name="rayLength">Length of the raycast</param>
/// <param name="layerMask">Layers to include</param>
/// <param name="ignoreTag">Tag to ignore</param>
/// <returns>True if something was hit. Results in hitInfo</returns>
public static bool RaycastIgnoreTag(
Ray ray, out RaycastHit hitInfo, float rayLength, int layerMask, in string ignoreTag)
{
if (ignoreTag.Length == 0)
{
if (Physics.Raycast(
ray, out hitInfo, rayLength, layerMask,
QueryTriggerInteraction.Ignore))
{
return true;
}
}
else
{
int closestHit = -1;
int numHits = Physics.RaycastNonAlloc(
ray, s_HitBuffer, rayLength, layerMask, QueryTriggerInteraction.Ignore);
for (int i = 0; i < numHits; ++i)
{
if (s_HitBuffer[i].collider.CompareTag(ignoreTag))
continue;
if (closestHit < 0 || s_HitBuffer[i].distance < s_HitBuffer[closestHit].distance)
closestHit = i;
}
if (closestHit >= 0)
{
hitInfo = s_HitBuffer[closestHit];
if (numHits == s_HitBuffer.Length)
s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
return true;
}
}
hitInfo = new RaycastHit();
return false;
}
/// <summary>
/// Perform a sphere cast, but pass through objects with a given tag
/// </summary>
/// <param name="rayStart">Start of the ray</param>
/// <param name="radius">Radius of the sphere cast</param>
/// <param name="dir">Normalized direction of the ray</param>
/// <param name="hitInfo">Results go here</param>
/// <param name="rayLength">Length of the ray</param>
/// <param name="layerMask">Layers to include</param>
/// <param name="ignoreTag">Tag to ignore</param>
/// <returns>True if something is hit. Results in hitInfo.</returns>
public static bool SphereCastIgnoreTag(
Vector3 rayStart, float radius, Vector3 dir,
out RaycastHit hitInfo, float rayLength,
int layerMask, in string ignoreTag)
{
int closestHit = -1;
int numPenetrations = 0;
float penetrationDistanceSum = 0;
int numHits = Physics.SphereCastNonAlloc(
rayStart, radius, dir, s_HitBuffer, rayLength, layerMask,
QueryTriggerInteraction.Ignore);
for (int i = 0; i < numHits; ++i)
{
var h = s_HitBuffer[i];
if (ignoreTag.Length > 0 && h.collider.CompareTag(ignoreTag))
continue;
// Collect overlapping items
if (h.distance == 0 && h.normal == -dir)
{
// hitInfo for overlapping colliders will have special
// values that are not helpful to the caller. Fix that here.
var scratchCollider = GetScratchCollider();
scratchCollider.radius = radius;
var c = h.collider;
if (Physics.ComputePenetration(
scratchCollider, rayStart, Quaternion.identity,
c, c.transform.position, c.transform.rotation,
out var offsetDir, out var offsetDistance))
{
h.point = rayStart + offsetDir * (offsetDistance - radius);
h.distance = offsetDistance - radius; // will be -ve
h.normal = offsetDir;
s_HitBuffer[i] = h;
if (h.distance < -0.0001f)
{
penetrationDistanceSum += h.distance;
if (s_PenetrationIndexBuffer.Length > numPenetrations + 1)
s_PenetrationIndexBuffer[numPenetrations++] = i;
}
}
else
{
continue; // don't know what's going on, just forget about it
}
}
if (closestHit < 0 || h.distance < s_HitBuffer[closestHit].distance)
{
closestHit = i;
}
}
// Naively combine penetrating items
if (numPenetrations > 1)
{
hitInfo = new RaycastHit();
for (int i = 0; i < numPenetrations; ++i)
{
var h = s_HitBuffer[s_PenetrationIndexBuffer[i]];
var t = h.distance / penetrationDistanceSum;
hitInfo.point += h.point * t;
hitInfo.distance += h.distance * t;
hitInfo.normal += h.normal * t;
}
hitInfo.normal = hitInfo.normal.normalized;
return true;
}
if (closestHit >= 0)
{
hitInfo = s_HitBuffer[closestHit];
if (numHits == s_HitBuffer.Length)
s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
return true;
}
hitInfo = new RaycastHit();
return false;
}
private static SphereCollider s_ScratchCollider;
private static GameObject s_ScratchColliderGameObject;
internal static SphereCollider GetScratchCollider()
{
if (s_ScratchColliderGameObject == null)
{
s_ScratchColliderGameObject = new GameObject("Cinemachine Scratch Collider");
s_ScratchColliderGameObject.hideFlags = HideFlags.HideAndDontSave;
s_ScratchColliderGameObject.transform.position = Vector3.zero;
s_ScratchColliderGameObject.SetActive(true);
s_ScratchCollider = s_ScratchColliderGameObject.AddComponent<SphereCollider>();
s_ScratchCollider.isTrigger = true;
var rb = s_ScratchColliderGameObject.AddComponent<Rigidbody>();
rb.detectCollisions = false;
rb.isKinematic = true;
}
return s_ScratchCollider;
}
internal static void DestroyScratchCollider()
{
if (s_ScratchColliderGameObject != null)
{
s_ScratchColliderGameObject.SetActive(false);
DestroyObject(s_ScratchColliderGameObject.GetComponent<Rigidbody>());
}
DestroyObject(s_ScratchCollider);
DestroyObject(s_ScratchColliderGameObject);
s_ScratchColliderGameObject = null;
s_ScratchCollider = null;
}
#endif
/// <summary>
/// Normalize a curve so that its X and Y axes range from 0 to 1
/// </summary>
/// <param name="curve">Curve to normalize</param>
/// <param name="normalizeX">If true, normalize the X axis</param>
/// <param name="normalizeY">If true, normalize the Y axis</param>
/// <returns>The normalized curve</returns>
public static AnimationCurve NormalizeCurve(
AnimationCurve curve, bool normalizeX, bool normalizeY)
{
if (!normalizeX && !normalizeY)
return curve;
Keyframe[] keys = curve.keys;
if (keys.Length > 0)
{
float minTime = keys[0].time;
float maxTime = minTime;
float minVal = keys[0].value;
float maxVal = minVal;
for (int i = 0; i < keys.Length; ++i)
{
minTime = Mathf.Min(minTime, keys[i].time);
maxTime = Mathf.Max(maxTime, keys[i].time);
minVal = Mathf.Min(minVal, keys[i].value);
maxVal = Mathf.Max(maxVal, keys[i].value);
}
float range = maxTime - minTime;
float timeScale = range < 0.0001f ? 1 : 1 / range;
range = maxVal - minVal;
float valScale = range < 1 ? 1 : 1 / range;
float valOffset = 0;
if (range < 1)
{
if (minVal > 0 && minVal + range <= 1)
valOffset = minVal;
else
valOffset = 1 - range;
}
for (int i = 0; i < keys.Length; ++i)
{
if (normalizeX)
keys[i].time = (keys[i].time - minTime) * timeScale;
if (normalizeY)
keys[i].value = ((keys[i].value - minVal) * valScale) + valOffset;
}
curve.keys = keys;
}
return curve;
}
}
}