Merge pull request #523 from ousttrue/update_meshutility

MeshIntegrator と MeshNormalizer を MeshUtility に移動
This commit is contained in:
ousttrue 2020-08-26 14:12:50 +09:00 committed by GitHub
commit da5406aefa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 920 additions and 450 deletions

View File

@ -6,7 +6,7 @@ using UnityEditor;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
[CustomPropertyDrawer(typeof(BoneMeshEraser.EraseBone))]
public class EraseBoneDrawer : PropertyDrawer
@ -51,7 +51,7 @@ namespace VRM
[SerializeField]
BoneMeshEraser.EraseBone[] m_eraseBones;
[MenuItem(SkinnedMeshUtility.MENU_KEY + "BoneMeshEraser Wizard", priority = SkinnedMeshUtility.MENU_PRIORITY)]
[MenuItem(MeshUtility.MENU_PARENT + "BoneMeshEraser Wizard", priority = MeshUtility.MENU_PRIORITY)]
static void CreateWizard()
{
ScriptableWizard.DisplayWizard<BoneMeshEraserWizard>("BoneMeshEraser", "Erase triangles by bone", "Erase");
@ -143,7 +143,7 @@ namespace VRM
var bones = m_skinnedMesh.bones;
var eraseBones = m_eraseBones
.Where(x => x.Erase)
.Select(x => bones.IndexOf(x.Bone))
.Select(x => Array.IndexOf(bones, x.Bone))
.ToArray();
var meshNode = new GameObject("BoneMeshEraser");
@ -168,7 +168,7 @@ namespace VRM
// save mesh to Assets
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
var prefab = SkinnedMeshUtility.GetPrefab(go);
var prefab = MeshUtility.GetPrefab(go);
if (prefab != null)
{
var prefabPath = AssetDatabase.GetAssetPath(prefab);

View File

@ -1,6 +1,8 @@
{
"name": "MeshUtility",
"references": [],
"name": "MeshUtility.Editor",
"references": [
"MeshUtility"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"

View File

@ -6,12 +6,24 @@ using UnityEditor;
namespace MeshUtility
{
public class MeshUtility : MonoBehaviour
public class MeshUtility
{
public const string MENU_PARENT = "Mesh Utility/";
public const int MENU_PRIORITY = 11;
private const string ASSET_SUFFIX = ".mesh.asset";
private const string MENU_NAME = "Mesh Utility/Separate Skinned Meshes Contained BlendShape";
private const string MENU_NAME = MENU_PARENT + "Separate Skinned Meshes Contained BlendShape";
private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero;
public static Object GetPrefab(GameObject instance)
{
#if UNITY_2018_2_OR_NEWER
return PrefabUtility.GetCorrespondingObjectFromSource(instance);
#else
return PrefabUtility.GetPrefabParent(go);
#endif
}
private enum BlendShapeLogic
{
WithBlendShape,
@ -51,7 +63,7 @@ namespace MeshUtility
private static void SeparationProcessing(GameObject go)
{
var outputObject = Instantiate(go);
var outputObject = GameObject.Instantiate(go);
var skinnedMeshRenderers = outputObject.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
@ -127,7 +139,7 @@ namespace MeshUtility
// put the mesh without BlendShape in a new SkinnedMeshRenderer
var srcGameObject = skinnedMeshRendererInput.gameObject;
var srcTransform = skinnedMeshRendererInput.transform.parent;
var targetObjectForMeshWithoutBS = Instantiate(srcGameObject);
var targetObjectForMeshWithoutBS = GameObject.Instantiate(srcGameObject);
targetObjectForMeshWithoutBS.name = srcGameObject.name + "_WithoutBlendShape";
targetObjectForMeshWithoutBS.transform.SetParent(srcTransform);
var skinnedMeshRendererWithoutBS = targetObjectForMeshWithoutBS.GetComponent<SkinnedMeshRenderer>();

View File

@ -2,7 +2,9 @@
Mesh processing tool in Unity platform.
## MeshSeparator
## Utilities
### MeshSeparator
Separate the target mesh into different categories based on given conditions.
@ -10,6 +12,15 @@ Currently support BlendShape mesh separation. See [documentation](Documentation/
<img src="Documentation/images/blendshape_separator.jpg" width="300">
### MeshIntegrator
Integrate all mesh in hierarchy.
### MeshNormalizer
Bake hierarchy. This is VRM normalize backend.
MeshNormalizer can do blendShape bake.
## Import MeshUtility
There are two ways to import MeshUtility into a Unity project.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fc6ddf08077eac64fb9e33738e1c314d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,16 +1,16 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UniGLTF;
// using UniGLTF;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VRM
namespace MeshUtility
{
[DisallowMultipleComponent]
public class VRMBindposeGizmo : MonoBehaviour
public class BindposeGizmo : MonoBehaviour
{
[SerializeField]
Mesh m_target;

View File

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
public static class BoneMeshEraser
{
@ -151,18 +150,6 @@ namespace VRM
return mesh;
}
public static int IndexOf(this Transform[] list, Transform target)
{
for (int i = 0; i < list.Length; ++i)
{
if (list[i] == target)
{
return i;
}
}
return -1;
}
public static IEnumerable<Transform> Ancestor(this Transform t)
{
yield return t;

View File

@ -1,16 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UniHumanoid;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
public static class BoneNormalizer
{
public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary<Transform, Transform> boneMap);
static (GameObject, Dictionary<Transform, Transform>) NormalizeHierarchy(GameObject go, CreateAvatarFunc createAvatar)
{
var boneMap = new Dictionary<Transform, Transform>();
//
// 回転・スケールの無いヒエラルキーをコピーする
//
var normalized = new GameObject(go.name + "(normalized)");
normalized.transform.position = go.transform.position;
CopyAndBuild(go.transform, normalized.transform, boneMap);
//
// 新しいヒエラルキーからAvatarを作る
//
{
var animator = normalized.AddComponent<Animator>();
var avatar = createAvatar(go, normalized, boneMap);
avatar.name = go.name + ".normalized";
animator.avatar = avatar;
}
return (normalized, boneMap);
}
/// <summary>
/// 回転とスケールを除去したヒエラルキーをコピーする
/// 回転とスケールを除去したヒエラルキーをコピーする
/// </summary>
/// <param name="src"></param>
/// <param name="dst"></param>
@ -31,100 +56,6 @@ namespace VRM
}
}
static IEnumerable<Transform> Traverse(this Transform t)
{
yield return t;
foreach (Transform child in t)
{
foreach (var x in child.Traverse())
{
yield return x;
}
}
}
static void EnforceTPose(GameObject go)
{
var animator = go.GetComponent<Animator>();
if (animator == null)
{
throw new ArgumentException("Animator with avatar is required");
}
var avatar = animator.avatar;
if (avatar == null)
{
throw new ArgumentException("avatar is required");
}
if (!avatar.isValid)
{
throw new ArgumentException("invalid avatar");
}
if (!avatar.isHuman)
{
throw new ArgumentException("avatar is not human");
}
HumanPoseTransfer.SetTPose(avatar, go.transform);
}
static GameObject NormalizeHierarchy(GameObject go, Dictionary<Transform, Transform> boneMap)
{
//
// 回転・スケールの無いヒエラルキーをコピーする
//
var normalized = new GameObject(go.name + "(normalized)");
normalized.transform.position = go.transform.position;
CopyAndBuild(go.transform, normalized.transform, boneMap);
//
// 新しいヒエラルキーからAvatarを作る
//
{
var src = go.GetComponent<Animator>();
var srcHumanBones = Enum.GetValues(typeof(HumanBodyBones))
.Cast<HumanBodyBones>()
.Where(x => x != HumanBodyBones.LastBone)
.Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
.Where(x => x.Value != null)
;
var map =
srcHumanBones
.Where(x => boneMap.ContainsKey(x.Value))
.ToDictionary(x => x.Key, x => boneMap[x.Value])
;
var animator = normalized.AddComponent<Animator>();
var vrmHuman = go.GetComponent<VRMHumanoidDescription>();
var avatarDescription = AvatarDescription.Create();
if (vrmHuman != null && vrmHuman.Description != null)
{
avatarDescription.armStretch = vrmHuman.Description.armStretch;
avatarDescription.legStretch = vrmHuman.Description.legStretch;
avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist;
avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist;
avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist;
avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist;
avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing;
avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF;
}
avatarDescription.SetHumanBones(map);
var avatar = avatarDescription.CreateAvatar(normalized.transform);
avatar.name = go.name + ".normalized";
animator.avatar = avatar;
var humanPoseTransfer = normalized.AddComponent<HumanPoseTransfer>();
humanPoseTransfer.Avatar = avatar;
}
return normalized;
}
class BlendShapeReport
{
string m_name;
@ -233,7 +164,7 @@ namespace VRM
if (boneMap.TryGetValue(srcBone, out Transform dstBone))
{
// 対応するボーンが存在する
var dstIndex = dstBones.IndexOf(dstBone);
var dstIndex = Array.IndexOf(dstBones, dstBone);
if (dstIndex == -1)
{
// ありえない。バグ
@ -539,45 +470,20 @@ namespace VRM
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
}
public struct NormalizedResult
{
public GameObject Root;
public Dictionary<Transform, Transform> BoneMap;
}
/// <summary>
/// モデルの正規化を実行する
/// 回転とスケールを除去したヒエラルキーのコピーを作成する(MeshをBakeする)
/// </summary>
/// <param name="go">対象モデルのルート</param>
/// <param name="forceTPose">強制的にT-Pose化するか</param>
/// <returns>正規化済みのモデル</returns>
public static GameObject Execute(GameObject go, bool forceTPose, bool clearBlendShapeBeforeNormalize)
/// <param name="go">対象のヒエラルキーのルート</param>
/// <param name="clearBlendShapeBeforeNormalize">BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する</param>
/// <param name="createAvatar">Avatarを作る関数</param>
/// <returns></returns>
public static (GameObject, Dictionary<Transform, Transform>) Execute(GameObject go,
bool clearBlendShapeBeforeNormalize, CreateAvatarFunc createAvatar)
{
Dictionary<Transform, Transform> boneMap = new Dictionary<Transform, Transform>();
//
// T-Poseにする
//
if (forceTPose)
{
var hips = go.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
var hipsPosition = hips.position;
var hipsRotation = hips.rotation;
try
{
EnforceTPose(go);
}
finally
{
hips.position = hipsPosition; // restore hipsPosition
hips.rotation = hipsRotation;
}
}
//
// 正規化されたヒエラルキーを作る
//
var normalized = NormalizeHierarchy(go, boneMap);
var (normalized, boneMap) = NormalizeHierarchy(go, createAvatar);
//
// 各メッシュから回転・スケールを取り除いてBinding行列を再計算する
@ -595,137 +501,7 @@ namespace VRM
NormalizeNoneSkinnedMesh(src, dst);
}
CopyVRMComponents(go, normalized, boneMap);
// return new NormalizedResult
// {
// Root = normalized,
// BoneMap = boneMap
// };
return normalized;
}
/// <summary>
/// VRMを構成するコンポーネントをコピーする。
/// </summary>
/// <param name="go">コピー元</param>
/// <param name="root">コピー先</param>
/// <param name="map">コピー元とコピー先の対応関係</param>
static void CopyVRMComponents(GameObject go, GameObject root,
Dictionary<Transform, Transform> map)
{
{
// blendshape
var src = go.GetComponent<VRMBlendShapeProxy>();
if (src != null)
{
var dst = root.AddComponent<VRMBlendShapeProxy>();
dst.BlendShapeAvatar = src.BlendShapeAvatar;
}
}
{
var secondary = go.transform.Find("secondary");
if (secondary == null)
{
secondary = go.transform;
}
var dstSecondary = root.transform.Find("secondary");
if (dstSecondary == null)
{
dstSecondary = new GameObject("secondary").transform;
dstSecondary.SetParent(root.transform, false);
}
// 揺れモノ
foreach (var src in go.transform.GetComponentsInChildren<VRMSpringBoneColliderGroup>())
{
var dst = map[src.transform];
var dstColliderGroup = dst.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
dstColliderGroup.Colliders = src.Colliders.Select(y =>
{
var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset));
return new VRMSpringBoneColliderGroup.SphereCollider
{
Offset = offset,
Radius = y.Radius
};
}).ToArray();
}
foreach (var src in go.transform.GetComponentsInChildren<VRMSpringBone>())
{
// Copy VRMSpringBone
var dst = dstSecondary.gameObject.AddComponent<VRMSpringBone>();
dst.m_comment = src.m_comment;
dst.m_stiffnessForce = src.m_stiffnessForce;
dst.m_gravityPower = src.m_gravityPower;
dst.m_gravityDir = src.m_gravityDir;
dst.m_dragForce = src.m_dragForce;
if (src.m_center != null)
{
dst.m_center = map[src.m_center];
}
dst.RootBones = src.RootBones.Select(x => map[x]).ToList();
dst.m_hitRadius = src.m_hitRadius;
if (src.ColliderGroups != null)
{
dst.ColliderGroups = src.ColliderGroups
.Select(x => map[x.transform].GetComponent<VRMSpringBoneColliderGroup>()).ToArray();
}
}
}
#pragma warning disable 0618
{
// meta(obsolete)
var src = go.GetComponent<VRMMetaInformation>();
if (src != null)
{
src.CopyTo(root);
}
}
#pragma warning restore 0618
{
// meta
var src = go.GetComponent<VRMMeta>();
if (src != null)
{
var dst = root.AddComponent<VRMMeta>();
dst.Meta = src.Meta;
}
}
{
// firstPerson
var src = go.GetComponent<VRMFirstPerson>();
if (src != null)
{
src.CopyTo(root, map);
}
}
{
// humanoid
var dst = root.AddComponent<VRMHumanoidDescription>();
var src = go.GetComponent<VRMHumanoidDescription>();
if (src != null)
{
dst.Avatar = src.Avatar;
dst.Description = src.Description;
}
else
{
var animator = go.GetComponent<Animator>();
if (animator != null)
{
dst.Avatar = animator.avatar;
}
}
}
return (normalized, boneMap);
}
}
}

View File

@ -2,7 +2,7 @@
using System.Linq;
namespace VRM
namespace MeshUtility
{
public static class MeshExtensions
{

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using UnityEngine;
namespace MeshUtility
{
[System.Serializable]
public class MeshIntegrationResult
{
public List<SkinnedMeshRenderer> SourceSkinnedMeshRenderers = new List<SkinnedMeshRenderer>();
public List<MeshRenderer> SourceMeshRenderers = new List<MeshRenderer>();
public SkinnedMeshRenderer IntegratedRenderer;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1c66a21d479b3e4a92eedd622d27f4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
public class MeshIntegrator
{

View File

@ -1,108 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
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)
/// <summary>
/// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する
/// </summary>
/// <param name="go"></param>
/// <param name="onlyBlendShapeRenderers">BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer</param>
/// <returns></returns>
public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers)
{
var result = new MeshIntegrationResult();
#if UNITY_2017_3_OR_NEWER
#else
return result;
#endif
var meshNode = new GameObject();
if (onlyBlendShapeRenderers)
{
@ -115,7 +28,7 @@ namespace VRM
meshNode.transform.SetParent(go.transform, false);
// レンダラから情報を集める
var integrator = new MeshIntegrator();
var integrator = new MeshUtility.MeshIntegrator();
if (onlyBlendShapeRenderers)
{
@ -145,12 +58,8 @@ namespace VRM
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();
@ -175,10 +84,10 @@ namespace VRM
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))
@ -201,7 +110,7 @@ namespace VRM
{
var renderer = x.GetComponent<MeshRenderer>();
var filter = x.GetComponent<MeshFilter>();
if (renderer != null &&
filter != null &&
renderer.gameObject.activeInHierarchy &&

View File

@ -0,0 +1,3 @@
{
"name": "MeshUtility"
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 71ab1919192903d44971eedbc26b24d1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,319 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace MeshUtility
{
public struct PosRot
{
public Vector3 Position;
public Quaternion Rotation;
public static PosRot FromGlobalTransform(Transform t)
{
return new PosRot
{
Position = t.position,
Rotation = t.rotation,
};
}
}
public class BlendShape
{
public string Name;
public BlendShape(string name)
{
Name = name;
}
public List<Vector3> Positions = new List<Vector3>();
public List<Vector3> Normals = new List<Vector3>();
public List<Vector3> Tangents = new List<Vector3>();
}
public static class UnityExtensions
{
public static Vector4 ReverseZ(this Vector4 v)
{
return new Vector4(v.x, v.y, -v.z, v.w);
}
public static Vector3 ReverseZ(this Vector3 v)
{
return new Vector3(v.x, v.y, -v.z);
}
[Obsolete]
public static Vector2 ReverseY(this Vector2 v)
{
return new Vector2(v.x, -v.y);
}
public static Vector2 ReverseUV(this Vector2 v)
{
return new Vector2(v.x, 1.0f - v.y);
}
public static Quaternion ReverseZ(this Quaternion q)
{
float angle;
Vector3 axis;
q.ToAngleAxis(out angle, out axis);
return Quaternion.AngleAxis(-angle, ReverseZ(axis));
}
public static Matrix4x4 Matrix4x4FromColumns(Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3)
{
#if UNITY_2017_1_OR_NEWER
return new Matrix4x4(c0, c1, c2, c3);
#else
var m = default(Matrix4x4);
m.SetColumn(0, c0);
m.SetColumn(1, c1);
m.SetColumn(2, c2);
m.SetColumn(3, c3);
return m;
#endif
}
public static Matrix4x4 Matrix4x4FromRotation(Quaternion q)
{
#if UNITY_2017_1_OR_NEWER
return Matrix4x4.Rotate(q);
#else
var m = default(Matrix4x4);
m.SetTRS(Vector3.zero, q, Vector3.one);
return m;
#endif
}
public static Matrix4x4 ReverseZ(this Matrix4x4 m)
{
m.SetTRS(m.ExtractPosition().ReverseZ(), m.ExtractRotation().ReverseZ(), m.ExtractScale());
return m;
}
public static Matrix4x4 MatrixFromArray(float[] values)
{
var m = new Matrix4x4();
m.m00 = values[0];
m.m10 = values[1];
m.m20 = values[2];
m.m30 = values[3];
m.m01 = values[4];
m.m11 = values[5];
m.m21 = values[6];
m.m31 = values[7];
m.m02 = values[8];
m.m12 = values[9];
m.m22 = values[10];
m.m32 = values[11];
m.m03 = values[12];
m.m13 = values[13];
m.m23 = values[14];
m.m33 = values[15];
return m;
}
// https://forum.unity.com/threads/how-to-assign-matrix4x4-to-transform.121966/
public static Quaternion ExtractRotation(this Matrix4x4 matrix)
{
Vector3 forward;
forward.x = matrix.m02;
forward.y = matrix.m12;
forward.z = matrix.m22;
Vector3 upwards;
upwards.x = matrix.m01;
upwards.y = matrix.m11;
upwards.z = matrix.m21;
return Quaternion.LookRotation(forward, upwards);
}
public static Vector3 ExtractPosition(this Matrix4x4 matrix)
{
Vector3 position;
position.x = matrix.m03;
position.y = matrix.m13;
position.z = matrix.m23;
return position;
}
public static Vector3 ExtractScale(this Matrix4x4 matrix)
{
Vector3 scale;
scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude;
scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude;
scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude;
return scale;
}
public static string RelativePathFrom(this Transform self, Transform root)
{
var path = new List<String>();
for (var current = self; current != null; current = current.parent)
{
if (current == root)
{
return String.Join("/", path.ToArray());
}
path.Insert(0, current.name);
}
throw new Exception("no RelativePath");
}
public static Transform GetChildByName(this Transform self, string childName)
{
foreach (Transform child in self)
{
if (child.name == childName)
{
return child;
}
}
throw new KeyNotFoundException();
}
public static Transform GetFromPath(this Transform self, string path)
{
var current = self;
var split = path.Split('/');
foreach (var childName in split)
{
current = current.GetChildByName(childName);
}
return current;
}
public static IEnumerable<Transform> GetChildren(this Transform self)
{
foreach (Transform child in self)
{
yield return child;
}
}
public static IEnumerable<Transform> Traverse(this Transform t)
{
yield return t;
foreach (Transform x in t)
{
foreach (Transform y in x.Traverse())
{
yield return y;
}
}
}
[Obsolete("Use FindDescendant(name)")]
public static Transform FindDescenedant(this Transform t, string name)
{
return FindDescendant(t, name);
}
public static Transform FindDescendant(this Transform t, string name)
{
return t.Traverse().First(x => x.name == name);
}
public static IEnumerable<Transform> Ancestors(this Transform t)
{
yield return t;
if (t.parent != null)
{
foreach (Transform x in t.parent.Ancestors())
{
yield return x;
}
}
}
public static float[] ToArray(this Quaternion q)
{
return new float[] { q.x, q.y, q.z, q.w };
}
public static float[] ToArray(this Vector3 v)
{
return new float[] { v.x, v.y, v.z };
}
public static float[] ToArray(this Vector4 v)
{
return new float[] { v.x, v.y, v.z, v.w };
}
public static float[] ToArray(this Color c)
{
return new float[] { c.r, c.g, c.b, c.a };
}
public static void ReverseZRecursive(this Transform root)
{
var globalMap = root.Traverse().ToDictionary(x => x, x => PosRot.FromGlobalTransform(x));
foreach (var x in root.Traverse())
{
x.position = globalMap[x].Position.ReverseZ();
x.rotation = globalMap[x].Rotation.ReverseZ();
}
}
public static Mesh GetSharedMesh(this Transform t)
{
var meshFilter = t.GetComponent<MeshFilter>();
if (meshFilter != null)
{
return meshFilter.sharedMesh;
}
var skinnedMeshRenderer = t.GetComponent<SkinnedMeshRenderer>();
if (skinnedMeshRenderer != null)
{
return skinnedMeshRenderer.sharedMesh;
}
return null;
}
public static Material[] GetSharedMaterials(this Transform t)
{
var renderer = t.GetComponent<Renderer>();
if (renderer != null)
{
return renderer.sharedMaterials;
}
return new Material[] { };
}
public static bool Has<T>(this Transform transform, T t) where T : Component
{
return transform.GetComponent<T>() == t;
}
public static T GetOrAddComponent<T>(this GameObject go) where T : Component
{
var c = go.GetComponent<T>();
if (c != null)
{
return c;
}
return go.AddComponent<T>();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5294813527b3278458026afc820dd63d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +1,14 @@
{
"name" : "com.vrmc.meshutility",
"displayName" : "MeshUtility",
"version" : "0.0.1",
"unity" : "2018.4",
"description" : "MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: <https://github.com/vrm-c/UniVRM/tree/master/Assets/MeshUtility>",
"keywords" : [ "mesh" ],
"author" : { "name": "VRM Consortium" }
}
{
"name": "com.vrmc.meshutility",
"version": "0.59.0",
"displayName": "MeshUtility",
"unity": "2018.4",
"description": "MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: <https://github.com/vrm-c/UniVRM/tree/master/Assets/MeshUtility>",
"keywords": [
"mesh"
],
"author": {
"name": "VRM Consortium"
}
}

View File

@ -4,7 +4,8 @@
"VRM",
"VRM.Samples",
"UniVRM.Editor.Tests",
"UniJSON"
"UniJSON",
"MeshUtility"
],
"optionalUnityReferences": [
"TestAssemblies"
@ -13,5 +14,9 @@
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@ -3,7 +3,7 @@ using System.IO;
using UniGLTF;
using UniJSON;
using UnityEngine;
using MeshUtility;
namespace VRM.Samples
{

View File

@ -1,7 +1,5 @@
fileFormatVersion: 2
guid: 5294813527b3278458026afc820dd63d
timeCreated: 1515606586
licenseType: Free
guid: bbcda9130f35803408b216dbc6be05b7
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -131,9 +131,18 @@ namespace VRM.DevOnly.PackageExporter
}
[MenuItem(VRMVersion.MENU + "/Export unitypackage")]
static void CreateUnityPackageWithoutBuild()
{
var folder = GetProjectRoot();
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
CreateUnityPackages(folder);
}
public static void CreateUnityPackageWithBuild()
{
//var folder = GetDesktop();
var folder = GetProjectRoot();
if (!Directory.Exists(folder))
{
@ -194,6 +203,7 @@ namespace VRM.DevOnly.PackageExporter
List = new []{
new GlobList("Assets/VRM"),
new GlobList("Assets/VRMShaders"),
new GlobList("Assets/MeshUtility"),
}
},
new PackageInfo("UniVRM-samples")

View File

@ -77,7 +77,7 @@ namespace VRM
.Distinct()
.ToArray();
var copyMesh = mesh.Copy(copyBlendShape: false);
var copyMesh = MeshUtility.MeshExtensions.Copy(mesh, copyBlendShape: false);
// 使われている BlendShape だけをコピーする
foreach (var i in usedBlendshapeIndexArray)
{
@ -182,7 +182,7 @@ namespace VRM
if (settings.PoseFreeze)
{
// BoneNormalizer.Execute は Copy を作って正規化する。UNDO無用
target = BoneNormalizer.Execute(target, settings.ForceTPose, false);
target = VRMBoneNormalizer.Execute(target, settings.ForceTPose, false);
destroy.Add(target);
}

View File

@ -141,7 +141,7 @@ namespace VRM
static bool EnableRenderer(Renderer renderer)
{
if (renderer.transform.Ancestor().Any(x => !x.gameObject.activeSelf))
if (renderer.transform.GetComponentsInParent<Transform>().Any(x => !x.gameObject.activeSelf))
{
// 自分か祖先に !activeSelf がいる
return false;

View File

@ -49,7 +49,7 @@ namespace VRM
var go = Selection.activeObject as GameObject;
// BoneNormalizer.Execute はコピーを正規化する。UNDO無用
Selection.activeGameObject = BoneNormalizer.Execute(go, true, false);
Selection.activeGameObject = VRMBoneNormalizer.Execute(go, true, false);
}
}
}

View File

@ -37,6 +37,24 @@ namespace VRM
}}
}}
";
const string MeshUtilityPath = "Assets/MeshUtility/package.json";
const string MeshUtilityTemplate = @"{{
{{
""name"": ""com.vrmc.meshutility"",
""version"": ""{0}.{1}.{2}"",
""displayName"": ""MeshUtility"",
""unity"": ""2018.4"",
""description"": ""MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: <https://github.com/vrm-c/UniVRM/tree/master/Assets/MeshUtility>"",
""keywords"": [
""mesh""
],
""author"": {{
""name"": ""VRM Consortium""
}}
}}
";
const string VRMPackagePath = "Assets/VRM/package.json";
const string VRMPackageTemplate = @"{{
""name"": ""com.vrmc.univrm"",
@ -54,7 +72,8 @@ namespace VRM
""name"": ""VRM Consortium""
}},
""dependencies"": {{
""com.vrmc.vrmshaders"": ""{0}.{1}.{2}""
""com.vrmc.vrmshaders"": ""{0}.{1}.{2}"",
""com.vrmc.meshutility"": ""{0}.{1}.{2}""
}}
}}
";
@ -96,6 +115,10 @@ namespace VRM
values[0],
values[1],
values[2]), utf8);
File.WriteAllText(MeshUtilityPath, string.Format(MeshUtilityTemplate,
values[0],
values[1],
values[2]), utf8);
File.WriteAllText(VRMPackagePath, string.Format(VRMPackageTemplate,
values[0],
values[1],

View File

@ -11,7 +11,7 @@ using Object = UnityEngine.Object;
namespace VRM
{
/// <summary>
/// 複数のメッシュをまとめ
/// 複数のメッシュをまとめて、BlendShapeClipに変更を反映す
/// </summary>
[DisallowMultipleComponent]
public static class MeshIntegratorEditor
@ -39,12 +39,12 @@ namespace VRM
private static void Foo()
{
var go = Selection.activeObject as GameObject;
Debug.Log(SkinnedMeshUtility.IsPrefab(go));
}
public static List<MeshIntegratorUtility.MeshIntegrationResult> Integrate(GameObject prefab)
public static List<MeshUtility.MeshIntegrationResult> Integrate(GameObject prefab)
{
Undo.RecordObject(prefab, "Mesh Integration");
var instance = SkinnedMeshUtility.InstantiatePrefab(prefab);
@ -63,18 +63,18 @@ namespace VRM
// Backup Exists
BackupVrmPrefab(prefab);
// Execute
var results = MeshIntegratorUtility.Integrate(instance, clips);
var results = VRMMeshIntegratorUtility.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)
{
@ -90,31 +90,31 @@ namespace VRM
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));
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);
@ -124,7 +124,7 @@ namespace VRM
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>();
}

View File

@ -58,7 +58,7 @@ namespace VRM
MaterialList[] m_duplicateMaterials;
[Header("Result")]
public MeshIntegratorUtility.MeshIntegrationResult[] integrationResults;
public MeshUtility.MeshIntegrationResult[] integrationResults;
[MenuItem(MENU_KEY)]
static void CreateWizard()
@ -124,7 +124,7 @@ namespace VRM
return;
}
m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false)
m_uniqueMaterials = MeshUtility.MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false)
.SelectMany(x => x.sharedMaterials)
.Distinct()
.ToArray();

View File

@ -55,7 +55,7 @@ namespace VRM
var src = new Mesh();
src.AddBlendShapeFrame("blendShape", 100.0f, null, null, null);
var dst = src.Copy(true);
var dst = MeshUtility.MeshExtensions.Copy(src, true);
MeshEquals(src, dst);
}

View File

@ -58,7 +58,7 @@ namespace VRM
map.Add(null, new GameObject("null"));
// map.Add(new GameObject("c"), null); // ありえないので Exception にしてある
var boneWeights = map.CreateBoneWeight(64).ToArray();
var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
var newBoneWeight = MeshUtility.BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
map.SrcBones.ToArray(), map.DstBones.ToArray());
// 正常系
@ -74,7 +74,7 @@ namespace VRM
map.Add(null, new GameObject("null"));
// map.Add(new GameObject("c"), null); // ありえないので Exception にしてある
var boneWeights = map.CreateBoneWeight(64).ToArray();
var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
var newBoneWeight = MeshUtility.BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
map.SrcBones.ToArray(), map.DstBones.ToArray());
// 4 つめが 0 になる

View File

@ -3,7 +3,8 @@
"references": [
"VRM",
"UniJSON",
"UniVRM.Editor"
"UniVRM.Editor",
"MeshUtility"
],
"optionalUnityReferences": [
"TestAssemblies"

View File

@ -3,7 +3,8 @@
"references": [
"VRM",
"UniJSON",
"UniHumanoid"
"UniHumanoid",
"MeshUtility"
],
"optionalUnityReferences": [],
"includePlatforms": [

View File

@ -208,7 +208,7 @@ namespace VRM
var eraseBones = bones.Select((x, i) =>
{
// 祖先に削除対象が存在するか
bool erase = x.Ancestor().Any(y => y == eraseRoot);
bool erase = x.GetComponentsInParent<Transform>().Any(y => y == eraseRoot);
return new
{
i,
@ -229,7 +229,7 @@ namespace VRM
renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER;
// 削除対象のボーンに対するウェイトを保持する三角形を除外して、一人称用のモデルを複製する
var headlessMesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones);
var headlessMesh = MeshUtility.BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones);
if (headlessMesh.triangles.Length == 0)
{
// 一人称用のmeshには描画すべき部分が無い(全部削除された)

View File

@ -4,8 +4,8 @@ namespace VRM
public static partial class VRMVersion
{
public const int MAJOR = 0;
public const int MINOR = 58;
public const int PATCH = 1;
public const string VERSION = "0.58.1";
public const int MINOR = 59;
public const int PATCH = 0;
public const string VERSION = "0.59.0";
}
}

View File

@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UniHumanoid;
using UnityEngine;
namespace VRM
{
public static class VRMBoneNormalizer
{
static void EnforceTPose(GameObject go)
{
var animator = go.GetComponent<Animator>();
if (animator == null)
{
throw new ArgumentException("Animator with avatar is required");
}
var avatar = animator.avatar;
if (avatar == null)
{
throw new ArgumentException("avatar is required");
}
if (!avatar.isValid)
{
throw new ArgumentException("invalid avatar");
}
if (!avatar.isHuman)
{
throw new ArgumentException("avatar is not human");
}
HumanPoseTransfer.SetTPose(avatar, go.transform);
}
/// <summary>
/// モデルの正規化を実行する
/// </summary>
/// <param name="go">対象モデルのルート</param>
/// <param name="forceTPose">強制的にT-Pose化するか</param>
/// <returns>正規化済みのモデル</returns>
public static GameObject Execute(GameObject go, bool forceTPose, bool clearBlendShapeBeforeNormalize)
{
//
// T-Poseにする
//
if (forceTPose)
{
var hips = go.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
var hipsPosition = hips.position;
var hipsRotation = hips.rotation;
try
{
EnforceTPose(go);
}
finally
{
hips.position = hipsPosition; // restore hipsPosition
hips.rotation = hipsRotation;
}
}
//
// 正規化されたヒエラルキーを作る
//
var (normalized, bMap) = MeshUtility.BoneNormalizer.Execute(go, clearBlendShapeBeforeNormalize, (_src, dst, boneMap) =>
{
var src = _src.GetComponent<Animator>();
var srcHumanBones = Enum.GetValues(typeof(HumanBodyBones))
.Cast<HumanBodyBones>()
.Where(x => x != HumanBodyBones.LastBone)
.Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
.Where(x => x.Value != null)
;
var map =
srcHumanBones
.Where(x => boneMap.ContainsKey(x.Value))
.ToDictionary(x => x.Key, x => boneMap[x.Value])
;
var animator = dst.AddComponent<Animator>();
var vrmHuman = go.GetComponent<VRMHumanoidDescription>();
var avatarDescription = AvatarDescription.Create();
if (vrmHuman != null && vrmHuman.Description != null)
{
avatarDescription.armStretch = vrmHuman.Description.armStretch;
avatarDescription.legStretch = vrmHuman.Description.legStretch;
avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist;
avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist;
avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist;
avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist;
avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing;
avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF;
}
avatarDescription.SetHumanBones(map);
var avatar = avatarDescription.CreateAvatar(dst.transform);
return avatar;
});
// humanPoseTransfer
// var animator = normalized.GetComponent<Animator>();
// var humanPoseTransfer = normalized.AddComponent<HumanPoseTransfer>();
// humanPoseTransfer.Avatar = animator.avatar;
CopyVRMComponents(go, normalized, bMap);
return normalized;
}
/// <summary>
/// VRMを構成するコンポーネントをコピーする。
/// </summary>
/// <param name="go">コピー元</param>
/// <param name="root">コピー先</param>
/// <param name="map">コピー元とコピー先の対応関係</param>
static void CopyVRMComponents(GameObject go, GameObject root,
Dictionary<Transform, Transform> map)
{
{
// blendshape
var src = go.GetComponent<VRMBlendShapeProxy>();
if (src != null)
{
var dst = root.AddComponent<VRMBlendShapeProxy>();
dst.BlendShapeAvatar = src.BlendShapeAvatar;
}
}
{
var secondary = go.transform.Find("secondary");
if (secondary == null)
{
secondary = go.transform;
}
var dstSecondary = root.transform.Find("secondary");
if (dstSecondary == null)
{
dstSecondary = new GameObject("secondary").transform;
dstSecondary.SetParent(root.transform, false);
}
// 揺れモノ
foreach (var src in go.transform.GetComponentsInChildren<VRMSpringBoneColliderGroup>())
{
var dst = map[src.transform];
var dstColliderGroup = dst.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
dstColliderGroup.Colliders = src.Colliders.Select(y =>
{
var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset));
return new VRMSpringBoneColliderGroup.SphereCollider
{
Offset = offset,
Radius = y.Radius
};
}).ToArray();
}
foreach (var src in go.transform.GetComponentsInChildren<VRMSpringBone>())
{
// Copy VRMSpringBone
var dst = dstSecondary.gameObject.AddComponent<VRMSpringBone>();
dst.m_comment = src.m_comment;
dst.m_stiffnessForce = src.m_stiffnessForce;
dst.m_gravityPower = src.m_gravityPower;
dst.m_gravityDir = src.m_gravityDir;
dst.m_dragForce = src.m_dragForce;
if (src.m_center != null)
{
dst.m_center = map[src.m_center];
}
dst.RootBones = src.RootBones.Select(x => map[x]).ToList();
dst.m_hitRadius = src.m_hitRadius;
if (src.ColliderGroups != null)
{
dst.ColliderGroups = src.ColliderGroups
.Select(x => map[x.transform].GetComponent<VRMSpringBoneColliderGroup>()).ToArray();
}
}
}
#pragma warning disable 0618
{
// meta(obsolete)
var src = go.GetComponent<VRMMetaInformation>();
if (src != null)
{
src.CopyTo(root);
}
}
#pragma warning restore 0618
{
// meta
var src = go.GetComponent<VRMMeta>();
if (src != null)
{
var dst = root.AddComponent<VRMMeta>();
dst.Meta = src.Meta;
}
}
{
// firstPerson
var src = go.GetComponent<VRMFirstPerson>();
if (src != null)
{
src.CopyTo(root, map);
}
}
{
// humanoid
var dst = root.AddComponent<VRMHumanoidDescription>();
var src = go.GetComponent<VRMHumanoidDescription>();
if (src != null)
{
dst.Avatar = src.Avatar;
dst.Description = src.Description;
}
else
{
var animator = go.GetComponent<Animator>();
if (animator != null)
{
dst.Avatar = animator.avatar;
}
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7c0aa7232aa52244aa546dcabf17fadd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UnityEngine;
namespace VRM
{
/// <summary>
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
/// </summary>
public static class VRMMeshIntegratorUtility
{
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<MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips)
{
var result = new List<MeshUtility.MeshIntegrationResult>();
var withoutBlendShape = MeshUtility.MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false);
if (withoutBlendShape.IntegratedRenderer != null)
{
result.Add(withoutBlendShape);
}
var onlyBlendShape = MeshUtility.MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true);
if (onlyBlendShape.IntegratedRenderer != null)
{
result.Add(onlyBlendShape);
FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root);
}
return result;
}
private static void FollowBlendshapeRendererChange(List<BlendShapeClip> clips, MeshUtility.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;
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8eebeb093136b7f429c0e9e7295816b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -6,10 +6,15 @@
"UniUnlit",
"UniHumanoid",
"UniJSON",
"ShaderProperty.Runtime"
"ShaderProperty.Runtime",
"MeshUtility"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@ -1,6 +1,6 @@
{
"name": "com.vrmc.univrm",
"version": "0.58.1",
"version": "0.59.0",
"displayName": "VRM",
"description": "VRM importer",
"unity": "2018.4",
@ -14,6 +14,7 @@
"name": "VRM Consortium"
},
"dependencies": {
"com.vrmc.vrmshaders": "0.58.1"
"com.vrmc.vrmshaders": "0.59.0",
"com.vrmc.meshutility": "0.59.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "com.vrmc.vrmshaders",
"version": "0.58.1",
"version": "0.59.0",
"displayName": "VRM Shaders",
"description": "VRM Shaders",
"unity": "2018.4",

View File

@ -1 +1 @@
m_EditorVersion: 2018.4.23f1
m_EditorVersion: 2018.4.25f1