using UnityEngine; using UnityEditor; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using System.IO; using System.Collections; using System.Collections.Generic; namespace Obi { [CustomEditor(typeof(ObiTriangleSkinMap))] public class ObiTriangleSkinMapEditor : Editor { public enum SubjectBeingEdited { Master, Slave, } public ObiTriangleSkinMap skinMap; SceneSetup[] oldSetup; Object oldSelection; GameObject masterObject; GameObject slaveObject; Material paintMaterial; Material standardMaterial; // skin channel painting stuff: static bool editMode = false; static bool paintMode = false; static int targetSkinChannel = 0; ObiRaycastBrush paintBrush; ObiSkinMapChannel currentProperty = null; Mesh masterMesh = null; public SubjectBeingEdited subject = SubjectBeingEdited.Master; protected IEnumerator routine; Vector3[] wsPositions = new Vector3[0]; public bool[] facingCamera = new bool[0]; SerializedProperty barycentricWeight; SerializedProperty normalAlignmentWeight; SerializedProperty elevationWeight; public void OnEnable() { skinMap = (ObiTriangleSkinMap)target; barycentricWeight = serializedObject.FindProperty("barycentricWeight"); normalAlignmentWeight = serializedObject.FindProperty("normalAlignmentWeight"); elevationWeight = serializedObject.FindProperty("elevationWeight"); paintBrush = new ObiRaycastBrush(skinMap.slave, () => { // As RecordObject diffs with the end of the current frame, // and this is a multi-frame operation, we need to use RegisterCompleteObjectUndo instead. Undo.RegisterCompleteObjectUndo(skinMap, "Paint skin channel"); }, () => { SceneView.RepaintAll(); }, () => { EditorUtility.SetDirty(skinMap); }); currentProperty = new ObiSkinMapChannel(this); paintBrush.brushMode = new ObiMasterSlavePaintBrushMode(currentProperty); Selection.selectionChanged += OnSelectionChange; } public void OnDisable() { Selection.selectionChanged -= OnSelectionChange; DestroyImmediate(masterMesh); ExitSkinEditMode(); } public override bool UseDefaultMargins() { return false; } void OnSelectionChange() { if (editMode) { if (masterObject != null && Selection.activeGameObject == masterObject) EditMaster(); if (slaveObject != null && Selection.activeGameObject == slaveObject) EditSlave(); } } private void EditMaster() { subject = SubjectBeingEdited.Master; paintBrush.raycastTarget = masterMesh; SceneView.RepaintAll(); } private void EditSlave() { subject = SubjectBeingEdited.Slave; paintBrush.raycastTarget = skinMap.slave; SceneView.RepaintAll(); } public void GetMaterials() { if (paintMaterial == null) paintMaterial = Resources.Load("PropertyGradientMaterial"); if (standardMaterial == null) standardMaterial = new Material(Shader.Find("Standard")); } public override void OnInspectorGUI() { serializedObject.UpdateIfRequiredOrScript(); GetMaterials(); if (!editMode) { UpdateNormalMode(); } else { UpdateEditMode(); } if (GUI.changed) { serializedObject.ApplyModifiedProperties(); // Apply transform changes back to the slave: if (slaveObject != null) skinMap.m_SlaveTransform.Apply(slaveObject.transform); } } private void MeshFromMasterBlueprint() { DestroyImmediate(masterMesh); masterMesh = new Mesh(); Vector3[] vertices = new Vector3[skinMap.master.activeParticleCount]; Vector3[] normals = new Vector3[skinMap.master.activeParticleCount]; List triangles = new List(); for (int i = 0; i < vertices.Length; ++i) vertices[i] = skinMap.master.positions[i]; for (int i = 0; i < normals.Length; ++i) normals[i] = skinMap.master.restNormals[i]; for (int i = 0; i < skinMap.master.deformableTriangles.Length; i += 3) { if (skinMap.master.deformableTriangles[i] >= skinMap.master.activeParticleCount || skinMap.master.deformableTriangles[i + 1] >= skinMap.master.activeParticleCount || skinMap.master.deformableTriangles[i + 2] >= skinMap.master.activeParticleCount) continue; triangles.Add(skinMap.master.deformableTriangles[i]); triangles.Add(skinMap.master.deformableTriangles[i+1]); triangles.Add(skinMap.master.deformableTriangles[i+2]); } masterMesh.vertices = vertices; masterMesh.normals = normals; masterMesh.SetTriangles(triangles, 0); } void EnterSkinEditMode() { if (!editMode) { #if (UNITY_2019_1_OR_NEWER) SceneView.duringSceneGui += this.OnSceneGUI; #else SceneView.onSceneGUIDelegate += this.OnSceneGUI; #endif oldSelection = Selection.activeObject; if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { ActiveEditorTracker.sharedTracker.isLocked = true; oldSetup = EditorSceneManager.GetSceneManagerSetup(); EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects); if (skinMap.master != null) { MeshFromMasterBlueprint(); masterObject = new GameObject("Master mesh", typeof(MeshRenderer), typeof(MeshFilter)); masterObject.GetComponent().material = standardMaterial; masterObject.GetComponent().sharedMesh = Instantiate(masterMesh); Selection.activeGameObject = masterObject; } if (skinMap.slave != null) { slaveObject = new GameObject("Slave mesh", typeof(MeshRenderer), typeof(MeshFilter)); slaveObject.GetComponent().material = standardMaterial; slaveObject.GetComponent().sharedMesh = Instantiate(skinMap.slave); skinMap.m_SlaveTransform.Apply(slaveObject.transform); } EditMaster(); SceneView.FrameLastActiveSceneView(); editMode = true; } } } void ExitSkinEditMode() { if (editMode) { editMode = false; ActiveEditorTracker.sharedTracker.isLocked = false; if (SceneManager.GetActiveScene().path.Length <= 0) { if (this.oldSetup != null && this.oldSetup.Length > 0) { EditorSceneManager.RestoreSceneManagerSetup(this.oldSetup); this.oldSetup = null; } else { EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects); } } Selection.activeObject = oldSelection; Tools.hidden = false; #if (UNITY_2019_1_OR_NEWER) SceneView.duringSceneGui -= this.OnSceneGUI; #else SceneView.onSceneGUIDelegate -= this.OnSceneGUI; #endif } } void UpdateNormalMode() { EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); EditorGUI.BeginChangeCheck(); ObiClothBlueprintBase master = EditorGUILayout.ObjectField("Master blueprint", skinMap.master, typeof(ObiClothBlueprintBase), false) as ObiClothBlueprintBase; if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(skinMap, "Set skin master"); skinMap.master = master; } EditorGUI.BeginChangeCheck(); Mesh slaveMesh = EditorGUILayout.ObjectField("Slave mesh", skinMap.slave, typeof(Mesh), false) as Mesh; if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(skinMap, "Set skin slave"); skinMap.slave = slaveMesh; } // Print skin info: if (skinMap.bound) { EditorGUILayout.HelpBox("Skin info generated." + skinMap.skinnedVertices.Count, MessageType.Info); } // Error / warning messages bool errors = false; if (skinMap.master == null) { EditorGUILayout.HelpBox("Please provide a master blueprint.", MessageType.Info); errors = true; } if (skinMap.slave == null) { EditorGUILayout.HelpBox("Please provide a slave mesh.", MessageType.Info); errors = true; } // Edit mode buttons: GUI.enabled = !errors && !Application.isPlaying; if (GUILayout.Button("Edit skin map")) EditorApplication.delayCall += EnterSkinEditMode; GUI.enabled = true; EditorGUILayout.EndVertical(); } void UpdateEditMode() { EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); EditorGUILayout.PropertyField(barycentricWeight); EditorGUILayout.PropertyField(normalAlignmentWeight); EditorGUILayout.PropertyField(elevationWeight); EditorGUI.BeginChangeCheck(); paintMode = GUILayout.Toggle(paintMode, new GUIContent("Paint skin", Resources.Load("PaintButton")), "LargeButton"); Tools.hidden = paintMode || subject == SubjectBeingEdited.Master; if (EditorGUI.EndChangeCheck()) SceneView.RepaintAll(); // Buttons: GUILayout.BeginHorizontal(); if (GUILayout.Button("Bind") && masterObject != null && slaveObject != null) { EditorUtility.SetDirty(target); CoroutineJob job = new CoroutineJob(); routine = job.Start(skinMap.Bind()); EditorCoroutine.ShowCoroutineProgressBar("Generating skinmap...", ref routine); EditorGUIUtility.ExitGUI(); } if (GUILayout.Button("Done")) EditorApplication.delayCall += ExitSkinEditMode; GUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); // skin channel selector: if (paintMode) { EditorGUILayout.Space(); GUILayout.Box(GUIContent.none, ObiEditorUtils.GetSeparatorLineStyle()); EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); // Brush parameters: paintBrush.radius = EditorGUILayout.Slider("Brush size", paintBrush.radius, 0.0001f, 0.5f); paintBrush.innerRadius = 1; paintBrush.opacity = 1; EditorGUI.BeginChangeCheck(); if (paintBrush.brushMode.needsInputValue) currentProperty.PropertyField(); if (EditorGUI.EndChangeCheck()) SceneView.RepaintAll(); GUILayout.BeginHorizontal(); if (GUILayout.Button(new GUIContent("Fill"), EditorStyles.miniButtonLeft)) { if (subject == SubjectBeingEdited.Master) skinMap.FillChannel(skinMap.m_MasterChannels,currentProperty.GetDefault()); else skinMap.FillChannel(skinMap.m_SlaveChannels, currentProperty.GetDefault()); SceneView.RepaintAll(); } if (GUILayout.Button(new GUIContent("Clear"), EditorStyles.miniButtonMid)) { if (subject == SubjectBeingEdited.Master) skinMap.ClearChannel(skinMap.m_MasterChannels, currentProperty.GetDefault()); else skinMap.ClearChannel(skinMap.m_SlaveChannels, currentProperty.GetDefault()); SceneView.RepaintAll(); } if (GUILayout.Button(new GUIContent("Copy"), EditorStyles.miniButtonMid)) targetSkinChannel = currentProperty.GetDefault(); if (GUILayout.Button(new GUIContent("Paste"), EditorStyles.miniButtonRight)) { if (subject == SubjectBeingEdited.Master) skinMap.CopyChannel(skinMap.m_MasterChannels, targetSkinChannel, currentProperty.GetDefault()); else skinMap.CopyChannel(skinMap.m_SlaveChannels, targetSkinChannel, currentProperty.GetDefault()); SceneView.RepaintAll(); } GUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } EditorGUILayout.Space(); GUILayout.Box(GUIContent.none, ObiEditorUtils.GetSeparatorLineStyle()); EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Slave transform", EditorStyles.boldLabel); if (GUILayout.Button("Reset", EditorStyles.miniButton)) skinMap.m_SlaveTransform.Reset(); EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(serializedObject.FindProperty("m_SlaveTransform")); EditorGUILayout.EndVertical(); } // OnSceneGUI doesnt seem to be called for ScriptableObjects, so we need to tap onto the (undocumented) SceneView class. public void OnSceneGUI(SceneView sceneView) { if (slaveObject != null) skinMap.m_SlaveTransform = new ObiTriangleSkinMap.SkinTransform(slaveObject.transform); // Change materials when painting weights: if (Event.current.type == EventType.Repaint) { UpdateSourceObject(); UpdateTargetObject(); } if (!paintMode || sceneView.camera == null) return; if (subject == SubjectBeingEdited.Master) { paintBrush.raycastTransform = Matrix4x4.identity; UpdateBrushPositions(masterMesh, sceneView.camera); } else { paintBrush.raycastTransform = skinMap.m_SlaveTransform.GetMatrix4X4(); UpdateBrushPositions(skinMap.slave, sceneView.camera); } // Update paintbrush: if (Camera.current != null && wsPositions != null) paintBrush.DoBrush(wsPositions); } private void UpdateBrushPositions(Mesh mesh, Camera cam) { System.Array.Resize(ref wsPositions, mesh.vertexCount); System.Array.Resize(ref facingCamera, mesh.vertexCount); Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; for (int i = 0; i < vertices.Length; ++i) { wsPositions[i] = paintBrush.raycastTransform.MultiplyPoint3x4(vertices[i]); facingCamera[i] = Vector3.Dot(masterObject.transform.TransformVector(normals[i]), cam.transform.position - wsPositions[i]) > 0; } } void UpdateSourceObject() { if (masterObject == null) return; masterObject.GetComponent().material = standardMaterial; if (Selection.activeGameObject != masterObject) return; Selection.objects = new Object[] { masterObject }; if (paintMode) { masterObject.GetComponent().material = paintMaterial; if (slaveObject != null) slaveObject.GetComponent().material = standardMaterial; Mesh mesh = masterObject.GetComponent().sharedMesh; Color[] colors = new Color[mesh.vertexCount]; Color active = ObiEditorSettings.GetOrCreateSettings().propertyGradient.Evaluate(1); Color inactive = ObiEditorSettings.GetOrCreateSettings().propertyGradient.Evaluate(0); for (int i = 0; i < mesh.vertexCount; i++) { if ((skinMap.m_MasterChannels[i] & (1 << currentProperty.GetDefault())) != 0) colors[i] = active; else colors[i] = inactive; } mesh.colors = colors; if (paintMaterial.SetPass(1)) { Color wireColor = ObiEditorSettings.GetOrCreateSettings().brushWireframeColor; Mesh wireMesh = Instantiate(mesh); for (int i = 0; i < paintBrush.weights.Length; i++) { colors[i] = wireColor * paintBrush.weights[i]; } wireMesh.colors = colors; GL.wireframe = true; Graphics.DrawMeshNow(wireMesh, masterObject.transform.localToWorldMatrix); GL.wireframe = false; DestroyImmediate(wireMesh); } } } void UpdateTargetObject() { if (slaveObject == null) return; slaveObject.GetComponent().material = standardMaterial; if (Selection.activeGameObject != slaveObject) return; Selection.objects = new Object[] { slaveObject }; if (paintMode) { slaveObject.GetComponent().material = paintMaterial; if (masterObject != null) masterObject.GetComponent().material = standardMaterial; Mesh mesh = slaveObject.GetComponent().sharedMesh; Color[] colors = new Color[mesh.vertexCount]; Color active = ObiEditorSettings.GetOrCreateSettings().propertyGradient.Evaluate(1); Color inactive = ObiEditorSettings.GetOrCreateSettings().propertyGradient.Evaluate(0); for (int i = 0; i < mesh.vertexCount; i++) { if ((skinMap.m_SlaveChannels[i] & (1 << currentProperty.GetDefault())) != 0) colors[i] = active; else colors[i] = inactive; } mesh.colors = colors; if (paintMaterial.SetPass(1)) { Color wireColor = ObiEditorSettings.GetOrCreateSettings().brushWireframeColor; Mesh wireMesh = Instantiate(mesh); for (int i = 0; i < paintBrush.weights.Length; i++) { colors[i] = wireColor * paintBrush.weights[i]; } wireMesh.colors = colors; GL.wireframe = true; Graphics.DrawMeshNow(wireMesh, slaveObject.transform.localToWorldMatrix); GL.wireframe = false; DestroyImmediate(wireMesh); } } } } }