ECM2
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
#if DEBUG
|
||||
//#define DEBUG_SHOW_RAYCAST_LINES
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace VLB
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL(Consts.Help.SD.UrlDynamicOcclusionRaycasting)]
|
||||
[AddComponentMenu(Consts.Help.SD.AddComponentMenuDynamicOcclusionRaycasting)]
|
||||
public class DynamicOcclusionRaycasting : DynamicOcclusionAbstractBase
|
||||
{
|
||||
public new const string ClassName = "DynamicOcclusionRaycasting";
|
||||
|
||||
/// <summary>
|
||||
/// Should it interact with 2D or 3D occluders?
|
||||
/// </summary>
|
||||
public Dimensions dimensions = Consts.DynOcclusion.RaycastingDimensionsDefault;
|
||||
|
||||
/// <summary>
|
||||
/// The beam can only be occluded by objects located on the layers matching this mask.
|
||||
/// It's very important to set it as restrictive as possible (checking only the layers which are necessary)
|
||||
/// to perform a more efficient process in order to increase the performance.
|
||||
/// </summary>
|
||||
public LayerMask layerMask = Consts.DynOcclusion.LayerMaskDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Should this beam be occluded by triggers or not?
|
||||
/// </summary>
|
||||
public bool considerTriggers = Consts.DynOcclusion.RaycastingConsiderTriggersDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum 'area' of the collider to become an occluder.
|
||||
/// Colliders smaller than this value will not block the beam.
|
||||
/// </summary>
|
||||
public float minOccluderArea = Consts.DynOcclusion.RaycastingMinOccluderAreaDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Approximated percentage of the beam to collide with the surface in order to be considered as occluder
|
||||
/// </summary>
|
||||
public float minSurfaceRatio = Consts.DynOcclusion.RaycastingMinSurfaceRatioDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Max angle (in degrees) between the beam and the surface in order to be considered as occluder
|
||||
/// </summary>
|
||||
public float maxSurfaceDot = Consts.DynOcclusion.RaycastingMaxSurfaceDotDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Alignment of the computed clipping plane:
|
||||
/// </summary>
|
||||
public PlaneAlignment planeAlignment = Consts.DynOcclusion.RaycastingPlaneAlignmentDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Translate the plane. We recommend to set a small positive offset in order to handle non-flat surface better.
|
||||
/// </summary>
|
||||
public float planeOffset = Consts.DynOcclusion.RaycastingPlaneOffsetDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Fade out the beam before the computed clipping plane in order to soften the transition.
|
||||
/// </summary>
|
||||
[FormerlySerializedAs("fadeDistanceToPlane")]
|
||||
public float fadeDistanceToSurface = Consts.DynOcclusion.RaycastingFadeDistanceToSurfaceDefault;
|
||||
|
||||
[System.Obsolete("Use 'fadeDistanceToSurface' instead")]
|
||||
public float fadeDistanceToPlane { get { return fadeDistanceToSurface; } set { fadeDistanceToSurface = value; } }
|
||||
|
||||
|
||||
public bool IsColliderHiddenByDynamicOccluder(Collider collider)
|
||||
{
|
||||
Debug.Assert(collider, "You should pass a valid Collider to VLB.DynamicOcclusion.IsColliderHiddenByDynamicOccluder");
|
||||
|
||||
if (!planeEquationWS.IsValid())
|
||||
return false;
|
||||
|
||||
var isInside = GeometryUtility.TestPlanesAABB(new Plane[] { planeEquationWS }, collider.bounds);
|
||||
return !isInside;
|
||||
}
|
||||
|
||||
public struct HitResult
|
||||
{
|
||||
public HitResult(ref RaycastHit hit3D)
|
||||
{
|
||||
point = hit3D.point;
|
||||
normal = hit3D.normal;
|
||||
distance = hit3D.distance;
|
||||
collider3D = hit3D.collider;
|
||||
collider2D = null;
|
||||
}
|
||||
|
||||
public HitResult(ref RaycastHit2D hit2D)
|
||||
{
|
||||
point = hit2D.point;
|
||||
normal = hit2D.normal;
|
||||
distance = hit2D.distance;
|
||||
collider2D = hit2D.collider;
|
||||
collider3D = null;
|
||||
}
|
||||
|
||||
public Vector3 point;
|
||||
public Vector3 normal;
|
||||
public float distance;
|
||||
|
||||
Collider2D collider2D;
|
||||
Collider collider3D;
|
||||
|
||||
public bool hasCollider { get { return collider2D || collider3D; } }
|
||||
|
||||
public string name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (collider3D) return collider3D.name;
|
||||
else if (collider2D) return collider2D.name;
|
||||
else return "null collider";
|
||||
}
|
||||
}
|
||||
|
||||
public Bounds bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (collider3D) return collider3D.bounds;
|
||||
else if (collider2D) return collider2D.bounds;
|
||||
else return new Bounds();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNull() { collider2D = null; collider3D = null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get information about the current occluder hit by the beam.
|
||||
/// Can be null if the beam is not occluded.
|
||||
/// </summary>
|
||||
HitResult m_CurrentHit;
|
||||
|
||||
protected override string GetShaderKeyword() { return ShaderKeywords.SD.OcclusionClippingPlane; }
|
||||
protected override MaterialManager.SD.DynamicOcclusion GetDynamicOcclusionMode() { return MaterialManager.SD.DynamicOcclusion.ClippingPlane; }
|
||||
|
||||
float m_RangeMultiplier = 1f;
|
||||
public Plane planeEquationWS { get; private set; }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public HitResult editorCurrentHitResult { get { return m_CurrentHit; } }
|
||||
|
||||
public struct EditorDebugData
|
||||
{
|
||||
public int lastFrameUpdate;
|
||||
}
|
||||
public EditorDebugData editorDebugData;
|
||||
|
||||
public static bool editorShowDebugPlane = true;
|
||||
public static bool editorRaycastAtEachFrame = true;
|
||||
private static bool editorPrefsLoaded = false;
|
||||
|
||||
public static void EditorLoadPrefs()
|
||||
{
|
||||
if (!editorPrefsLoaded)
|
||||
{
|
||||
editorShowDebugPlane = UnityEditor.EditorPrefs.GetBool(EditorPrefsStrings.DynOcclusion.PrefShowDebugPlane, true);
|
||||
editorRaycastAtEachFrame = UnityEditor.EditorPrefs.GetBool(EditorPrefsStrings.DynOcclusion.PrefRaycastingEditor, true);
|
||||
editorPrefsLoaded = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void OnValidateProperties()
|
||||
{
|
||||
base.OnValidateProperties();
|
||||
minOccluderArea = Mathf.Max(minOccluderArea, 0f);
|
||||
fadeDistanceToSurface = Mathf.Max(fadeDistanceToSurface, 0f);
|
||||
}
|
||||
|
||||
protected override void OnEnablePostValidate()
|
||||
{
|
||||
m_CurrentHit.SetNull();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorLoadPrefs();
|
||||
editorDebugData.lastFrameUpdate = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
SetHitNull();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
var triggerZone = GetComponent<TriggerZone>();
|
||||
if (triggerZone)
|
||||
{
|
||||
m_RangeMultiplier = Mathf.Max(1f, triggerZone.rangeMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 GetRandomVectorAround(Vector3 direction, float angleDiff)
|
||||
{
|
||||
var halfAngle = angleDiff * 0.5f;
|
||||
return Quaternion.Euler(Random.Range(-halfAngle, halfAngle), Random.Range(-halfAngle, halfAngle), Random.Range(-halfAngle, halfAngle)) * direction;
|
||||
}
|
||||
|
||||
QueryTriggerInteraction queryTriggerInteraction { get { return considerTriggers ? QueryTriggerInteraction.Collide : QueryTriggerInteraction.Ignore; } }
|
||||
|
||||
float raycastMaxDistance { get { return m_Master.raycastDistance * m_RangeMultiplier * m_Master.GetLossyScale().z; } }
|
||||
|
||||
HitResult GetBestHit(Vector3 rayPos, Vector3 rayDir)
|
||||
{
|
||||
return dimensions == Dimensions.Dim2D ? GetBestHit2D(rayPos, rayDir) : GetBestHit3D(rayPos, rayDir);
|
||||
}
|
||||
|
||||
HitResult GetBestHit3D(Vector3 rayPos, Vector3 rayDir)
|
||||
{
|
||||
var hits = Physics.RaycastAll(rayPos, rayDir, raycastMaxDistance, layerMask.value, queryTriggerInteraction);
|
||||
|
||||
int bestHit = -1;
|
||||
float bestLength = float.MaxValue;
|
||||
for (int i = 0; i < hits.Length; ++i)
|
||||
{
|
||||
if (hits[i].collider.gameObject != m_Master.gameObject) // skip collider from TriggerZone
|
||||
{
|
||||
if (hits[i].collider.bounds.GetMaxArea2D() >= minOccluderArea)
|
||||
{
|
||||
if (hits[i].distance < bestLength)
|
||||
{
|
||||
bestLength = hits[i].distance;
|
||||
bestHit = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_SHOW_RAYCAST_LINES
|
||||
Debug.DrawLine(rayPos, rayPos + rayDir * raycastMaxDistance, bestHit != -1 ? Color.green : Color.red);
|
||||
#endif
|
||||
if (bestHit != -1)
|
||||
return new HitResult(ref hits[bestHit]);
|
||||
else
|
||||
return new HitResult();
|
||||
}
|
||||
|
||||
HitResult GetBestHit2D(Vector3 rayPos, Vector3 rayDir)
|
||||
{
|
||||
var hits = Physics2D.RaycastAll(new Vector2(rayPos.x, rayPos.y), new Vector2(rayDir.x, rayDir.y), raycastMaxDistance, layerMask.value);
|
||||
|
||||
int bestHit = -1;
|
||||
float bestLength = float.MaxValue;
|
||||
for (int i = 0; i < hits.Length; ++i)
|
||||
{
|
||||
if (!considerTriggers && hits[i].collider.isTrigger) // do not query triggers if considerTriggers is disabled
|
||||
continue;
|
||||
|
||||
if (hits[i].collider.gameObject != m_Master.gameObject) // skip collider from TriggerZone
|
||||
{
|
||||
if (hits[i].collider.bounds.GetMaxArea2D() >= minOccluderArea)
|
||||
{
|
||||
if (hits[i].distance < bestLength)
|
||||
{
|
||||
bestLength = hits[i].distance;
|
||||
bestHit = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_SHOW_RAYCAST_LINES
|
||||
Debug.DrawLine(rayPos, rayPos + rayDir * raycastMaxDistance, bestHit != -1 ? Color.green : Color.red);
|
||||
#endif
|
||||
if (bestHit != -1)
|
||||
return new HitResult(ref hits[bestHit]);
|
||||
else
|
||||
return new HitResult();
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Max2D = Down,
|
||||
Max3D = Right,
|
||||
};
|
||||
uint m_PrevNonSubHitDirectionId = 0;
|
||||
|
||||
uint GetDirectionCount() { return dimensions == Dimensions.Dim2D ? ((uint)Direction.Max2D + 1) : ((uint)Direction.Max3D + 1); }
|
||||
|
||||
Vector3 GetDirection(uint dirInt)
|
||||
{
|
||||
dirInt = dirInt % GetDirectionCount();
|
||||
switch (dirInt)
|
||||
{
|
||||
case (uint)Direction.Up: return m_Master.raycastGlobalUp;
|
||||
case (uint)Direction.Right: return m_Master.raycastGlobalRight;
|
||||
case (uint)Direction.Down: return -m_Master.raycastGlobalUp;
|
||||
case (uint)Direction.Left: return -m_Master.raycastGlobalRight;
|
||||
}
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
|
||||
bool IsHitValid(ref HitResult hit, Vector3 forwardVec)
|
||||
{
|
||||
if (hit.hasCollider)
|
||||
{
|
||||
float dot = Vector3.Dot(hit.normal, -forwardVec);
|
||||
return dot >= maxSurfaceDot;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnProcessOcclusion(ProcessOcclusionSource source)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
editorDebugData.lastFrameUpdate = Time.frameCount;
|
||||
#endif
|
||||
var raycastGlobalForward = m_Master.raycastGlobalForward;
|
||||
var bestHit = GetBestHit(transform.position, raycastGlobalForward);
|
||||
|
||||
if (IsHitValid(ref bestHit, raycastGlobalForward))
|
||||
{
|
||||
if (minSurfaceRatio > 0.5f)
|
||||
{
|
||||
var raycastDistance = m_Master.raycastDistance;
|
||||
for (uint i = 0; i < GetDirectionCount(); i++)
|
||||
{
|
||||
var dir3 = GetDirection(i + m_PrevNonSubHitDirectionId) * (minSurfaceRatio * 2 - 1);
|
||||
dir3.Scale(transform.localScale);
|
||||
var startPt = transform.position + dir3 * m_Master.coneRadiusStart;
|
||||
var newPt = transform.position + dir3 * m_Master.coneRadiusEnd + raycastGlobalForward * raycastDistance;
|
||||
|
||||
var bestHitSub = GetBestHit(startPt, (newPt - startPt).normalized);
|
||||
if (IsHitValid(ref bestHitSub, raycastGlobalForward))
|
||||
{
|
||||
if (bestHitSub.distance > bestHit.distance)
|
||||
{
|
||||
bestHit = bestHitSub;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PrevNonSubHitDirectionId = i;
|
||||
bestHit.SetNull();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bestHit.SetNull();
|
||||
}
|
||||
|
||||
SetHit(ref bestHit);
|
||||
return bestHit.hasCollider;
|
||||
}
|
||||
|
||||
void SetHit(ref HitResult hit)
|
||||
{
|
||||
if (!hit.hasCollider)
|
||||
{
|
||||
SetHitNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (planeAlignment)
|
||||
{
|
||||
case PlaneAlignment.Beam:
|
||||
SetClippingPlane(new Plane(-m_Master.raycastGlobalForward, hit.point));
|
||||
break;
|
||||
case PlaneAlignment.Surface:
|
||||
default:
|
||||
SetClippingPlane(new Plane(hit.normal, hit.point));
|
||||
break;
|
||||
}
|
||||
|
||||
m_CurrentHit = hit;
|
||||
}
|
||||
}
|
||||
|
||||
void SetHitNull()
|
||||
{
|
||||
SetClippingPlaneOff();
|
||||
m_CurrentHit.SetNull();
|
||||
}
|
||||
|
||||
protected override void OnModifyMaterialCallback(MaterialModifier.Interface owner)
|
||||
{
|
||||
Debug.Assert(owner != null);
|
||||
var planeWS = planeEquationWS;
|
||||
owner.SetMaterialProp(ShaderProperties.SD.DynamicOcclusionClippingPlaneWS, new Vector4(planeWS.normal.x, planeWS.normal.y, planeWS.normal.z, planeWS.distance));
|
||||
owner.SetMaterialProp(ShaderProperties.SD.DynamicOcclusionClippingPlaneProps, fadeDistanceToSurface);
|
||||
}
|
||||
|
||||
void SetClippingPlane(Plane planeWS)
|
||||
{
|
||||
planeWS = planeWS.TranslateCustom(planeWS.normal * planeOffset);
|
||||
SetPlaneWS(planeWS);
|
||||
Debug.Assert(m_MaterialModifierCallbackCached != null);
|
||||
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), m_MaterialModifierCallbackCached);
|
||||
}
|
||||
|
||||
void SetClippingPlaneOff()
|
||||
{
|
||||
SetPlaneWS(new Plane());
|
||||
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), null);
|
||||
}
|
||||
|
||||
void SetPlaneWS(Plane planeWS)
|
||||
{
|
||||
planeEquationWS = planeWS;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
m_DebugPlaneLocal = planeWS;
|
||||
if (m_DebugPlaneLocal.IsValid())
|
||||
{
|
||||
float dist;
|
||||
if (m_DebugPlaneLocal.Raycast(new Ray(transform.position, m_Master.raycastGlobalForward), out dist))
|
||||
m_DebugPlaneLocal.distance = dist; // compute local distance
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
// In Editor, process raycasts at each frame update
|
||||
if (!editorRaycastAtEachFrame)
|
||||
SetHitNull();
|
||||
else
|
||||
ProcessOcclusion(ProcessOcclusionSource.EditorUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
Plane m_DebugPlaneLocal;
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (!editorShowDebugPlane)
|
||||
return;
|
||||
|
||||
if (m_DebugPlaneLocal.IsValid())
|
||||
{
|
||||
var planePos = transform.position + m_DebugPlaneLocal.distance * m_Master.raycastGlobalForward;
|
||||
float planeDistNormalized = Mathf.Clamp01(Mathf.InverseLerp(0f, m_Master.raycastDistance, m_DebugPlaneLocal.distance));
|
||||
float planeSize = Mathf.Lerp(m_Master.coneRadiusStart, m_Master.coneRadiusEnd, planeDistNormalized);
|
||||
|
||||
var color = m_Master.ComputeColorAtDepth(planeDistNormalized).ComputeComplementaryColor(false);
|
||||
Utils.GizmosDrawPlane(
|
||||
m_DebugPlaneLocal.normal,
|
||||
planePos,
|
||||
color,
|
||||
Matrix4x4.identity,
|
||||
planeSize,
|
||||
planeSize * 0.5f);
|
||||
|
||||
UnityEditor.Handles.color = color;
|
||||
UnityEditor.Handles.DrawWireDisc(planePos,
|
||||
m_DebugPlaneLocal.normal,
|
||||
planeSize * (minSurfaceRatio * 2 - 1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user