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); } }
/** initialize a new ivy root */ public void seed(Vector3 seedPos) { reset(); roots.Clear(); IvyNode tmpNode = new IvyNode(); tmpNode.pos = seedPos; tmpNode.primaryDir = new Vector3(0.0f, 1.0f, 0.0f); tmpNode.adhesionVector = new Vector3(0.0f, 0.0f, 0.0f); tmpNode.length = 0.0f; tmpNode.floatingLength = 0.0f; tmpNode.climb = true; IvyRoot tmpRoot = new IvyRoot(); tmpRoot.nodes.Add(tmpNode); tmpRoot.alive = true; tmpRoot.parents = 0; roots.Add(tmpRoot); }
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 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); }
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); }
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; } } }
/** creates the ivy triangle mesh */ public void birth() { //evolve a gaussian filter over the adhesian vectors float [] gaussian = { 1.0f, 2.0f, 4.0f, 7.0f, 9.0f, 10.0f, 9.0f, 7.0f, 4.0f, 2.0f, 1.0f }; foreach (var root in roots) { for (int g = 0; g < 5; ++g) { for (int node = 0; node < root.nodes.Count; node++) { Vector3 e = Vector3.zero; for (int i = -5; i <= 5; ++i) { Vector3 tmpAdhesion = Vector3.zero; if ((node + i) < 0) { tmpAdhesion = root.nodes[0].adhesionVector; } if ((node + i) >= root.nodes.Count) { tmpAdhesion = root.nodes[root.nodes.Count - 1].adhesionVector; } if (((node + i) >= 0) && ((node + i) < root.nodes.Count)) { tmpAdhesion = root.nodes[node + i].adhesionVector; } e += tmpAdhesion * gaussian[i + 5]; } root.nodes[node].smoothAdhesionVector = e / 56.0f; } foreach (var _node in root.nodes) { _node.adhesionVector = _node.smoothAdhesionVector; } } } //parameters that depend on the scene object bounding sphere float local_ivyLeafSize = IvyManager.SceneObjMesh.boundingSphereRadius * ivySize * ivyLeafSize; float local_ivyBranchSize = IvyManager.SceneObjMesh.boundingSphereRadius * ivySize * ivyBranchSize; //reset existing geometry reset(); //set data path path = "../textures/"; //create material for leafs BasicMaterial tmpMaterial = new BasicMaterial(); tmpMaterial.id = 1; tmpMaterial.name = "leaf_adult"; tmpMaterial.texFile = IvyManager.LeafAdultTexPathName; materials.Add(tmpMaterial); //create second material for leafs tmpMaterial = new BasicMaterial(); tmpMaterial.id = 2; tmpMaterial.name = "leaf_young"; tmpMaterial.texFile = IvyManager.LeafYoungTexPathName; materials.Add(tmpMaterial); //create material for branches tmpMaterial = new BasicMaterial(); tmpMaterial.id = 3; tmpMaterial.name = "branch"; tmpMaterial.texFile = IvyManager.branchTexPathName; materials.Add(tmpMaterial); //create leafs foreach (var root in roots) { //simple multiplier, just to make it a more dense for (int i = 0; i < 10; ++i) { //srand(i + (root - roots.begin()) * 10); foreach (var node in root.nodes) { IvyNode back_node = root.nodes[root.nodes.Count - 1]; //weight depending on ratio of node length to total length float weight = Mathf.Pow(node.length / back_node.length, 0.7f); //test: the probability of leaves on the ground is increased float groundIvy = Mathf.Max(0.0f, -Vector3.Dot(new Vector3(0.0f, 1.0f, 0.0f), node.adhesionVector.normalized)); weight += groundIvy * Mathf.Pow(1.0f - node.length / back_node.length, 2.0f); //random influence float probability = Random.value; if (probability * weight > leafProbability) { //alignment weight depends on the adhesion "strength" float alignmentWeight = node.adhesionVector.magnitude; //horizontal angle (+ an epsilon vector, otherwise there's a problem at 0?and 90?.. mmmh) float phi = vector2ToPolar(new Vector2(node.adhesionVector.z, node.adhesionVector.x).normalized + new Vector2(Vector2.kEpsilon, Vector2.kEpsilon)) - Mathf.PI * 0.5f; //vertical angle, trimmed by 0.5 float theta = Vector3.Angle(node.adhesionVector, new Vector3(0.0f, -1.0f, 0.0f)) * 0.5f; //center of leaf quad Vector3 center = node.pos + new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)).normalized *local_ivyLeafSize; //size of leaf float sizeWeight = 1.5f - (Mathf.Cos(weight * 2.0f * Mathf.PI) * 0.5f + 0.5f); //random influence phi += Random.Range(-0.5f, 0.5f) * (1.3f - alignmentWeight); theta += Random.Range(-0.5f, 0.5f) * (1.1f - alignmentWeight); //create vertices BasicVertex tmpVertex = new BasicVertex(); tmpVertex.pos = center + new Vector3(-local_ivyLeafSize * sizeWeight, 0.0f, local_ivyLeafSize * sizeWeight); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 0.0f, 1.0f), theta); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 1.0f, 0.0f), phi); tmpVertex.pos += new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)).normalized *local_ivyLeafSize *sizeWeight * 0.5f; vertices.Add(tmpVertex); tmpVertex = new BasicVertex(); tmpVertex.pos = center + new Vector3(local_ivyLeafSize * sizeWeight, 0.0f, local_ivyLeafSize * sizeWeight); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 0.0f, 1.0f), theta); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 1.0f, 0.0f), phi); tmpVertex.pos += new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)).normalized *local_ivyLeafSize *sizeWeight * 0.5f; vertices.Add(tmpVertex); tmpVertex = new BasicVertex(); tmpVertex.pos = center + new Vector3(-local_ivyLeafSize * sizeWeight, 0.0f, -local_ivyLeafSize * sizeWeight); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 0.0f, 1.0f), theta); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 1.0f, 0.0f), phi); tmpVertex.pos += new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)).normalized *local_ivyLeafSize *sizeWeight * 0.5f; vertices.Add(tmpVertex); tmpVertex = new BasicVertex(); tmpVertex.pos = center + new Vector3(local_ivyLeafSize * sizeWeight, 0.0f, -local_ivyLeafSize * sizeWeight); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 0.0f, 1.0f), theta); tmpVertex.pos = rotateAroundAxis(tmpVertex.pos, center, new Vector3(0.0f, 1.0f, 0.0f), phi); tmpVertex.pos += new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)).normalized *local_ivyLeafSize *sizeWeight * 0.5f; vertices.Add(tmpVertex); //create texCoords BasicTexCoord tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(0.0f, 1.0f); texCoords.Add(tmpTexCoord); tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(1.0f, 1.0f); texCoords.Add(tmpTexCoord); tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(0.0f, 0.0f); texCoords.Add(tmpTexCoord); tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(1.0f, 0.0f); texCoords.Add(tmpTexCoord); //create triangle BasicTriangle tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 1; float _probability = Random.value; if (_probability * weight > leafProbability) { tmpTriangle.matid = 2; } tmpTriangle.v0id = (uint)vertices.Count - 1; tmpTriangle.v1id = (uint)vertices.Count - 3; tmpTriangle.v2id = (uint)vertices.Count - 2; tmpTriangle.t0id = (uint)vertices.Count - 1; tmpTriangle.t1id = (uint)vertices.Count - 3; tmpTriangle.t2id = (uint)vertices.Count - 2; triangles.Add(tmpTriangle); BasicTriangle tmpTriangle2 = new BasicTriangle(); tmpTriangle2.matid = tmpTriangle.matid; tmpTriangle2.v0id = (uint)vertices.Count - 2; tmpTriangle2.v1id = (uint)vertices.Count - 0; tmpTriangle2.v2id = (uint)vertices.Count - 1; tmpTriangle2.t0id = (uint)vertices.Count - 2; tmpTriangle2.t1id = (uint)vertices.Count - 0; tmpTriangle2.t2id = (uint)vertices.Count - 1; triangles.Add(tmpTriangle2); } } } } //branches foreach (var root in roots) { //process only roots with more than one node if (root.nodes.Count == 1) { continue; } //branch diameter depends on number of parents float local_ivyBranchDiameter = 1.0f / (float)(root.parents + 1) + 1.0f; for (int node = 0; node < root.nodes.Count - 1; node++) { //weight depending on ratio of node length to total length float weight = root.nodes[node].length / root.nodes[root.nodes.Count - 1].length; //create trihedral vertices Vector3 up = new Vector3(0.0f, -1.0f, 0.0f); Vector3 basis = (root.nodes[node + 1].pos - root.nodes[node].pos).normalized; Vector3 b0 = Vector3.Cross(up, basis).normalized *local_ivyBranchDiameter *local_ivyBranchSize *(1.3f - weight) + root.nodes[node].pos; Vector3 b1 = rotateAroundAxis(b0, root.nodes[node].pos, basis, 2.09f); Vector3 b2 = rotateAroundAxis(b0, root.nodes[node].pos, basis, 4.18f); //create vertices BasicVertex tmpVertex = new BasicVertex(); tmpVertex.pos = b0; vertices.Add(tmpVertex); tmpVertex = new BasicVertex(); tmpVertex.pos = b1; vertices.Add(tmpVertex); tmpVertex = new BasicVertex(); tmpVertex.pos = b2; vertices.Add(tmpVertex); //create texCoords BasicTexCoord tmpTexCoord = new BasicTexCoord(); float texV = (node % 2 == 0 ? 1.0f : 0.0f); tmpTexCoord.uv = new Vector2(0.0f, texV); texCoords.Add(tmpTexCoord); tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(0.3f, texV); texCoords.Add(tmpTexCoord); tmpTexCoord = new BasicTexCoord(); tmpTexCoord.uv = new Vector2(0.6f, texV); texCoords.Add(tmpTexCoord); if (node == 0) { continue; } //create triangle BasicTriangle tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 3; tmpTriangle.v1id = (uint)vertices.Count - 0; tmpTriangle.v2id = (uint)vertices.Count - 4; tmpTriangle.t0id = (uint)vertices.Count - 3; tmpTriangle.t1id = (uint)vertices.Count - 0; tmpTriangle.t2id = (uint)vertices.Count - 4; triangles.Add(tmpTriangle); tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 4; tmpTriangle.v1id = (uint)vertices.Count - 0; tmpTriangle.v2id = (uint)vertices.Count - 1; tmpTriangle.t0id = (uint)vertices.Count - 4; tmpTriangle.t1id = (uint)vertices.Count - 0; tmpTriangle.t2id = (uint)vertices.Count - 1; triangles.Add(tmpTriangle); tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 4; tmpTriangle.v1id = (uint)vertices.Count - 1; tmpTriangle.v2id = (uint)vertices.Count - 5; tmpTriangle.t0id = (uint)vertices.Count - 4; tmpTriangle.t1id = (uint)vertices.Count - 1; tmpTriangle.t2id = (uint)vertices.Count - 5; triangles.Add(tmpTriangle); tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 5; tmpTriangle.v1id = (uint)vertices.Count - 1; tmpTriangle.v2id = (uint)vertices.Count - 2; tmpTriangle.t0id = (uint)vertices.Count - 5; tmpTriangle.t1id = (uint)vertices.Count - 1; tmpTriangle.t2id = (uint)vertices.Count - 2; triangles.Add(tmpTriangle); tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 5; tmpTriangle.v1id = (uint)vertices.Count - 2; tmpTriangle.v2id = (uint)vertices.Count - 0; tmpTriangle.t0id = (uint)vertices.Count - 5; tmpTriangle.t1id = (uint)vertices.Count - 2; tmpTriangle.t2id = (uint)vertices.Count - 0; triangles.Add(tmpTriangle); tmpTriangle = new BasicTriangle(); tmpTriangle.matid = 3; tmpTriangle.v0id = (uint)vertices.Count - 5; tmpTriangle.v1id = (uint)vertices.Count - 0; tmpTriangle.v2id = (uint)vertices.Count - 3; tmpTriangle.t0id = (uint)vertices.Count - 5; tmpTriangle.t1id = (uint)vertices.Count - 0; tmpTriangle.t2id = (uint)vertices.Count - 3; triangles.Add(tmpTriangle); } } //initialize ivy mesh //loadTextures(); prepareData(); calculateVertexNormals(); prepareData(); //createDisplayList(true); }
/** one single grow iteration */ public void grow() { //parameters that depend on the scene object bounding sphere float local_ivySize = IvyManager.SceneObjMesh.boundingSphereRadius * ivySize; float local_maxFloatLength = IvyManager.SceneObjMesh.boundingSphereRadius * maxFloatLength; //normalize weights of influence float sum = primaryWeight + randomWeight + adhesionWeight; primaryWeight /= sum; randomWeight /= sum; adhesionWeight /= sum; //lets grow foreach (var root in roots) { //process only roots that are alive if (!root.alive) { continue; } IvyNode lastnode = root.nodes[root.nodes.Count - 1]; //let the ivy die, if the maximum float length is reached if (lastnode.floatingLength > local_maxFloatLength) { root.alive = false; } //grow vectors: primary direction, random influence, and adhesion of scene objectss //primary vector = weighted sum of previous grow vectors Vector3 primaryVector = lastnode.primaryDir; //random influence plus a little upright vector Vector3 randomVector = (new Vector3(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)) + new Vector3(0.0f, 0.2f, 0.0f)).normalized; //adhesion influence to the nearest triangle = weighted sum of previous adhesion vectors Vector3 adhesionVector = computeAdhesion(lastnode.pos); //compute grow vector Vector3 growVector = local_ivySize * (primaryVector * primaryWeight + randomVector * randomWeight + adhesionVector * adhesionWeight); //gravity influence //compute gravity vector Vector3 gravityVector = local_ivySize * new Vector3(0.0f, -1.0f, 0.0f) * gravityWeight; //gravity depends on the floating length gravityVector *= Mathf.Pow(lastnode.floatingLength / local_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.pos + growVector + gravityVector; //combine alive state with result of the collision detection, e.g. let the ivy die in case of a collision detection problem root.alive = root.alive && computeCollision(lastnode.pos, ref newPos, ref climbing); //update grow vector due to a changed newPos growVector = newPos - lastnode.pos - gravityVector; //create next ivy node IvyNode tmpNode = new IvyNode(); tmpNode.pos = newPos; tmpNode.primaryDir = (0.5f * lastnode.primaryDir + 0.5f * growVector.normalized).normalized; tmpNode.adhesionVector = adhesionVector; tmpNode.length = lastnode.length + (newPos - lastnode.pos).magnitude; tmpNode.floatingLength = climbing ? 0.0f : lastnode.floatingLength + (newPos - lastnode.pos).magnitude; tmpNode.climb = climbing; root.nodes.Add(tmpNode); } //lets produce child ivys foreach (var root in roots) { //process only roots that are alive if (!root.alive) { continue; } //process only roots up to hierarchy level 3, results in a maximum hierarchy level of 4 if (root.parents > 3) { continue; } //add child ivys on existing ivy nodes foreach (var node in root.nodes) { //weight depending on ratio of node length to total length float weight = 1.0f - (Mathf.Cos(node.length / root.nodes[root.nodes.Count - 1].length * 2.0f * Mathf.PI) * 0.5f + 0.5f); //random influence float probability = Random.value; if (probability * weight > branchingProbability) { //new ivy node IvyNode tmpNode = new IvyNode(); tmpNode.pos = node.pos; tmpNode.primaryDir = new Vector3(0.0f, 1.0f, 0.0f); tmpNode.adhesionVector = new Vector3(0.0f, 0.0f, 0.0f); tmpNode.length = 0.0f; tmpNode.floatingLength = node.floatingLength; tmpNode.climb = true; //new ivy root IvyRoot tmpRoot = new IvyRoot(); tmpRoot.nodes.Add(tmpNode); tmpRoot.alive = true; tmpRoot.parents = root.parents + 1; roots.Add(tmpRoot); //limit the branching to only one new root per iteration, so return return; } } } }