static void PackBranchUV2s(IvyGraph graph) { // remember: this can only happen AFTER vertices and UV1s are generated, we're packing them into columns var rootsWithUVs = graph.roots.Where(root => root.meshSegments > 0).ToArray(); int branchCount = rootsWithUVs.Length; int meshSegmentCount = 0; // placeholder, will depend on root float gridIncrementX = 1f / branchCount; float gridIncrementY = 0f; // placeholder, will depend on root texCoordsAll.Clear(); // reuse uv0 list int uvCounter = 0; Vector2 gridPointer = Vector2.zero; Vector2 gridIncrement = Vector2.zero; for (int u = 0; u < branchCount; u++) { meshSegmentCount = rootsWithUVs[u].meshSegments; gridIncrementY = 1f / (meshSegmentCount - 1); // segmentRow.y is always 0f, so that's why we -1 here gridIncrement = new Vector2(gridIncrementX, gridIncrementY); for (int v = 0; v < meshSegmentCount; v++) { gridPointer = new Vector2(u, v); for (int i = 0; i < 3 && uvCounter < texCoordsAll.Count; i++) { texCoordsAll[uvCounter + i] = Vector2.Scale(gridPointer, gridIncrement) + new Vector2((0.5f * i) * (gridIncrementX - branchUV2packMargin), 0f); } uvCounter += 3; } } branchMesh.SetUVs(1, texCoordsAll); }
static void PackLeafUV2s(IvyGraph graph) { // remember: this can only happen AFTER vertices and UV1s are generated, we're packing them into a grid int leafCount = leafUVsAll.Count / 4; int gridSize = Mathf.CeilToInt(Mathf.Sqrt(leafCount)); float gridIncrement = 1f / gridSize; leafUVsAll.Clear(); // reuse uv0 list int uvCounter = 0; Vector2 gridPointer = Vector2.zero; // TODO: implement pack margin... but probably not a big deal since leaf textures are already transparent along edges for (int v = 0; v < gridSize; v++) { for (int u = 0; u < gridSize; u++) { gridPointer = new Vector2(u, v); for (int i = 0; i < 4 && uvCounter < leafUVsAll.Count; i++) { leafUVsAll[uvCounter + i] = (gridPointer + leafUVsAll[uvCounter + i]) * gridIncrement; } uvCounter += 4; } } leafMesh.SetUVs(1, leafUVsAll); }
public static void ForceRandomIvyBranch(IvyGraph graph, IvyProfile ivyProfile) { var randomRoot = graph.roots[0]; var randomNode = randomRoot.nodes[Random.Range(0, randomRoot.nodes.Count)]; var randomLength = randomNode.cS + Mathf.Lerp(ivyProfile.minLength * 1.5f, ivyProfile.maxLength, Random.value); TryGrowIvyBranch(graph, ivyProfile, randomRoot, randomNode, randomLength); }
void OnEnable() { currentIvyGraph = null; currentIvyGraphMove = null; iconVisOn = EditorGUIUtility.IconContent("animationvisibilitytoggleon").image; iconVisOff = EditorGUIUtility.IconContent("animationvisibilitytoggleoff").image; iconLeaf = EditorGUIUtility.IconContent("tree_icon_leaf").image; iconMesh = EditorGUIUtility.IconContent("MeshRenderer Icon").image; iconExport = EditorGUIUtility.IconContent("PrefabModel Icon").image; iconTrash = EditorGUIUtility.IconContent("TreeEditor.Trash").image; iconPaint = EditorGUIUtility.IconContent("ClothInspector.PaintTool").image; iconMove = EditorGUIUtility.IconContent("MoveTool").image; }
public static void InitOrRefreshRoot(IvyGraph ivyGraph, IvyProfile ivyProfile) { if (ivyGraph.rootGO == null) { ivyGraph.rootGO = new GameObject("HederaObject"); ivyGraph.rootGO.transform.SetParent(ivyGraph.rootBehavior); } SetStaticEditorFlag(ivyGraph.rootGO, StaticEditorFlags.BatchingStatic, ivyProfile.markMeshAsStatic); SetStaticEditorFlag(ivyGraph.rootGO, StaticEditorFlags.ContributeGI, ivyProfile.useLightmapping); var rootTrans = ivyGraph.rootGO.transform; rootTrans.position = ivyGraph.seedPos; rootTrans.rotation = Quaternion.identity; rootTrans.localScale = Vector3.one; ivyGraph.rootGO.name = string.Format(ivyProfile.namePrefix, ivyGraph.roots.Count, ivyGraph.seedPos); }
static bool TryGrowIvyBranch(IvyGraph graph, IvyProfile ivyProfile, IvyRoot root, IvyNode fromNode, float forceMinLength = -1f) { //weight depending on ratio of node length to total length float weight = 1f; //Mathf.PerlinNoise( fromNode.localPos.x + fromNode.lengthCumulative, fromNode.length + fromNode.localPos.y + fromNode.localPos.z); // - ( Mathf.Cos( fromNode.length / root.nodes[root.nodes.Count-1].length * 2.0f * Mathf.PI) * 0.5f + 0.5f ); var nearbyRootCount = graph.roots.Where(r => (r.nodes[0].p - fromNode.p).sqrMagnitude < ivyProfile.ivyStepDistance * ivyProfile.ivyStepDistance).Count(); if (forceMinLength <= 0f) { if (graph.roots.Count >= ivyProfile.maxBranchesTotal || nearbyRootCount > ivyProfile.branchingProbability * 2.5f || root.childCount > ivyProfile.branchingProbability * 3.5f || root.nodes.Count < 3 || root.parents > ivyProfile.branchingProbability * 9f || ivyProfile.maxLength - fromNode.cS < ivyProfile.minLength || Random.value * Mathf.Clamp(weight, 0f, 1f - ivyProfile.branchingProbability) > ivyProfile.branchingProbability ) { return(false); } } //new ivy node IvyNode newRootNode = new IvyNode(); newRootNode.p = fromNode.p; newRootNode.g = Vector3.Lerp(fromNode.g, Vector3.up, 0.5f).normalized; newRootNode.c = fromNode.c; newRootNode.s = 0.0f; newRootNode.cS = forceMinLength > 0f ? 0f : fromNode.cS; newRootNode.fS = forceMinLength > 0f ? 0f : fromNode.fS; newRootNode.cl = true; //new ivy root IvyRoot newRoot = new IvyRoot(); newRoot.nodes.Add(newRootNode); newRoot.isAlive = true; newRoot.parents = root.parents + 1; newRoot.forceMinLength = forceMinLength; graph.roots.Add(newRoot); root.childCount++; return(true); }
public static void ForceIvyGrowth(IvyGraph graph, IvyProfile ivyProfile, Vector3 newPos, Vector3 newNormal) { newPos -= graph.seedPos; // convert to local space // find the nearest root end node, and continue off of it // var closestRoot = graph.roots.OrderBy( root => Vector3.Distance( newPos, root.nodes.Last().localPos ) ).FirstOrDefault(); // if ( closestRoot == null ) { // return; // } var closestRoot = graph.roots[0]; var lastNode = closestRoot.nodes[closestRoot.nodes.Count - 1]; var growVector = newPos - lastNode.p; var newNode = new IvyNode(); newNode.p = newPos; newNode.g = (0.5f * lastNode.g + 0.5f * growVector.normalized).normalized; //newNode.adhesionVector = ComputeAdhesion( newPos, ivyProfile ); //if ( newNode.adhesionVector.sqrMagnitude < 0.01f ) { newNode.c = -newNormal; //} newNode.s = lastNode.s + growVector.magnitude; newNode.cS = lastNode.cS + growVector.magnitude; newNode.fS = 0f; newNode.cl = true; closestRoot.nodes.Add(newNode); closestRoot.useCachedBranchData = false; closestRoot.useCachedLeafData = false; // TryGrowIvyBranch( graph, ivyProfile, closestRoot, newNode ); var cache = IvyRoot.GetMeshCacheFor(closestRoot); cache.debugLineSegmentsList.Add(lastNode.p + graph.seedPos); cache.debugLineSegmentsList.Add(newPos + graph.seedPos); cache.debugLineSegmentsArray = cache.debugLineSegmentsList.ToArray(); if (graph.generateMeshDuringGrowth) { IvyMesh.GenerateMesh(graph, ivyProfile); } }
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(); }
static void CreateIvyMeshObject(IvyGraph graph, IvyProfile profile, Mesh mesh, bool isLeaves = false) { var PartObj = new GameObject("HederaMesh"); PartObj.transform.parent = graph.rootGO.transform; PartObj.transform.localPosition = Vector3.zero; if (!isLeaves) { graph.branchMF = PartObj.AddComponent <MeshFilter>(); graph.branchMF.sharedMesh = mesh; graph.branchR = PartObj.AddComponent <MeshRenderer>(); } else { graph.leafMF = PartObj.AddComponent <MeshFilter>(); graph.leafMF.sharedMesh = mesh; graph.leafR = PartObj.AddComponent <MeshRenderer>(); } }
public void DrawDebugIvy(IvyGraph graph) { // Handles.zTest = UnityEngine.Rendering.CompareFunction.Always; foreach (var root in graph.roots) { if (root.nodes.Count < 2) { continue; } if (root.debugLineSegmentsArray == null || root.debugLineSegmentsArray.Length != (root.nodes.Count - 1) * 2 || Vector3.SqrMagnitude(root.debugLineSegmentsArray[0] - (root.nodes[0].p + graph.seedPos)) > 0.01f) { IvyCore.RegenerateDebugLines(graph.seedPos, root); // Debug.LogFormat("regenerting {0}", Vector3.SqrMagnitude(root.debugLineSegmentsArray[0] - (root.nodes[0].p+graph.seedPos)) ); } if (root.debugLineSegmentsArray != null) { Handles.DrawLines(root.debugLineSegmentsArray); } } }
public static IvyGraph SeedNewIvyGraph(IvyProfile ivyProfile, Vector3 seedPos, Vector3 primaryGrowDir, Vector3 adhesionVector, Transform root, bool generateMeshPreview = false) { var graph = new IvyGraph(); graph.roots.Clear(); graph.seedPos = seedPos; graph.seedNormal = adhesionVector; graph.generateMeshDuringGrowth = generateMeshPreview; graph.rootBehavior = root; IvyNode tmpNode = new IvyNode(); tmpNode.p = Vector3.zero; //seedPos; tmpNode.g = primaryGrowDir; tmpNode.c = adhesionVector; tmpNode.s = 0.0f; tmpNode.cS = 0f; tmpNode.fS = 0.0f; tmpNode.cl = true; IvyRoot tmpRoot = new IvyRoot(); tmpRoot.nodes.Add(tmpNode); tmpRoot.isAlive = true; graph.isGrowing = true; tmpRoot.parents = 0; graph.roots.Add(tmpRoot); if (graph.generateMeshDuringGrowth) { IvyMesh.GenerateMesh(graph, ivyProfile); Undo.RegisterCreatedObjectUndo(graph.rootGO, "Hedera > Paint Ivy"); } return(graph); }
public static void GrowIvyStep(IvyGraph graph, IvyProfile ivyProfile) { // if there are no longer any live roots, then we're dead if (graph.isGrowing) { graph.isGrowing = graph.roots.Where(root => root.isAlive).Count() > 0; } if (!graph.isGrowing) { return; } //lets grow foreach (var root in graph.roots) { //process only roots that are alive if (!root.isAlive) { continue; } IvyNode lastNode = root.nodes[root.nodes.Count - 1]; //let the ivy die, if the maximum float length is reached if (lastNode.cS > ivyProfile.maxLength || (lastNode.cS > Mathf.Max(root.forceMinLength, ivyProfile.minLength) && lastNode.fS > ivyProfile.maxFloatLength)) { // Debug.LogFormat("root death! cum dist: {0:F2}, floatLength {1:F2}", lastNode.lengthCumulative, lastNode.floatingLength); root.isAlive = false; SmoothGaussianAdhesion(root); continue; } //grow vectors: primary direction, random influence, and adhesion of scene objectss //primary vector = weighted sum of previous grow vectors plus a little bit upwards Vector3 primaryVector = Vector3.Normalize(lastNode.g * 2f + Vector3.up); //random influence plus a little upright vector Vector3 exploreVector = lastNode.p - root.nodes[0].p; if (exploreVector.magnitude > 1f) { exploreVector = exploreVector.normalized; } exploreVector *= Mathf.PingPong(root.nodes[0].p.sqrMagnitude * root.parents + lastNode.cS * 0.69f, 1f); Vector3 randomVector = (Random.onUnitSphere * 0.5f + exploreVector).normalized; //adhesion influence to the nearest triangle = weighted sum of previous adhesion vectors Vector3 adhesionVector = ComputeAdhesion(lastNode.p + graph.seedPos, ivyProfile); if (adhesionVector.sqrMagnitude <= 0.01f) { adhesionVector = lastNode.c; } //compute grow vector Vector3 growVector = ivyProfile.ivyStepDistance * Vector3.Normalize( primaryVector * ivyProfile.primaryWeight + randomVector * Mathf.Max(0.01f, ivyProfile.randomWeight) + adhesionVector * ivyProfile.adhesionWeight ); //gravity influence Vector3 gravityVector = ivyProfile.ivyStepDistance * Vector3.down * ivyProfile.gravityWeight; //gravity depends on the floating length gravityVector *= Mathf.Pow(lastNode.fS / ivyProfile.maxFloatLength, 0.7f); //next possible ivy node //climbing state of that ivy node, will be set during collision detection bool climbing = false; //compute position of next ivy node Vector3 newPos = lastNode.p + growVector + gravityVector; //combine alive state with result of the collision detection, e.g. let the ivy die in case of a collision detection problem Vector3 adhesionFromRaycast = adhesionVector; // convert newPos to world position, just for the collision calc newPos += graph.seedPos; root.isAlive = root.isAlive && ComputeCollision(0.01f, lastNode.p + graph.seedPos, ref newPos, ref climbing, ref adhesionFromRaycast, ivyProfile.collisionMask); newPos -= graph.seedPos; //update grow vector due to a changed newPos growVector = newPos - lastNode.p - gravityVector; // +graph.seedPos to convert back to world space var cache = IvyRoot.GetMeshCacheFor(root); cache.debugLineSegmentsList.Add(lastNode.p + graph.seedPos); cache.debugLineSegmentsList.Add(newPos + graph.seedPos); // cache line segments cache.debugLineSegmentsArray = cache.debugLineSegmentsList.ToArray(); //create next ivy node IvyNode newNode = new IvyNode(); newNode.p = newPos; newNode.g = (0.5f * lastNode.g + 0.5f * growVector.normalized).normalized; newNode.c = adhesionVector; //Vector3.Lerp(adhesionVector, adhesionFromRaycast, 0.5f); newNode.s = lastNode.s + (newPos - lastNode.p).magnitude; newNode.cS = lastNode.cS + (newPos - lastNode.p).magnitude; newNode.fS = climbing ? 0.0f : lastNode.fS + (newPos - lastNode.p).magnitude; newNode.cl = climbing; root.nodes.Add(newNode); root.useCachedBranchData = false; root.useCachedLeafData = false; if (!root.isAlive) { SmoothGaussianAdhesion(root); } var randomNode = root.nodes[Random.Range(0, root.nodes.Count)]; if (TryGrowIvyBranch(graph, ivyProfile, root, randomNode)) { break; } } }
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) ); }
static bool GenerateMeshData(IvyGraph ivyGraph, IvyProfile ivyProfile, bool forceGeneration = false) { var p = ivyProfile; //branches foreach (var root in ivyGraph.roots) { var cache = IvyRoot.GetMeshCacheFor(root); if (root.useCachedBranchData && !forceGeneration) { combinedTriangleIndices.Clear(); cache.triangles.ForEach(localIndex => combinedTriangleIndices.Add(localIndex + verticesAll.Count)); trianglesAll.AddRange(combinedTriangleIndices); verticesAll.AddRange(cache.vertices); texCoordsAll.AddRange(cache.texCoords); continue; } root.useCachedBranchData = true; //process only roots with more than one node if (root.nodes.Count < 2) { continue; } cache.vertices.Clear(); cache.texCoords.Clear(); cache.triangles.Clear(); //branch diameter depends on number of parents AND branch taper float local_ivyBranchDiameter = 1.0f / Mathf.Lerp(1f, 1f + root.parents, ivyProfile.branchTaper); // smooth the line... which increases points a lot allPoints = root.nodes.Select(node => node.p).ToList(); var useThesePoints = allPoints; if (ivyProfile.branchSmooth > 1) { SmoothLineCatmullRomNonAlloc(allPoints, smoothPoints, ivyProfile.branchSmooth); useThesePoints = smoothPoints; } // generate simplified points for each root, to make it less wavy AND save tris if (!root.isAlive && ivyProfile.branchOptimize > 0f) { newPoints.Clear(); newPoints.AddRange(SimplificationHelpers.Simplify <Vector3>( useThesePoints, (vec1, vec2) => vec1 == vec2, (vec) => vec.x, (vec) => vec.y, (vec) => vec.z, ivyProfile.branchOptimize * ivyProfile.ivyStepDistance * 0.5f, false )); useThesePoints = newPoints; } // I'm not sure why there's this bug when we use Catmull Rom + line simplify, but let's do this hacky fix // if ( ivyProfile.branchSmooth > 1 && ivyProfile.branchOptimize > 0f ) { // useThesePoints.ForEach( delegate(Vector3 point) { // if ( float.IsInfinity(point.x) ) {point.x = 0f;} // if ( float.IsInfinity(point.y) ) {point.y = 0f;} // if ( float.IsInfinity(point.z) ) {point.z = 0f;} // } ); // } for (int n = 0; n < useThesePoints.Count; n++) { if (verticesAll.Count >= 65531) { Debug.LogWarning("Hedera: ending branch generation early, reached ~65536 vertex limit on mesh " + ivyGraph.seedPos + "... but this could technically be solved in Unity 2017.3+ or later with 32-bit index formats for meshes? The exercise is left to the reader."); break; } cache.meshSegments = n + 1; //weight depending on ratio of node length to total length float taper = 1f * n / useThesePoints.Count; taper = Mathf.Lerp(1f, (1f - taper) * taper, ivyProfile.branchTaper); //create trihedral vertices... TODO: let user specify how many sides? Vector3 up = Vector3.down; Vector3 basis = Vector3.Normalize(n < useThesePoints.Count - 1 ? useThesePoints[n + 1] - useThesePoints[n] : -(useThesePoints[n] - useThesePoints[n - 1])); // Debug.DrawLine( newPoints[node+1] + ivyGraph.seedPos, newPoints[node] + ivyGraph.seedPos, Color.cyan, 5f, false); int edges = 3; // TODO: finish this, make it configurable float texV = (n % 2 == 0 ? 1f : 0.0f); // vertical UV tiling for (int b = 0; b < edges; b++) { // generate vertices if (b == 0) { branchVertBasis[b] = Vector3.Cross(up, basis).normalized *Mathf.Max(0.001f, local_ivyBranchDiameter * p.ivyBranchSize * taper) + useThesePoints[n]; } else { branchVertBasis[b] = RotateAroundAxis(branchVertBasis[0], useThesePoints[n], basis, 6.283f * b / edges); } cache.vertices.Add(branchVertBasis[b]); // generate UVs cache.texCoords.Add(new Vector2(1f * b / (edges - 1), texV)); // add triangles // AddTriangle(root, 4, 1, 5); // AddTriangle(root, 5, 1, 2); // TODO: finish this } if (n == 0) // start cap { if (taper > 0f) { AddTriangle(cache, 1, 2, 3); } continue; } AddTriangle(cache, 4, 1, 5); AddTriangle(cache, 5, 1, 2); AddTriangle(cache, 5, 2, 6); AddTriangle(cache, 6, 2, 3); AddTriangle(cache, 6, 3, 1); AddTriangle(cache, 6, 1, 4); if (n == useThesePoints.Count - 1 && taper > 0f) // end cap { AddTriangle(cache, 3, 2, 1); } } combinedTriangleIndices.Clear(); cache.triangles.ForEach(localIndex => combinedTriangleIndices.Add(localIndex + verticesAll.Count)); trianglesAll.AddRange(combinedTriangleIndices); verticesAll.AddRange(cache.vertices); texCoordsAll.AddRange(cache.texCoords); } if (ivyProfile.ivyLeafSize <= 0.001f || ivyProfile.leafProbability <= 0.001f) { return(true); } //create leafs allLeafPoints.Clear(); foreach (var root in ivyGraph.roots) { // don't bother on small roots if (root.nodes.Count <= 2) { root.useCachedLeafData = false; continue; } var cache = IvyRoot.GetMeshCacheFor(root); // use cached mesh data for leaves only if (a) we're supposed to, and (b) if not using vertex colors OR vertex colors seem valid if (root.useCachedLeafData && !forceGeneration && (!ivyProfile.useVertexColors || cache.leafVertices.Count == cache.leafVertexColors.Count)) { combinedTriangleIndices.Clear(); cache.leafTriangles.ForEach(index => combinedTriangleIndices.Add(index + leafVerticesAll.Count)); leafTrianglesAll.AddRange(combinedTriangleIndices); allLeafPoints.AddRange(cache.leafPoints); leafVerticesAll.AddRange(cache.leafVertices); leafUVsAll.AddRange(cache.leafUVs); if (ivyProfile.useVertexColors) { leafColorsAll.AddRange(cache.leafVertexColors); } continue; } root.useCachedLeafData = true; cache.leafPoints.Clear(); cache.leafVertices.Clear(); cache.leafUVs.Clear(); cache.leafTriangles.Clear(); cache.leafVertexColors.Clear(); // simple multiplier, just to make it a more dense for (int i = 0; i < 1; ++i) { var leafPositions = GetAllSamplePosAlongRoot(root, p.ivyLeafSize); // for(int n=0; n<root.nodes.Count; n++) foreach (var kvp in leafPositions) { if (leafVerticesAll.Count >= 65530) { Debug.LogWarning("Hedera: ending leaf generation early, reached ~65536 vertex limit on mesh " + ivyGraph.seedPos + "... but this could technically be solved in Unity 2017.3+ or later with 32-bit index formats for meshes? The exercise is left to the reader."); break; } int n = kvp.Value; Vector3 newLeafPos = kvp.Key; var node = root.nodes[n]; // // do not generate a leaf on the first few nodes // if ( n <= 1 ) { // || n >= root.nodes.Count // continue; // } // probability of leaves on the ground is increased float groundedness = Vector3.Dot(Vector3.down, node.c.normalized); if (groundedness < -0.02f) { groundedness -= 0.1f; groundedness *= 3f; } else { groundedness = (groundedness - 0.25f) * 0.5f; } groundedness *= ivyProfile.leafSunlightBonus * p.leafProbability; // don't spawn a leaf on top of another leaf bool badLeafPos = false; float leafSqrSize = p.ivyLeafSize * p.ivyLeafSize * Mathf.Clamp(1f - p.leafProbability - groundedness, 0.01f, 2f); for (int f = 0; f < allLeafPoints.Count; f++) { if (Vector3.SqrMagnitude(allLeafPoints[f] - newLeafPos) < leafSqrSize) { badLeafPos = true; break; } } if (badLeafPos) { continue; } IvyNode previousNode = root.nodes[Mathf.Max(0, n - 1)]; float randomSpreadHack = 0.25f; if (n <= 1 || n == root.nodes.Count - 1) { randomSpreadHack = 0f; } // randomize leaf probability // guarantee a leaf on the first or last node if ((Random.value + groundedness > 1f - p.leafProbability) || randomSpreadHack == 0f) { cache.leafPoints.Add(node.p); allLeafPoints.Add(node.p); //center of leaf quad Vector3 up = (newLeafPos - previousNode.p).normalized; Vector3 right = Vector3.Cross(up, node.c); Vector3 center = newLeafPos - node.c.normalized * 0.05f + (up * Random.Range(-1f, 1f) + right * Random.Range(-1f, 1f)) * randomSpreadHack * p.ivyLeafSize; //size of leaf float sizeWeight = 1.5f - (Mathf.Abs(Mathf.Cos(2.0f * Mathf.PI)) * 0.5f + 0.5f); float leafSize = p.ivyLeafSize * sizeWeight + Random.Range(-p.ivyLeafSize, p.ivyLeafSize) * 0.1f + (p.ivyLeafSize * groundedness); leafSize = Mathf.Max(0.01f, leafSize); Quaternion facing = node.c.sqrMagnitude < 0.001f ? Quaternion.identity : Quaternion.LookRotation(Vector3.Lerp(-node.c, Vector3.up, Mathf.Clamp01(0.68f - Mathf.Abs(groundedness)) * ivyProfile.leafSunlightBonus), Random.onUnitSphere); AddLeafVertex(cache, center, new Vector3(-1f, 1f, 0f), leafSize, facing); AddLeafVertex(cache, center, new Vector3(1f, 1f, 0f), leafSize, facing); AddLeafVertex(cache, center, new Vector3(-1f, -1f, 0f), leafSize, facing); AddLeafVertex(cache, center, new Vector3(1f, -1f, 0f), leafSize, facing); cache.leafUVs.Add(new Vector2(1.0f, 1.0f)); cache.leafUVs.Add(new Vector2(0.0f, 1.0f)); cache.leafUVs.Add(new Vector2(1.0f, 0.0f)); cache.leafUVs.Add(new Vector2(0.0f, 0.0f)); if (ivyProfile.useVertexColors) { var randomColor = ivyProfile.leafVertexColors.Evaluate(Random.value); cache.leafVertexColors.Add(randomColor); cache.leafVertexColors.Add(randomColor); cache.leafVertexColors.Add(randomColor); cache.leafVertexColors.Add(randomColor); } // calculate normal of the leaf tri, and make it face outwards // var normal = GetNormal( // ivyGraph.leafVertices[ivyGraph.leafVertices.Count - 2], // ivyGraph.leafVertices[ivyGraph.leafVertices.Count - 4], // ivyGraph.leafVertices[ivyGraph.leafVertices.Count - 3] // ); // if ( Vector3.Dot( normal, node.adhesionVector) < 0f) { // AddLeafTriangle(ivyGraph, 2, 4, 3); // AddLeafTriangle(ivyGraph, 3, 1, 2); // } else { AddLeafTriangle(cache, 1, 3, 4); AddLeafTriangle(cache, 4, 2, 1); // } } } combinedTriangleIndices.Clear(); cache.leafTriangles.ForEach(index => combinedTriangleIndices.Add(index + leafVerticesAll.Count)); leafTrianglesAll.AddRange(combinedTriangleIndices); leafVerticesAll.AddRange(cache.leafVertices); leafUVsAll.AddRange(cache.leafUVs); if (ivyProfile.useVertexColors) { leafColorsAll.AddRange(cache.leafVertexColors); } } } return(true); }
// 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); } } } } } }