Skip to content

Commit 6db6794

Browse files
committed
Merge branch 'master' into UNI-35294-export-legacy-skinned-mesh-anim
2 parents abfdc00 + bc06dff commit 6db6794

File tree

10 files changed

+8944
-101
lines changed

10 files changed

+8944
-101
lines changed

Assets/FbxExporters/Editor/FbxExporter.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,8 +1025,17 @@ private bool ExportSkin (SkinnedMeshRenderer skinnedMesh,
10251025
/// </summary>
10261026
private void SetVertexWeights (MeshInfo meshInfo, Dictionary<int, FbxCluster> boneCluster)
10271027
{
1028+
HashSet<int> visitedVertices = new HashSet<int> ();
1029+
10281030
// set the vertex weights for each bone
10291031
for (int i = 0; i < meshInfo.BoneWeights.Length; i++) {
1032+
var actualIndex = ControlPointToIndex [meshInfo.Vertices [i]];
1033+
1034+
if (visitedVertices.Contains (actualIndex)) {
1035+
continue;
1036+
}
1037+
visitedVertices.Add (actualIndex);
1038+
10301039
var boneWeights = meshInfo.BoneWeights;
10311040
int[] indices = {
10321041
boneWeights [i].boneIndex0,
@@ -1048,7 +1057,8 @@ boneWeights [i].weight3
10481057
if (!boneCluster.ContainsKey (indices [j])) {
10491058
continue;
10501059
}
1051-
boneCluster [indices [j]].AddControlPointIndex (ControlPointToIndex[meshInfo.Vertices[i]], weights [j]);
1060+
// add vertex and weighting on vertex to this bone's cluster
1061+
boneCluster [indices [j]].AddControlPointIndex (actualIndex, weights [j]);
10521062
}
10531063
}
10541064
}
@@ -1551,6 +1561,12 @@ public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPai
15511561
return true;
15521562
}
15531563

1564+
if (uniPropertyName.StartsWith("field of view", ct))
1565+
{
1566+
prop = new FbxPropertyChannelPair("FieldOfView", null);
1567+
return true;
1568+
}
1569+
15541570
prop = new FbxPropertyChannelPair ();
15551571
return false;
15561572
}

Assets/FbxExporters/Editor/FbxPrefabAutoUpdater.cs

Lines changed: 150 additions & 69 deletions
Large diffs are not rendered by default.

Assets/FbxExporters/Editor/UnitTests/ExporterTestBase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,13 @@ protected virtual GameObject ExportSelection(params Object[] selected)
233233
return fbxRoot;
234234
}
235235

236+
protected virtual string ExportSelectedObjects(string filename, params Object[] selected)
237+
{
238+
string fbxFileName = FbxExporters.Editor.ModelExporter.ExportObjects(filename, selected);
239+
240+
return fbxFileName;
241+
}
242+
236243
/// <summary>
237244
/// Compares two hierarchies, asserts that they match precisely.
238245
/// The root can be allowed to mismatch. That's normal with

Assets/FbxExporters/Editor/UnitTests/FbxAnimationTest.cs

