/// ---------------------------------------------
/// 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;
}
}
}