using System; using System.Linq; using System.Collections.Generic; using UniGLTF; using UnityEngine; using UniJSON; using System.Threading.Tasks; using VRMShaders; using Object = UnityEngine.Object; namespace VRM { public class VRMImporterContext : ImporterContext { VRMData _data; public VRM.glTF_VRM_extensions VRM { get { return _data.VrmExtension; } } public VRMImporterContext( VRMData data, IReadOnlyDictionary externalObjectMap = null, ITextureDeserializer textureDeserializer = null, IMaterialDescriptorGenerator materialGenerator = null) : base(data.Data, externalObjectMap, textureDeserializer, materialGenerator ?? new VRMMaterialDescriptorGenerator(data.VrmExtension)) { _data = data; TextureDescriptorGenerator = new VrmTextureDescriptorGenerator(Data, VRM); } #region OnLoad protected override async Task OnLoadHierarchy(IAwaitCaller awaitCaller, Func MeasureTime) { Root.name = "VRM"; using (MeasureTime("VRM LoadMeta")) { await LoadMetaAsync(awaitCaller); } await awaitCaller.NextFrame(); using (MeasureTime("VRM LoadHumanoid")) { LoadHumanoid(); } await awaitCaller.NextFrame(); using (MeasureTime("VRM LoadBlendShapeMaster")) { LoadBlendShapeMaster(); } await awaitCaller.NextFrame(); using (MeasureTime("VRM LoadSecondary")) { VRMSpringUtility.LoadSecondary(Root.transform, Nodes, VRM.secondaryAnimation); } await awaitCaller.NextFrame(); using (MeasureTime("VRM LoadFirstPerson")) { LoadFirstPerson(); } } async Task LoadMetaAsync(IAwaitCaller awaitCaller) { if (awaitCaller == null) { throw new ArgumentNullException(); } var meta = await ReadMetaAsync(awaitCaller); var _meta = Root.AddComponent(); _meta.Meta = meta; Meta = meta; } void LoadFirstPerson() { var firstPerson = Root.AddComponent(); var gltfFirstPerson = VRM.firstPerson; if (gltfFirstPerson.firstPersonBone != -1) { firstPerson.FirstPersonBone = Nodes[gltfFirstPerson.firstPersonBone]; firstPerson.FirstPersonOffset = gltfFirstPerson.firstPersonBoneOffset; } else { // fallback firstPerson.SetDefault(); firstPerson.FirstPersonOffset = gltfFirstPerson.firstPersonBoneOffset; } firstPerson.TraverseRenderers(this); // LookAt var lookAtHead = Root.AddComponent(); lookAtHead.OnImported(this); } void LoadBlendShapeMaster() { BlendShapeAvatar = ScriptableObject.CreateInstance(); BlendShapeAvatar.name = "BlendShape"; var transformMeshTable = new Dictionary(); foreach (var transform in Root.transform.Traverse()) { if (transform.GetSharedMesh() != null) { transformMeshTable.Add(transform.GetSharedMesh(), transform); } } var blendShapeList = VRM.blendShapeMaster.blendShapeGroups; if (blendShapeList != null && blendShapeList.Count > 0) { foreach (var x in blendShapeList) { BlendShapeAvatar.Clips.Add(LoadBlendShapeBind(x, transformMeshTable)); } } var proxy = Root.AddComponent(); BlendShapeAvatar.CreateDefaultPreset(); proxy.BlendShapeAvatar = BlendShapeAvatar; } BlendShapeClip LoadBlendShapeBind(glTF_VRM_BlendShapeGroup group, Dictionary transformMeshTable) { var asset = ScriptableObject.CreateInstance(); var groupName = group.name; var prefix = "BlendShape."; while (groupName.FastStartsWith(prefix)) { groupName = groupName.Substring(prefix.Length); } asset.name = "BlendShape." + groupName; if (group != null) { asset.BlendShapeName = groupName; asset.Preset = CacheEnum.TryParseOrDefault(group.presetName, true); asset.IsBinary = group.isBinary; if (asset.Preset == BlendShapePreset.Unknown) { // fallback asset.Preset = CacheEnum.TryParseOrDefault(group.name, true); } asset.Values = group.binds.Select(x => { var mesh = Meshes[x.mesh].Mesh; var node = transformMeshTable[mesh]; var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(node, Root.transform); return new BlendShapeBinding { RelativePath = relativePath, Index = x.index, Weight = x.weight, }; }) .ToArray(); asset.MaterialValues = group.materialValues.Select(x => { var value = new Vector4(); for (int i = 0; i < x.targetValue.Length; ++i) { switch (i) { case 0: value.x = x.targetValue[0]; break; case 1: value.y = x.targetValue[1]; break; case 2: value.z = x.targetValue[2]; break; case 3: value.w = x.targetValue[3]; break; } } var material = MaterialFactory.Materials .Select(y => y.Asset) .FirstOrDefault(y => y.name == x.materialName); var propertyName = x.propertyName; if (x.propertyName.FastEndsWith("_ST_S") || x.propertyName.FastEndsWith("_ST_T")) { propertyName = x.propertyName.Substring(0, x.propertyName.Length - 2); } var binding = default(MaterialValueBinding?); if (material != null) { try { binding = new MaterialValueBinding { MaterialName = x.materialName, ValueName = x.propertyName, TargetValue = value, BaseValue = material.GetColor(propertyName), }; } catch (Exception) { // do nothing } } return binding; }) .Where(x => x.HasValue) .Select(x => x.Value) .ToArray(); } return asset; } static String ToHumanBoneName(HumanBodyBones b) { foreach (var x in HumanTrait.BoneName) { if (x.Replace(" ", "") == b.ToString()) { return x; } } throw new KeyNotFoundException(); } static SkeletonBone ToSkeletonBone(Transform t) { var sb = new SkeletonBone(); sb.name = t.name; sb.position = t.localPosition; sb.rotation = t.localRotation; sb.scale = t.localScale; return sb; } private void LoadHumanoid() { AvatarDescription = VRM.humanoid.ToDescription(Nodes); AvatarDescription.name = "AvatarDescription"; HumanoidAvatar = AvatarDescription.CreateAvatar(Root.transform); if (!HumanoidAvatar.isValid || !HumanoidAvatar.isHuman) { throw new Exception("fail to create avatar"); } HumanoidAvatar.name = "VrmAvatar"; var humanoid = Root.AddComponent(); humanoid.Avatar = HumanoidAvatar; humanoid.Description = AvatarDescription; var animator = Root.GetComponent(); if (animator == null) { animator = Root.AddComponent(); } animator.avatar = HumanoidAvatar; // default としてとりあえず設定する // https://docs.unity3d.com/ScriptReference/Renderer-probeAnchor.html var head = animator.GetBoneTransform(HumanBodyBones.Head); foreach (var smr in animator.GetComponentsInChildren()) { smr.probeAnchor = head; } } #endregion public UniHumanoid.AvatarDescription AvatarDescription; public Avatar HumanoidAvatar; public BlendShapeAvatar BlendShapeAvatar; public VRMMetaObject Meta; public async Task ReadMetaAsync(IAwaitCaller awaitCaller, bool createThumbnail = false) { if (awaitCaller == null) { throw new ArgumentNullException(); } var meta = ScriptableObject.CreateInstance(); meta.name = "Meta"; meta.ExporterVersion = VRM.exporterVersion; var gltfMeta = VRM.meta; meta.Version = gltfMeta.version; // model version meta.Author = gltfMeta.author; meta.ContactInformation = gltfMeta.contactInformation; meta.Reference = gltfMeta.reference; meta.Title = gltfMeta.title; if (gltfMeta.texture >= 0) { var (key, param) = GltfTextureImporter.CreateSrgb(Data, gltfMeta.texture, Vector2.zero, Vector2.one); meta.Thumbnail = await TextureFactory.GetTextureAsync(param, awaitCaller) as Texture2D; } meta.AllowedUser = gltfMeta.allowedUser; meta.ViolentUssage = gltfMeta.violentUssage; meta.SexualUssage = gltfMeta.sexualUssage; meta.CommercialUssage = gltfMeta.commercialUssage; meta.OtherPermissionUrl = gltfMeta.otherPermissionUrl; meta.LicenseType = gltfMeta.licenseType; meta.OtherLicenseUrl = gltfMeta.otherLicenseUrl; return meta; } public override void TransferOwnership(TakeResponsibilityForDestroyObjectFunc take) { // VRM-0 は SubAssetKey を使っていないので default で済ます // VRM 固有のリソース(ScriptableObject) take(default, HumanoidAvatar); HumanoidAvatar = null; take(default, Meta); Meta = null; take(default, AvatarDescription); AvatarDescription = null; foreach (var x in BlendShapeAvatar.Clips) { take(default, x); { // do nothing } } take(default, BlendShapeAvatar); BlendShapeAvatar = null; // GLTF のリソース base.TransferOwnership(take); } public override void Dispose() { // VRM specific if (HumanoidAvatar != null) { UnityObjectDestoyer.DestroyRuntimeOrEditor(HumanoidAvatar); } if (Meta != null) { UnityObjectDestoyer.DestroyRuntimeOrEditor(Meta); } if (AvatarDescription != null) { UnityObjectDestoyer.DestroyRuntimeOrEditor(AvatarDescription); } if (BlendShapeAvatar != null) { foreach (var clip in BlendShapeAvatar.Clips) { UnityObjectDestoyer.DestroyRuntimeOrEditor(clip); } UnityObjectDestoyer.DestroyRuntimeOrEditor(BlendShapeAvatar); } base.Dispose(); } } }