-
Notifications
You must be signed in to change notification settings - Fork 866
Expand file tree
/
Copy pathMaterialReferenceBuilder.cs
More file actions
180 lines (155 loc) · 7.16 KB
/
MaterialReferenceBuilder.cs
File metadata and controls
180 lines (155 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System;
using Object = UnityEngine.Object;
namespace UnityEditor.Rendering.Universal
{
internal static class MaterialReferenceBuilder
{
public static readonly Dictionary<Type, List<MethodInfo>> MaterialReferenceLookup;
static MaterialReferenceBuilder()
{
MaterialReferenceLookup = GetMaterialReferenceLookup();
}
private static Dictionary<Type, List<MethodInfo>> GetMaterialReferenceLookup()
{
var result = new Dictionary<Type, List<MethodInfo>>();
var allObjectsWithMaterialProperties = TypeCache.GetTypesDerivedFrom<Object>()
.Where(type => type.GetProperties().Any(HasMaterialProperty));
foreach (var property in allObjectsWithMaterialProperties)
{
if (!result.ContainsKey(property))
{
result.Add(property, new List<MethodInfo>());
}
var materialProps = GetMaterialPropertiesWithoutLeaking(property);
foreach (var prop in materialProps)
{
result[property].Add(prop.GetGetMethod());
}
}
return result;
}
private static bool HasMaterialProperty(PropertyInfo prop)
{
return prop.PropertyType == typeof(Material) || prop.PropertyType == typeof(Material[]);
}
private static List<Material> GetMaterials(Object obj)
{
var result = new List<Material>();
var allMaterialProperties = obj.GetType().GetMaterialPropertiesWithoutLeaking();
foreach (var property in allMaterialProperties)
{
var value = property.GetGetMethod().GetMaterialFromMethod(obj, (methodName, objectName) =>
$"The method {methodName} was not found on {objectName}. This property will not be indexed.");
if (value is Material materialResult)
{
result.Add(materialResult);
}
else if (value is Material[] materialList)
{
result.AddRange(materialList);
}
}
return result;
}
/// <summary>
/// Gets all of the types in the Material Reference lookup that are components. Used to determine whether to run the
/// method directly or on the component
/// </summary>
/// <returns>List of types that are components</returns>
public static List<Type> GetComponentTypes()
{
return MaterialReferenceLookup.Keys.Where(key => typeof(Component).IsAssignableFrom(key)).ToList();
}
/// <summary>
/// Gets all material properties from an object or a component of an object
/// </summary>
/// <param name="obj">The GameObject or Scriptable Object</param>
/// <returns>List of Materials</returns>
public static List<Material> GetMaterialsFromObject(Object obj)
{
var result = new List<Material>();
if (obj is GameObject go)
{
foreach (var key in GetComponentTypes())
{
var components = go.GetComponentsInChildren(key);
foreach (var component in components)
{
result.AddRange(GetMaterials(component));
}
}
}
else
{
result.AddRange(GetMaterials(obj));
}
return result.Distinct().ToList();
}
/// <summary>
/// Text Mesh pro will sometimes be missing the GetFontSharedMaterials method, even though the property is supposed
/// to have that method. This gracefully handles that case.
/// </summary>
/// <param name="method">The Method being invoked</param>
/// <param name="obj">The Unity Object the method is invoked upon</param>
/// <param name="generateErrorString">The function that takes the method name and object name and produces an error string</param>
/// <returns>The resulting object from invoking the method on the Object</returns>
/// <exception cref="Exception">Any exception that is not the missing method exception</exception>
public static object GetMaterialFromMethod(this MethodInfo method,
Object obj,
Func<string, string, string> generateErrorString)
{
object result = null;
try
{
result = method.Invoke(obj, null);
}
catch (Exception e)
{
// swallow the missing method exception, there's nothing we can do about it at this point
// and we've already checked for other possible null exceptions here
if ((e.InnerException is NullReferenceException))
{
Debug.LogWarning(generateErrorString(method.Name, obj.name));
}
else
{
throw e;
}
}
return result;
}
/// <summary>
/// Gets the SharedMaterial(s) properties when there are shared materials so that we don't leak material instances into the scene
/// </summary>
/// <param name="property">The property Type that we are getting the SharedMaterial(s) properties from</param>
/// <returns>List of shared material properties and other material properties that won't leak material instances</returns>
public static IEnumerable<PropertyInfo> GetMaterialPropertiesWithoutLeaking(this Type property)
{
var materialProps = property.GetProperties().Where(HasMaterialProperty).ToList();
// if there is a sharedMaterial property or sharedMaterials property, remove the property that will leak materials
var sharedMaterialProps =
materialProps.Where(prop => prop.Name.Contains("shared", StringComparison.InvariantCultureIgnoreCase)).ToList();
var propsToRemove = sharedMaterialProps
.Select(prop => prop.Name.ToLowerInvariant().Replace("shared", string.Empty))
.ToList();
materialProps.RemoveAll(prop => propsToRemove.Contains(prop.Name.ToLowerInvariant()));
// also remove any property which has no setter
materialProps.RemoveAll(prop => prop.SetMethod == null);
return materialProps;
}
/// <summary>
/// Get whether or not a Material is considered readonly (Built In Resource)
/// </summary>
/// <param name="material">The Material to test</param>
/// <returns>Boolean of whether or not that Material is considered readonly</returns>
public static bool GetIsReadonlyMaterial(Material material)
{
var assetPath = AssetDatabase.GetAssetPath(material);
return string.IsNullOrEmpty(assetPath) || assetPath.Equals(@"Resources/unity_builtin_extra", StringComparison.OrdinalIgnoreCase);
}
}
}