mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-15 07:00:10 -05:00
Merge pull request #521 from ousttrue/rebase_runtimeMeshIntegrator
Squashed commit of the following:
This commit is contained in:
commit
a4824dcf60
|
|
@ -57,6 +57,11 @@ namespace UniGLTF
|
|||
}
|
||||
}
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get { return Path.GetFileName(Value); }
|
||||
}
|
||||
|
||||
public string FileNameWithoutExtension
|
||||
{
|
||||
get { return Path.GetFileNameWithoutExtension(Value); }
|
||||
|
|
|
|||
|
|
@ -168,11 +168,7 @@ namespace VRM
|
|||
|
||||
// save mesh to Assets
|
||||
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
|
||||
#else
|
||||
var prefab = PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
var prefab = SkinnedMeshUtility.GetPrefab(go);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
|
|
|
|||
|
|
@ -1,461 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// 複数のメッシュをまとめる
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public static class MeshIntegrator
|
||||
{
|
||||
const string MENU_KEY = SkinnedMeshUtility.MENU_KEY + "MeshIntegrator";
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
const string ASSET_WITH_BLENDSHAPE_SUFFIX = ".blendshape.asset";
|
||||
|
||||
[MenuItem(MENU_KEY, true, SkinnedMeshUtility.MENU_PRIORITY)]
|
||||
private static bool ExportValidate()
|
||||
{
|
||||
return Selection.activeObject != null && Selection.activeObject is GameObject;
|
||||
}
|
||||
|
||||
[MenuItem(MENU_KEY, false, SkinnedMeshUtility.MENU_PRIORITY)]
|
||||
private static void ExportFromMenu()
|
||||
{
|
||||
var go = Selection.activeObject as GameObject;
|
||||
|
||||
Integrate(go);
|
||||
}
|
||||
|
||||
public static SkinnedMeshRenderer Integrate(GameObject go)
|
||||
{
|
||||
var without_blendshape = _Integrate(go, false);
|
||||
if (without_blendshape == null)
|
||||
{
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// save mesh to Assets
|
||||
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
|
||||
#else
|
||||
var prefab = PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}_{2}{3}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
go.name,
|
||||
ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
assetPath = string.Format("Assets/{0}{1}", go.name, ASSET_SUFFIX);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(without_blendshape.sharedMesh, assetPath);
|
||||
return without_blendshape;
|
||||
}
|
||||
|
||||
struct SubMesh
|
||||
{
|
||||
public List<int> Indices;
|
||||
public Material Material;
|
||||
}
|
||||
|
||||
class BlendShape
|
||||
{
|
||||
public int VertexOffset;
|
||||
public string Name;
|
||||
public float FrameWeight;
|
||||
public Vector3[] Positions;
|
||||
public Vector3[] Normals;
|
||||
public Vector3[] Tangents;
|
||||
}
|
||||
|
||||
class Integrator
|
||||
{
|
||||
// public List<SkinnedMeshRenderer> Renderers { get; private set; }
|
||||
public List<Vector3> Positions { get; private set; }
|
||||
public List<Vector3> Normals { get; private set; }
|
||||
public List<Vector2> UV { get; private set; }
|
||||
public List<Vector4> Tangents { get; private set; }
|
||||
public List<BoneWeight> BoneWeights { get; private set; }
|
||||
|
||||
public List<SubMesh> SubMeshes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<Matrix4x4> BindPoses { get; private set; }
|
||||
public List<Transform> Bones { get; private set; }
|
||||
|
||||
public List<BlendShape> BlendShapes { get; private set; }
|
||||
public void AddBlendShapesToMesh(Mesh mesh)
|
||||
{
|
||||
Dictionary<string, BlendShape> map = new Dictionary<string, BlendShape>();
|
||||
|
||||
foreach (var x in BlendShapes)
|
||||
{
|
||||
BlendShape bs = null;
|
||||
if (!map.TryGetValue(x.Name, out bs))
|
||||
{
|
||||
bs = new BlendShape();
|
||||
bs.Positions = Positions.ToArray();
|
||||
bs.Normals = Normals.ToArray();
|
||||
bs.Tangents = Tangents.Select(y => (Vector3)y).ToArray();
|
||||
bs.Name = x.Name;
|
||||
bs.FrameWeight = x.FrameWeight;
|
||||
map.Add(x.Name, bs);
|
||||
}
|
||||
|
||||
var j = x.VertexOffset;
|
||||
for (int i = 0; i < x.Positions.Length; ++i, ++j)
|
||||
{
|
||||
bs.Positions[j] = x.Positions[i];
|
||||
bs.Normals[j] = x.Normals[i];
|
||||
bs.Tangents[j] = x.Tangents[i];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in map)
|
||||
{
|
||||
//Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key);
|
||||
mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight,
|
||||
kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents);
|
||||
}
|
||||
}
|
||||
|
||||
public Integrator()
|
||||
{
|
||||
// Renderers = new List<SkinnedMeshRenderer>();
|
||||
|
||||
Positions = new List<Vector3>();
|
||||
Normals = new List<Vector3>();
|
||||
UV = new List<Vector2>();
|
||||
Tangents = new List<Vector4>();
|
||||
BoneWeights = new List<BoneWeight>();
|
||||
|
||||
SubMeshes = new List<SubMesh>();
|
||||
|
||||
BindPoses = new List<Matrix4x4>();
|
||||
Bones = new List<Transform>();
|
||||
|
||||
BlendShapes = new List<BlendShape>();
|
||||
}
|
||||
|
||||
static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset)
|
||||
{
|
||||
if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset;
|
||||
if (bw.weight1 > 0) bw.boneIndex1 += boneIndexOffset;
|
||||
if (bw.weight2 > 0) bw.boneIndex2 += boneIndexOffset;
|
||||
if (bw.weight3 > 0) bw.boneIndex3 += boneIndexOffset;
|
||||
return bw;
|
||||
}
|
||||
|
||||
public void Push(MeshRenderer renderer)
|
||||
{
|
||||
var meshFilter = renderer.GetComponent<MeshFilter>();
|
||||
if (meshFilter == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh filter", renderer.name);
|
||||
return;
|
||||
}
|
||||
var mesh = meshFilter.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
||||
return;
|
||||
}
|
||||
|
||||
var indexOffset = Positions.Count;
|
||||
var boneIndexOffset = Bones.Count;
|
||||
|
||||
Positions.AddRange(mesh.vertices
|
||||
.Select(x => renderer.transform.TransformPoint(x))
|
||||
);
|
||||
Normals.AddRange(mesh.normals
|
||||
.Select(x => renderer.transform.TransformVector(x))
|
||||
);
|
||||
UV.AddRange(mesh.uv);
|
||||
Tangents.AddRange(mesh.tangents
|
||||
.Select(t =>
|
||||
{
|
||||
var v = renderer.transform.TransformVector(t.x, t.y, t.z);
|
||||
return new Vector4(v.x, v.y, v.z, t.w);
|
||||
})
|
||||
);
|
||||
|
||||
var self = renderer.transform;
|
||||
var bone = self.parent;
|
||||
if (bone == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} is root gameobject.", self.name);
|
||||
return;
|
||||
}
|
||||
var bindpose = bone.worldToLocalMatrix;
|
||||
|
||||
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length)
|
||||
.Select(x => new BoneWeight()
|
||||
{
|
||||
boneIndex0 = Bones.Count,
|
||||
weight0 = 1,
|
||||
})
|
||||
);
|
||||
|
||||
BindPoses.Add(bindpose);
|
||||
Bones.Add(bone);
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
||||
var mat = renderer.sharedMaterials[i];
|
||||
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
||||
if (sameMaterialSubMeshIndex >= 0)
|
||||
{
|
||||
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubMeshes.Add(new SubMesh
|
||||
{
|
||||
Indices = indices.ToList(),
|
||||
Material = mat,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(SkinnedMeshRenderer renderer)
|
||||
{
|
||||
var mesh = renderer.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Renderers.Add(renderer);
|
||||
|
||||
var indexOffset = Positions.Count;
|
||||
var boneIndexOffset = Bones.Count;
|
||||
|
||||
Positions.AddRange(mesh.vertices);
|
||||
Normals.AddRange(mesh.normals);
|
||||
UV.AddRange(mesh.uv);
|
||||
Tangents.AddRange(mesh.tangents);
|
||||
|
||||
if (mesh.vertexCount == mesh.boneWeights.Length)
|
||||
{
|
||||
BoneWeights.AddRange(mesh.boneWeights.Select(x => AddBoneIndexOffset(x, boneIndexOffset)).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => new BoneWeight()).ToArray());
|
||||
}
|
||||
|
||||
BindPoses.AddRange(mesh.bindposes);
|
||||
Bones.AddRange(renderer.bones);
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
||||
var mat = renderer.sharedMaterials[i];
|
||||
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
||||
if (sameMaterialSubMeshIndex >= 0)
|
||||
{
|
||||
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubMeshes.Add(new SubMesh
|
||||
{
|
||||
Indices = indices.ToList(),
|
||||
Material = mat,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mesh.blendShapeCount; ++i)
|
||||
{
|
||||
var positions = (Vector3[])mesh.vertices.Clone();
|
||||
var normals = (Vector3[])mesh.normals.Clone();
|
||||
var tangents = mesh.tangents.Select(x => (Vector3)x).ToArray();
|
||||
|
||||
mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents);
|
||||
BlendShapes.Add(new BlendShape
|
||||
{
|
||||
VertexOffset = indexOffset,
|
||||
FrameWeight = mesh.GetBlendShapeFrameWeight(i, 0),
|
||||
Name = mesh.GetBlendShapeName(i),
|
||||
Positions = positions,
|
||||
Normals = normals,
|
||||
Tangents = tangents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<Transform> Traverse(Transform parent)
|
||||
{
|
||||
if (parent.gameObject.activeSelf)
|
||||
{
|
||||
yield return parent;
|
||||
|
||||
foreach (Transform child in parent)
|
||||
{
|
||||
foreach (var x in Traverse(child))
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public IEnumerable<SkinnedMeshRenderer> EnumerateRenderer(Transform root, bool hasBlendShape)
|
||||
{
|
||||
foreach (var x in Traverse(root))
|
||||
{
|
||||
var renderer = x.GetComponent<SkinnedMeshRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
if (renderer.sharedMesh != null)
|
||||
{
|
||||
if (renderer.gameObject.activeSelf)
|
||||
{
|
||||
if (renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape)
|
||||
{
|
||||
yield return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<MeshRenderer> EnumerateMeshRenderer(Transform root)
|
||||
{
|
||||
foreach (var x in Traverse(root))
|
||||
{
|
||||
var renderer = x.GetComponent<MeshRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
var filter = x.GetComponent<MeshFilter>();
|
||||
if (filter != null && filter.sharedMesh != null && renderer.gameObject.activeSelf)
|
||||
{
|
||||
yield return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<Transform> Ancestors(Transform self)
|
||||
{
|
||||
yield return self;
|
||||
|
||||
if (self.parent != null)
|
||||
{
|
||||
foreach (var x in Ancestors(self.parent))
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SkinnedMeshRenderer _Integrate(GameObject go, bool hasBlendShape)
|
||||
{
|
||||
var meshNode = new GameObject();
|
||||
if (hasBlendShape)
|
||||
{
|
||||
meshNode.name = "MeshIntegrator(BlendShape)";
|
||||
}
|
||||
else
|
||||
{
|
||||
meshNode.name = "MeshIntegrator";
|
||||
}
|
||||
meshNode.transform.SetParent(go.transform, false);
|
||||
|
||||
var renderers = EnumerateRenderer(go.transform, hasBlendShape).ToArray();
|
||||
|
||||
// Root objectを選出する
|
||||
var root = renderers.Select(x => x.rootBone != null ? x.rootBone : x.transform)
|
||||
.Select(x => Ancestors(x).Reverse().ToArray())
|
||||
.Aggregate((a, b) =>
|
||||
{
|
||||
int i = 0;
|
||||
for(; i<a.Length && i<b.Length; ++i)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return a.Take(i).ToArray();
|
||||
})
|
||||
.Last()
|
||||
;
|
||||
Debug.LogFormat("root bone: {0}", root.name);
|
||||
|
||||
// レンダラから情報を集める
|
||||
var integrator = new Integrator();
|
||||
foreach(var x in renderers)
|
||||
{
|
||||
integrator.Push(x);
|
||||
}
|
||||
|
||||
foreach (var meshRenderer in EnumerateMeshRenderer(go.transform))
|
||||
{
|
||||
integrator.Push(meshRenderer);
|
||||
}
|
||||
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "integrated";
|
||||
|
||||
if (integrator.Positions.Count > ushort.MaxValue)
|
||||
{
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count);
|
||||
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
#else
|
||||
throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString()));
|
||||
#endif
|
||||
}
|
||||
|
||||
mesh.vertices = integrator.Positions.ToArray();
|
||||
mesh.normals = integrator.Normals.ToArray();
|
||||
mesh.uv = integrator.UV.ToArray();
|
||||
mesh.tangents = integrator.Tangents.ToArray();
|
||||
mesh.boneWeights = integrator.BoneWeights.ToArray();
|
||||
mesh.subMeshCount = integrator.SubMeshes.Count;
|
||||
for (var i = 0; i < integrator.SubMeshes.Count; ++i)
|
||||
{
|
||||
mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i);
|
||||
}
|
||||
mesh.bindposes = integrator.BindPoses.ToArray();
|
||||
|
||||
if (hasBlendShape)
|
||||
{
|
||||
integrator.AddBlendShapesToMesh(mesh);
|
||||
}
|
||||
|
||||
var integrated = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
integrated.sharedMesh = mesh;
|
||||
integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray();
|
||||
integrated.bones = integrator.Bones.ToArray();
|
||||
|
||||
return integrated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UniGLTF;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// 複数のメッシュをまとめる
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public static class MeshIntegratorEditor
|
||||
{
|
||||
const string MENU_KEY = "Assets/UnityEditorScripts/MeshIntegrator";
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
[MenuItem(MENU_KEY, true)]
|
||||
private static bool ExportValidate()
|
||||
{
|
||||
return Selection.activeObject != null &&
|
||||
Selection.activeObject is GameObject &&
|
||||
SkinnedMeshUtility.IsPrefab(Selection.activeObject);
|
||||
}
|
||||
|
||||
[MenuItem(MENU_KEY, false)]
|
||||
private static void ExportFromMenu()
|
||||
{
|
||||
var go = Selection.activeObject as GameObject;
|
||||
|
||||
Integrate(go);
|
||||
}
|
||||
|
||||
[MenuItem("Assets/fooa", false)]
|
||||
private static void Foo()
|
||||
{
|
||||
var go = Selection.activeObject as GameObject;
|
||||
|
||||
Debug.Log(SkinnedMeshUtility.IsPrefab(go));
|
||||
}
|
||||
|
||||
|
||||
public static List<MeshIntegratorUtility.MeshIntegrationResult> Integrate(GameObject prefab)
|
||||
{
|
||||
Undo.RecordObject(prefab, "Mesh Integration");
|
||||
var instance = SkinnedMeshUtility.InstantiatePrefab(prefab);
|
||||
|
||||
|
||||
var clips = new List<BlendShapeClip>();
|
||||
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
|
||||
if (proxy != null && proxy.BlendShapeAvatar != null)
|
||||
{
|
||||
clips = proxy.BlendShapeAvatar.Clips;
|
||||
}
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
Undo.RecordObject(clip, "Mesh Integration");
|
||||
}
|
||||
|
||||
// Backup Exists
|
||||
BackupVrmPrefab(prefab);
|
||||
|
||||
// Execute
|
||||
var results = MeshIntegratorUtility.Integrate(instance, clips);
|
||||
|
||||
foreach (var res in results)
|
||||
{
|
||||
if (res.IntegratedRenderer == null) continue;
|
||||
|
||||
SaveMeshAsset(res.IntegratedRenderer.sharedMesh, instance, res.IntegratedRenderer.gameObject.name);
|
||||
Undo.RegisterCreatedObjectUndo(res.IntegratedRenderer.gameObject, "Integrate Renderers");
|
||||
}
|
||||
|
||||
// destroy source renderers
|
||||
foreach (var res in results)
|
||||
{
|
||||
foreach (var renderer in res.SourceSkinnedMeshRenderers)
|
||||
{
|
||||
Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
|
||||
renderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
foreach (var renderer in res.SourceMeshRenderers)
|
||||
{
|
||||
Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
|
||||
renderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply to Prefab
|
||||
SkinnedMeshUtility.ApplyChangesToPrefab(instance);
|
||||
Object.DestroyImmediate(instance);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void BackupVrmPrefab(GameObject rootPrefab)
|
||||
{
|
||||
var proxy = rootPrefab.GetComponent<VRMBlendShapeProxy>();
|
||||
|
||||
var srcAvatar = proxy.BlendShapeAvatar;
|
||||
var dstAvatar = (BlendShapeAvatar) BackupAsset(srcAvatar, rootPrefab);
|
||||
|
||||
var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip) BackupAsset(x, rootPrefab));
|
||||
dstAvatar.Clips = clipMapper.Values.ToList();
|
||||
|
||||
var dstPrefab = BackupAsset(rootPrefab, rootPrefab);
|
||||
var dstInstance = SkinnedMeshUtility.InstantiatePrefab(dstPrefab);
|
||||
dstInstance.GetComponent<VRMBlendShapeProxy>().BlendShapeAvatar = dstAvatar;
|
||||
SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance);
|
||||
Object.DestroyImmediate(dstInstance);
|
||||
}
|
||||
|
||||
private static T BackupAsset<T>(T asset, GameObject rootPrefab) where T : UnityEngine.Object
|
||||
{
|
||||
var srcAssetPath = UnityPath.FromAsset(asset);
|
||||
var assetName = srcAssetPath.FileName;
|
||||
|
||||
var backupDir = "MeshIntegratorBackup";
|
||||
var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(backupDir);
|
||||
backupPath.EnsureFolder();
|
||||
var dstAssetPath = backupPath.Child(assetName);
|
||||
|
||||
AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value);
|
||||
return dstAssetPath.LoadAsset<T>();
|
||||
}
|
||||
|
||||
private static string GetRootPrefabPath(GameObject go)
|
||||
{
|
||||
var prefab = SkinnedMeshUtility.IsPrefab(go) ? go : SkinnedMeshUtility.GetPrefab(go);
|
||||
var assetPath = "";
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/", Path.GetDirectoryName(prefabPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
assetPath = string.Format("Assets/");
|
||||
}
|
||||
assetPath = assetPath.Replace(@"\", @"/");
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
private static void SaveMeshAsset(Mesh mesh, GameObject go, string name)
|
||||
{
|
||||
var assetPath = Path.Combine(GetRootPrefabPath(go), string.Format("{0}{1}",
|
||||
name,
|
||||
ASSET_SUFFIX
|
||||
));
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(mesh, assetPath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ namespace VRM
|
|||
{
|
||||
public class MeshIntegratorWizard : ScriptableWizard
|
||||
{
|
||||
const string MENU_KEY = "Assets/UnityEditorScripts/MeshIntegratorWizard";
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_root;
|
||||
|
||||
|
|
@ -56,9 +58,9 @@ namespace VRM
|
|||
MaterialList[] m_duplicateMaterials;
|
||||
|
||||
[Header("Result")]
|
||||
public Mesh integrated;
|
||||
public MeshIntegratorUtility.MeshIntegrationResult[] integrationResults;
|
||||
|
||||
[MenuItem(SkinnedMeshUtility.MENU_KEY + "MeshIntegrator Wizard", priority = SkinnedMeshUtility.MENU_PRIORITY)]
|
||||
[MenuItem(MENU_KEY)]
|
||||
static void CreateWizard()
|
||||
{
|
||||
ScriptableWizard.DisplayWizard<MeshIntegratorWizard>("MeshIntegrator", "Integrate and close window", "Integrate");
|
||||
|
|
@ -122,7 +124,7 @@ namespace VRM
|
|||
return;
|
||||
}
|
||||
|
||||
m_uniqueMaterials = MeshIntegrator.EnumerateRenderer(m_root.transform, false)
|
||||
m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false)
|
||||
.SelectMany(x => x.sharedMaterials)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
|
@ -148,13 +150,7 @@ namespace VRM
|
|||
return;
|
||||
}
|
||||
|
||||
var renderer = MeshIntegrator.Integrate(m_root);
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
integrated = renderer.sharedMesh;
|
||||
integrationResults = MeshIntegratorEditor.Integrate(m_root).ToArray();
|
||||
}
|
||||
|
||||
void OnWizardCreate()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,42 @@
|
|||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
public static class SkinnedMeshUtility
|
||||
{
|
||||
public const string MENU_KEY = "GameObject/UnityEditorScripts/";
|
||||
public const int MENU_PRIORITY = 11;
|
||||
|
||||
public static Object GetPrefab(GameObject instance)
|
||||
{
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
return PrefabUtility.GetCorrespondingObjectFromSource(instance);
|
||||
#else
|
||||
return PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsPrefab(Object instance)
|
||||
{
|
||||
return instance != null && PrefabUtility.GetPrefabType(instance) == PrefabType.Prefab;
|
||||
}
|
||||
|
||||
public static void ApplyChangesToPrefab(GameObject instance)
|
||||
{
|
||||
var prefab = GetPrefab(instance);
|
||||
if (prefab == null) return;
|
||||
|
||||
PrefabUtility.ReplacePrefab(instance, prefab, ReplacePrefabOptions.ConnectToPrefab);
|
||||
}
|
||||
|
||||
public static GameObject InstantiatePrefab(GameObject prefab)
|
||||
{
|
||||
if (!IsPrefab(prefab)) return null;
|
||||
|
||||
return (GameObject) PrefabUtility.InstantiatePrefab(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
253
Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs
Normal file
253
Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
public class MeshIntegrator
|
||||
{
|
||||
public struct SubMesh
|
||||
{
|
||||
public List<int> Indices;
|
||||
public Material Material;
|
||||
}
|
||||
|
||||
public class BlendShape
|
||||
{
|
||||
public int VertexOffset;
|
||||
public string Name;
|
||||
public float FrameWeight;
|
||||
public Vector3[] Positions;
|
||||
public Vector3[] Normals;
|
||||
public Vector3[] Tangents;
|
||||
}
|
||||
|
||||
// public List<SkinnedMeshRenderer> Renderers { get; private set; }
|
||||
public List<Vector3> Positions { get; private set; }
|
||||
public List<Vector3> Normals { get; private set; }
|
||||
public List<Vector2> UV { get; private set; }
|
||||
public List<Vector4> Tangents { get; private set; }
|
||||
public List<BoneWeight> BoneWeights { get; private set; }
|
||||
|
||||
public List<SubMesh> SubMeshes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<Matrix4x4> BindPoses { get; private set; }
|
||||
public List<Transform> Bones { get; private set; }
|
||||
|
||||
public List<BlendShape> BlendShapes { get; private set; }
|
||||
public void AddBlendShapesToMesh(Mesh mesh)
|
||||
{
|
||||
Dictionary<string, BlendShape> map = new Dictionary<string, BlendShape>();
|
||||
|
||||
foreach (var x in BlendShapes)
|
||||
{
|
||||
BlendShape bs = null;
|
||||
if (!map.TryGetValue(x.Name, out bs))
|
||||
{
|
||||
bs = new BlendShape();
|
||||
bs.Positions = new Vector3[Positions.Count];
|
||||
bs.Normals = new Vector3[Normals.Count];
|
||||
bs.Tangents = new Vector3[Tangents.Count];
|
||||
bs.Name = x.Name;
|
||||
bs.FrameWeight = x.FrameWeight;
|
||||
map.Add(x.Name, bs);
|
||||
}
|
||||
|
||||
var j = x.VertexOffset;
|
||||
for (int i = 0; i < x.Positions.Length; ++i, ++j)
|
||||
{
|
||||
bs.Positions[j] = x.Positions[i];
|
||||
bs.Normals[j] = x.Normals[i];
|
||||
bs.Tangents[j] = x.Tangents[i];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in map)
|
||||
{
|
||||
//Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key);
|
||||
mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight,
|
||||
kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents);
|
||||
}
|
||||
}
|
||||
|
||||
public MeshIntegrator()
|
||||
{
|
||||
// Renderers = new List<SkinnedMeshRenderer>();
|
||||
|
||||
Positions = new List<Vector3>();
|
||||
Normals = new List<Vector3>();
|
||||
UV = new List<Vector2>();
|
||||
Tangents = new List<Vector4>();
|
||||
BoneWeights = new List<BoneWeight>();
|
||||
|
||||
SubMeshes = new List<SubMesh>();
|
||||
|
||||
BindPoses = new List<Matrix4x4>();
|
||||
Bones = new List<Transform>();
|
||||
|
||||
BlendShapes = new List<BlendShape>();
|
||||
}
|
||||
|
||||
static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset)
|
||||
{
|
||||
if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset;
|
||||
if (bw.weight1 > 0) bw.boneIndex1 += boneIndexOffset;
|
||||
if (bw.weight2 > 0) bw.boneIndex2 += boneIndexOffset;
|
||||
if (bw.weight3 > 0) bw.boneIndex3 += boneIndexOffset;
|
||||
return bw;
|
||||
}
|
||||
|
||||
public void Push(MeshRenderer renderer)
|
||||
{
|
||||
var meshFilter = renderer.GetComponent<MeshFilter>();
|
||||
if (meshFilter == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh filter", renderer.name);
|
||||
return;
|
||||
}
|
||||
var mesh = meshFilter.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
||||
return;
|
||||
}
|
||||
|
||||
var indexOffset = Positions.Count;
|
||||
var boneIndexOffset = Bones.Count;
|
||||
|
||||
Positions.AddRange(mesh.vertices
|
||||
.Select(x => renderer.transform.TransformPoint(x))
|
||||
);
|
||||
Normals.AddRange(mesh.normals
|
||||
.Select(x => renderer.transform.TransformVector(x))
|
||||
);
|
||||
UV.AddRange(mesh.uv);
|
||||
Tangents.AddRange(mesh.tangents
|
||||
.Select(t =>
|
||||
{
|
||||
var v = renderer.transform.TransformVector(t.x, t.y, t.z);
|
||||
return new Vector4(v.x, v.y, v.z, t.w);
|
||||
})
|
||||
);
|
||||
|
||||
var self = renderer.transform;
|
||||
var bone = self.parent;
|
||||
if (bone == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} is root gameobject.", self.name);
|
||||
return;
|
||||
}
|
||||
var bindpose = bone.worldToLocalMatrix;
|
||||
|
||||
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length)
|
||||
.Select(x => new BoneWeight()
|
||||
{
|
||||
boneIndex0 = Bones.Count,
|
||||
weight0 = 1,
|
||||
})
|
||||
);
|
||||
|
||||
BindPoses.Add(bindpose);
|
||||
Bones.Add(bone);
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
||||
var mat = renderer.sharedMaterials[i];
|
||||
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
||||
if (sameMaterialSubMeshIndex >= 0)
|
||||
{
|
||||
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubMeshes.Add(new SubMesh
|
||||
{
|
||||
Indices = indices.ToList(),
|
||||
Material = mat,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Push(SkinnedMeshRenderer renderer)
|
||||
{
|
||||
var mesh = renderer.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Renderers.Add(renderer);
|
||||
|
||||
var indexOffset = Positions.Count;
|
||||
var boneIndexOffset = Bones.Count;
|
||||
|
||||
Positions.AddRange(mesh.vertices);
|
||||
Normals.AddRange(mesh.normals);
|
||||
UV.AddRange(mesh.uv);
|
||||
Tangents.AddRange(mesh.tangents);
|
||||
|
||||
if (mesh.vertexCount == mesh.boneWeights.Length)
|
||||
{
|
||||
BoneWeights.AddRange(mesh.boneWeights.Select(x => AddBoneIndexOffset(x, boneIndexOffset)).ToArray());
|
||||
BindPoses.AddRange(mesh.bindposes);
|
||||
Bones.AddRange(renderer.bones);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bone Count 0 の SkinnedMeshRenderer
|
||||
var rigidBoneWeight = new BoneWeight
|
||||
{
|
||||
boneIndex0 = boneIndexOffset,
|
||||
weight0 = 1f,
|
||||
};
|
||||
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => rigidBoneWeight).ToArray());
|
||||
BindPoses.Add(renderer.transform.localToWorldMatrix);
|
||||
Bones.Add(renderer.transform);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
||||
var mat = renderer.sharedMaterials[i];
|
||||
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
||||
if (sameMaterialSubMeshIndex >= 0)
|
||||
{
|
||||
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubMeshes.Add(new SubMesh
|
||||
{
|
||||
Indices = indices.ToList(),
|
||||
Material = mat,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mesh.blendShapeCount; ++i)
|
||||
{
|
||||
var positions = (Vector3[])mesh.vertices.Clone();
|
||||
var normals = (Vector3[])mesh.normals.Clone();
|
||||
var tangents = mesh.tangents.Select(x => (Vector3)x).ToArray();
|
||||
|
||||
mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents);
|
||||
BlendShapes.Add(new BlendShape
|
||||
{
|
||||
VertexOffset = indexOffset,
|
||||
FrameWeight = mesh.GetBlendShapeFrameWeight(i, 0),
|
||||
Name = mesh.GetBlendShapeName(i),
|
||||
Positions = positions,
|
||||
Normals = normals,
|
||||
Tangents = tangents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 547dd57b50bf4820a570336659345084
|
||||
timeCreated: 1560168946
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
public static class MeshIntegratorUtility
|
||||
{
|
||||
[System.Serializable]
|
||||
public class MeshIntegrationResult
|
||||
{
|
||||
public List<SkinnedMeshRenderer> SourceSkinnedMeshRenderers = new List<SkinnedMeshRenderer>();
|
||||
public List<MeshRenderer> SourceMeshRenderers = new List<MeshRenderer>();
|
||||
public SkinnedMeshRenderer IntegratedRenderer;
|
||||
}
|
||||
|
||||
public static bool IntegrateRuntime(GameObject vrmRootObject)
|
||||
{
|
||||
if (vrmRootObject == null) return false;
|
||||
var proxy = vrmRootObject.GetComponent<VRMBlendShapeProxy>();
|
||||
if (proxy == null) return false;
|
||||
var avatar = proxy.BlendShapeAvatar;
|
||||
if (avatar == null) return false;
|
||||
var clips = avatar.Clips;
|
||||
|
||||
var results = Integrate(vrmRootObject, clips);
|
||||
if (results.Any(x => x.IntegratedRenderer == null)) return false;
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
foreach (var renderer in result.SourceSkinnedMeshRenderers)
|
||||
{
|
||||
Object.Destroy(renderer);
|
||||
}
|
||||
|
||||
foreach (var renderer in result.SourceMeshRenderers)
|
||||
{
|
||||
Object.Destroy(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips)
|
||||
{
|
||||
var result = new List<MeshIntegratorUtility.MeshIntegrationResult>();
|
||||
|
||||
var withoutBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: false);
|
||||
if (withoutBlendShape.IntegratedRenderer != null)
|
||||
{
|
||||
result.Add(withoutBlendShape);
|
||||
}
|
||||
|
||||
var onlyBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: true);
|
||||
if (onlyBlendShape.IntegratedRenderer != null)
|
||||
{
|
||||
result.Add(onlyBlendShape);
|
||||
FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void FollowBlendshapeRendererChange(List<BlendShapeClip> clips, MeshIntegrationResult result, GameObject root)
|
||||
{
|
||||
if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return;
|
||||
|
||||
var rendererDict = result.SourceSkinnedMeshRenderers
|
||||
.ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x);
|
||||
|
||||
var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
|
||||
for (var i = 0; i < clip.Values.Length; ++i)
|
||||
{
|
||||
var val = clip.Values[i];
|
||||
if (rendererDict.ContainsKey(val.RelativePath))
|
||||
{
|
||||
var srcRenderer = rendererDict[val.RelativePath];
|
||||
var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index);
|
||||
var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name);
|
||||
|
||||
val.RelativePath = dstPath;
|
||||
val.Index = newIndex;
|
||||
}
|
||||
|
||||
clip.Values[i] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyBlendShapeRenderers)
|
||||
{
|
||||
var result = new MeshIntegrationResult();
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
#else
|
||||
return result;
|
||||
#endif
|
||||
|
||||
var meshNode = new GameObject();
|
||||
if (onlyBlendShapeRenderers)
|
||||
{
|
||||
meshNode.name = "MeshIntegrator(BlendShape)";
|
||||
}
|
||||
else
|
||||
{
|
||||
meshNode.name = "MeshIntegrator";
|
||||
}
|
||||
meshNode.transform.SetParent(go.transform, false);
|
||||
|
||||
// レンダラから情報を集める
|
||||
var integrator = new MeshIntegrator();
|
||||
|
||||
if (onlyBlendShapeRenderers)
|
||||
{
|
||||
foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, true))
|
||||
{
|
||||
integrator.Push(x);
|
||||
result.SourceSkinnedMeshRenderers.Add(x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, false))
|
||||
{
|
||||
integrator.Push(x);
|
||||
result.SourceSkinnedMeshRenderers.Add(x);
|
||||
}
|
||||
|
||||
foreach (var x in EnumerateMeshRenderer(go.transform))
|
||||
{
|
||||
integrator.Push(x);
|
||||
result.SourceMeshRenderers.Add(x);
|
||||
}
|
||||
}
|
||||
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "integrated";
|
||||
|
||||
if (integrator.Positions.Count > ushort.MaxValue)
|
||||
{
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count);
|
||||
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
#else
|
||||
throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString()));
|
||||
#endif
|
||||
}
|
||||
|
||||
mesh.vertices = integrator.Positions.ToArray();
|
||||
mesh.normals = integrator.Normals.ToArray();
|
||||
mesh.uv = integrator.UV.ToArray();
|
||||
mesh.tangents = integrator.Tangents.ToArray();
|
||||
mesh.boneWeights = integrator.BoneWeights.ToArray();
|
||||
mesh.subMeshCount = integrator.SubMeshes.Count;
|
||||
for (var i = 0; i < integrator.SubMeshes.Count; ++i)
|
||||
{
|
||||
mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i);
|
||||
}
|
||||
mesh.bindposes = integrator.BindPoses.ToArray();
|
||||
|
||||
if (onlyBlendShapeRenderers)
|
||||
{
|
||||
integrator.AddBlendShapesToMesh(mesh);
|
||||
}
|
||||
|
||||
var integrated = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
integrated.sharedMesh = mesh;
|
||||
integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray();
|
||||
integrated.bones = integrator.Bones.ToArray();
|
||||
result.IntegratedRenderer = integrated;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IEnumerable<SkinnedMeshRenderer> EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape)
|
||||
{
|
||||
foreach (var x in Traverse(root))
|
||||
{
|
||||
var renderer = x.GetComponent<SkinnedMeshRenderer>();
|
||||
if (renderer != null &&
|
||||
renderer.gameObject.activeInHierarchy &&
|
||||
renderer.sharedMesh != null &&
|
||||
renderer.enabled &&
|
||||
renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape)
|
||||
{
|
||||
yield return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<MeshRenderer> EnumerateMeshRenderer(Transform root)
|
||||
{
|
||||
foreach (var x in Traverse(root))
|
||||
{
|
||||
var renderer = x.GetComponent<MeshRenderer>();
|
||||
var filter = x.GetComponent<MeshFilter>();
|
||||
|
||||
if (renderer != null &&
|
||||
filter != null &&
|
||||
renderer.gameObject.activeInHierarchy &&
|
||||
filter.sharedMesh != null)
|
||||
{
|
||||
yield return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Transform> Traverse(Transform parent)
|
||||
{
|
||||
if (parent.gameObject.activeSelf)
|
||||
{
|
||||
yield return parent;
|
||||
|
||||
foreach (Transform child in parent)
|
||||
{
|
||||
foreach (var x in Traverse(child))
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a982d9d30c0145038245b0214dc2f2e4
|
||||
timeCreated: 1560190306
|
||||
Loading…
Reference in New Issue
Block a user