Lines changed: 169 additions & 20 deletions
Large diffs are not rendered by default.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using UnityEngine;
2+
using UnityEditor;
3+
using NUnit.Framework;
4+
using System.Collections.Generic;
5+
6+
namespace FbxExporters.UnitTests
7+
{
8+
public class MyKeyComparer : IComparer<Keyframe>
9+
{
10+
public int Compare(Keyframe a, Keyframe b)
11+
{
12+
Debug.Log(string.Format("a.value: {0}, b.value: {1}, a.time: {2}, b.time: {3}", a.value, b.value, a.time, b.time));
13+
return a.time == b.time && a.value == b.value ? 0 : 1;
14+
}
15+
}
16+
17+
public class FbxCameraTest : ExporterTestBase
18+
{
19+
20+
[Test]
21+
public void AnimationWithCameraFOVTest()
22+
{
23+
string filename = GetRandomFbxFilePath();
24+
GameObject go = new GameObject();
25+
go.name = "originalCamera";
26+
Camera camera = go.AddComponent(typeof(Camera)) as Camera;
27+
Animation anim = go.AddComponent(typeof(Animation)) as Animation;
28+
29+
Keyframe[] keys = new Keyframe[3];
30+
keys[0] = new Keyframe(0.0f, 1f);
31+
keys[1] = new Keyframe(1.0f, 2f);
32+
keys[2] = new Keyframe(2.0f, 3f);
33+
34+
AnimationCurve curve = new AnimationCurve(keys);
35+
36+
AnimationClip clip = new AnimationClip();
37+
38+
clip.legacy = true;
39+
40+
clip.SetCurve("", typeof(Camera), "field of view", curve);
41+
42+
anim.AddClip(clip, "test");
43+
44+
//export the object
45+
var exported = FbxExporters.Editor.ModelExporter.ExportObject(filename, go);
46+
47+
Assert.That(exported, Is.EqualTo(filename));
48+
49+
// TODO: Uni-34492 change importer settings of (newly exported model)
50+
// so that it's not resampled and it is legacy animation
51+
{
52+
ModelImporter modelImporter = AssetImporter.GetAtPath(filename) as ModelImporter;
53+
Assert.That(modelImporter, Is.Not.Null);
54+
modelImporter.resampleCurves = false;
55+
AssetDatabase.ImportAsset(filename);
56+
modelImporter.animationType = ModelImporterAnimationType.Legacy;
57+
AssetDatabase.ImportAsset(filename);
58+
}
59+
60+
Object[] objects = AssetDatabase.LoadAllAssetsAtPath(filename);
61+
62+
AnimationClip exportedClip = null;
63+
foreach (Object o in objects)
64+
{
65+
exportedClip = o as AnimationClip;
66+
if (exportedClip != null) break;
67+
}
68+
69+
Assert.IsNotNull(exportedClip);
70+
exportedClip.legacy = true;
71+
72+
EditorCurveBinding exportedEditorCurveBinding = AnimationUtility.GetCurveBindings(exportedClip)[0];
73+
74+
AnimationCurve exportedCurve = AnimationUtility.GetEditorCurve(exportedClip, exportedEditorCurveBinding);
75+
76+
Assert.That(exportedCurve.keys.Length, Is.EqualTo(keys.Length));
77+
78+
//check imported animation against original
79+
Assert.That(exportedCurve.keys, Is.EqualTo(keys).Using<Keyframe>(new MyKeyComparer()), string.Format("key doesn't match"));
80+
}
81+
82+
}
83+
}

Assets/FbxExporters/Editor/UnitTests/FbxPrefabAutoUpdaterTest.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,55 @@ public void ReplaceTest ()
146146
}
147147

148148
}
149+
150+
public class FbxPrefabAutoUpdaterRemappingTest : ExporterTestBase
151+
{
152+
[Test]
153+
public void RemappingTest()
154+
{
155+
//Create a hierarchy of objects
156+
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
157+
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
158+
sphere.transform.SetParent(cube.transform);
159+
GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
160+
cylinder.transform.SetParent(sphere.transform);
161+
162+
string filePath = GetRandomFbxFilePath();
163+
164+
// Convert to linked prefab instance (auto-updating prefab)
165+
GameObject cubePrefabInstance = ConvertToModel.Convert(cube, fbxFullPath: filePath);
166+
Object cubePrefabParent = PrefabUtility.GetPrefabParent(cubePrefabInstance);
167+
168+
// In FbxPrefab Component of Cube, add SphereFBX/Sphere name mapping
169+
FbxPrefab fbxPrefabScript = cubePrefabInstance.transform.GetComponent<FbxPrefab>();
170+
FbxPrefab.StringPair stringpair = new FbxPrefab.StringPair();
171+
stringpair.FBXObjectName = "SphereFBX";
172+
stringpair.UnityObjectName = "Sphere";
173+
fbxPrefabScript.NameMapping.Add(stringpair);
174+
PrefabUtility.ReplacePrefab(cubePrefabInstance, cubePrefabParent);
175+
176+
//Create second FBX
177+
GameObject cube2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
178+
GameObject sphere2 = GameObject.CreatePrimitive(PrimitiveType.Sphere);
179+
// Change name of Sphere to SphereFBX
180+
sphere2.transform.name = "SphereFBX";
181+
sphere2.transform.SetParent(cube2.transform);
182+
GameObject cylinder2 = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
183+
cylinder2.transform.SetParent(sphere2.transform);
184+
185+
//export our updated hierarchy to the same file path as the original
186+
SleepForFileTimestamp();
187+
// "Import" model to Unity (Exporting modified FBX to Unity to see if the remapping works)
188+
ExportSelectedObjects(filePath, cube2);
189+
AssetDatabase.Refresh();
190+
191+
// Assert Check Sphere = SphereFBX
192+
Assert.IsTrue(cubePrefabInstance != null);
193+
Assert.IsTrue(cubePrefabInstance.GetComponent<MeshFilter>().sharedMesh != null);
194+
Assert.IsTrue(cubePrefabInstance.transform.GetChild(0).name == "SphereFBX");
195+
Assert.IsTrue(cubePrefabInstance.transform.GetChild(0).GetComponent<MeshFilter>().sharedMesh != null);
196+
}
197+
}
149198
}
150199

