Skip to content

Commit f380732

Browse files
authored
Merge pull request #117 from Unity-Technologies/uni-24135-allow-probuilderizing
Uni 24135 allow probuilderizing
2 parents c85bde8 + 136350f commit f380732

File tree

3 files changed

+343
-70
lines changed

3 files changed

+343
-70
lines changed

Assets/FbxExporters/Editor/FbxExporter.cs

Lines changed: 181 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,24 +1353,192 @@ private static GameObject GetGameObject (Object obj)
13531353
}
13541354

13551355
/// <summary>
1356-
/// Get a mesh renderer's mesh.
1356+
/// If your MonoBehaviour knows about some custom geometry that
1357+
/// isn't in a MeshFilter or SkinnedMeshRenderer, use
1358+
/// RegisterMeshCallback to get a callback when the exporter tries
1359+
/// to export your component.
1360+
///
1361+
/// The callback should return true, and output the mesh you want.
1362+
///
1363+
/// Return false if you don't want to drive this game object.
1364+
///
1365+
/// Return true and output a null mesh if you don't want the
1366+
/// exporter to output anything.
13571367
/// </summary>
1358-
private MeshInfo GetMeshInfo (GameObject gameObject, bool requireRenderer = true)
1368+
public delegate bool GetMeshForComponent<T>(T component, out Mesh mesh) where T : MonoBehaviour;
1369+
public delegate bool GetMeshForComponent(MonoBehaviour component, out Mesh mesh);
1370+
1371+
/// <summary>
1372+
/// Map from type (must be a MonoBehaviour) to callback.
1373+
/// The type safety is lost; the caller must ensure it at run-time.
1374+
/// </summary>
1375+
static Dictionary<System.Type, GetMeshForComponent> MeshForComponentCallbacks
1376+
= new Dictionary<System.Type, GetMeshForComponent>();
1377+
1378+
/// <summary>
1379+
/// Register a callback to invoke if the object has a component of type T.
1380+
///
1381+
/// This function is prefered over the other mesh callback
1382+
/// registration methods because it's type-safe, efficient, and
1383+
/// invocation order between types can be controlled in the UI by
1384+
/// reordering the components.
1385+
///
1386+
/// It's an error to register a callback for a component that
1387+
/// already has one, unless 'replace' is set to true.
1388+
/// </summary>
1389+
public static void RegisterMeshCallback<T>(GetMeshForComponent<T> callback, bool replace = false)
1390+
where T: UnityEngine.MonoBehaviour
13591391
{
1360-
// Two possibilities: it's a skinned mesh, or we have a mesh filter.
1361-
Mesh mesh;
1362-
var meshFilter = gameObject.GetComponent<MeshFilter> ();
1363-
if (meshFilter) {
1364-
mesh = meshFilter.sharedMesh;
1365-
} else {
1366-
var renderer = gameObject.GetComponent<SkinnedMeshRenderer> ();
1367-
if (!renderer) {
1368-
mesh = null;
1392+
// Under the hood we lose type safety, but don't let the user notice!
1393+
RegisterMeshCallback(typeof(T),
1394+
(MonoBehaviour component, out Mesh mesh) => callback((T)component, out mesh),
1395+
replace);
1396+
}
1397+
1398+
/// <summary>
1399+
/// Register a callback to invoke if the object has a component of type T.
1400+
///
1401+
/// The callback will be invoked with an argument of type T, it's
1402+
/// safe to downcast.
1403+
///
1404+
/// Normally you'll want to use the generic form, but this one is
1405+
/// easier to use with reflection.
1406+
/// </summary>
1407+
public static void RegisterMeshCallback(System.Type t,
1408+
GetMeshForComponent callback,
1409+
bool replace = false)
1410+
{
1411+
if (!t.IsSubclassOf(typeof(MonoBehaviour))) {
1412+
throw new System.Exception("Registering a callback for a type that isn't derived from MonoBehaviour: " + t);
1413+
}
1414+
if (!replace && MeshForComponentCallbacks.ContainsKey(t)) {
1415+
throw new System.Exception("Replacing a callback for type " + t);
1416+
}
1417+
MeshForComponentCallbacks[t] = callback;
1418+
}
1419+
1420+
/// <summary>
1421+
/// Delegate used to convert a GameObject into a mesh.
1422+
///
1423+
/// This is useful if you want to have broader control over
1424+
/// the export process than the GetMeshForComponent callbacks
1425+
/// provide. But it's less efficient because you'll get a callback
1426+
/// on every single GameObject.
1427+
/// </summary>
1428+
public delegate bool GetMeshForObject(GameObject gameObject, out Mesh mesh);
1429+
1430+
static List<GetMeshForObject> MeshForObjectCallbacks = new List<GetMeshForObject>();
1431+
1432+
/// <summary>
1433+
/// Register a callback to invoke on every GameObject we export.
1434+
///
1435+
/// Avoid doing this if you can use a callback that depends on type.
1436+
///
1437+
/// The GameObject-based callbacks are checked before the
1438+
/// component-based ones.
1439+
///
1440+
/// Multiple GameObject-based callbacks can be registered; they are
1441+
/// checked in order of registration.
1442+
/// </summary>
1443+
public static void RegisterMeshObjectCallback(GetMeshForObject callback)
1444+
{
1445+
MeshForObjectCallbacks.Add(callback);
1446+
}
1447+
1448+
/// <summary>
1449+
/// Forget the callback linked to a component of type T.
1450+
/// </summary>
1451+
public static void UnRegisterMeshCallback<T>()
1452+
{
1453+
MeshForComponentCallbacks.Remove(typeof(T));
1454+
}
1455+
1456+
/// <summary>
1457+
/// Forget the callback linked to a component of type T.
1458+
/// </summary>
1459+
public static void UnRegisterMeshCallback(System.Type t)
1460+
{
1461+
MeshForComponentCallbacks.Remove(t);
1462+
}
1463+
1464+
/// <summary>
1465+
/// Forget a GameObject-based callback.
1466+
/// </summary>
1467+
public static void UnRegisterMeshCallback(GetMeshForObject callback)
1468+
{
1469+
MeshForObjectCallbacks.Remove(callback);
1470+
}
1471+
1472+
/// <summary>
1473+
/// Get the mesh we want to use for a GameObject.
1474+
/// May be null.
1475+
/// </summary>
1476+
Mesh ChooseMeshForObject(GameObject gameObject)
1477+
{
1478+
// First allow the object-based callbacks to have a hack at it.
1479+
foreach(var callback in MeshForObjectCallbacks) {
1480+
Mesh mesh;
1481+
if (callback(gameObject, out mesh)) {
1482+
return mesh;
1483+
}
1484+
}
1485+
1486+
// Next iterate over components and allow the component-based
1487+
// callbacks to have a hack at it. This is complicated by the
1488+
// potential of subclassing.
1489+
Mesh defaultMesh = null;
1490+
foreach(var component in gameObject.GetComponents<Component>()) {
1491+
if (!component) {
1492+
continue;
1493+
}
1494+
var monoBehaviour = component as MonoBehaviour;
1495+
if (!monoBehaviour) {
1496+
// Check for default handling. But don't commit yet.
1497+
if (defaultMesh) {
1498+
continue;
1499+
}
1500+
var meshFilter = component as MeshFilter;
1501+
if (meshFilter) {
1502+
defaultMesh = (component as MeshFilter).sharedMesh;
1503+
continue;
1504+
}
1505+
var smr = component as SkinnedMeshRenderer;
1506+
if (smr) {
1507+
defaultMesh = new Mesh();
1508+
smr.BakeMesh(defaultMesh);
1509+
continue;
1510+
}
13691511
} else {
1370-
mesh = new Mesh ();
1371-
renderer.BakeMesh (mesh);
1512+
// Check if we have custom behaviour for this component type, or
1513+
// one of its base classes.
1514+
if (!monoBehaviour.enabled) {
1515+
continue;
1516+
}
1517+
var componentType = monoBehaviour.GetType ();
1518+
do {
1519+
GetMeshForComponent callback;
1520+
if (MeshForComponentCallbacks.TryGetValue (componentType, out callback)) {
1521+
Mesh mesh;
1522+
if (callback (monoBehaviour, out mesh)) {
1523+
return mesh;
1524+
}
1525+
}
1526+
componentType = componentType.BaseType;
1527+
} while(componentType.IsSubclassOf (typeof(MonoBehaviour)));
13721528
}
13731529
}
1530+
1531+
// If we're here, custom handling didn't work.
1532+
// Revert to default handling.
1533+
return defaultMesh;
1534+
}
1535+
1536+
/// <summary>
1537+
/// Get the mesh for an object in an easy-to-use format.
1538+
/// </summary>
1539+
private MeshInfo GetMeshInfo (GameObject gameObject)
1540+
{
1541+
var mesh = ChooseMeshForObject(gameObject);
13741542
if (!mesh) {
13751543
return new MeshInfo(null);
13761544
}

Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,18 @@ protected string GetRandomPrefabAssetPath() {
121121
}
122122

123123
/// <summary>
124-
/// Creates a test hierarchy.
125-
///
126-
/// No two nodes in the hierarchy have the same name.
124+
/// Creates a test hierarchy of cubes.
125+
/// Root
126+
/// -> Parent1
127+
/// ----> Child1
128+
/// ----> Child2
129+
/// -> Parent2
130+
/// ----> Child3
127131
/// </summary>
128132
/// <returns>The hierarchy root.</returns>
129133
public GameObject CreateHierarchy (string rootname = "Root")
130134
{
131-
// Create the following hierarchy:
132-
// Root
133-
// -> Parent1
134-
// ----> Child1
135-
// ----> Child2
136-
// -> Parent2
137-
// ----> Child3
138-
139-
var root = CreateGameObject (rootname);
135+
var root = new GameObject (rootname);
140136
SetTransform (root.transform,
141137
new Vector3 (3, 4, -6),
142138
new Vector3 (45, 10, 34),
@@ -180,22 +176,24 @@ private void SetTransform (Transform t, Vector3 pos, Vector3 rot, Vector3 scale)
180176
/// <summary>
181177
/// Creates a GameObject.
182178
/// </summary>
183-
/// <returns>The created GameObject.</returns>
184-
/// <param name="name">Name.</param>
185-
/// <param name="parent">Parent.</param>
186-
public GameObject CreateGameObject (string name, Transform parent = null)
179+
public GameObject CreateGameObject (string name, Transform parent = null, PrimitiveType type = PrimitiveType.Cube)
187180
{
188-
var go = new GameObject (name);
181+
var go = GameObject.CreatePrimitive (type);
182+
go.name = name;
189183
go.transform.SetParent (parent);
190184
return go;
191185
}
192186

193187
// Helper for the tear-down. This is run from the editor's update loop.
194188
void DeleteOnNextUpdate()
195189
{
196-
Directory.Delete(filePath, recursive: true);
197-
AssetDatabase.Refresh();
198190
EditorApplication.update -= DeleteOnNextUpdate;
191+
try {
192+
Directory.Delete(filePath, recursive: true);
193+
AssetDatabase.Refresh();
194+
} catch(IOException) {
195+
// ignore -- something else must have deleted this.
196+
}
199197
}
200198

201199
[TearDown]

0 commit comments

Comments
 (0)