示例#1
0
        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 ForceIvyGrowth(IvyGraph graph, IvyProfile ivyProfile, Vector3 newPos, Vector3 newNormal)
        {
            newPos -= graph.seedPos; // convert to local space

            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.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;

            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);
            }
        }
示例#3
0
        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);
        }
        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;
 }
示例#6
0
        public static void InitOrRefreshRoot(IvyGraph ivyGraph, IvyProfile ivyProfile)
        {
            if (ivyGraph.rootGO == null)
            {
                ivyGraph.rootGO = new GameObject("HederaObject");
                ivyGraph.rootGO.transform.SetParent(ivyGraph.rootBehavior);
            }
#if UNITY_EDITOR
            SetStaticEditorFlag(ivyGraph.rootGO, StaticEditorFlags.BatchingStatic, ivyProfile.markMeshAsStatic);
            SetStaticEditorFlag(ivyGraph.rootGO, StaticEditorFlags.ContributeGI, ivyProfile.useLightmapping);
#endif
            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 TryToDestroyMeshes(IvyBehavior ivyBehavior, IvyGraph ivyGraph, bool suppressConfirm = false)
        {
#if UNITY_EDITOR
            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();
#endif
        }
示例#9
0
        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);
                }
            }
        }
    private void Start()
    {
        Time.timeScale = 1;
        IvyCore _ = new IvyCore();

        foreach ((Vector3 position, Vector3 normal) in GetRoots())
        {
            //Debug.Log($"This position: {transform.position}, Root position: {root.transform.position}");
            IvyGraph ivyGraph = IvyCore.SeedNewIvyGraph(profileAsset.ivyProfile, position, Vector3.up, normal, transform, true);
            ivyGraph.isGrowing = true;
            ivyGraphs.Add(ivyGraph);

            float branchPercentage = Mathf.Clamp(ivyGraph.roots[0].nodes.Last().cS / profileAsset.ivyProfile.maxLength, 0f, 0.38f);
            int branchCount = Mathf.FloorToInt(profileAsset.ivyProfile.maxBranchesTotal * branchPercentage * profileAsset.ivyProfile.branchingProbability);
            for (int b = 0; b < branchCount; b++)
            {
                IvyCore.ForceRandomIvyBranch(ivyGraph, profileAsset.ivyProfile);
            }
        }

            //Debug.Log($"Mask: {LayerMask.GetMask("Default", "UserLayerB")}");
    }
        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);
#if UNITY_EDITOR
                Undo.RegisterCreatedObjectUndo(graph.rootGO, "Hedera > Paint Ivy");
#endif
            }

            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;
                }
            }
        }
示例#14
0
        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 && UNITY_EDITOR
                MeshUtility.Optimize(branchMesh);
                #endif
                ivyGraph.branchMF.sharedMesh = branchMesh;
#if UNITY_EDITOR
                ivyGraph.branchR.sharedMaterial = ivyProfile.branchMaterial != null ? ivyProfile.branchMaterial : AssetDatabase.GetBuiltinExtraResource <Material>("Default-Diffuse.mat");
#else
                ivyGraph.branchR.sharedMaterial = ivyProfile.branchMaterial;
#endif
            }

            // 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 && UNITY_EDITOR
                MeshUtility.Optimize(leafMesh);
#endif
                ivyGraph.leafMF.sharedMesh = leafMesh;
#if UNITY_EDITOR
                ivyGraph.leafR.sharedMaterial = ivyProfile.leafMaterial != null ? ivyProfile.leafMaterial : AssetDatabase.GetBuiltinExtraResource <Material>("Default-Diffuse.mat");
#else
                ivyGraph.leafR.sharedMaterial = ivyProfile.leafMaterial;
#endif
            }
            // 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;
            }

            // 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 );
             *              }
             *          }
             *      }
             *  }
             * }
             */
        }
        // 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();
        }
示例#17
0
        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);
        }