/// <summary> /// 指定されたブレンドシェイプに、<see cref="BlendShapePreset.Neutral"/>、および<see cref="BlendShapePreset.Blink"/>に含まれるシェイプキーと /// 同一のシェイプキーが含まれていれば、そのシェイプキーを複製します。 /// </summary> /// <param name="avatar"></param> /// <param name="clip"></param> /// <param name="clips"></param> /// <returns>置換後のVRMのブレンドシェイプ一覧。</returns> private static IEnumerable <VRMBlendShapeClip> DuplicateShapeKeyToUnique( GameObject avatar, VRMBlendShapeClip clip, IEnumerable <VRMBlendShapeClip> clips ) { string[] neutralAndBlinkShapeKeyNames = new[] { BlendShapePreset.Neutral, BlendShapePreset.Blink } .Select(selector: blendShapePreset => clips.FirstOrDefault(c => c.Preset == blendShapePreset)) .SelectMany(selector: blendShapeClip => blendShapeClip ? blendShapeClip.ShapeKeyValues : new Dictionary <string, float>()) .Select(selector: nameAndWeight => nameAndWeight.Key) .ToArray(); Mesh mesh = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath).GetSharedMesh(); foreach (var(oldName, weight) in clip.ShapeKeyValues) { if (!neutralAndBlinkShapeKeyNames.Contains(oldName)) { continue; } var newName = BlendShapeReplacer.FeelingsShapeKeyPrefix + oldName; var index = mesh.GetBlendShapeIndex(oldName); var frameCount = mesh.GetBlendShapeFrameCount(index); for (var i = 0; i < frameCount; i++) { var deltaVertices = new Vector3[mesh.vertexCount]; var deltaNormals = new Vector3[mesh.vertexCount]; var deltaTangents = new Vector3[mesh.vertexCount]; mesh.GetBlendShapeFrameVertices(index, i, deltaVertices, deltaNormals, deltaTangents); mesh.AddBlendShapeFrame( newName, mesh.GetBlendShapeFrameWeight(shapeIndex: index, frameIndex: i), deltaVertices, deltaNormals, deltaTangents ); } EditorUtility.SetDirty(mesh); clips = VRMUtility.ReplaceShapeKeyName(clips, oldName, newName); } return(clips); }
/// <summary> /// 変換ダイアログを開きます。 /// </summary> /// <param name="avatar"></param> internal static void Open(GameObject avatar) { var wizard = DisplayWizard <Wizard>(Converter.Name + " " + Converter.Version, _("Duplicate and Convert")); Vector2 defaultMinSize = Wizard.MinSize; defaultMinSize.y = Wizard.MinHeightWhenOpen; wizard.minSize = defaultMinSize; wizard.minSize = Wizard.MinSize; wizard.avatar = avatar.GetComponent <Animator>(); wizard.LoadSettings(); if (string.IsNullOrEmpty(wizard.blendShapeForFingerpoint)) { var surprise = VRMUtility.GetBlendShapeClips(wizard.avatar) .FirstOrDefault(clip => clip.Preset == BlendShapePreset.Unknown && clip.BlendShapeName.StartsWith("Surprise", ignoreCase: true, culture: null)); if (surprise != null) { wizard.blendShapeForFingerpoint = surprise.BlendShapeName; } } }
/// <summary> /// アニメーションクリップに、指定されたマテリアルを追加します。 /// </summary> /// <param name="avatar"></param> /// <param name="animationClip"></param> /// <param name="name"></param> /// <param name="binding"></param> /// <param name="secondsList"></param> private static void SetBlendShapeCurve( AnimationClip animationClip, GameObject avatar, string vrmBlendShapeName, IEnumerable <MaterialValueBinding> bindings, IEnumerable <float> secondsList ) { var materials = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath) .GetComponent <SkinnedMeshRenderer>().sharedMaterials; var materialIndex = materials.ToList().FindIndex(m => m.name == bindings.First().MaterialName); var material = Duplicator.DuplicateAssetToFolder <Material>( source: materials[materialIndex], prefabInstance: avatar, fileName: $"{materials[materialIndex].name}-{vrmBlendShapeName}.mat" ); VRMUtility.Bake(material, bindings); AnimationUtility.SetObjectReferenceCurve( animationClip, new EditorCurveBinding() { path = VRChatUtility.AutoBlinkMeshPath, type = typeof(SkinnedMeshRenderer), propertyName = $"m_Materials.Array.data[{materialIndex}]", }, secondsList .Select(seconds => new ObjectReferenceKeyframe() { time = seconds, value = material }).ToArray() ); }
private void OnWizardCreate() { if (string.IsNullOrEmpty(this.destinationPath)) { string sourcePath = this.GetAssetsPath(vrm: this.avatar.gameObject); this.destinationPath = UnityPath.FromUnityPath(sourcePath).Parent .Child(Path.GetFileNameWithoutExtension(sourcePath) + " (VRChat).prefab").Value; } else { UnityPath destinationFolderUnityPath = UnityPath.FromUnityPath(this.destinationPath).Parent; while (!destinationFolderUnityPath.IsDirectoryExists) { destinationFolderUnityPath = destinationFolderUnityPath.Parent; } this.destinationPath = destinationFolderUnityPath.Child(Path.GetFileName(this.destinationPath)).Value; } string destinationPath = EditorUtility.SaveFilePanelInProject( "", Path.GetFileName(path: this.destinationPath), "prefab", "", Path.GetDirectoryName(path: this.destinationPath) ); if (string.IsNullOrEmpty(destinationPath)) { Wizard.Open(avatar: this.avatar.gameObject); return; } this.destinationPath = destinationPath; // プレハブ、およびシーン上のプレハブインスタンスのBlueprint IDを取得 string prefabBlueprintId = ""; var blueprintIds = new Dictionary <int, string>(); var previousPrefab = AssetDatabase.LoadMainAssetAtPath(this.destinationPath) as GameObject; if (previousPrefab) { var pipelineManager = previousPrefab.GetComponent <PipelineManager>(); prefabBlueprintId = pipelineManager ? pipelineManager.blueprintId : ""; GameObject[] previousRootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); blueprintIds = previousRootGameObjects .Where(root => PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(root) == this.destinationPath) .Select(root => { var manager = root.GetComponent <PipelineManager>(); var blueprintId = manager ? manager.blueprintId : ""; return(new { index = Array.IndexOf(previousRootGameObjects, root), blueprintId = blueprintId != prefabBlueprintId ? blueprintId : "", }); }).ToDictionary( keySelector: indexAndBlueprintId => indexAndBlueprintId.index, elementSelector: indexAndBlueprintId => indexAndBlueprintId.blueprintId ); } GameObject prefabInstance = Duplicator.Duplicate( sourceAvatar: this.avatar.gameObject, destinationPath: this.destinationPath, notCombineRendererObjectNames: this.notCombineRendererObjectNames, combineMeshesAndSubMeshes: this.combineMeshes ); var messages = new List <Converter.Message>(); if (this.forQuest) { messages.AddRange(Wizard.GenerateQuestLimitationsErrorMessages(prefab: prefabInstance)); } this.SaveSettings(); foreach (VRMSpringBone springBone in this.GetSpringBonesWithComments(prefab: prefabInstance, comments: this.excludedSpringBoneComments) .SelectMany(springBone => springBone)) { UnityEngine.Object.DestroyImmediate(springBone); } var clips = VRMUtility.GetAllVRMBlendShapeClips(avatar: this.avatar.gameObject); messages.AddRange(Converter.Convert( prefabInstance: prefabInstance, clips: clips, swayingObjectsConverterSetting: this.swayingObjects, takingOverSwayingParameters: this.takeOverSwayingParameters, swayingParametersConverter: this.swayingParametersConverter, enableAutoEyeMovement: this.enableEyeMovement, addedShouldersPositionY: this.shoulderHeights, moveEyeBoneToFrontForEyeMovement: this.moveEyeBoneToFrontForEyeMovement, forQuest: this.forQuest, addedArmaturePositionY: this.armatureHeight, useAnimatorForBlinks: this.useAnimatorForBlinks, useShapeKeyNormalsAndTangents: this.useShapeKeyNormalsAndTangents, vrmBlendShapeForFINGERPOINT: !string.IsNullOrEmpty(this.blendShapeForFingerpoint) ? VRMUtility.GetUserDefinedBlendShapeClip(clips, this.blendShapeForFingerpoint) as VRMBlendShapeClip : null )); // 変換前のプレハブのPipeline ManagerのBlueprint IDを反映 if (!string.IsNullOrEmpty(prefabBlueprintId)) { prefabInstance.GetComponent <PipelineManager>().blueprintId = prefabBlueprintId; } if (this.postConverting != null) { this.postConverting(prefabInstance, prefabInstance.GetComponent <VRMMeta>()); } PrefabUtility.ApplyPrefabInstance(prefabInstance, InteractionMode.AutomatedAction); // 変換前のプレハブインスタンスのPipeline ManagerのBlueprint IDを反映 GameObject[] rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var(avatarIndex, blueprintId) in blueprintIds) { if (string.IsNullOrEmpty(blueprintId)) { continue; } rootGameObjects[avatarIndex].GetComponent <PipelineManager>().blueprintId = blueprintId; } if (blueprintIds.Count > 0) { // シーンのルートに、すでに他のプレハブインスタンスが存在していれば、変換用のインスタンスは削除 UnityEngine.Object.DestroyImmediate(prefabInstance); } ResultDialog.Open(messages: messages); }
protected override bool DrawWizardGUI() { base.DrawWizardGUI(); isValid = true; if (this.callbackFunctions) { Type callBackFunctions = this.callbackFunctions.GetClass(); this.swayingParametersConverter = Delegate.CreateDelegate( type: typeof(Converter.SwayingParametersConverter), target: callBackFunctions, method: "SwayingParametersConverter", ignoreCase: false, throwOnBindFailure: false ) as Converter.SwayingParametersConverter; this.postConverting = Delegate.CreateDelegate( type: typeof(Wizard.PostConverting), target: callBackFunctions, method: "PostConverting", ignoreCase: false, throwOnBindFailure: false ) as Wizard.PostConverting; } var indentStyle = new GUIStyle() { padding = new RectOffset() { left = Wizard.Indent } }; EditorGUILayout.LabelField( (this.swayingParametersConverter != null ? "☑" : "☐") + " public static DynamicBoneParameters SwayingParametersConverter(SpringBoneParameters, BoneInfo)", indentStyle ); EditorGUILayout.LabelField( (this.postConverting != null ? "☑" : "☐") + " public static void PostConverting(GameObject, VRMMeta)", indentStyle ); #if VRC_SDK_VRCSDK2 || VRC_SDK_VRCSDK3 foreach (var type in Converter.RequiredComponents) { if (!this.avatar.GetComponent(type)) { EditorGUILayout.HelpBox(string.Format(_("Not set “{0}” component."), type), MessageType.Error); isValid = false; } } IEnumerable <string> excludedSpringBoneComments = this.excludedSpringBoneComments.Except(new[] { "" }); if (excludedSpringBoneComments.Count() > 0) { IEnumerable <string> comments = excludedSpringBoneComments.Except( this.GetSpringBonesWithComments(prefab: this.avatar.gameObject, comments: excludedSpringBoneComments) .Select(commentAndSpringBones => commentAndSpringBones.Key) ); if (comments.Count() > 0) { EditorGUILayout.HelpBox(string.Join(separator: "\n• ", value: new[] { _("VRMSpringBones with the below Comments do not exist.") } .Concat(comments).ToArray()), MessageType.Warning); } } if (this.combineMeshes) { IEnumerable <string> notCombineRendererObjectNames = this.notCombineRendererObjectNames.Except(new[] { "" }); if (notCombineRendererObjectNames.Count() > 0) { IEnumerable <string> names = notCombineRendererObjectNames.Except( this.avatar.GetComponentsInChildren <SkinnedMeshRenderer>() .Concat <Component>(this.avatar.GetComponentsInChildren <MeshRenderer>()) .Select(renderer => renderer.name) ); if (names.Count() > 0) { EditorGUILayout.HelpBox(string.Join(separator: "\n• ", value: new[] { _("Renderers on the below name GameObject do not exist.") } .Concat(names).ToArray()), MessageType.Warning); } } } else { EditorGUILayout.HelpBox(_("If you do not “Combine Meshes”," + " and any of VRMBlendShapes references meshes other than the mesh having most shape keys" + " or the mesh is not direct child of the avatar root," + " the avatar will not be converted correctly."), MessageType.Warning); } if (!string.IsNullOrEmpty(this.blendShapeForFingerpoint) && !VRMUtility.GetUserDefinedBlendShapeClip(this.avatar, this.blendShapeForFingerpoint)) { EditorGUILayout.HelpBox(string.Format( _("There is no user-defined VRMBlensShape with the name “{0}”."), this.blendShapeForFingerpoint ), MessageType.Warning); } string version = VRChatUtility.GetSupportedUnityVersion(); if (version != "" && Application.unityVersion != version) { EditorGUILayout.HelpBox(string.Format( _("Unity {0} is running. If you are using a different version than {1}, VRChat SDK might not work correctly. Recommended using Unity downloaded from {2} ."), Application.unityVersion, version, VRChatUtility.DownloadURL ), MessageType.Warning); } if (!isValid || !this.forQuest) { return(true); } AvatarPerformanceStats statistics = new AvatarPerformanceStats(); AvatarPerformance.CalculatePerformanceStats( avatarName: avatar.GetComponent <VRMMeta>().Meta.Title, avatarObject: this.avatar.gameObject, perfStats: statistics ); int currentPolycount = (int)statistics.polyCount; int maxPolycount = VRChatUtility.AvatarPerformanceStatsLevelSets["Quest"].medium.polyCount; if (currentPolycount > maxPolycount) { EditorGUILayout.HelpBox(string.Format( _("The number of polygons is {0}."), currentPolycount ) + string.Format( _("If this value exceeds {0}, the avatar will not shown under the default user setting."), maxPolycount ), MessageType.Error); } #else EditorGUILayout.HelpBox(_("VRChat SDK2 or SDK3 has not been imported."), MessageType.Error); isValid = false; #endif return(true); }