/// --------------------------------------------- /// Senses Pack for Behavior Designer Pro /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.AddOns.SensesPack.Runtime.Emitters { using Opsive.BehaviorDesigner.AddOns.SensesPack.Runtime.Utility; using Unity.Burst; using Unity.Collections; using Unity.Mathematics; using Unity.Jobs; using UnityEngine; /// /// Manages the creation, tracking, and querying of traces (like scent trails or blood splatter) in the game world. /// Uses an octree data structure for efficient spatial queries of traces. /// [BurstCompile] public class TraceManager : MonoBehaviour { private static TraceManager s_Instance; public static TraceManager Instance { get { if (s_Instance == null) { s_Instance = new GameObject("TraceManager").AddComponent(); } return s_Instance; } } [Tooltip("The bounds of the world space where traces can exist.")] [SerializeField] protected Bounds m_WorldBounds = new Bounds(Vector3.zero, new Vector3(1000, 1000, 1000)); private OctreeNode m_Octree; private NativeList m_AllTraces; private NativeList m_TraceIndiciesToRemove; private JobHandle m_UpdateJobHandle; /// /// Called when the object is enabled. Sets up the singleton instance and initializes native collections. /// private void OnEnable() { s_Instance = this; m_Octree = new OctreeNode(m_WorldBounds); m_AllTraces = new NativeList(Allocator.Persistent); m_TraceIndiciesToRemove = new NativeList(Allocator.Persistent); } /// /// Adds a new trace to the system. /// /// The trace to add. public void Add(Trace trace) { m_UpdateJobHandle.Complete(); m_Octree = m_Octree.Insert(trace); m_AllTraces.Add(trace); } /// /// Updates the trace system by removing traces that have faded out. /// private void Update() { m_UpdateJobHandle.Complete(); // Remove traces that have faded. for (int i = m_TraceIndiciesToRemove.Length - 1; i >= 0; --i) { m_Octree = m_Octree.Remove(m_AllTraces[m_TraceIndiciesToRemove[i]]); m_AllTraces.RemoveAtSwapBack(m_TraceIndiciesToRemove[i]); } m_TraceIndiciesToRemove.Clear(); var updateJob = new UpdateTracesJob { Time = Time.time, Traces = m_AllTraces, TraceIndiciesToRemove = m_TraceIndiciesToRemove }; m_UpdateJobHandle = updateJob.Schedule(); } /// /// Gets the combined intensity of all traces at a given position. /// /// The position to check for traces. /// The maximum range at which traces can be detected. /// The total intensity of all traces at the given position, weighted by distance. [BurstCompile] public float GetIntensityAt(Vector3 position, float range) { var nearbyTraces = new NativeList(Allocator.Temp); var traceCount = m_Octree.Query(new Bounds(position, Vector3.one * range), nearbyTraces); var totalIntensity = 0f; for (int i = 0; i < traceCount; ++i) { var distance = math.length((float3)position - nearbyTraces[i].Position); totalIntensity += nearbyTraces[i].GetIntensity(Time.time) * (1f - distance / range); } nearbyTraces.Dispose(); return totalIntensity; } /// /// Gets the combined intensity of all traces at a given position and the trace with the largest intensity. /// /// The position to check for traces. /// The maximum range at which traces can be detected. /// A tuple with the total intensity of all traces at the given position (weighted by distance) and the trace with the largest intensity. [BurstCompile] public (float, Vector3) GetIntensityPositionAt(Vector3 position, float range) { var nearbyTraces = new NativeList(Allocator.Temp); var traceCount = m_Octree.Query(new Bounds(position, Vector3.one * range), nearbyTraces); var totalIntensity = 0f; var largestIntensity = 0f; var closestPosition = position; for (int i = 0; i < traceCount; ++i) { var distance = math.length((float3)position - nearbyTraces[i].Position); var intensity = nearbyTraces[i].GetIntensity(Time.time) * (1f - distance / range); totalIntensity += intensity; if (intensity > largestIntensity) { largestIntensity = intensity; closestPosition = nearbyTraces[i].Position; } } nearbyTraces.Dispose(); return (totalIntensity, closestPosition); } /// /// Job that identifies traces that have faded out and need to be removed. /// [BurstCompile] private struct UpdateTracesJob : IJob { [Tooltip("The current time.")] public float Time; [Tooltip("The list of all traces in the system.")] public NativeList Traces; [Tooltip("The list that will store the indices of traces that need to be removed.")] public NativeList TraceIndiciesToRemove; /// /// Executes the job by checking each trace's intensity and marking it for removal if it has faded out. /// [BurstCompile] public void Execute() { for (int i = 0; i < Traces.Length; ++i) { if (Traces[i].GetIntensity(Time) <= 0) { TraceIndiciesToRemove.Add(i); } } } } /// /// Called when the object is disabled. Cleans up native collections. /// private void OnDisable() { m_UpdateJobHandle.Complete(); m_Octree.Dispose(); m_AllTraces.Dispose(); m_TraceIndiciesToRemove.Dispose(); s_Instance = null; } /// /// Reset the static variables for domain reloading. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void DomainReset() { s_Instance = null; } /// /// Draws the octree when the component is selected in the editor. /// private void OnDrawGizmosSelected() { #if UNITY_EDITOR var originalColor = Gizmos.color; Gizmos.color = Editor.BehaviorDesignerSettings.Instance.DefaultGizmosColor; DrawOctreeNode(m_Octree); Gizmos.color = originalColor; #endif } /// /// The radius of the octree node sphere visualization. /// private const float c_NodeRadius = 0.1f; /// /// Draws the octree node to the scene view. /// /// The node that should be drawn. private void DrawOctreeNode(OctreeNode node) { Gizmos.DrawWireCube(node.NodeBounds.center, node.NodeBounds.size); if (node.NodeObjects.IsCreated) { for (int i = 0; i < node.NodeObjects.Length; i++) { Gizmos.DrawSphere(node.NodeObjects[i].Position, node.NodeObjects[i].Intensity * c_NodeRadius); } } if (node.NodeChildren.IsCreated) { for (int i = 0; i < node.NodeChildren.Length; i++) { DrawOctreeNode(node.NodeChildren[i]); } } } } }