// Magica Cloth 2. // Copyright (c) 2023 MagicaSoft. // https://magicasoft.jp using UnityEditor; using UnityEngine; namespace MagicaCloth2 { /// /// MagicaClothコンポーネントのエディタ拡張 /// [CustomEditor(typeof(MagicaCloth))] [CanEditMultipleObjects] public class MagicaClothEditor : Editor { //========================================================================================= private void Awake() { // 選択のたびに呼ばれるのでMonoの動作とは異なる //Debug.Log("MagicaClothEditor.Awake"); } private void OnEnable() { //Debug.Log("MagicaClothEditor.OnEnable"); ClothEditorManager.OnEditMeshBuildComplete += OnEditMeshBuildComplete; } private void OnDisable() { //Debug.Log("MagicaClothEditor.OnDisable"); ClothEditorManager.OnEditMeshBuildComplete -= OnEditMeshBuildComplete; ClothPainter.ExitPaint(); } private void OnDestroy() { // 選択が外れるたびに呼ばれるのでMonoの動作とは異なる //Debug.Log("MagicaClothEditor.OnDestroy"); //Debug.Log(target != null); } private void OnValidate() { // どうもMonoのValidateは違う //Debug.Log("MagicaClothEditor.OnValidate"); } private void Reset() { //Debug.Log("MagicaClothEditor.Reset"); } //========================================================================================= int oldAcitve = -1; //========================================================================================= /// /// 編集用のセレクションデータを取得する /// /// /// /// public SelectionData GetSelectionData(MagicaCloth cloth, VirtualMesh editMesh) { // すでにセレクションデータが存在し、かつユーザー編集データならばコンバートする var selectionData = ClothEditorManager.CreateAutoSelectionData(cloth, cloth.SerializeData, editMesh); if (cloth.GetSerializeData2().selectionData != null && cloth.GetSerializeData2().selectionData.userEdit) { //Debug.Log($"セレクションデータコンバート!"); selectionData.ConvertFrom(cloth.GetSerializeData2().selectionData); selectionData.userEdit = true; } return selectionData; } /// /// エディットメッシュの構築完了通知(成否問わず) /// void OnEditMeshBuildComplete() { //Debug.Log($"MagicaClothInspector. OnEditMeshBuildComplete."); Repaint(); } /// /// インスペクターGUI /// public override void OnInspectorGUI() { var cloth = target as MagicaCloth; // 状態 DispVersion(); DispStatus(); DispProxyMesh(); // 設定 serializedObject.Update(); Undo.RecordObject(cloth, "MagicaCloth2"); EditorGUILayout.Space(); ClothMainInspector(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); ClothParameterInspector(); EditorGUILayout.Space(); EditorGUILayout.Space(); GizmoInspector(); serializedObject.ApplyModifiedProperties(); //DrawDefaultInspector(); // アクティブが変更された場合は編集メッシュを再構築する int nowActive = cloth.isActiveAndEnabled ? 1 : 0; if (nowActive != oldAcitve) { //Debug.Log($"[{cloth.name}] rebuild. active:{nowActive}"); oldAcitve = nowActive; ClothEditorManager.RegisterComponent(cloth, nowActive > 0 ? GizmoType.Active : 0, true); } } /// /// クロスペイントの適用 /// /// internal void ApplyClothPainter(SelectionData selectionData) { if (selectionData == null || selectionData.IsValid() == false) return; var cloth = target as MagicaCloth; // セレクションデータ格納 ClothEditorManager.ApplySelectionData(cloth, selectionData); } /// /// クロスペイントの変更による編集メッシュの再構築 /// internal void UpdateEditMesh() { var cloth = target as MagicaCloth; // 編集用メッシュの再構築 ClothEditorManager.RegisterComponent(cloth, GizmoType.Active, true); // 強制更新 } //========================================================================================= void DispVersion() { EditorGUILayout.LabelField($"Version {AboutMenu.MagicaClothVersion}"); //using (new EditorGUILayout.HorizontalScope()) //{ // //GUILayout.FlexibleSpace(); // EditorGUILayout.Space(); // EditorGUILayout.LabelField($"Version {AboutMenu.MagicaClothVersion}", GUILayout.Width(100)); //} } void DispStatus() { var cloth = target as MagicaCloth; ResultCode result; if (EditorApplication.isPlaying) { result = cloth.Process.Result; } else { result = ClothEditorManager.GetResultCode(cloth); } MessageType mtype = MessageType.Info; if (result.IsError()) mtype = MessageType.Error; else if (result.IsWarning()) mtype = MessageType.Warning; var infoMessage = result.GetResultInformation(); if (infoMessage != null) EditorGUILayout.HelpBox($"{result.GetResultString()}\n{infoMessage}", mtype); else EditorGUILayout.HelpBox(result.GetResultString(), mtype); } void DispProxyMesh() { var cloth = target as MagicaCloth; VirtualMesh vmesh = null; if (EditorApplication.isPlaying) { vmesh = cloth.Process?.ProxyMesh; } else { vmesh = ClothEditorManager.GetEditMesh(cloth); } if (vmesh == null) return; StaticStringBuilder.Clear(); if (EditorApplication.isPlaying) StaticStringBuilder.AppendLine("[Proxy Mesh]"); else StaticStringBuilder.AppendLine("[Edit Mesh]"); StaticStringBuilder.AppendLine($"Vertex: {vmesh.VertexCount}"); StaticStringBuilder.AppendLine($"Edge: {vmesh.EdgeCount}"); StaticStringBuilder.AppendLine($"Triangle: {vmesh.TriangleCount}"); StaticStringBuilder.AppendLine($"SkinBoneCount: {vmesh.SkinBoneCount}"); StaticStringBuilder.Append($"TransformCount: {vmesh.TransformCount}"); EditorGUILayout.HelpBox(StaticStringBuilder.ToString(), MessageType.Info); } void ClothMainInspector() { var cloth = target as MagicaCloth; EditorGUILayout.LabelField("Main", EditorStyles.boldLabel); // Cloth { var clothType = serializedObject.FindProperty("serializeData.clothType"); EditorGUILayout.PropertyField(clothType, new GUIContent("Cloth Type")); var paintMode = serializedObject.FindProperty("serializeData.paintMode"); using (new EditorGUI.IndentLevelScope()) { if (cloth.SerializeData.clothType == ClothProcess.ClothType.BoneCloth) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.rootBones")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.connectionMode")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.rootRotation")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.rotationalInterpolation")); } else if (cloth.SerializeData.clothType == ClothProcess.ClothType.MeshCloth) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.sourceRenderers")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.reductionSetting")); } EditorGUILayout.Space(); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.updateMode")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.animationPoseRatio")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.normalAxis")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.normalAlignmentSetting.alignmentMode"), new GUIContent("Normal Alignment")); if (cloth.SerializeData.normalAlignmentSetting.alignmentMode == NormalAlignmentSettings.AlignmentMode.Transform) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.normalAlignmentSetting.adjustmentTransform")); } if (cloth.SerializeData.clothType == ClothProcess.ClothType.MeshCloth) { EditorGUILayout.Space(); EditorGUILayout.PropertyField(paintMode); if (paintMode.enumValueIndex != 0) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.paintMaps")); } } } // ペイントボタン if (paintMode.enumValueIndex == 0) { EditorGUILayout.Space(); PaintButton(ClothPainter.PaintMode.Attribute); } else EditorGUILayout.Space(); } // Custom Skinning Foldout("Custom Skinning", serializedObject.FindProperty("serializeData.customSkinningSetting.enable"), null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.customSkinningSetting.skinningBones")); }); } void ClothParameterInspector() { var cloth = target as MagicaCloth; ClothPresetUtility.DrawPresetButton(cloth, cloth.SerializeData); // Force Foldout("Force", null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.gravity"), new GUIContent("Gravity")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.gravityDirection")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.gravityFalloff")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.damping")); }); // Angle Restoration Foldout("Angle Restoration", serializedObject.FindProperty("serializeData.angleRestorationConstraint.useAngleRestoration"), null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.angleRestorationConstraint.stiffness")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.angleRestorationConstraint.velocityAttenuation")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.angleRestorationConstraint.gravityFalloff")); } ); // Angle Limit Foldout("Angle Limit", serializedObject.FindProperty("serializeData.angleLimitConstraint.useAngleLimit"), null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.angleLimitConstraint.limitAngle")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.angleLimitConstraint.stiffness")); } ); // Shape Foldout("Shape Restoration", null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.distanceConstraint.stiffness"), new GUIContent("Distance Stiffness")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.tetherConstraint.distanceCompression"), new GUIContent("Tether Compression")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.triangleBendingConstraint.stiffness"), new GUIContent("Triangle Bending Stiffness")); }); // Inertia Foldout("Inertia", null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.movementInertia")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.rotationInertia")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.depthInertia")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.centrifualAcceleration")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.movementSpeedLimit")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.rotationSpeedLimit")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.inertiaConstraint.particleSpeedLimit")); }); // Motion Foldout("Movement Limit", null, () => { var useMaxDistance = serializedObject.FindProperty("serializeData.motionConstraint.useMaxDistance"); var useBackstop = serializedObject.FindProperty("serializeData.motionConstraint.useBackstop"); EditorGUILayout.PropertyField(useMaxDistance); using (new EditorGUI.DisabledScope(!useMaxDistance.boolValue)) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.motionConstraint.maxDistance")); } EditorGUILayout.PropertyField(useBackstop); using (new EditorGUI.DisabledScope(!useBackstop.boolValue)) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.motionConstraint.backstopRadius")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.motionConstraint.backstopDistance")); } EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.motionConstraint.stiffness")); var paintMode = serializedObject.FindProperty("serializeData.paintMode"); if (paintMode.enumValueIndex == 0) PaintButton(ClothPainter.PaintMode.Motion); } ); // Collider Collision Foldout("Collider Collision", null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.colliderCollisionConstraint.mode")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.radius")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.colliderCollisionConstraint.friction")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.colliderCollisionConstraint.colliderList")); } ); // Self Collision Foldout("Self Collision", "Self Collision (Beta)", () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.selfCollisionConstraint.selfMode")); var syncMode = serializedObject.FindProperty("serializeData.selfCollisionConstraint.syncMode"); EditorGUILayout.PropertyField(syncMode); if (syncMode.enumValueIndex != 0) { EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.selfCollisionConstraint.syncPartner")); } EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.selfCollisionConstraint.surfaceThickness")); EditorGUILayout.PropertyField(serializedObject.FindProperty("serializeData.selfCollisionConstraint.clothMass")); } ); } /// /// 各プロパティの設定範囲.デフォルトは(0.0 ~ 1.0) /// /// /// public static Vector2 GetPropertyMinMax(string propertyName) { var minmax = new Vector2(0.0f, 1.0f); switch (propertyName) { case "radius": minmax.Set(0.001f, 0.5f); break; case "limitAngle": minmax.Set(0.0f, 180.0f); break; case "maxDistance": minmax.Set(0.0f, 5.0f); break; case "surfaceThickness": minmax.Set(Define.System.SelfCollisionThicknessMin, Define.System.SelfCollisionThicknessMax); break; case "movementSpeedLimit": minmax.Set(0.0f, Define.System.MaxMovementSpeedLimit); break; case "rotationSpeedLimit": minmax.Set(0.0f, Define.System.MaxRotationSpeedLimit); break; case "particleSpeedLimit": minmax.Set(0.0f, Define.System.MaxParticleSpeedLimit); break; } return minmax; } void GizmoInspector() { #if MC2_DEBUG EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData")); #else FoldOut("Gizmos", null, () => { EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.always")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.enable")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.ztest")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.position")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.axis")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.shape")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.baseLine")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.depth")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.collider")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.animatedPosition")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.animatedAxis")); EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.animatedShape")); //EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.basicPosition")); //EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.basicAxis")); //EditorGUILayout.PropertyField(serializedObject.FindProperty("gizmoSerializeData.clothDebugSettings.basicShape")); }); #endif } //========================================================================================= /// /// 折りたたみ制御 /// /// 折りたたみ保存キー /// /// 内容描画アクション /// 有効フラグアクション(null=無効) /// 現在の有効フラグ public void Foldout( string foldKey, string title = null, System.Action drawAct = null, System.Action enableAct = null, bool enable = true ) { var style = new GUIStyle("ShurikenModuleTitle"); style.font = new GUIStyle(EditorStyles.label).font; style.border = new RectOffset(15, 7, 4, 4); style.fixedHeight = 22; style.contentOffset = new Vector2(20f, -2f); var rect = GUILayoutUtility.GetRect(16f, 22f, style); GUI.backgroundColor = Color.white; GUI.Box(rect, title ?? foldKey, style); var e = Event.current; bool foldOut = EditorPrefs.GetBool(foldKey); if (enableAct == null) { if (e.type == EventType.Repaint) { var arrowRect = new Rect(rect.x + 4f, rect.y + 2f, 13f, 13f); EditorStyles.foldout.Draw(arrowRect, false, false, foldOut, false); } } else { // 有効チェック var toggleRect = new Rect(rect.x + 4f, rect.y + 4f, 13f, 13f); bool sw = GUI.Toggle(toggleRect, enable, string.Empty, new GUIStyle("ShurikenCheckMark")); if (sw != enable) { enableAct(sw); } } if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition)) { foldOut = !foldOut; EditorPrefs.SetBool(foldKey, foldOut); e.Use(); } if (foldOut && drawAct != null) { using (new EditorGUI.IndentLevelScope()) { using (new EditorGUI.DisabledScope(!enable)) { drawAct(); } } } } /// /// 折りたたみ制御(Boolプロパティによるチェックあり) /// /// /// /// /// public void Foldout( string foldKey, SerializedProperty boolProperty, string title = null, System.Action drawAct = null ) { Foldout( foldKey, title, drawAct, (sw) => boolProperty.boolValue = sw, boolProperty.boolValue ); } void FoldOut(string key, string title = null, System.Action drawAct = null) { bool foldOut1 = EditorPrefs.GetBool(key); bool foldOut2 = EditorGUILayout.Foldout(foldOut1, title ?? key); if (foldOut2) { using (new EditorGUI.IndentLevelScope()) { drawAct?.Invoke(); } } if (foldOut1 != foldOut2) { EditorPrefs.SetBool(key, foldOut2); } } void PaintButton(ClothPainter.PaintMode paintMode) { if (EditorApplication.isPlaying) return; var cloth = target as MagicaCloth; using (new EditorGUILayout.HorizontalScope()) { switch (paintMode) { case ClothPainter.PaintMode.Attribute: GUI.backgroundColor = new Color(0.5f, 1.0f, 0.5f); break; case ClothPainter.PaintMode.Motion: GUI.backgroundColor = new Color(0.0f, 1.0f, 1.0f); break; } EditorGUILayout.Space(); bool edit = ClothPainter.HasEditCloth(cloth); //var icon = edit ? EditorGUIUtility.IconContent("winbtn_win_close") : EditorGUIUtility.IconContent("d_editicon.sml"); //var icon = EditorGUIUtility.IconContent("d_Grid.PaintTool");// 良い var icon = EditorGUIUtility.IconContent("d_editicon.sml"); if (GUILayout.Button(icon, GUILayout.Width(40))) { if (edit == false) { // 最新の編集メッシュからセレクションデータを生成する var editMesh = ClothEditorManager.GetEditMesh(cloth); if (editMesh != null) { // すでにセレクションデータが存在し、かつユーザー編集データならばコンバートする var selectionData = GetSelectionData(cloth, editMesh); // セレクションデータにメッシュの最大接続距離を記録する selectionData.maxConnectionDistance = editMesh.maxVertexDistance.Value; // ペイント開始 ClothPainter.EnterPaint(paintMode, this, cloth, editMesh, selectionData); SceneView.RepaintAll(); } } else { ClothPainter.ExitPaint(); SceneView.RepaintAll(); } } EditorGUILayout.Space(); GUI.backgroundColor = Color.white; } EditorGUILayout.Space(10); } } }