public static IvyGraph MergeVisibleIvyGraphs(IvyBehavior ivyBehavior, IvyProfile ivyProfile, bool rebuildMesh = true) { var graphsToMerge = ivyBehavior.ivyGraphs.Where(graph => graph.isVisible).ToList(); if (graphsToMerge == null || graphsToMerge.Count == 0) { return(null); } var mainGraph = graphsToMerge[0]; for (int i = 0; i < ivyBehavior.ivyGraphs.Count; i++) { var graph = ivyBehavior.ivyGraphs[i]; if (!graph.isVisible || graph == mainGraph) { continue; } // convert merged graph's localPos to mainGraph's localPos foreach (var root in graph.roots) { foreach (var node in root.nodes) { node.p += graph.seedPos - mainGraph.seedPos; } } mainGraph.roots.AddRange(graph.roots); if (graph.rootGO != null) { DestroyObject(graph.rootGO); } TryToDestroyMeshes(ivyBehavior, graph); ivyBehavior.ivyGraphs.Remove(graph); i--; } if (rebuildMesh) { Undo.RegisterFullObjectHierarchyUndo(mainGraph.rootGO, "Hedera > Merge Visible"); IvyMesh.GenerateMesh(mainGraph, ivyProfile, ivyProfile.useLightmapping, true); AssetDatabase.SaveAssets(); } return(mainGraph); }
public static void TryToDestroyMeshes(IvyBehavior ivyBehavior, IvyGraph ivyGraph, bool suppressConfirm = false) { var go = ivyGraph.rootGO != null ? ivyGraph.rootGO : ivyBehavior.gameObject; // if it's a prefab or prefab instance, then confirm before destroying if (IsPartOfPrefab(go) && !suppressConfirm) { if (!ConfirmDestroyMeshes(go.name)) { return; } } // if it's not part of a prefab, then we can safely destroy the mesh var asset = GetDataAsset(go); TryDestroyMesh(ivyGraph.branchMeshID, asset, false); TryDestroyMesh(ivyGraph.leafMeshID, asset, false); AssetDatabase.SaveAssets(); }
// got working painter code from https://github.com/marmitoTH/Unity-Prefab-Placement-Editor private void OnSceneGUI() { if (ivyBehavior == null) { ivyBehavior = (IvyBehavior)target; } Handles.color = ivyBehavior.debugColor; foreach (var graph in ivyBehavior.ivyGraphs) { if (graph.isVisible && (graph.branchMF == null || graph.branchR == null || (graph.rootGO != null && Vector3.SqrMagnitude(graph.rootGO.transform.position - graph.seedPos) > 0.001f))) { DrawThiccDisc(graph.seedPos, graph.seedNormal, 0.05f); DrawDebugIvy(graph); } } Event current = Event.current; // change current editor tool for painting and positioning if ((isPlantingModeActive || currentIvyGraphMove != null)) { if (Tools.current != Tool.None) { LastTool = Tools.current; Tools.current = Tool.None; Tools.hidden = true; } } else if (LastTool != Tool.None) { Tools.current = LastTool; LastTool = Tool.None; Tools.hidden = false; } if (currentIvyGraphMove != null) { if (current.type == EventType.MouseDrag) { Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Move Ivy Seed Position"); if (currentIvyGraphMove.rootGO != null) { Undo.RegisterCompleteObjectUndo(currentIvyGraphMove.rootGO, "Hedera > Move Ivy Seed Position"); } } isPlantingModeActive = false; currentIvyGraphMove.seedPos = Handles.PositionHandle(currentIvyGraphMove.seedPos, Quaternion.identity); if (currentIvyGraphMove.rootGO != null) { currentIvyGraphMove.rootGO.transform.position = currentIvyGraphMove.seedPos; } Handles.Label(currentIvyGraphMove.seedPos, currentIvyGraphMove.seedPos.ToString()); } if (!isPlantingModeActive) { return; } int controlId = GUIUtility.GetControlID(GetHashCode(), FocusType.Passive); MousePosition(); if (Event.current.type == EventType.Repaint) { deltaTime = EditorApplication.timeSinceStartup - lastEditorTime; lastEditorTime = EditorApplication.timeSinceStartup; } if ((current.type == EventType.MouseDrag || current.type == EventType.MouseDown)) { if (current.button == 0 && (lastPos == Vector3.zero || CanDraw()) && !current.shift) { mouseDirection = Vector3.MoveTowards(mouseDirection, (mousePos - lastPos).normalized, System.Convert.ToSingle(deltaTime)); lastPos = mousePos; if (current.type == EventType.MouseDown) { ivyBehavior.transform.localScale = Vector3.one; Undo.SetCurrentGroupName("Hedera > Paint Ivy"); Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Paint Ivy"); currentIvyGraph = IvyCore.SeedNewIvyGraph(ivyBehavior.profileAsset.ivyProfile, lastPos, Vector3.up, -mouseNormal, ivyBehavior.transform, ivyBehavior.generateMeshDuringGrowth); currentIvyGraph.isGrowing = false; ivyBehavior.ivyGraphs.Add(currentIvyGraph); } else if (currentIvyGraph != null) { IvyCore.ForceIvyGrowth(currentIvyGraph, ivyBehavior.profileAsset.ivyProfile, lastPos, mouseNormal); } } // else if (current.button == 0 && current.shift) // { // erase // lastPos = mousePos; // if (current.type == EventType.MouseDown) { // Undo.SetCurrentGroupName( "Hedera > Erase Ivy"); // } else { // } // } } if (current.type == EventType.MouseUp) { ivyBehavior.transform.localScale = Vector3.one; lastPos = Vector3.zero; if (currentIvyGraph != null) { currentIvyGraph.isGrowing = ivyBehavior.enableGrowthSim; if (currentIvyGraph.isGrowing && currentIvyGraph.roots.Count > 0) { float branchPercentage = Mathf.Clamp(currentIvyGraph.roots[0].nodes.Last().cS / ivyBehavior.profileAsset.ivyProfile.maxLength, 0f, 0.38f); int branchCount = Mathf.FloorToInt(ivyBehavior.profileAsset.ivyProfile.maxBranchesTotal * branchPercentage * ivyBehavior.profileAsset.ivyProfile.branchingProbability); for (int b = 0; b < branchCount; b++) { IvyCore.ForceRandomIvyBranch(currentIvyGraph, ivyBehavior.profileAsset.ivyProfile); } } else { IvyCore.needToSaveAssets = true; } currentIvyGraph = null; } } if (Event.current.type == EventType.Layout) { HandleUtility.AddDefaultControl(controlId); } SceneView.RepaintAll(); }
//called whenever the inspector gui gets rendered public override void OnInspectorGUI() { EditorGUILayout.Space(); if (ivyBehavior == null) { ivyBehavior = (IvyBehavior)target; } wasPartOfPrefab = IvyCore.IsPartOfPrefab(ivyBehavior.gameObject); bool isInARealScene = !string.IsNullOrEmpty(ivyBehavior.gameObject.scene.path) && ivyBehavior.gameObject.activeInHierarchy; if (isInARealScene) { lastDataAsset = IvyCore.GetDataAsset(ivyBehavior.gameObject); } EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUI.BeginChangeCheck(); ivyBehavior.profileAsset = EditorGUILayout.ObjectField(ivyBehavior.profileAsset, typeof(IvyProfileAsset), false) as IvyProfileAsset; if (EditorGUI.EndChangeCheck() || (ivyProfileEditor == null && ivyBehavior.profileAsset != null)) { ivyProfileEditor = Editor.CreateEditor(ivyBehavior.profileAsset); ((IvyProfileEditor)ivyProfileEditor).viewedFromMonobehavior = true; } // destroy old editor / cleanup if (ivyBehavior.profileAsset == null && ivyProfileEditor != null) { DestroyImmediate(ivyProfileEditor); } if (ivyBehavior.profileAsset == null || ivyProfileEditor == null) { EditorGUILayout.HelpBox("Please assign an Ivy Profile Asset.", MessageType.Warning); if (GUILayout.Button("Create new Ivy Profile Asset...")) { var newAsset = IvyCore.CreateNewAsset(""); if (newAsset != null) { ivyBehavior.profileAsset = newAsset; ivyBehavior.showProfileFoldout = true; Selection.activeGameObject = ivyBehavior.gameObject; } } EditorGUILayout.EndVertical(); return; } var ivyProfile = ivyBehavior.profileAsset.ivyProfile; if (!IvyCore.ivyBehaviors.Contains(ivyBehavior)) { IvyCore.ivyBehaviors.Add(ivyBehavior); } GUIContent content = null; EditorGUI.indentLevel++; ivyBehavior.showProfileFoldout = EditorGUILayout.Foldout(ivyBehavior.showProfileFoldout, "Ivy Profile Settings", true); EditorGUI.indentLevel--; if (EditorGUILayout.BeginFadeGroup(ivyBehavior.showProfileFoldout ? 1 : 0)) { ivyProfileEditor.OnInspectorGUI(); } EditorGUILayout.EndFadeGroup(); EditorGUILayout.EndVertical(); DrawUILine(); GUILayout.Label("Ivy Painter", EditorStyles.boldLabel); if (!isInARealScene) { EditorGUILayout.HelpBox("Painting / mesh generation only works in saved scenes on active game objects.\n- Save the scene?\n- Put this game object in a saved scene?\n- Make sure it is active?", MessageType.Error); GUI.enabled = false; } // if Gizmos aren't drawn in scene view, then we can't paint anything since OnSceneGUI() is no longer called... but this warning is only supported in Unity 2019.1 or newer // see issue: https://github.com/radiatoryang/hedera/issues/6 #if UNITY_2019_1_OR_NEWER if (SceneView.lastActiveSceneView.drawGizmos == false) { GUI.enabled = false; EditorGUILayout.HelpBox("Gizmos are disabled in the Scene View, which breaks OnSceneGUI(), so ivy painting is disabled.", MessageType.Error); } #endif // plant root creation button var oldColor = GUI.color; GUI.color = isPlantingModeActive ? Color.yellow : Color.Lerp(Color.yellow, oldColor, 0.69f); content = new GUIContent(!isPlantingModeActive ? " Start Painting Ivy": " Stop Painting Ivy", iconPaint, "while painting, left-click and drag in the Scene view on any collider"); if (GUILayout.Button(content, GUILayout.Height(20))) { isPlantingModeActive = !isPlantingModeActive; } GUI.color = oldColor; content = new GUIContent(" Enable Growth Sim AI", "If disabled, then you can just paint ivy without simulation or AI, which is useful when you want small strokes or full control."); ivyBehavior.enableGrowthSim = EditorGUILayout.ToggleLeft(content, ivyBehavior.enableGrowthSim); content = new GUIContent(" Make Mesh During Painting / Growth", "Generate 3D ivy mesh during painting and growth. Very cool, but very processing intensive. If your computer gets very slow while painting, then disable this."); ivyBehavior.generateMeshDuringGrowth = EditorGUILayout.ToggleLeft(content, ivyBehavior.generateMeshDuringGrowth); int visibleIvy = ivyBehavior.ivyGraphs.Where(ivy => ivy.isVisible).Count(); GUI.enabled = isInARealScene && visibleIvy > 0; EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); content = new GUIContent(" Re-mesh Visible", iconMesh, "Remake meshes for all visible ivy, all at once. Useful when you change your ivy profile settings, and want to see the new changes."); if (GUILayout.Button(content, EditorStyles.miniButtonLeft, GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth * 0.45f), GUILayout.Height(16))) { if (EditorUtility.DisplayDialog("Hedera: Remake All Visible Meshes", string.Format("Are you sure you want to remake {0} meshes all at once? It also might be very slow or crash your editor.", visibleIvy), "YES!", "Maybe not...")) { foreach (var ivy in ivyBehavior.ivyGraphs) { if (!ivy.isVisible) { continue; } if (ivy.rootGO != null) { Undo.RegisterFullObjectHierarchyUndo(ivy.rootGO, "Hedera > Re-mesh Visible"); } else { IvyMesh.InitOrRefreshRoot(ivy, ivyProfile); Undo.RegisterCreatedObjectUndo(ivy.rootGO, "Hedera > Re-mesh Visible"); } IvyMesh.GenerateMesh(ivy, ivyProfile, ivyProfile.useLightmapping, true); } } } content = new GUIContent(" Merge Visible", iconLeaf, "Merge all visible ivy into a single ivy / single mesh. This is (usually) good for optimizing the 3D performance of your scene, especially if you have a lot of ivy everywhere."); if (GUILayout.Button(content, EditorStyles.miniButtonRight, GUILayout.Height(16))) { if (EditorUtility.DisplayDialog("Hedera: Merge All Visible Ivy Strokes", string.Format("Are you sure you want to merge {0} ivy plants into one?", visibleIvy), "YES!", "Maybe not...")) { Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Merge Visible"); Undo.SetCurrentGroupName("Hedera > Merge Visible"); //var rootIvyB = IvyCore.StartDestructiveEdit(ivyBehavior, applyAllOverrides:true ); IvyCore.MergeVisibleIvyGraphs(ivyBehavior, ivyProfile); // IvyCore.CommitDestructiveEdit(); } } EditorGUILayout.EndHorizontal(); GUI.enabled = isInARealScene; if (ivyBehavior.ivyGraphs.Count == 0) { EditorGUILayout.HelpBox("To paint Ivy, first click [Start Painting Ivy]... then hold down [Left Mouse Button] on a collider in the Scene view, and drag.", MessageType.Info); } lastMeshIDs.Clear(); IvyGraph ivyGraphObjJob = null; // used to pull .OBJ export out of the for() loop var oldBGColor = GUI.backgroundColor; var pulseColor = Color.Lerp(oldBGColor, Color.yellow, Mathf.PingPong(System.Convert.ToSingle(EditorApplication.timeSinceStartup) * 2f, 1f)); for (int i = 0; i < ivyBehavior.ivyGraphs.Count; i++) { GUI.enabled = isInARealScene; var ivy = ivyBehavior.ivyGraphs[i]; if (ivy.isGrowing) { GUI.backgroundColor = pulseColor; } lastMeshIDs.Add(ivy.leafMeshID); lastMeshIDs.Add(ivy.branchMeshID); EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); GUI.backgroundColor = oldBGColor; GUI.color = ivy.isVisible ? oldColor : Color.gray; var eyeIcon = ivy.isVisible ? iconVisOn : iconVisOff; content = new GUIContent(eyeIcon, "Click to toggle visibility for this ivy plant.\n(Enable / disable the game object.)"); if (GUILayout.Button(content, EditorStyles.miniButtonLeft, GUILayout.Height(16), GUILayout.Width(24))) { ivy.isVisible = !ivy.isVisible; if (ivy.rootGO != null) { ivy.rootGO.SetActive(ivy.isVisible); } } GUI.color = oldColor; GUI.color = ivy != currentIvyGraphMove ? oldColor : Color.gray; content = new GUIContent(iconMove, "Click to start moving the seed position for this ivy plant."); if (GUILayout.Button(content, EditorStyles.miniButtonRight, GUILayout.Height(16), GUILayout.Width(24))) { if (ivy.rootGO != null) { ivy.rootGO.transform.position = ivy.seedPos; } currentIvyGraphMove = ivy == currentIvyGraphMove ? null : ivy; } GUI.color = oldColor; if (ivy.rootGO != null) { GUI.enabled = false; EditorGUILayout.ObjectField(ivy.rootGO, typeof(GameObject), true); GUI.enabled = isInARealScene; } else { string ivyLabel = string.Format( "(no mesh) {0} ivy", ivy.roots.Count, ivy.seedPos ); GUILayout.Label(ivyLabel, EditorStyles.miniLabel); } if (!ivy.isGrowing) { content = new GUIContent(iconMesh, "Make (or remake) the 3D mesh for this ivy"); if (GUILayout.Button(content, EditorStyles.miniButtonLeft, GUILayout.Width(24), GUILayout.Height(16))) { if (ivy.rootGO != null) { Undo.RegisterFullObjectHierarchyUndo(ivy.rootGO, "Hedera > Make Mesh"); } else { IvyMesh.InitOrRefreshRoot(ivy, ivyProfile); Undo.RegisterCreatedObjectUndo(ivy.rootGO, "Hedera > Make Mesh"); } IvyMesh.GenerateMesh(ivy, ivyProfile, ivyProfile.useLightmapping, true); Repaint(); } GUI.enabled = ivy.branchMF != null || ivy.leafMF != null; content = new GUIContent("OBJ", iconExport, "Export ivy mesh to .OBJ file\n(Note: .OBJs only support one UV channel so they cannot have lightmap UVs, Unity must unwrap them upon import)"); if (GUILayout.Button(content, EditorStyles.miniButtonMid, GUILayout.Width(24), GUILayout.Height(16))) { ivyGraphObjJob = ivy; } GUI.enabled = isInARealScene; content = new GUIContent(iconTrash, "Delete this ivy as well as its mesh objects."); if (GUILayout.Button(content, EditorStyles.miniButtonRight, GUILayout.Width(24), GUILayout.Height(16))) { if (ivy.rootGO != null) { IvyCore.DestroyObject(ivy.rootGO); } IvyCore.TryToDestroyMeshes(ivyBehavior, ivy); Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Delete Ivy"); ivyBehavior.ivyGraphs.Remove(ivy); EditorGUILayout.EndHorizontal(); i--; continue; } } else { if (GUILayout.Button("Stop Growing", EditorStyles.miniButton)) { ivy.isGrowing = false; } } EditorGUILayout.EndHorizontal(); } if (ivyBehavior.ivyGraphs.Where(ivy => ivy.isGrowing).Count() > 0) { EditorGUILayout.Space(); GUI.color = pulseColor; if (GUILayout.Button("Stop All Growing")) { IvyCore.ForceStopGrowing(); } GUI.color = oldColor; } GUI.enabled = true; EditorGUILayout.Space(); content = new GUIContent("Debug Color", "When ivy doesn't have a mesh, Hedera will visualize the ivy structure as a debug wireframe with this color in the Scene view."); ivyBehavior.debugColor = EditorGUILayout.ColorField(content, ivyBehavior.debugColor); EditorGUILayout.Space(); // was getting GUI errors doing OBJ export inline, so let's do it outside of the for() loop if (ivyGraphObjJob != null) { var filename = ObjExport.SaveObjFile(new GameObject[] { ivyGraphObjJob.rootGO }, true); if (isInARealScene && !string.IsNullOrEmpty(filename) && filename.StartsWith(Application.dataPath) && AssetDatabase.IsMainAssetAtPathLoaded("Assets" + filename.Substring(Application.dataPath.Length)) ) { int choice = EditorUtility.DisplayDialogComplex("Hedera: Instantiate .OBJ into scene?", "You just exported ivy into a .OBJ into your project.\nDo you want to replace the ivy with the .OBJ?", "Yes, and delete old ivy", "No, don't instantiate", "Yes, and hide old ivy"); if (choice == 0 || choice == 2) { var prefab = AssetDatabase.LoadAssetAtPath <Object>("Assets" + filename.Substring(Application.dataPath.Length)); var newObj = (GameObject)PrefabUtility.InstantiatePrefab(prefab); Undo.RegisterCreatedObjectUndo(newObj, "Hedera > Instantiate OBJ"); newObj.transform.SetParent(ivyBehavior.transform); newObj.transform.position = ivyGraphObjJob.seedPos; var renders = newObj.GetComponentsInChildren <Renderer>(); renders[0].material = ivyProfile.branchMaterial; if (renders.Length > 1) { renders[1].material = ivyProfile.leafMaterial; } if (choice == 0) // remove old ivy { if (ivyGraphObjJob.rootGO != null) { IvyCore.DestroyObject(ivyGraphObjJob.rootGO); } IvyCore.TryToDestroyMeshes(ivyBehavior, ivyGraphObjJob); Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Instantiate OBJ"); ivyBehavior.ivyGraphs.Remove(ivyGraphObjJob); } else // just turn off old ivy { Undo.RegisterCompleteObjectUndo(ivyBehavior, "Hedera > Instantiate OBJ"); ivyGraphObjJob.isVisible = false; if (ivyGraphObjJob.rootGO != null) { ivyGraphObjJob.rootGO.SetActive(false); } } } } } }