151200
namespace FbxExporters.PerformanceTests {

Assets/FbxExporters/Editor/UnitTests/ModelExporterTest.cs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,9 @@ private void CompareMeshComponentAttributes(Mesh mesh, Mesh fbxMesh)
507507
Assert.AreEqual (mesh.tangents, fbxMesh.tangents);
508508
}
509509

510-
[Test]
511-
public void TestSkinnedMeshExport(){
512-
// for now use this cowboy taken from the asset store as the test file
513-
// TODO: find a better/simpler test file
514-
var fbxPath = "FbxExporters/Editor/UnitTests/Models/Cowboy/cowboyMidPoly(riged).fbx";
515-
510+
private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer originalSkinnedMesh, out SkinnedMeshRenderer exportedSkinnedMesh){
516511
// add fbx to scene
517-
GameObject originalFbxObj = AssetDatabase.LoadMainAssetAtPath("Assets/" + fbxPath) as GameObject;
512+
GameObject originalFbxObj = AssetDatabase.LoadMainAssetAtPath("Assets/" + fileToExport) as GameObject;
518513
Assert.IsNotNull (originalFbxObj);
519514
GameObject originalGO = GameObject.Instantiate (originalFbxObj);
520515
Assert.IsTrue (originalGO);
@@ -526,11 +521,36 @@ public void TestSkinnedMeshExport(){
526521
GameObject fbxObj = AssetDatabase.LoadMainAssetAtPath(filename) as GameObject;
527522
Assert.IsTrue (fbxObj);
528523

529-
var originalSkinnedMesh = originalGO.GetComponentInChildren<SkinnedMeshRenderer> ();
524+
originalSkinnedMesh = originalGO.GetComponentInChildren<SkinnedMeshRenderer> ();
530525
Assert.IsNotNull (originalSkinnedMesh);
531526

532-
var exportedSkinnedMesh = fbxObj.GetComponentInChildren<SkinnedMeshRenderer> ();
527+
exportedSkinnedMesh = fbxObj.GetComponentInChildren<SkinnedMeshRenderer> ();
533528
Assert.IsNotNull (exportedSkinnedMesh);
529+
}
530+
531+
public class SkinnedMeshTestDataClass
532+
{
533+
public static System.Collections.IEnumerable TestCases1 {
534+
get {
535+
// for now use this cowboy taken from the asset store as the test file
536+
// TODO: find a better/simpler test file
537+
yield return "Models/Cowboy/cowboyMidPoly(riged).fbx";
538+
}
539+
}
540+
public static System.Collections.IEnumerable TestCases2 {
541+
get {
542+
yield return "Models/SimpleMan/SimpleMan.fbx";
543+
}
544+
}
545+
}
546+
547+
[Test, TestCaseSource(typeof(SkinnedMeshTestDataClass), "TestCases1")]
548+
public void TestSkinnedMeshExport(string fbxPath){
549+
fbxPath = FindPathInUnitTests (fbxPath);
550+
Assert.That (fbxPath, Is.Not.Null);
551+
552+
SkinnedMeshRenderer originalSkinnedMesh, exportedSkinnedMesh;
553+
ExportSkinnedMesh (fbxPath, out originalSkinnedMesh, out exportedSkinnedMesh);
534554

535555
Assert.IsTrue (originalSkinnedMesh.name == exportedSkinnedMesh.name ||
536556
(originalSkinnedMesh.transform.parent == null && exportedSkinnedMesh.transform.parent == null));
@@ -594,5 +614,68 @@ public void TestSkinnedMeshExport(){
594614
var expWeights = exportedMesh.boneWeights;
595615
Assert.IsNotNull (expWeights);
596616
}
617+
618+
[Test, TestCaseSource(typeof(SkinnedMeshTestDataClass), "TestCases2")]
619+
public void TestBoneWeightExport(string fbxPath){
620+
fbxPath = FindPathInUnitTests (fbxPath);
621+
Assert.That (fbxPath, Is.Not.Null);
622+
623+
SkinnedMeshRenderer originalSkinnedMesh, exportedSkinnedMesh;
624+
ExportSkinnedMesh (fbxPath, out originalSkinnedMesh, out exportedSkinnedMesh);
625+
626+
var origVerts = originalSkinnedMesh.sharedMesh.vertices;
627+
Assert.That (origVerts, Is.Not.Null);
628+
629+
var expVerts = exportedSkinnedMesh.sharedMesh.vertices;
630+
Assert.That (expVerts, Is.Not.Null);
631+
632+
var origBoneWeights = originalSkinnedMesh.sharedMesh.boneWeights;
633+
Assert.That (origBoneWeights, Is.Not.Null);
634+
Assert.That (origBoneWeights.Length, Is.GreaterThan (0));
635+
636+
var expBoneWeights = exportedSkinnedMesh.sharedMesh.boneWeights;
637+
Assert.That (expBoneWeights, Is.Not.Null);
638+
Assert.That (expBoneWeights.Length, Is.GreaterThan (0));
639+
640+
var origBones = originalSkinnedMesh.bones;
641+
var expBones = exportedSkinnedMesh.bones;
642+
643+
int comparisonCount = 0;
644+
int minVertCount = Mathf.Min (origVerts.Length, expVerts.Length);
645+
for(int i = 0; i < minVertCount; i++){
646+
for (int j = 0; j < minVertCount; j++) {
647+
if (origVerts [i] == expVerts [j]) {
648+
// compare bone weights
649+
var origBw = origBoneWeights[i];
650+
var expBw = expBoneWeights [j];
651+
652+
var indexMsg = "Bone index {0} doesn't match";
653+
var nameMsg = "bone names don't match";
654+
655+
Assert.That (expBw.boneIndex0, Is.EqualTo (origBw.boneIndex0), string.Format(indexMsg, 0));
656+
Assert.That (expBones[expBw.boneIndex0].name, Is.EqualTo (origBones[origBw.boneIndex0].name), nameMsg);
657+
658+
Assert.That (expBw.boneIndex1, Is.EqualTo (origBw.boneIndex1), string.Format(indexMsg, 1));
659+
Assert.That (expBones[expBw.boneIndex1].name, Is.EqualTo (origBones[origBw.boneIndex1].name), nameMsg);
660+
661+
Assert.That (expBw.boneIndex2, Is.EqualTo (origBw.boneIndex2), string.Format(indexMsg, 2));
662+
Assert.That (expBones[expBw.boneIndex2].name, Is.EqualTo (origBones[origBw.boneIndex2].name), nameMsg);
663+
664+
Assert.That (expBw.boneIndex3, Is.EqualTo (origBw.boneIndex3), string.Format(indexMsg, 3));
665+
Assert.That (expBones[expBw.boneIndex3].name, Is.EqualTo (origBones[origBw.boneIndex3].name), nameMsg);
666+
667+
var message = "Bone weight {0} doesn't match";
668+
Assert.That (expBw.weight0, Is.EqualTo (origBw.weight0).Within(0.001f), string.Format(message, 0));
669+
Assert.That (expBw.weight1, Is.EqualTo (origBw.weight1).Within(0.001f), string.Format(message, 1));
670+
Assert.That (expBw.weight2, Is.EqualTo (origBw.weight2).Within(0.001f), string.Format(message, 2));
671+
Assert.That (expBw.weight3, Is.EqualTo (origBw.weight3).Within(0.001f), string.Format(message, 3));
672+
673+
comparisonCount++;
674+
break;
675+
}
676+
}
677+
}
678+
Debug.LogWarningFormat ("Compared {0} out of a possible {1} bone weights", comparisonCount, minVertCount);
679+
}
597680
}
598681
}

0 commit comments

Comments
 (0)