Skip to content

Commit

Permalink
Expose ExportMesh API using PrimKey array
Browse files Browse the repository at this point in the history
refactor, fix compilation errors, clean up PrimKey > UniquePrimitive rename
  • Loading branch information
robertlong authored and hybridherbst committed Aug 22, 2022
1 parent 773c384 commit 0059e09
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 111 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,6 @@ TestProjects/*/UserSettings
TestProjects/*/.vsconfig
TestProjects/*/Builds
TestProjects/*/Assets/WSATestCertificate.pfx*

# MacOS Metadata
.DS_Store
24 changes: 15 additions & 9 deletions UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/GLTFSceneExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ExportOptions
internal readonly GLTFSettings settings;

public ExportOptions() : this(GLTFSettings.GetOrCreateSettings()) { }

public ExportOptions(GLTFSettings settings)
{
if (!settings) settings = GLTFSettings.GetOrCreateSettings();
Expand Down Expand Up @@ -116,10 +116,15 @@ private struct ImageInfo
private const int GLTFHeaderSize = 12;
private const int SectionHeaderSize = 8;

protected struct PrimKey
/// <summary>
/// A Primitive is a combination of Mesh + Material(s). It also contains a reference to the original SkinnedMeshRenderer,
/// if any, since that's the only way to get the actual current weights to export a blend shape primitive.
/// </summary>
public struct UniquePrimitive
{
public bool Equals(PrimKey other)
public bool Equals(UniquePrimitive other)
{
if (!Equals(SkinnedMeshRenderer, other.SkinnedMeshRenderer)) return false;
if (!Equals(Mesh, other.Mesh)) return false;
if (Materials == null && other.Materials == null) return true;
if (!(Materials != null && other.Materials != null)) return false;
Expand All @@ -134,7 +139,7 @@ public bool Equals(PrimKey other)

public override bool Equals(object obj)
{
return obj is PrimKey other && Equals(other);
return obj is UniquePrimitive other && Equals(other);
}

public override int GetHashCode()
Expand All @@ -155,9 +160,10 @@ public override int GetHashCode()

public Mesh Mesh;
public Material[] Materials;
public SkinnedMeshRenderer SkinnedMeshRenderer; // needed for BlendShape export, since Unity stores the actually used blend shape weights on the renderer. see ExporterMeshes.ExportBlendShapes
}

private readonly Dictionary<PrimKey, MeshId> _primOwner = new Dictionary<PrimKey, MeshId>();
private readonly Dictionary<UniquePrimitive, MeshId> _primOwner = new Dictionary<UniquePrimitive, MeshId>();

#region Settings

Expand Down Expand Up @@ -700,12 +706,12 @@ private NodeId ExportNode(Transform nodeTransform)
_root.Nodes.Add(node);

// children that are primitives get put in a mesh
GameObject[] primitives, nonPrimitives;
FilterPrimitives(nodeTransform, out primitives, out nonPrimitives);
FilterPrimitives(nodeTransform, out GameObject[] primitives, out GameObject[] nonPrimitives);
if (primitives.Length > 0)
{
node.Mesh = ExportMesh(nodeTransform.name, primitives);
RegisterPrimitivesWithNode(node, primitives);
var uniquePrimitives = GetUniquePrimitivesFromGameObjects(primitives);
node.Mesh = ExportMesh(nodeTransform.name, uniquePrimitives);
RegisterPrimitivesWithNode(node, uniquePrimitives);
}

exportNodeMarker.End();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
using System.Linq;
using System.Reflection;
using GLTF.Schema;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityGLTF.Extensions;

#if UNITY_EDITOR // required for in-editor access to non-readable meshes
using UnityEditor;
#endif

namespace UnityGLTF
{
public partial class GLTFSceneExporter
Expand All @@ -28,50 +31,118 @@ private struct BlendShapeAccessors
private readonly Dictionary<Mesh, MeshAccessors> _meshToPrims = new Dictionary<Mesh, MeshAccessors>();
private readonly Dictionary<Mesh, BlendShapeAccessors> _meshToBlendShapeAccessors = new Dictionary<Mesh, BlendShapeAccessors>();

private void RegisterPrimitivesWithNode(Node node, IEnumerable<GameObject> primitives)
private void RegisterPrimitivesWithNode(Node node, List<UniquePrimitive> uniquePrimitives)
{
// associate unity meshes with gltf mesh id
foreach (var primKey in uniquePrimitives)
{
_primOwner[primKey] = node.Mesh;
}
}

private static List<UniquePrimitive> GetUniquePrimitivesFromGameObjects(IEnumerable<GameObject> primitives)
{
var primKeys = new List<UniquePrimitive>();

foreach (var prim in primitives)
{
var smr = prim.GetComponent<SkinnedMeshRenderer>();
if (smr != null)
Mesh meshObj = null;
SkinnedMeshRenderer smr = null;
var filter = prim.GetComponent<MeshFilter>();
if (filter)
{
_primOwner[new PrimKey { Mesh = smr.sharedMesh, Materials = smr.sharedMaterials }] = node.Mesh;
meshObj = filter.sharedMesh;
}
else
{
var filter = prim.GetComponent<MeshFilter>();
var renderer = prim.GetComponent<MeshRenderer>();
_primOwner[new PrimKey { Mesh = filter.sharedMesh, Materials = renderer.sharedMaterials }] = node.Mesh;
smr = prim.GetComponent<SkinnedMeshRenderer>();
if (smr)
{
meshObj = smr.sharedMesh;
}
}

if (!meshObj)
{
Debug.LogWarning($"MeshFilter.sharedMesh on GameObject:{prim.name} is missing, skipping", prim);
exportPrimitiveMarker.End();
return null;
}


#if UNITY_EDITOR
if (!MeshIsReadable(meshObj) && EditorUtility.IsPersistent(meshObj))
{
#if UNITY_2019_3_OR_NEWER
var assetPath = AssetDatabase.GetAssetPath(meshObj);
if(assetPath?.Length > 30) assetPath = "..." + assetPath.Substring(assetPath.Length - 30);
if(EditorUtility.DisplayDialog("Exporting mesh but mesh is not readable",
$"The mesh {meshObj.name} is not readable. Do you want to change its import settings and make it readable now?\n\n" + assetPath,
"Make it readable", "No, skip mesh",
DialogOptOutDecisionType.ForThisSession, MakeMeshReadableDialogueDecisionKey))
#endif
{
var path = AssetDatabase.GetAssetPath(meshObj);
var importer = AssetImporter.GetAtPath(path) as ModelImporter;
if (importer)
{
importer.isReadable = true;
importer.SaveAndReimport();
}
}
#if UNITY_2019_3_OR_NEWER
else
{
Debug.LogWarning($"The mesh {meshObj.name} is not readable. Skipping", null);
exportPrimitiveMarker.End();
return null;
}
#endif
}
#endif

if (Application.isPlaying && !MeshIsReadable(meshObj))
{
Debug.LogWarning($"The mesh {meshObj.name} is not readable. Skipping", null);
exportPrimitiveMarker.End();
return null;
}

var renderer = prim.GetComponent<MeshRenderer>();
if (!renderer) smr = prim.GetComponent<SkinnedMeshRenderer>();

if(!renderer && !smr)
{
Debug.LogWarning("GameObject does have neither renderer nor SkinnedMeshRenderer! " + prim.name, prim);
exportPrimitiveMarker.End();
return null;
}

var materialsObj = renderer ? renderer.sharedMaterials : smr.sharedMaterials;

var primKey = new UniquePrimitive();
primKey.Mesh = meshObj;
primKey.Materials = materialsObj;
primKey.SkinnedMeshRenderer = smr;

primKeys.Add(primKey);
}

return primKeys;
}

public MeshId ExportMesh(string name, GameObject[] primitives)
public MeshId ExportMesh(string name, List<UniquePrimitive> uniquePrimitives)
{
exportMeshMarker.Begin();

// check if this set of primitives is already a mesh
MeshId existingMeshId = null;
var key = new PrimKey();
foreach (var prim in primitives)

foreach (var prim in uniquePrimitives)
{
var smr = prim.GetComponent<SkinnedMeshRenderer>();
if (smr != null)
{
key.Mesh = smr.sharedMesh;
key.Materials = smr.sharedMaterials;
}
else
{
var filter = prim.GetComponent<MeshFilter>();
var renderer = prim.GetComponent<MeshRenderer>();
key.Mesh = filter.sharedMesh;
key.Materials = renderer.sharedMaterials;
}

MeshId tempMeshId;
if (_primOwner.TryGetValue(key, out tempMeshId) && (existingMeshId == null || tempMeshId == existingMeshId))
if (_primOwner.TryGetValue(prim, out tempMeshId) && (existingMeshId == null || tempMeshId == existingMeshId))
{
existingMeshId = tempMeshId;
}
Expand All @@ -96,10 +167,10 @@ public MeshId ExportMesh(string name, GameObject[] primitives)
mesh.Name = name;
}

mesh.Primitives = new List<MeshPrimitive>(primitives.Length);
foreach (var prim in primitives)
mesh.Primitives = new List<MeshPrimitive>(uniquePrimitives.Count);
foreach (var primKey in uniquePrimitives)
{
MeshPrimitive[] meshPrimitives = ExportPrimitive(prim, mesh);
MeshPrimitive[] meshPrimitives = ExportPrimitive(primKey, mesh);
if (meshPrimitives != null)
{
mesh.Primitives.AddRange(meshPrimitives);
Expand All @@ -124,87 +195,19 @@ public MeshId ExportMesh(string name, GameObject[] primitives)
}

// a mesh *might* decode to multiple prims if there are submeshes
private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh)
private MeshPrimitive[] ExportPrimitive(UniquePrimitive primKey, GLTFMesh mesh)
{
exportPrimitiveMarker.Begin();

Mesh meshObj = null;
SkinnedMeshRenderer smr = null;
var filter = gameObject.GetComponent<MeshFilter>();
if (filter)
{
meshObj = filter.sharedMesh;
}
else
{
smr = gameObject.GetComponent<SkinnedMeshRenderer>();
if (smr)
{
meshObj = smr.sharedMesh;
}
}
if (!meshObj)
{
Debug.LogWarning($"MeshFilter.sharedMesh on GameObject:{gameObject.name} is missing, skipping", gameObject);
exportPrimitiveMarker.End();
return null;
}

#if UNITY_EDITOR
if (!MeshIsReadable(meshObj) && EditorUtility.IsPersistent(meshObj))
{
#if UNITY_2019_3_OR_NEWER
var assetPath = AssetDatabase.GetAssetPath(meshObj);
if(assetPath?.Length > 30) assetPath = "..." + assetPath.Substring(assetPath.Length - 30);
if(EditorUtility.DisplayDialog("Exporting mesh but mesh is not readable",
$"The mesh {meshObj.name} is not readable. Do you want to change its import settings and make it readable now?\n\n" + assetPath,
"Make it readable", "No, skip mesh",
DialogOptOutDecisionType.ForThisSession, MakeMeshReadableDialogueDecisionKey))
#endif
{
var path = AssetDatabase.GetAssetPath(meshObj);
var importer = AssetImporter.GetAtPath(path) as ModelImporter;
if (importer)
{
importer.isReadable = true;
importer.SaveAndReimport();
}
}
#if UNITY_2019_3_OR_NEWER
else
{
Debug.LogWarning($"The mesh {meshObj.name} is not readable. Skipping", null);
exportPrimitiveMarker.End();
return null;
}
#endif
}
#endif

if (Application.isPlaying && !MeshIsReadable(meshObj))
{
Debug.LogWarning($"The mesh {meshObj.name} is not readable. Skipping", null);
exportPrimitiveMarker.End();
return null;
}

var renderer = gameObject.GetComponent<MeshRenderer>();
if (!renderer) smr = gameObject.GetComponent<SkinnedMeshRenderer>();

if(!renderer && !smr)
{
Debug.LogWarning("GameObject does have neither renderer nor SkinnedMeshRenderer! " + gameObject.name, gameObject);
exportPrimitiveMarker.End();
return null;
}
var materialsObj = renderer ? renderer.sharedMaterials : smr.sharedMaterials;
Mesh meshObj = primKey.Mesh;
Material[] materialsObj = primKey.Materials;

var prims = new MeshPrimitive[meshObj.subMeshCount];
List<MeshPrimitive> nonEmptyPrims = null;
var vertices = meshObj.vertices;
if (vertices.Length < 1)
{
Debug.LogWarning("MeshFilter does not contain any vertices, won't export: " + gameObject.name, gameObject);
Debug.LogWarning("MeshFilter does not contain any vertices, won't export: " + meshObj.name, meshObj);
exportPrimitiveMarker.End();
return null;
}
Expand Down Expand Up @@ -285,7 +288,7 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh)

primitive.Material = null;

ExportBlendShapes(smr, meshObj, submesh, primitive, mesh);
ExportBlendShapes(primKey.SkinnedMeshRenderer, meshObj, submesh, primitive, mesh);

accessors.subMeshPrimitives.Add(submesh, primitive);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private void ExportSkinFromNode(Transform transform)
{
exportSkinFromNodeMarker.Begin();

PrimKey key = new PrimKey();
UniquePrimitive key = new UniquePrimitive();
UnityEngine.Mesh mesh = GetMeshFromGameObject(transform.gameObject);
key.Mesh = mesh;
key.Materials = GetMaterialsFromGameObject(transform.gameObject);
Expand Down

0 comments on commit 0059e09

Please sign in to comment.