/// --------------------------------------------- /// Senses Pack for Behavior Designer Pro /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.AddOns.SensesPack.Runtime.Emitters { using System.Collections.Generic; using UnityEngine; /// /// Manages surface types and textures in the game world, providing functionality to identify and query surface properties. /// public class SurfaceManager : MonoBehaviour { private static SurfaceManager s_Instance; private static SurfaceManager Instance { get { if (s_Instance == null) { s_Instance = new GameObject("SurfaceManager").AddComponent(); s_MaskID = Shader.PropertyToID("_Mask"); s_SecondaryTextureID = Shader.PropertyToID("_MainTex2"); } return s_Instance; } } private static int s_MaskID; private static int s_SecondaryTextureID; /// /// Represets a default surface listed within the SurfaceManager. /// [System.Serializable] public struct ObjectSurface { [Tooltip("The type of surface represented.")] [SerializeField] private SurfaceType m_SurfaceType; [Tooltip("The textures which go along with the specified SurfaceType.")] [SerializeField] private Texture[] m_Textures; public SurfaceType SurfaceType { get { return m_SurfaceType; } set { m_SurfaceType = value; } } public Texture[] Textures { get { return m_Textures; } set { m_Textures = value; } } } [Tooltip("An array of SurfaceTypes which are paired to a UV position within a texture.")] [SerializeField] protected ObjectSurface[] m_ObjectSurfaces; [Tooltip("The name of the main texture property that should be retrieved.")] [SerializeField] protected string m_MainTexturePropertyName = "_BaseMap"; [Tooltip("The fallback SurfaceType if no SurfaceTypes can be found.")] [SerializeField] protected SurfaceType m_FallbackSurfaceType; private int m_MainTexturePropertyID; private Dictionary m_TextureObjectSurfaceMap = new Dictionary(); private Dictionary m_TextureSurfaceTypeMap = new Dictionary(); private Dictionary m_GameObjectSurfaceIdentifiersMap = new Dictionary(); private Dictionary m_GameObjectSurfacesTypesMap = new Dictionary(); private Dictionary m_GameObjectComplexMaterialsMap = new Dictionary(); private Dictionary m_GameObjectRendererMap = new Dictionary(); private Dictionary m_GameObjectMainTextureMap = new Dictionary(); /// /// Initialize the default values. /// private void Awake() { // PropertyToID cannot be initialized within a MonoBehaviour constructor. m_MainTexturePropertyID = Shader.PropertyToID(m_MainTexturePropertyName); InitObjectSurfaces(); s_MaskID = Shader.PropertyToID("_Mask"); s_SecondaryTextureID = Shader.PropertyToID("_MainTex2"); } /// /// The object has been enabled. /// private void OnEnable() { s_Instance = this; } /// /// Stores all the textures added as an object surface to the TextureObjectSurfaceMap dictionary (if using the default UV) or /// the UVTextureObjectSurfaceMap dictionary (if using any other UV). /// protected void InitObjectSurfaces() { if (m_ObjectSurfaces == null) { return; } for (int i = 0; i < m_ObjectSurfaces.Length; ++i) { for (int j = 0; j < m_ObjectSurfaces[i].Textures.Length; ++j) { if (m_ObjectSurfaces[i].Textures[j] == null) { continue; } if (m_TextureSurfaceTypeMap.ContainsKey(m_ObjectSurfaces[i].Textures[j])) { continue; } m_TextureSurfaceTypeMap.Add(m_ObjectSurfaces[i].Textures[j], m_ObjectSurfaces[i].SurfaceType); } } } public static SurfaceType GetSurfaceType(GameObject target) { return Instance.GetSurfaceTypeInternal(target); } /// /// Returns the SurfaceType based on the RaycastHit. /// /// The RaycastHit which caused the SurfaceEffect to spawn. /// The collider of the object that was hit. /// The SurfaceType based on the RaycastHit. Can be null. private SurfaceType GetSurfaceTypeInternal(GameObject target) { if (target == null) { return null; } // The SurfaceType on the SurfaceIdentifier can provide a unique SurfaceType for that collider. Therefore it should be tested first. var surfaceIdentifier = GetSurfaceIdentifier(target); if (surfaceIdentifier != null) { if (surfaceIdentifier.SurfaceType != null) { return surfaceIdentifier.SurfaceType; } } SurfaceType surfaceType; if (!m_GameObjectSurfacesTypesMap.TryGetValue(target, out surfaceType)) { var texture = GetMainTexture(target); if (texture != null) { surfaceType = GetSurfaceType(texture); } m_GameObjectSurfacesTypesMap.Add(target, surfaceType); } return surfaceType; } /// /// Returns the SurfaceIdentifier for the specified collider. /// /// The collider to retrieve the SurfaceIdentifier of. /// The SurfaceIdentifier for the specified collider. Can be null. private SurfaceIdentifier GetSurfaceIdentifier(GameObject target) { SurfaceIdentifier surfaceIdentifier; if (!m_GameObjectSurfaceIdentifiersMap.TryGetValue(target, out surfaceIdentifier)) { // Try to find a SurfaceIdentifier on the GameObject. surfaceIdentifier = target.GetComponent(); // If there is no SurfaceIdentifier on the GameObject then try to find a SurfaceIdentifier withinin the children or parent. if (surfaceIdentifier == null) { surfaceIdentifier = target.GetComponentInChildren(); if (surfaceIdentifier == null) { surfaceIdentifier = target.GetComponentInParent(); } } m_GameObjectSurfaceIdentifiersMap.Add(target, surfaceIdentifier); } return surfaceIdentifier; } /// /// Returns the texture of the specified collider. /// /// The collider to get the texture of. /// The texture of the specified collider. Can be null. private Texture GetMainTexture(GameObject target) { if (target == null) { return null; } Texture texture; if (!m_GameObjectMainTextureMap.TryGetValue(target, out texture)) { // The texture is retrieved from the renderer. var hitRenderer = GetRenderer(target); if (hitRenderer != null && hitRenderer.sharedMaterial != null && hitRenderer.sharedMaterial.HasProperty(m_MainTexturePropertyID)) { texture = hitRenderer.sharedMaterial.GetTexture(m_MainTexturePropertyID); } m_GameObjectMainTextureMap.Add(target, texture); } return texture; } /// /// Returns the main renderer of the specified collider. /// /// The collider to get the renderer of. /// The main Renderer of the specified collider. Can be null. private Renderer GetRenderer(GameObject target) { if (target == null) { return null; } Renderer hitRenderer; if (!m_GameObjectRendererMap.TryGetValue(target, out hitRenderer)) { // Try to get a renderer on the collider's GameObject. hitRenderer = target.GetComponent(); // If no renderer exists, try to find a renderer in the collider's children. if (hitRenderer == null || !hitRenderer.enabled || hitRenderer is SkinnedMeshRenderer) { var childRenderers = target.GetComponentsInChildren(); for (int i = 0; i < childRenderers.Length; ++i) { if (childRenderers[i] == hitRenderer || !childRenderers[i].enabled || hitRenderer is SkinnedMeshRenderer) { continue; } hitRenderer = childRenderers[i]; break; } } // If no renderer exists, try to find a renderer in the collider's parent. if (hitRenderer == null || !hitRenderer.enabled || hitRenderer is SkinnedMeshRenderer) { var parentRenderers = target.GetComponentsInParent(); for (int i = 0; i < parentRenderers.Length; ++i) { if (parentRenderers[i] == hitRenderer || !parentRenderers[i].enabled || hitRenderer is SkinnedMeshRenderer) { continue; } hitRenderer = parentRenderers[i]; break; } } // SkinnedMeshRenderers can not have their triangles fetched. if (hitRenderer != null && (!hitRenderer.enabled || hitRenderer is SkinnedMeshRenderer)) { hitRenderer = null; } m_GameObjectRendererMap.Add(target, hitRenderer); } return hitRenderer; } /// /// Returns the SurfaceType for the specified texture. /// /// The texture to get the surface type of. /// The SurfaceType for the specified texture. Can be null. private SurfaceType GetSurfaceType(Texture texture) { if (texture == null) { return null; } SurfaceType surfaceType; if (!m_TextureSurfaceTypeMap.TryGetValue(texture, out surfaceType)) { ObjectSurface objectSurface; if (m_TextureObjectSurfaceMap.TryGetValue(texture, out objectSurface)) { surfaceType = objectSurface.SurfaceType; } m_TextureSurfaceTypeMap.Add(texture, surfaceType); } return surfaceType; } /// /// The object has been disabled. /// private void OnDisable() { s_Instance = null; } /// /// Reset the static variables for domain reloading. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void DomainReset() { s_Instance = null; } } }