Search Unity

Procedural terrain - Trees generation

Discussion in 'Scripting' started by Bratveja, Jul 21, 2018.

  1. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi! I'm currently working on procedural generation of a terrain! I've managed to find tutorial about it but I got to the moment where I had to spawn the trees. My idea was getting a random vertices from the generated mesh and spawning the trees on them. After many hours of struggling and looking around the internet I've finally found a solution for this problem too. But the trees are not spawning where I want them! My mesh is split on couple of regions and i want to spawn them on a given region. The image is showing what exactly I mean. So my question is how can I spawn them on the grass region? Before I post the script: I'm using empty objects as spawn points and generating them on random vertices of the generated mesh. Then I'm instantiating the trees on those points.

    regions.png

    And these are the tutorials I used: Credit ---



    https://gist.github.com/v21/5378391

    Here is my script:

    Code (CSharp):
    1. public class MapGenerator : MonoBehaviour
    2.     {
    3.  
    4.         public enum DrawMode { NoiseMap, ColorMap, Mesh, FallofMap }
    5.         public DrawMode drawMode;
    6.  
    7.         const int mapChunkSize = 241;
    8.         public float noiseScale;
    9.         [Range(0, 6)]
    10.         public int levelOfDetail;
    11.         public int octaves;
    12.         [Range(0, 1)]
    13.         public float persistance;
    14.         public float lacunarity;
    15.  
    16.         public int seed;
    17.         public Vector2 offset;
    18.  
    19.         public bool useFalloff;
    20.         public GameObject mesh;
    21.         public MeshCollider mCollider;
    22.  
    23.         #region TreeSpawnPoints
    24.         public List<Transform> tree1SpawnPoints = new List<Transform>();
    25.         public List<Transform> tree2SpawnPoints = new List<Transform>();
    26.         public List<Transform> tree3SpawnPoints = new List<Transform>();
    27.         public List<GameObject> treeObj = new List<GameObject>();
    28.         #endregion
    29.  
    30.         public float meshHeightMultiplier;
    31.         public AnimationCurve meshHeightCurve;
    32.  
    33.         public bool autoUpdate;
    34.         [Range(0, 5)]
    35.         public float timerBeforeTreeSpawn = 10;
    36.         public float timer = 0;
    37.         bool timerEnd = false;
    38.  
    39.         private Vector3 randomPoint;
    40.  
    41.         public TerrainType[] regions;
    42.         public bool isSpawned = true;
    43.         float[,] fallofMap;
    44.  
    45.         //Generating map on awake with random seed and generating collider after the map is generated
    46.         private void Awake()
    47.         {
    48.             seed = Random.Range(0, 800);
    49.             GenerateMap();
    50.             fallofMap = FallOfGenerator.GenerateFallofMap(mapChunkSize);
    51.             if (mesh.GetComponent<MeshCollider>() == true)
    52.             {
    53.                 Destroy(mesh.GetComponent<MeshCollider>());
    54.                 mesh.AddComponent<MeshCollider>();
    55.             }
    56.         }
    57.  
    58.         private void Start()
    59.         {
    60.             mCollider = mesh.GetComponent<MeshCollider>();
    61.         }
    62.  
    63.         private void Update()
    64.         {
    65.             //timer for late tree spawn so we can generate mesh and spawn them on the new mesh
    66.             if(!timerEnd)
    67.             {
    68.                 timer += Time.deltaTime;
    69.                 if (timer > timerBeforeTreeSpawn)
    70.                 {
    71.                     mCollider = mesh.GetComponent<MeshCollider>();  
    72.                     GenerateMapTrees();
    73.                     timerEnd = true;
    74.                 }
    75.             }
    76.  
    77.         }
    78.  
    79.         //generating the noise map on object
    80.         public void GenerateMapTrees()
    81.         {
    82.  
    83.             for (int y = 0; y < mapChunkSize; y++)
    84.             {
    85.                 for (int x = 0; x < mapChunkSize; x++)
    86.                 {
    87.  
    88.                     //checking if isSpawned is true so we can spawn trees only 1 time
    89.                     if (isSpawned)
    90.                     {
    91.                         //TREE AT LAND
    92.                             if (regions[2].height > regions[1].height)
    93.                             {
    94.  
    95.               //generatin trees at the given empty objects that are spawned randomly on the  generated mesh
    96.               //and we're getting on of the vertices on the mesh and than we're spawning the empty object so
    97.                                 //we can spawn the given tree object
    98.                                 //looping through the whole list so we can generate tree for each spawn point
    99.                                 //generating random spawnpoints on the collider of the object
    100.                                 //TREE 1 AT LAND
    101.                                     for (int t1 = 0; t1 < tree1SpawnPoints.Count; t1++)
    102.                                     {
    103.                                     for (int _t1 = 0; _t1 < regions[2]._tree1SpawnPoints.Count; _t1++)
    104.                                     {
    105.                                         Vector3 randomPoint = GetRandomPointOnMesh(mCollider.sharedMesh);
    106.                                         randomPoint += mCollider.transform.position;
    107.  
    108.                                         float coords1X = randomPoint.x;
    109.                                         float coords1Y = randomPoint.y;
    110.                                         float coords1Z = randomPoint.z;
    111.  
    112.                                         Instantiate(regions[2]._tree1SpawnPoints[_t1],
    113.                                         new Vector3(coords1X, coords1Y, coords1Z),
    114.                                         regions[2]._tree1SpawnPoints[_t1].transform.rotation);
    115.  
    116.                                         regions[2]._treeObj = treeObj[2];
    117.                                         tree1SpawnPoints[y * mapChunkSize + x] = regions[2]._tree1SpawnPoints[t1];
    118.                                         Instantiate(regions[2]._treeObj, new Vector3(coords1X, coords1Y, coords1Z),
    119.                                         regions[2]._tree1SpawnPoints[t1].transform.rotation);
    120.                                     }
    121.                                 }
    122.                             }
    123.                         }
    124.  
    125.                     isSpawned = false;
    126.                     }
    127.             }
    128.         }
    129.  
    130.         public void GenerateMap()
    131.         {
    132.             //saving the generated noise map from the noise GenScript
    133.             float[,] noiseMap = NoiseGen.GenerateNoiseMap(mapChunkSize, mapChunkSize, seed,                                noiseScale, octaves, persistance, lacunarity, offset);
    134.  
    135.             Color[] colorMap = new Color[mapChunkSize * mapChunkSize];
    136.  
    137.             for (int y = 0; y < mapChunkSize; y++)
    138.             {
    139.                 for (int x = 0; x < mapChunkSize; x++)
    140.                 {
    141.                     //clamping the noise map to be between 0 and 1 from the generated falloff map
    142.                     if (useFalloff)
    143.                     {
    144.                         noiseMap[x, y] = Mathf.Clamp01(noiseMap[x, y] - fallofMap[x, y]);
    145.                     }
    146.  
    147.                     float currentHeight = noiseMap[x, y];
    148.  
    149.                     //setting the collors of each region
    150.                     for (int i = 0; i < regions.Length; i++)
    151.                     {
    152.                         if (currentHeight <= regions[i].height)
    153.                         {
    154.                             colorMap[y * mapChunkSize + x] = regions[i].color;
    155.                             break;
    156.                         }
    157.                     }
    158.                 }
    159.             }
    160.  
    161.  
    162.             #region IfNeeded
    163.  
    164.             //displaying solo maps if choosen
    165.             MapDisplay display = FindObjectOfType<MapDisplay>();
    166.             if (drawMode == DrawMode.NoiseMap)
    167.             {
    168.                 display.DrawTexture(TextureGenerator.TextureFromHeightMap(noiseMap));
    169.             }
    170.             else if (drawMode == DrawMode.ColorMap)
    171.             {
    172.                 display.DrawTexture(TextureGenerator.TextureFromColorMap(colorMap, mapChunkSize,                                mapChunkSize));
    173.             }
    174.             else if (drawMode == DrawMode.Mesh)
    175.             {
    176.                 display.DrawMesh(MeshGenerator.GenerateTerrainMesh(noiseMap, meshHeightMultiplier,                            meshHeightCurve, levelOfDetail), TextureGenerator.TextureFromColorMap(colorMap,                                      mapChunkSize, mapChunkSize));
    177.             }
    178.             else if (drawMode == DrawMode.FallofMap)
    179.             {
    180.                                                                                                                       display.DrawTexture(TextureGenerator.TextureFromHeightMap(FallOfGenerator.GenerateFallofMap(mapChunkSize)));
    181.             }
    182.             #endregion
    183.         }
    184.  
    185.         //with this method a random point on the mesh is generated
    186.         Vector3 GetRandomPointOnMesh(Mesh mesh)
    187.         {
    188.             float[] sizes = GetTriSizes(mesh.triangles, mesh.vertices);
    189.             float[] cSizes = new float[sizes.Length];
    190.             float total = 0;
    191.  
    192.             for (int i = 0; i < sizes.Length; i++)
    193.             {
    194.                 total += sizes[i];
    195.                 cSizes[i] = total;
    196.             }
    197.  
    198.             float randomSample = Random.value * total;
    199.  
    200.             int triIndex = -1;
    201.  
    202.             for (int l = 0; l < sizes.Length; l++)
    203.             {
    204.                 if (randomSample <= cSizes[l])
    205.                 {
    206.                     triIndex = l;
    207.                     break;
    208.                 }
    209.             }
    210.  
    211.             if (triIndex == -1)
    212.             {
    213.                 Debug.LogError("triIndex shold never be -1");
    214.             }
    215.  
    216.             Vector3 a = mesh.vertices[mesh.triangles[triIndex * 3]];
    217.             Vector3 b = mesh.vertices[mesh.triangles[triIndex * 3 + 1]];
    218.             Vector3 c = mesh.vertices[mesh.triangles[triIndex * 3 + 2]];
    219.  
    220.             float r = Random.value;
    221.             float s = Random.value;
    222.  
    223.             if (r + s >= 1)
    224.             {
    225.                 r = 1 - r;
    226.                 s = 1 - s;
    227.             }
    228.  
    229.             Vector3 pointOnMesh = a + r * (b - a) + s * (c - a);
    230.             return pointOnMesh;
    231.         }
    232.  
    233.         float[] GetTriSizes(int[] tris, Vector3[] verts)
    234.         {
    235.             int triCont = tris.Length / 3;
    236.             float[] sizes = new float[triCont];
    237.             for (int i = 0; i < triCont; i++)
    238.             {
    239.                 sizes[i] = .5f * Vector3.Cross(verts[tris[i * 3 + 1]] - verts[tris[i * 3]], verts[tris[i * 3 + 2]] - verts[tris[i * 3]]).magnitude;
    240.             }
    241.  
    242.             return sizes;
    243.         }
    244.  
    245.         //clamping values
    246.         private void OnValidate()
    247.         {
    248.             if (lacunarity < 1)
    249.             {
    250.                 lacunarity = 1;
    251.             }
    252.             if (octaves < 0)
    253.             {
    254.                 octaves = 0;
    255.             }
    256.             fallofMap = FallOfGenerator.GenerateFallofMap(mapChunkSize);
    257.  
    258.         }
    259.     }
    260.  
    261.     [System.Serializable]
    262.     public struct TerrainType
    263.     {
    264.         public string name;
    265.         public float height;
    266.         public Color color;
    267.         public List<Transform> _tree1SpawnPoints;
    268.         public List<Transform> _tree2SpawnPoints;
    269.         public List<Transform> _tree3SpawnPoints;
    270.         public GameObject _treeObj;
    271.     }
    272.    
     
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    The region colours (in your image) are being determined using this code:
    Code (CSharp):
    1.                 float currentHeight = noiseMap[x, y];
    2.  
    3.                 //setting the collors of each region
    4.                 for (int i = 0; i < regions.Length; i++)
    5.                 {
    6.                     if (currentHeight <= regions[i].height)
    Notice the height check against the noiseMap at the given mesh coordinate (x,y). Therefore, presumably in the
    GetRandomPointOnMesh
    method (which is, I believe, where the tree spawn locations are being calculated), a similar check should be made to determine that the mesh(x,y) is in the appropriate region. See if that helps.
     
  3. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    This tutorial might help as an alternative:
     
  4. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi! Thanks for the reply. But I think i didn't get it right : I'm checking for the height as you said I should do. But still they're spawning everywhere. I assumed it should be from the andom value that was given so I've changed it (hard code it) to be the second region which is the grass but still it didnt worked. : The code:

    Code (CSharp):
    1.     //with this method a random point on the mesh is generated
    2.     Vector3 GetRandomPointOnMesh(Mesh mesh)
    3.     {
    4.         float[] sizes = GetTriSizes(mesh.triangles, mesh.vertices);
    5.         float[] cSizes = new float[sizes.Length];
    6.         float total = 0;
    7.  
    8.         for (int i = 0; i < sizes.Length; i++)
    9.         {
    10.             total += sizes[i];
    11.             cSizes[i] = total;
    12.         }
    13.  
    14.         float randomSample = Random.value * total;
    15.  
    16.         int triIndex = -1;
    17.  
    18.         for (int l = 0; l < sizes.Length; l++)
    19.         {
    20.             if (randomSample <= cSizes[l])
    21.             {
    22.                 triIndex = l;
    23.                 break;
    24.             }
    25.         }
    26.  
    27.         if (triIndex == -1)
    28.         {
    29.             Debug.LogError("triIndex shold never be -1");
    30.         }
    31.  
    32.         Vector3 a = mesh.vertices[mesh.triangles[triIndex * 3]];
    33.         Vector3 b = mesh.vertices[mesh.triangles[triIndex * 3 + 1]];
    34.         Vector3 c = mesh.vertices[mesh.triangles[triIndex * 3 + 2]];
    35.  
    36.         float r = Random.Range(regions[2].height, regions[2].height);
    37.         float s = Random.Range(regions[2].height, regions[2].height);
    38.  
    39.         if (r + s >= 1)
    40.         {
    41.             r = 1 - r;
    42.             s = 1 - s;
    43.         }
    44.  
    45.         for (int x = 0; x < mapChunkSize; x++)
    46.         {
    47.             for (int y = 0; y < mapChunkSize; y++)
    48.             {
    49.                 float currentHeigth = noiseMap[x, y];
    50.                 for (int i = 0; i < regions.Length; i++)
    51.                 {
    52.                     if (currentHeigth <= regions[i].height)
    53.                     {
    54.                         Vector3 pointOnMesh = a + r * (b - a) + s * (c - a);
    55.                         return pointOnMesh;
    56.                     }
    57.                 }
    58.             }
    59.         }
    60.         return new Vector3(0, 0, 0);
    61.     }
     
  5. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi, just got to my pc will check it out and will let you know if it did help.Thanks for the reply!
     
  6. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    What about using something like this? I am not completely sure whether to reference the height from the noise map or the mesh data (both have been referenced in our discussion above). So you will most likely need to adjust the code here but hopefully it gives an idea of something you could try :-
    Code (CSharp):
    1.     void RecordPossibleTreeSpawns()
    2.     {
    3.         int treeRegion = 2; // Set to region in which trees should spawn
    4.         float treeMin = regions[treeRegion    ].height;
    5.         float treeMax = regions[treeRegion + 1].height;
    6.  
    7.         for (int x = 0; x < mapChunkSize; x++)
    8.         {
    9.             for (int y = 0; y < mapChunkSize; y++)
    10.             {
    11.                 float currentHeigth = noiseMap[x, y];
    12.  
    13.                 if (currentHeigth >= treeMin && currentHeigth <= treeMax)
    14.                     m_treeSpawns.Add(new Vector3(x, y, currentHeigth));
    15.             }
    16.         }
    17.     }
    18.  
    19.     Vector3 GetUnusedRandomTreeSpawn()
    20.     {
    21.         var randIndex = Random.Range(0, m_treeSpawns.Count);
    22.         var chosenVec = m_treeSpawns[randIndex];
    23.         m_treeSpawns.RemoveAt(randIndex);
    24.         return chosenVec;
    25.     }
    26.  
    27.     List<Vector3> m_treeSpawns = new List<Vector3>();
     
  7. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi! After adding your script and modifying my GenerateMapTree like this:

    Code (CSharp):
    1.  
    2. public void GenerateMapTrees()
    3.     {
    4.         for (int y = 0; y < mapChunkSize; y++)
    5.         {
    6.             for (int x = 0; x < mapChunkSize; x++)
    7.             {
    8.  
    9.                 //checking if isSpawned is true so we can spawn trees only 1 time
    10.                 if (isSpawned)
    11.                 {
    12.                     //TREE AT LAND
    13.                     if (regions[2].height > regions[1].height)
    14.                     {
    15.  
    16.                         //generatin trees at the given empty objects that are spawned randomly on the  generated mesh
    17.                         //and we're getting on of the vertices on the mesh and than we're spawning the empty object so
    18.                         //we can spawn the given tree object
    19.                         //looping through the whole list so we can generate tree for each spawn point
    20.                         //generating random spawnpoints on the collider of the object
    21.                         //TREE 1 AT LAND
    22.                         for (int t1 = 0; t1 < tree1SpawnPoints.Count; t1++)
    23.                         {
    24.                             for (int _t1 = 0; _t1 < regions[2]._tree1SpawnPoints.Count; _t1++)
    25.                             {
    26.                                 RecordPossibleTreeSpawns();
    27.  
    28.                                 float coords1X = GetUnusedRandomTreeSpawn().x;
    29.                                 float coords1Y = GetUnusedRandomTreeSpawn().y;
    30.                                 float coords1Z = GetUnusedRandomTreeSpawn().z;
    31.  
    32.                                 //Instantiate(regions[2]._tree1SpawnPoints[_t1],
    33.                                 //new Vector3(coords1X, coords1Y, coords1Z),
    34.                                 //regions[2]._tree1SpawnPoints[_t1].transform.rotation);
    35.  
    36.                                 regions[2]._treeObj = treeObj[2];
    37.                                 //tree1SpawnPoints[y * mapChunkSize + x] = regions[2]._tree1SpawnPoints[t1];
    38.                                 Instantiate(regions[2]._treeObj, new Vector3(coords1X, coords1Y, coords1Z),
    39.                                 regions[2]._tree1SpawnPoints[t1].transform.rotation);
    40.                             }
    41.                         }
    42.                     }
    43.                 }
    44.  
    45.                 isSpawned = false;
    46.             }
    47.         }
    48.     }
    49.  
    I'm spawning the trees in the air. I think this:

    Code (CSharp):
    1.  
    2. public static class TextureGenerator
    3. {
    4.     public static Texture2D TextureFromColorMap(Color[] colorMap, int width, int height )
    5.     {
    6.         Texture2D texture = new Texture2D(width, height);
    7.         texture.filterMode = FilterMode.Point;
    8.         texture.wrapMode = TextureWrapMode.Clamp;
    9.         texture.SetPixels(colorMap);
    10.         texture.Apply();
    11.         return texture;
    12.     }
    13.  
    14.     public static Texture2D TextureFromHeightMap(float[,] heightMap)
    15.     {
    16.         int width = heightMap.GetLength(0);
    17.         int height = heightMap.GetLength(1);
    18.  
    19.         Color[] colourMap = new Color[width * height];
    20.         for (int y = 0; y < height; y++)
    21.         {
    22.             for (int x = 0; x < width; x++)
    23.             {
    24.                 colourMap[y * width + x] = Color.Lerp(Color.black, Color.white, heightMap[x, y]);
    25.             }
    26.         }
    27.         return TextureFromColorMap(colourMap, width, height);
    28.     }
    29. }
    30.  
    Will help you to understand how the color is used or at least I think It will.
     
  8. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Are they following the contours if the landscape, even if they are up in the air?
     
  9. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Nope. They look like this: https://imgur.com/P9JVMSr
     
  10. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Ok, so how about trying this- make the X and Z dimensions be 2 and Y be unrestricted. So now you have a 2x2 footprint of the land. It will drastically reduce the landscape size and possibly limit it to containing just a single region type.

    Once you have that, then if there is no region 2, then no trees should be generated. So you can more easily check the logic that disallows trees in inappropriate regions.

    Likewise, if there is a region 2, then it should help to follow the landscape values in the debugger to see where (what height) the trees should be placed at.
     
  11. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi! Sorry for the delayed reply. So I did everything you said and now I'm spawning them at the given height but not on the position of the mesh but on some random world pos coords. My question is how can i get the positions of the vertices on the green region. Or is there any way i can detect the colors of the mesh and based on that to spawn the trees on this specific color map/texture?
     
  12. NeverConvex

    NeverConvex

    Joined:
    Jun 26, 2013
    Posts:
    88
    It looks like colourMap[y * width + x] stores the color of your mesh at a given x, y value. Why don't you use that to determine if a location is the right color? If you have them spawning at appropriate heights, all you should need to do is check that both the height and color are correct before declaring a location a possible tree spawn place (although I thought height was uniquely determined by color and vice versa from your code, so it seems odd that you'd need to check them separately).
     
  13. Bratveja

    Bratveja

    Joined:
    Mar 11, 2015
    Posts:
    29
    Hi! This is exactly what I'm doing now will update what I came up with. And I got no idea why this is happening the color map is based on the height of the mesh so I can check only the height sadly I cannot access the position of the given color and assigned it to the inst method. I thought of storing every triangle that have height bigger than the water region and smaller than the mountain region in a List and the checking for the positions of the triangles. But this is going to be way too slow when the loading screen is introduced. This probably can be optimized with some kind of algorithm but I will have to look deeper for it. For now if what you said work I'm not probably going to try something else until the opt part.
     
  14. DRpatak

    DRpatak

    Joined:
    Dec 14, 2014
    Posts:
    3
    Hello. I know you solved the problem by now but just in case i will offer simple solution to your problem. I dont know how far into tutorial you are and if you finished it by now.

    All what you need is there in terrain mesh generation as there you define Y vertex position by evaluating height value.

    Code (CSharp):
    1.     public static class RandomTreeGenerator {
    2.          public static void GenerateTrees(float[,] heightmap, NoiseData noiseData, ColorRegions regions) {
    3.             int width = 250;
    4.             int height = 250;
    5.  
    6.             // Offset center of terrain chunk.
    7.             float offsetX = width / -2f;
    8.             float offsetY = width / 2f;
    9.  
    10.             // limit tree occurence
    11.             float brushStrength = 0.50f;
    12.  
    13.             for (int x = 0; x < height; x++) {
    14.                 for (int y = 0; y < width; y++) {
    15.                     float tmpData = noiseData.Curve.Evaluate(heightmap[x, y]);
    16.                     // Evaluated data must be within region 2 start height and region 3 start height.
    17.                     if (tmpData > regions.regionList[2].startHeight && tmpData < regions.regionList[3].startHeight) {
    18.                         // For position you can use same math as it was used when you generated terrain mesh.
    19.                         Vector3 tmpPosition = new Vector3((offsetX + x) * noiseData.scale, (tmpData * noiseData.heightMultiplyer) * noiseData.scale, (offsetY - y) * noiseData.scale);
    20.                         // Using perlin will guarantee us same tree layout when we reload certain seed.
    21.                         float _perlin = CalculatePerlin(y, x, width, height, noiseData.seed, noiseData.scale, Vector2.zero);
    22.                         if (_perlin < brushStrength) {
    23.                             GameObject _go = Object.Instantiate(prefab, parent, false);
    24.                             _go.transform.position = tmpPosition;
    25.                             // Do rest of the stuff with gameobject. Note that you can also seed scale, type of tree, rotation of tree as well...
    26.                             // This will enable you to have same tree layout every time you build same seed.
    27.                         }
    28.                     }
    29.                 }
    30.             }
    31.         }
    32.         private static float CalculatePerlin(int x, int y, int width, int height, int seed, float scale, Vector2 offset) {
    33.             float _x = ((float)x + seed) / width * scale + offset.x;
    34.             float _y = ((float)y + seed) / height * scale - offset.y;
    35.  
    36.             return Mathf.PerlinNoise(_x, _y);
    37.         }
    38.     }
    Also this code should not be used as solution as many can be done to optimize. First using tree pool instead Instantiate is a must. You should do tree positions when you do terrain mesh generation on another thread and return custom class containing Vertices, Triangles, Uvs and TreePositions and what else you could do at that instance. This will save you couple of double for loops. Using Action and Func would save you dozent of ifs, whiles, fors and bools.

    Code (CSharp):
    1.  
    2. // this should be in another class and executed on another thread.
    3. public void GenerateTerrain(Data data, Action<Results> callback){
    4.     Results _results = new Results();
    5.    // Do heavy calculations that take time...
    6.   _result =;
    7.    callback.Invoke(_result);
    8. }
    9.  
    10. // somewhere on monobehaviour class
    11. void Start(){
    12.  SomeClass.GenerateTerrain(data (results) => {
    13.    // wait until callback was invoked.
    14.     if(results != null) {}
    15.     else {}
    16.    });
    17. }
    18.  
    19.  
    But again that is how i would do it. Dont take me wrong im not trying to teach you as im in no position to do that, but consider this as advice for you to take into consideration.
    I hope this helps you or anyone else in need. Regards!
     
    Last edited: Oct 21, 2018
  15. ScottGameDevelopment

    ScottGameDevelopment

    Joined:
    Apr 7, 2020
    Posts:
    42
    Hello everyone, I know it's a couple of years on since this was addressed but has anyone got a easier way of spawning trees on a procedural terrain mesh after completing all 21 episodes of Sabastian leagues tutorials
     
    attackontitanisop likes this.
  16. attackontitanisop

    attackontitanisop

    Joined:
    May 9, 2021
    Posts:
    1
    Guys is there any simple way and why the hell is Sabastian is not doing something about this he should upload some videos teaching us the way of doing it properly
     
  17. meozdemir90

    meozdemir90

    Joined:
    Jun 5, 2020
    Posts:
    6
    Just an idea, by using noise generator creating a new biomes/humidity map/heat maps and then using this maps we can create trees whatsoever i will drop here couple links to combine these ideas

    https://code2d.wordpress.com/2020/07/21/tilemaps/ here you can define bounds of spawns

    https://gamedevacademy.org/procedural-2d-maps-unity-tutorial/ here you can create biomes and use these maps as bounds again, i believe biomes would be much more beneficial if you want to add dynamic weather system and whatsoever
     
  18. Spencerino1337

    Spencerino1337

    Joined:
    Feb 7, 2021
    Posts:
    2
    For anyone that is following Sebastian Lague's tutorial, I have figured a way to do it - not sure if its the best way of doing things, but it works (for now).

    Create tree gen script, and attach it to the map generator. Create a static transform on said script, this will hold the transform for each terrain chunk that is generated, which we can then use to convert each vertex to a world position.

    Then, in the EndlessTerrain script, in the UpdateTerrainChunk method, we can set this static transform to the terrain chunk's mesh's transform.

    Finally, we can instantiate an object depending on randomness and height values of the vertex as shown below using a method within the tree gen script. Simple, rough, but im sure this might be easy to expand upon for biomes etc.

    Code (CSharp):
    1. public static Transform chunkTransform;
    Code (CSharp):
    1. TreeGeneration.chunkTransform = meshObject.transform;
    Code (CSharp):
    1. public void SpawnTrees(Vector3[] vertices)
    2.     {
    3.         Matrix4x4 localToWorld = chunkTransform.localToWorldMatrix;
    4.         for (int i = 0; i < vertices.Length; i++)
    5.         {
    6.             Vector3 worldPt = localToWorld.MultiplyPoint3x4(vertices[i]);
    7.  
    8.             if (worldPt.y > 3 && worldPt.y < 25 && UnityEngine.Random.Range(0, 200) == 5)
    9.             {
    10.                 Instantiate(tree, worldPt, Quaternion.identity);
    11.             }
    12.         }
    13.     }
     
  19. nico646c

    nico646c

    Joined:
    Jul 15, 2022
    Posts:
    1
    Can you show more precisely how you implemented this in Sebastian Lague's tutorial?
    Could not figure out how you got the "tree" reference?
     
  20. Spencerino1337

    Spencerino1337

    Joined:
    Feb 7, 2021
    Posts:
    2
    The "tree" variable will be a public GameObject listed in the TreeGeneration.cs script. Just a heads up, I wrote this before Sebastien got around to refactoring his code in the last couple episodes, if your code is fully up to date - this won't work exactly like this, you will have to adapt it somehow. Hope this helps! :)
     
  21. rex89555

    rex89555

    Joined:
    Oct 21, 2021
    Posts:
    1
    where do we call the spawn trees method?
     
  22. meozdemir90

    meozdemir90

    Joined:
    Jun 5, 2020
    Posts:
    6
    Probably in update terrain chunk method or load method after he does factoring