Skip to content

Commit a9041e1

Browse files
authored
UT-3419 Duplicate meshes export as instances. (#536)
Duplicate meshes export as instances even when not apart of a prefab.
1 parent 8b9a2c5 commit a9041e1

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

com.unity.formats.fbx/Editor/FbxExporter.cs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ internal enum FbxNodeRelationType
196196
/// Used for enforcing unique names on export.
197197
/// </summary>
198198
Dictionary<string, int> TextureNameToIndexMap = new Dictionary<string, int>();
199+
200+
Dictionary<Mesh, FbxNode> MeshToFbxNodeMap = new Dictionary<Mesh, FbxNode>();
199201

200202
/// <summary>
201203
/// Format for creating unique names
@@ -1313,7 +1315,7 @@ internal bool ExportTransform (UnityEngine.Transform unityTransform, FbxNode fbx
13131315
}
13141316

13151317
/// <summary>
1316-
/// if this game object is a model prefab then export with shared components
1318+
/// if this game object is a model prefab or the model has already been exported, then export with shared components
13171319
/// </summary>
13181320
[SecurityPermission(SecurityAction.LinkDemand)]
13191321
private bool ExportInstance(GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
@@ -1322,31 +1324,46 @@ private bool ExportInstance(GameObject unityGo, FbxScene fbxScene, FbxNode fbxNo
13221324
{
13231325
return false;
13241326
}
1325-
1326-
PrefabAssetType unityPrefabType = PrefabUtility.GetPrefabAssetType(unityGo);
1327-
// only export as an instance if the GameObject is part of a prefab instance
1328-
if (unityPrefabType == PrefabAssetType.MissingAsset ||
1329-
unityPrefabType == PrefabAssetType.NotAPrefab ||
1330-
PrefabUtility.GetPrefabInstanceStatus(unityGo) != PrefabInstanceStatus.Connected)
1331-
{
1332-
return false;
1333-
}
13341327

13351328
Object unityPrefabParent = PrefabUtility.GetCorrespondingObjectFromSource(unityGo);
13361329

1337-
if (Verbose)
1338-
Debug.Log (string.Format ("exporting instance {0}({1})", unityGo.name, unityPrefabParent.name));
1339-
13401330
FbxMesh fbxMesh = null;
13411331

1342-
if (!SharedMeshes.TryGetValue (unityPrefabParent.GetInstanceID(), out fbxMesh))
1332+
if (unityPrefabParent != null && !SharedMeshes.TryGetValue (unityPrefabParent.GetInstanceID(), out fbxMesh))
13431333
{
1334+
if (Verbose)
1335+
Debug.Log (string.Format ("exporting instance {0}({1})", unityGo.name, unityPrefabParent.name));
1336+
13441337
if (ExportMesh (unityGo, fbxNode) && fbxNode.GetMesh() != null) {
13451338
SharedMeshes [unityPrefabParent.GetInstanceID()] = fbxNode.GetMesh ();
13461339
return true;
13471340
}
13481341
return false;
13491342
}
1343+
// check if mesh is shared between 2 objects that are not prefabs
1344+
else if (unityPrefabParent == null)
1345+
{
1346+
// check if same mesh has already been exported
1347+
MeshFilter unityGoMesh = unityGo.GetComponent<MeshFilter>();
1348+
if (unityGoMesh != null && MeshToFbxNodeMap.ContainsKey(unityGoMesh.sharedMesh))
1349+
{
1350+
fbxMesh = MeshToFbxNodeMap[unityGoMesh.sharedMesh].GetMesh();
1351+
}
1352+
// export mesh as normal and add it to list
1353+
else
1354+
{
1355+
if (unityGoMesh != null)
1356+
{
1357+
MeshToFbxNodeMap.Add(unityGoMesh.sharedMesh, fbxNode);
1358+
}
1359+
return false;
1360+
}
1361+
}
1362+
1363+
if (fbxMesh == null)
1364+
{
1365+
return false;
1366+
}
13501367

13511368
// We don't export the mesh because we already have it from the parent, but we still need to assign the material
13521369
var renderer = unityGo.GetComponent<Renderer>();

com.unity.formats.fbx/Tests/FbxApiTests/PublicAPITest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class PublicAPITest : ExporterTestBaseAPI
1515
public override void Init()
1616
{
1717
base.Init();
18-
m_toExport = new GameObject[] {CreateGameObjectToExport(), CreateGameObjectToExport()};
18+
m_toExport = new GameObject[] {CreateGameObjectToExport(PrimitiveType.Cube), CreateGameObjectToExport(PrimitiveType.Sphere) };
1919
}
2020

2121
[TearDown]
@@ -32,9 +32,9 @@ public override void Term()
3232
/// Creates a GameObject to export.
3333
/// </summary>
3434
/// <returns>The game object to export.</returns>
35-
private GameObject CreateGameObjectToExport ()
35+
private GameObject CreateGameObjectToExport (PrimitiveType type = PrimitiveType.Sphere)
3636
{
37-
return GameObject.CreatePrimitive (PrimitiveType.Sphere);
37+
return GameObject.CreatePrimitive (type);
3838
}
3939

4040
[Test]

com.unity.formats.fbx/Tests/FbxTests/ModelExporterTest.cs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ public void TestExporterCallbacks()
232232
{
233233
var tree = CreateHierarchy();
234234
var tester = new CallbackTester(tree.transform, GetRandomFbxFilePath());
235-
var n = tree.GetComponentsInChildren<Transform>().Length;
235+
var nbTransforms = tree.GetComponentsInChildren<Transform>().Length;
236+
// UT-3419: because cubes are duplicates, there are less model callbacks than transforms
237+
// 1 for the root and 1 for the cube model all the gameobjects share
238+
var nbMeshCallbacks = 2;
236239

237240
// No callbacks registered => no calls.
238241
tester.Verify(0, 0);
@@ -241,34 +244,34 @@ public void TestExporterCallbacks()
241244
ModelExporter.RegisterMeshCallback<FbxPrefab>(tester.CallbackForFbxPrefab);
242245

243246
// No fbprefab => no component calls, but every object called.
244-
tester.Verify(0, n);
247+
tester.Verify(0, nbMeshCallbacks);
245248

246249
// Add a fbxprefab, check every object called and the prefab called.
247250
tree.transform.Find("Parent1").gameObject.AddComponent<FbxPrefab>();
248-
tester.Verify(1, n);
251+
tester.Verify(1, nbTransforms);
249252

250253
// Make the object report it's replacing everything => no component calls.
251-
tester.Verify(0, n, objectResult: true);
254+
tester.Verify(0, nbTransforms, objectResult: true);
252255

253256
// Make sure we can't register for a component twice, but we can
254257
// for an object. Register twice for an object means two calls per
255258
// object.
256259
Assert.That( () => ModelExporter.RegisterMeshCallback<FbxPrefab>(tester.CallbackForFbxPrefab),
257260
Throws.Exception);
258261
ModelExporter.RegisterMeshObjectCallback(tester.CallbackForObject);
259-
tester.Verify(1, 2 * n);
262+
tester.Verify(1, 2 * nbTransforms);
260263

261264
// Register twice but return true => only one call per object.
262-
tester.Verify(0, n, objectResult: true);
265+
tester.Verify(0, nbTransforms, objectResult: true);
263266

264267
// Unregister once => only one call per object, and no more for the prefab.
265268
ModelExporter.UnRegisterMeshCallback<FbxPrefab>();
266269
ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject);
267-
tester.Verify(0, n);
270+
tester.Verify(0, nbMeshCallbacks);
268271

269272
// Legal to unregister if already unregistered.
270273
ModelExporter.UnRegisterMeshCallback<FbxPrefab>();
271-
tester.Verify(0, n);
274+
tester.Verify(0, nbMeshCallbacks);
272275

273276
// Register same callback twice gets back to original state.
274277
ModelExporter.UnRegisterMeshObjectCallback(tester.CallbackForObject);
@@ -302,11 +305,12 @@ public void TestExporterCallbacks()
302305
ModelExporter.ExportObject(filename, tree);
303306
ModelExporter.UnRegisterMeshCallback<FbxPrefab>();
304307

308+
// UT-3419 Parent1 and Parent2 are instances the same mesh, so they should point to the same mesh
305309
asset = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
306310
assetMesh = asset.transform.Find("Parent1").GetComponent<MeshFilter>().sharedMesh;
307311
Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length);
308312
assetMesh = asset.transform.Find("Parent2").GetComponent<MeshFilter>().sharedMesh;
309-
Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length);
313+
Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length);
310314

