// 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(); }
public static void GenerateMesh(IvyGraph ivyGraph, IvyProfile ivyProfile, bool doUV2s = false, bool forceGeneration = false) { // avoid GC allocations by reusing static lists verticesAll.Clear(); texCoordsAll.Clear(); trianglesAll.Clear(); leafVerticesAll.Clear(); leafUVsAll.Clear(); leafTrianglesAll.Clear(); leafColorsAll.Clear(); // the main mesh generation actually happens here; if it can't generate a mesh, then stop if (!GenerateMeshData(ivyGraph, ivyProfile, forceGeneration)) { return; } ivyGraph.dirtyUV2s = !doUV2s; InitOrRefreshRoot(ivyGraph, ivyProfile); var myAsset = IvyCore.GetDataAsset(ivyGraph.rootGO); // Branch mesh debug // Debug.Log( "branchVertices: " + verticesAll.Count ); // Debug.Log( "branchTris: " + string.Join(", ", trianglesAll.Select( tri => tri.ToString() ).ToArray()) ); // foreach ( var vert in verticesAll ) { // Debug.DrawRay( vert + ivyGraph.seedPos, Vector3.up, Color.cyan, 1f, false ); // } if (ivyProfile.ivyBranchSize < 0.0001f) { if (ivyGraph.branchMF != null) { IvyCore.DestroyObject(ivyGraph.branchMF.gameObject); } IvyCore.TryDestroyMesh(ivyGraph.branchMeshID, myAsset, true); } else { CheckMeshDataAsset(ref ivyGraph.branchMeshID, myAsset, ivyProfile.meshCompress); branchMesh = myAsset.meshList[ivyGraph.branchMeshID]; if (ivyGraph.branchMF == null || ivyGraph.branchR == null) { CreateIvyMeshObject(ivyGraph, ivyProfile, branchMesh, false); } RefreshMeshObject(ivyGraph.branchMF, ivyProfile); branchMesh.Clear(); ivyGraph.branchMF.name = ivyGraph.rootGO.name + "_Branches"; ivyGraph.branchR.shadowCastingMode = ivyProfile.castShadows; ivyGraph.branchR.receiveShadows = ivyProfile.receiveShadows; branchMesh.name = ivyGraph.branchMF.name; branchMesh.SetVertices(verticesAll); branchMesh.SetUVs(0, texCoordsAll); if (ivyProfile.useLightmapping && doUV2s) { PackBranchUV2s(ivyGraph); } branchMesh.SetTriangles(trianglesAll, 0); branchMesh.RecalculateBounds(); branchMesh.RecalculateNormals(); branchMesh.RecalculateTangents(); #if UNITY_2017_1_OR_NEWER MeshUtility.Optimize(branchMesh); #endif ivyGraph.branchMF.sharedMesh = branchMesh; ivyGraph.branchR.sharedMaterial = ivyProfile.branchMaterial != null ? ivyProfile.branchMaterial : AssetDatabase.GetBuiltinExtraResource <Material>("Default-Diffuse.mat"); } // Leaves mesh debug // Debug.Log( "leafVertices: " + ivyGraph.leafVertices.Count ); // Debug.Log( "leafTris: " + string.Join(", ", ivyGraph.leafTriangles.Select( tri => tri.ToString() ).ToArray()) ); // don't do leaf mesh if it's unnecessary if (ivyProfile.leafProbability < 0.001f) { if (ivyGraph.leafMF != null) { IvyCore.DestroyObject(ivyGraph.leafMF.gameObject); } IvyCore.TryDestroyMesh(ivyGraph.leafMeshID, myAsset, true); } else { CheckMeshDataAsset(ref ivyGraph.leafMeshID, myAsset, ivyProfile.meshCompress); leafMesh = myAsset.meshList[ivyGraph.leafMeshID]; if (ivyGraph.leafMF == null || ivyGraph.leafR == null) { CreateIvyMeshObject(ivyGraph, ivyProfile, leafMesh, true); } RefreshMeshObject(ivyGraph.leafMF, ivyProfile); leafMesh.Clear(); ivyGraph.leafMF.name = ivyGraph.rootGO.name + "_Leaves"; ivyGraph.leafR.shadowCastingMode = ivyProfile.castShadows; ivyGraph.leafR.receiveShadows = ivyProfile.receiveShadows; leafMesh.name = ivyGraph.leafMF.name; leafMesh.SetVertices(leafVerticesAll); leafMesh.SetUVs(0, leafUVsAll); if (ivyProfile.useLightmapping && doUV2s) { PackLeafUV2s(ivyGraph); } leafMesh.SetTriangles(leafTrianglesAll, 0); if (ivyProfile.useVertexColors) { leafMesh.SetColors(leafColorsAll); } leafMesh.RecalculateBounds(); leafMesh.RecalculateNormals(); leafMesh.RecalculateTangents(); #if UNITY_2017_1_OR_NEWER MeshUtility.Optimize(leafMesh); #endif ivyGraph.leafMF.sharedMesh = leafMesh; ivyGraph.leafR.sharedMaterial = ivyProfile.leafMaterial != null ? ivyProfile.leafMaterial : AssetDatabase.GetBuiltinExtraResource <Material>("Default-Diffuse.mat"); } // EditorUtility.SetDirty( myAsset ); // AssetDatabase.SaveAssets(); // AssetDatabase.ImportAsset( AssetDatabase.GetAssetPath(myAsset) ); }
//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.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); } } } } } }
public override void OnInspectorGUI() { var profileAsset = (IvyProfileAsset)target; var ivyProfile = profileAsset.ivyProfile; var content = new GUIContent(); if (!viewedFromMonobehavior) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.HelpBox("This is an Ivy Profile, it controls how your ivy will look. But to paint anything, you must use an Ivy Behavior on a game object.", MessageType.Info); if (GUILayout.Button("Create new Ivy Game Object in Scene")) { IvyCore.CreateNewIvyGameObject(profileAsset); } EditorGUILayout.EndVertical(); IvyEditor.DrawUILine(); } if (!ivyProfile.showAdvanced) { EditorGUILayout.HelpBox("Hover over each label to learn more.\nIf you mess up, click Reset To Defaults.", MessageType.Info); } content = new GUIContent("Reset to Defaults", "This will set almost all ivy settings back to their default settings, which has been tested to work OK."); if (GUILayout.Button(content, EditorStyles.miniButton)) { if (EditorUtility.DisplayDialog("Hedera: Reset Ivy Profile to Default Settings", "Are you sure you want to reset this ivy profile back to default settings?", "Yes, reset!", "Cancel")) { Undo.RegisterCompleteObjectUndo(profileAsset, "Hedera > Reset Settings"); ivyProfile.ResetSettings(); ivyProfile.branchMaterial = IvyCore.TryToFindDefaultBranchMaterial(); ivyProfile.leafMaterial = IvyCore.TryToFindDefaultLeafMaterial(); EditorUtility.SetDirty(profileAsset); } } EditorGUILayout.Space(); content = new GUIContent(" Show Extra Settings", "If enabled, exposes ALL the ivy settings for customization, which can be ovewhelming at first.\n(default: false)"); ivyProfile.showAdvanced = EditorGUILayout.ToggleLeft(content, ivyProfile.showAdvanced); EditorGUILayout.Space(); GUI.changed = false; EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); content = new GUIContent("Growth Sim", "When you paint ivy, it will try to grow on nearby surfaces. These settings control how much it should grow automatically."); ivyProfile.showGrowthFoldout = EditorGUILayout.Foldout(ivyProfile.showGrowthFoldout, content, true); if (ivyProfile.showGrowthFoldout) { content = new GUIContent("Length", "Force branches to grow within this min / max range.\n(default: 1.0 - 3.0)"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(content, GUILayout.MaxWidth(60)); EditorGUI.indentLevel--; ivyProfile.minLength = Mathf.Clamp(EditorGUILayout.DelayedFloatField(ivyProfile.minLength, GUILayout.MaxWidth(28)), 0.01f, ivyProfile.maxLength - 0.01f); EditorGUILayout.MinMaxSlider(ref ivyProfile.minLength, ref ivyProfile.maxLength, 0.01f, 10f); ivyProfile.maxLength = Mathf.Clamp(EditorGUILayout.DelayedFloatField(ivyProfile.maxLength, GUILayout.MaxWidth(28)), ivyProfile.minLength + 0.01f, 10f); EditorGUI.indentLevel++; EditorGUILayout.EndHorizontal(); content = new GUIContent("Branch Chance %", "The probability a branch can make another branch. Higher values = many more branches. At 100%, most plants will reach their Max Branch Limit.\n(default: 25%"); ivyProfile.branchingProbability = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.branchingProbability * 100f), 0, 100) * 0.01f; if (ivyProfile.showAdvanced) { EditorGUILayout.Space(); EditorGUI.indentLevel++; EditorGUILayout.LabelField("EXTRA GROW OPTIONS", EditorStyles.miniLabel); content = new GUIContent("Max Branches", "Maximum total branches allowed per plant, basically how much the plant will spread. (Hint: If you want ivy to go specific places, then paint multiple plants and merge them.)\n(default: 64)"); ivyProfile.maxBranchesTotal = EditorGUILayout.IntSlider(content, ivyProfile.maxBranchesTotal, 1, 256); content = new GUIContent("Step Distance", "How far ivy tries to move per frame, the simulation resolution. (default: 0.1)\nSmaller = more curvy, smoother, more polygons.\nLarger = more angular and fewer polygons."); ivyProfile.ivyStepDistance = EditorGUILayout.Slider(content, ivyProfile.ivyStepDistance, 0.01f, 0.5f); content = new GUIContent("Float Length", "How far ivy can 'float' with no surface to cling on to.\n(default: 1)"); ivyProfile.maxFloatLength = EditorGUILayout.Slider(content, ivyProfile.maxFloatLength, 0.001f, 2f); content = new GUIContent("Cling Distance", "How far ivy can detect surfaces to cling on to. Larger values make ivy 'smarter', but simulation will be slower and more expensive.\n(default: 1.0)"); ivyProfile.maxAdhesionDistance = EditorGUILayout.Slider(content, ivyProfile.maxAdhesionDistance, 0.01f, 2f); content = new GUIContent("Collision Mask", "Which layers the ivy should collide with / cling to. Also determines which collision layers you can paint on.\n(default: Everything except Ignore Raycast)"); ivyProfile.collisionMask = EditorGUILayout.MaskField(content, InternalEditorUtility.LayerMaskToConcatenatedLayersMask(ivyProfile.collisionMask), InternalEditorUtility.layers); if (ivyProfile.collisionMask == 0) { EditorGUILayout.HelpBox("Collision Mask shouldn't be Nothing. That means you can't paint on anything, and ivy can't cling or climb.", MessageType.Warning); } EditorGUI.indentLevel--; EditorGUILayout.Space(); } } IvyEditor.DrawUILine(); // ========= content = new GUIContent("Growth AI", "When you simulate ivy, it will try new growth directions using these influence settings."); ivyProfile.showAIFoldout = EditorGUILayout.Foldout(ivyProfile.showAIFoldout, content, true); if (ivyProfile.showAIFoldout) { content = new GUIContent("Random Spread %", "How much to randomly roam outwards in different directions. At 100%, ivy will spread out to cover more of an area.\n(default: 100%)"); ivyProfile.randomWeight = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.randomWeight * 100f), 0, 400) * 0.01f; if (ivyProfile.showAdvanced) { EditorGUILayout.Space(); EditorGUI.indentLevel++; EditorGUILayout.LabelField("EXTRA AI OPTIONS", EditorStyles.miniLabel); content = new GUIContent("Plant Follow %", "How much to maintain current path and/or grow upwards toward the sun.\n(default: 50%)"); ivyProfile.primaryWeight = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.primaryWeight * 100f), 0, 400) * 0.01f; content = new GUIContent("Gravity Weight %", "How much gravity should pull down floating branches.\n(default: 300%)"); ivyProfile.gravityWeight = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.gravityWeight * 100f), 0, 400) * 0.01f; content = new GUIContent("Surface Cling %", "How much to cling / adhere to nearby surfaces.\n(default: 100%)"); ivyProfile.adhesionWeight = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.adhesionWeight * 100f), 0, 400) * 0.01f; EditorGUI.indentLevel--; EditorGUILayout.Space(); } } IvyEditor.DrawUILine(); // ========= content = new GUIContent("3D Mesh Settings", "After ivy is done growing, you can make a 3D mesh model based on its path. These settings control how the ivy 3D model will look."); ivyProfile.showMeshFoldout = EditorGUILayout.Foldout(ivyProfile.showMeshFoldout, content, true); if (ivyProfile.showMeshFoldout) { content = new GUIContent("Branch Thickness", "Width (diameter) of the branch meshes in world units. (default: 0.05)"); ivyProfile.ivyBranchSize = EditorGUILayout.Slider(content, ivyProfile.ivyBranchSize, 0f, 0.5f); if (ivyProfile.ivyBranchSize == 0f) { EditorGUILayout.HelpBox("No branches when Thickness is 0", MessageType.Warning); } content = new GUIContent("Leaf Size Radius", "Radial size of leaves in world units. Smaller leaves = more leaves (to maintain the same % of coverage) which means more polygons, so bigger leaves are usually better for performance.\n(default: 0.15)"); ivyProfile.ivyLeafSize = EditorGUILayout.Slider(content, ivyProfile.ivyLeafSize, 0.05f, 1f); content = new GUIContent("Leaf Density %", "How many leaves on each branch. 0% means no leaves, 100% is very bushy.\n(default: 50%)"); ivyProfile.leafProbability = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.leafProbability * 100f), 0, 100) * 0.01f; if (ivyProfile.leafProbability == 0f) { EditorGUILayout.HelpBox("No leaves when Leaf Density is 0%", MessageType.Warning); } content = new GUIContent("Vertex Colors", "Randomize leaf mesh's vertex colors, based on a gradient. Make sure your leaf material's shader supports vertex colors, and the default Hedera foliage shader supports vertex colors. If disabled then no vertex colors will be generated, which saves memory.\n(default: true)"); ivyProfile.useVertexColors = EditorGUILayout.Toggle(content, ivyProfile.useVertexColors); if (ivyProfile.useVertexColors) { content = new GUIContent("Leaf Colors", "Leaves will pick random vertex colors anywhere along this gradient.\n(default: white > green > yellow)"); #if UNITY_2017_1_OR_NEWER EditorGUI.indentLevel++; ivyProfile.leafVertexColors = EditorGUILayout.GradientField(content, ivyProfile.leafVertexColors); EditorGUI.indentLevel--; #else EditorGUILayout.HelpBox("Can't display gradient editor in Unity 5.6 or earlier, for boring reasons. You'll have to edit the gradient in debug inspector.", MessageType.Error); // ivyProfile.leafVertexColors.colorKeys[0].color = EditorGUILayout.ColorField( content, ivyProfile.leafVertexColors.colorKeys[0].color ); #endif } content = new GUIContent("Branch Material", "Unity material to use for branches. It doesn't need to be very high resolution or detailed, unless you set your Branch Thickness to be very wide. Example materials can be found in /Hedera/Runtime/Materials/"); ivyProfile.branchMaterial = EditorGUILayout.ObjectField(content, ivyProfile.branchMaterial, typeof(Material), false) as Material; if (ivyProfile.branchMaterial == null) { EditorGUILayout.HelpBox("Branch Material is undefined. Branch meshes will use default material.", MessageType.Warning); } content = new GUIContent("Leaf Material", "Unity material to use for leaves. Example materials are in /Hedera/Runtime/Materials/"); ivyProfile.leafMaterial = EditorGUILayout.ObjectField(content, ivyProfile.leafMaterial, typeof(Material), false) as Material; if (ivyProfile.leafMaterial == null) { EditorGUILayout.HelpBox("Leaf Material is undefined. Leaf meshes will use default material.", MessageType.Warning); } if (ivyProfile.showAdvanced) { EditorGUILayout.Space(); EditorGUI.indentLevel++; EditorGUILayout.LabelField("EXTRA MESH OPTIONS", EditorStyles.miniLabel); content = new GUIContent("Name Format", "All 3D ivy object names use this template, with numbered placeholders to fill-in data.\n{0}: branch count\n{1}: seed position\n(default: 'Ivy[{0}]{1}')"); ivyProfile.namePrefix = EditorGUILayout.DelayedTextField(content, ivyProfile.namePrefix); content = new GUIContent("Batching Static", "Set ivy meshes to 'batch' draw calls for huge performance gains. Always enable this unless you're moving / rotating / scaling the ivy during the game.\n(default: true)"); ivyProfile.markMeshAsStatic = EditorGUILayout.Toggle(content, ivyProfile.markMeshAsStatic); content = new GUIContent("Lighting Static", "Set ivy meshes to use lightmapping AND generate lightmap UV2s for the ivy.\n- Make sure your lightmap luxel resolution is high enough, or else it'll probably look very spotty.\n- Also make sure your lightmap atlas size is big enough, or else the lightmapped ivy won't batch.\n- If disabled, meshes won't have UV2s, which saves memory.\n(default: false)"); ivyProfile.useLightmapping = EditorGUILayout.Toggle(content, ivyProfile.useLightmapping); content = new GUIContent("Mesh Compress", "How much to compress ivy meshes for a smaller file size in the build. However, higher compression can introduce small flaws or glitches in the mesh.\n(default: Low)"); ivyProfile.meshCompress = (IvyProfile.MeshCompression)EditorGUILayout.EnumPopup(content, ivyProfile.meshCompress); content = new GUIContent("Cast Shadows", "The Cast Shadows setting on the ivy mesh renderers.\n(default: On)"); ivyProfile.castShadows = (UnityEngine.Rendering.ShadowCastingMode)EditorGUILayout.EnumPopup(content, ivyProfile.castShadows); content = new GUIContent("Receive Shadow", "The Receive Shadows setting on the ivy mesh renderers.\n(default: True)"); ivyProfile.receiveShadows = EditorGUILayout.Toggle(content, ivyProfile.receiveShadows); content = new GUIContent("Smooth Count", "How many Catmull-Rom spline subdivisions to add to each branch to smooth out the line. For example, a value of 2 doubles your branch vert count.\n- Don't set it too high, and make sure you set Simplify above 0%.\n- value of 1 means no smoothing.\n(default: 2)"); ivyProfile.branchSmooth = EditorGUILayout.IntSlider(content, ivyProfile.branchSmooth, 1, 4); content = new GUIContent("Simplify %", "How much to optimize branch meshes, to try to retain the main shape with fewer segments. High percentages will lower verts and polycounts for branches, but might look jagged or lose small details. 0% means no optimization will happen.\n(default: 50%)"); ivyProfile.branchOptimize = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.branchOptimize * 100f), 0, 100) * 0.01f; content = new GUIContent("Taper %", "How much to taper / sharpen branch mesh ends. Looks great for organic objects. But for ropes, cables, or pipes, you should set to 0% to maintain a constant thickness.\n(default: 100%)"); ivyProfile.branchTaper = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.branchTaper * 100f), 0, 100) * 0.01f; content = new GUIContent("Leaf Sunlight %", "Approximates how ivy wants to be in the sun. Leaves will face upwards more. Ivy will grow more leaves on floors / roofs, and fewer leaves on ceilings. 0% means leaves will spawn evenly regardless of surface, and align leaves with surface.\n(default: 100%)"); ivyProfile.leafSunlightBonus = EditorGUILayout.IntSlider(content, Mathf.RoundToInt(ivyProfile.leafSunlightBonus * 100f), 0, 100) * 0.01f; EditorGUI.indentLevel--; EditorGUILayout.Space(); } } EditorGUI.indentLevel--; if (EditorGUI.EndChangeCheck()) { Undo.RegisterCompleteObjectUndo(profileAsset, "Hedera > Edit Ivy Settings"); EditorUtility.SetDirty(profileAsset); } EditorGUILayout.Space(); }