using System; using System.Collections; using System.Linq; using LibMMD.Model; using UnityEngine; namespace LibMMD.Unity3D { internal sealed class MmdModelHierarchyBuilder { private readonly MmdModel _model; private readonly GameObject _owner; private GameObject[] _bones; private GameObject _boneRootGameObject; internal MmdModelHierarchyBuilder(MmdModel model, GameObject owner) { _model = model; _owner = owner ?? throw new ArgumentNullException(nameof(owner)); } internal GameObject BoneRootGameObject => _boneRootGameObject; internal IEnumerator CreateBonesIncremental(GameObject rootGameObject, MmdModelOptions options, Action completed) { if (_model == null) { completed(Array.Empty()); yield break; } var bones = new GameObject[_model.Bones.Length]; _bones = bones; var slice = new MmdLoadTimeSlicer(options); for (var i = 0; i < bones.Length; i++) { var bone = new GameObject(_model.Bones[i].Name); bone.transform.position = _model.Bones[i].Position; bones[i] = bone; if (slice.ShouldYield()) yield return null; } var rootObj = new GameObject("Model"); _boneRootGameObject = rootObj; var modelRootTransform = rootObj.transform; _owner.GetComponent().rootBone = modelRootTransform; modelRootTransform.SetParent(rootGameObject.transform, false); rootObj.transform.localPosition = Vector3.zero; rootObj.transform.localRotation = Quaternion.identity; rootObj.transform.localScale = Vector3.one; if (slice.ShouldYield()) yield return null; for (var i = 0; i < bones.Length; i++) { var parentBoneIndex = _model.Bones[i].ParentIndex; bones[i].transform.SetParent( parentBoneIndex < bones.Length && parentBoneIndex >= 0 ? bones[parentBoneIndex].transform : modelRootTransform, true); if (slice.ShouldYield()) yield return null; } completed(bones); } // internal (not private) so ModelLoadPreparer's background-thread mesh-array build can // reuse the same conversion instead of duplicating it. internal static BoneWeight ConvertBoneWeight(SkinningOperator op) { var ret = new BoneWeight(); switch (op.Type) { case SkinningOperator.SkinningType.SkinningBdef1: var bdef1 = (SkinningOperator.Bdef1) op.Param; ret.boneIndex0 = bdef1.BoneId; ret.weight0 = 1.0f; break; case SkinningOperator.SkinningType.SkinningBdef2: var bdef2 = (SkinningOperator.Bdef2) op.Param; ret.boneIndex0 = bdef2.BoneId[0]; ret.boneIndex1 = bdef2.BoneId[1]; ret.weight0 = bdef2.BoneWeight; ret.weight1 = 1 - bdef2.BoneWeight; break; case SkinningOperator.SkinningType.SkinningBdef4: var bdef4 = (SkinningOperator.Bdef4) op.Param; ret.boneIndex0 = bdef4.BoneId[0]; ret.boneIndex1 = bdef4.BoneId[1]; ret.boneIndex2 = bdef4.BoneId[2]; ret.boneIndex3 = bdef4.BoneId[3]; ret.weight0 = bdef4.BoneWeight[0]; ret.weight1 = bdef4.BoneWeight[1]; ret.weight2 = bdef4.BoneWeight[2]; ret.weight3 = bdef4.BoneWeight[3]; break; case SkinningOperator.SkinningType.SkinningSdef: var sdef = (SkinningOperator.Sdef) op.Param; ret.boneIndex0 = sdef.BoneId[0]; ret.boneIndex1 = sdef.BoneId[1]; ret.weight0 = sdef.BoneWeight; ret.weight1 = 1 - sdef.BoneWeight; break; default: throw new ArgumentOutOfRangeException(); } return ret; } internal void BuildBindpose(Mesh mesh, SkinnedMeshRenderer smr, bool fillMaterials, UnityEngine.Material[] materials) { var bindposes = _bones.Select(x => x.transform.worldToLocalMatrix).ToArray(); mesh.bindposes = bindposes; smr.sharedMesh = mesh; smr.bones = _bones.Select(x => x.transform).ToArray(); smr.updateWhenOffscreen = true; if (fillMaterials) { smr.materials = materials; } } } }