311315
// Try again, but this time pick on Parent2 by name (different just
312316
// to make sure we don't pass if the previous pass didn't
@@ -325,11 +329,12 @@ public void TestExporterCallbacks()
325329
ModelExporter.ExportObject(filename, tree);
326330
ModelExporter.UnRegisterMeshObjectCallback(callback);
327331

332+
// UT-3419 Parent1 and Parent2 are instances the same mesh, so they should point to the same mesh
328333
asset = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
329334
assetMesh = asset.transform.Find("Parent1").GetComponent<MeshFilter>().sharedMesh;
330335
Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length);
331336
assetMesh = asset.transform.Find("Parent2").GetComponent<MeshFilter>().sharedMesh;
332-
Assert.AreEqual(sphereMesh.triangles.Length, assetMesh.triangles.Length);
337+
Assert.AreEqual(cubeMesh.triangles.Length, assetMesh.triangles.Length);
333338
}
334339

335340
[Test]
@@ -1079,5 +1084,25 @@ public void TestPreserveImportSettings()
10791084
newGuid = AssetDatabase.AssetPathToGUID(filename);
10801085
Assert.AreEqual(originalGuid, newGuid);
10811086
}
1087+
1088+
// UT-3419 Test that identical models export as instances
1089+
[Test]
1090+
public void TestInstanceExport()
1091+
{
1092+
// create root with 2 identical children
1093+
var filename = GetRandomFbxFilePath();
1094+
GameObject root = new GameObject("root");
1095+
GameObject child1 = CreateGameObject("child1", root.transform);
1096+
GameObject child2 = CreateGameObject("child2", root.transform);
1097+
1098+
// check export was successful
1099+
var result = ModelExporter.ExportObject(filename, root);
1100+
Assert.That(result, Is.Not.Null);
1101+
Assert.AreEqual(filename, result);
1102+
1103+
// check that both children reference the same mesh
1104+
GameObject fbxObj = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
1105+
Assert.AreEqual(fbxObj.transform.GetChild(0).GetComponent<MeshFilter>().sharedMesh.name, fbxObj.transform.GetChild(1).GetComponent<MeshFilter>().sharedMesh.name);
1106+
}
10821107
}
10831108
}

0 commit comments

Comments
 (0)