Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Need help with spacing trees on terrain

Discussion in 'Scripting' started by Shreddedcoconut, Nov 14, 2021.

  1. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    Hello,

    I'm currently writing a script to spawn trees across the selected terrain. Everything works as it should, but I would like to space the trees from each other, maybe using a min/max distance? I've got no idea how to do this, here's my current script, which generates random coordinates on the terrain:

    Code (CSharp):
    1. private static Vector3[] RandomCoordinates(int howManyCoords, float spacing, float maxDist)
    2.         {
    3.             List<Vector3> vector3s = new List<Vector3>();
    4.             List<Vector3> prevCoords = new List<Vector3>();
    5.  
    6.             string title = "";
    7.             string message = "";
    8.             float percent = 0f;
    9.            
    10.             for (int i = 0; i < howManyCoords; i++)
    11.             {
    12.             generate:
    13.                 float randX = 0f;
    14.                 float randZ = 0f;
    15.                 float yVal = 0f;
    16.                 randX = Random.Range(xTerrainPos, xTerrainPos + terrainWidth);
    17.                     randZ = Random.Range(zTerrainPos, zTerrainPos + terrainLength);
    18.                     yVal = terrain.SampleHeight(new Vector3(randX, 0, randZ));
    19.  
    20.                     yVal = yVal + yOffset;
    21.  
    22.                 if (prevCoords.Contains(new Vector3(randX, yVal, randZ)))
    23.                 {
    24.                     goto generate;
    25.                 }
    26.                 else
    27.                 {
    28.                         prevCoords.Add(new Vector3(randX, yVal, randZ));
    29.                         vector3s.Add(new Vector3(randX, yVal, randZ));
    30.                 }
    31.             }
    32.  
    33.             return vector3s.ToArray();
    34.         }
    xTerrainPos, terrainWidth, zTerrainPos, terrainLength, and terrain work like they should. Don't pay attention to those variables.

    So, I want to check if the prevCoords array contains anything yet (I already know how to do this by the way), and then, if it does contain at least one Vector3, I want to space the next generated coordinates from the previous generated coordinates using the spacing variable, but limit it from going too far from the previous generated coordinates using the maxDist variable.

    Any help would be greatly appreciated!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Shreddedcoconut likes this.
  3. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    I'll take a look at that, thanks!
     
    Kurt-Dekker likes this.
  4. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    If your trees have colliders, one of the simplest solutions to this is to sample a random coordinate, and then use something like a SphereCast to check whether or not your randomly selected tree meets the conditions. If they don't, resample a random coordinate. Can even use SphereCast for both min and max distance. I would not doubt that there are more efficient solutions, but as long as you're not generating multiple planes of trees multiple times per second, I don't think it would be a problem to be a bit slower. One thing that I would note about this method is that you should have an alternate exit condition to exit out of your random sampling supposing that there are no more available locations for trees to spawn.
     
    Shreddedcoconut and Lethn like this.
  5. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Was just going to write something similar but instead of a sphere cast I would have the trees spawn above the terrain and then shoot a raycast below to see what they hit and if the position of the tree is the same another tree I would just instantiate another at a random point. It's a bit of a basic method compared to the others but it meant I didn't have to deal with any weirdness spawning the trees on the terrain.
     
    Shreddedcoconut likes this.
  6. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    Thanks!
     
  7. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    My problem isn't with spawning the trees, it's keeping them close enough to each other. I call goto generate; when the generated coordinates are the same as a previously generated Vector3.

    How would I go about generated coordinates within a certain radius of the previously generated coordinates?
     
  8. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Unless I'm not understanding the problem, wouldn't the SphereCast solution I mentioned still work for that? All you need to do is use a SphereCast and check to make sure a tree is within the range.
     
    Shreddedcoconut likes this.
  9. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    Oh, sorry, I didn't read your post correctly (my bad!) How would I go about doing that exactly? Thanks!
     
  10. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Here's some pseudocode which I imagine how it would go, I didn't have an editor open so I didn't actually test to see if it works. On that note, above I meant to say OverlapSphere instead of SphereCast.

    Code (CSharp):
    1. public void GenerateTrees(int number, float minDist, float maxDist, int maxAttempts = 100)
    2. {
    3.     // generate the first tree
    4.     Instantiate(treePrefab, randomCoord(), randomRotation());
    5.  
    6.     Vector3 position = randomCoord();
    7.     int attempts = 0;
    8.     for(int i=1; i<number; i++)
    9.     {
    10.         while(!treeCondition(position, minDist, maxDist))
    11.         {
    12.             if(attempts++ > maxAttempts)
    13.                 return; // exit if there are too many failed attempts
    14.             position = randomCoord();
    15.         }
    16.         Instantiate(treePrefab, position, randomRotation());
    17.         attempts = 0;
    18.     }
    19. }
    20.  
    21. private bool treeCondition(Vector3 position, float minDist, float maxDist)
    22. {
    23.     bool min = Physics.OverlapSphere(position, minDist, treeLayerMask);
    24.     bool max = Physics.OverlapSphere(position, maxDist, treeLayerMask);
    25.     return !min && max;
    26. }
     
    Shreddedcoconut likes this.
  11. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    It looks really good, I'll adapt it to my code and get back to you :) Thanks!
     
  12. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    Actually, Physics.OverlapSphere is a Collider[], so it can't be used as a bool. Is there a similar function that can be used as a bool?
     
  13. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    Well, you can still solve it with that concept, just check the size of the collection it returns.
     
    Shreddedcoconut likes this.
  14. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    I tried to use .Length > 0 but it always returns false, no matter what. Any idea why?
     
  15. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    My guess is the LayerMask? But it's hard to tell without the full picture.
     
    Shreddedcoconut likes this.
  16. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    I decided to change the function to a bool[] array, and have the first bool in the array be the minimum distance, then the second bool be the maximum distance. I can access them by doing
    Code (CSharp):
    1.  
    2. bool[] minMax = treeCondition(pos, min, max);
    3. if (minMax[0] == false && minMax[1] == true)
    4. {
    5. }
    6. else
    7. {
    8. }
    But the only problem is, the first bool, being the minimum distance, is always false. So, I decided to try it without the LayerMask parameter, and it is still always false. I can't figure out why though.

    Both the first and second bool are determined by the Collider[] length, like this:
    Code (CSharp):
    1. private bool[] treeCondition(Vector3 position, float minDist, float maxDist)
    2. {
    3.     return new bool[] { Physics.OverlapSphere(position, minDist, treeLayerMask).Length > 0, Physics.OverlapSphere(position, maxDist, treeLayerMask).Length > 0 };
    4. }
     
    Last edited: Nov 14, 2021
  17. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    For specifically Debugging this issue, I would first try both of the conditions isolated (eg. only do the minDist and see if it works, and then do the maxDist), and printing out their Length values. You might also want to try changing the maxAttempts to be a larger number if your random number generator is large, or change your randomPosition() to return a smaller range of numbers.

    One thing I might add is OverlapSphere won't pick up anything if your trees don't have Colliders, which I'm just adding incase you missed that.
     
    Shreddedcoconut likes this.
  18. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    The number size for both the random number generator and the randomPosition() are entirely user-defined, so it could possibly be a number from 1 to 1,000,000 (or higher, I guess)

    I misspoke in my earlier post, I mean that the first bool, being the minimum distance, always returns true, when I need it to be false. I can call
    goto generate;
    if it does not return the wanted value (
    minMax[0] == false
    is the wanted value), but the biggest issue is that by doing this, there is a very very slim chance that it'll return the desired value, so it would keep going back to generate, and take a long, long time just to generate one Vector3 that is considered acceptable, and take hours upon hours to finish generating even a small amount of coordinates.
     
  19. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,593
    The purpose of limiting the numbers of the random numbers was for testing purposes; to help you figure out if the problem is with the code, or that the sample size is too large, I'm not asking you to limit your sizes for release purposes.

    May I see the full code? it's hard to piece together the problem with only snippets of the problem.

    One thing I might also add is I highly recommend avoiding
    goto
    , as it's something that can easily obfuscate code, and is generally regarded as bad code practice in higher level languages.
     
    Last edited: Nov 14, 2021
    Shreddedcoconut likes this.
  20. Shreddedcoconut

    Shreddedcoconut

    Joined:
    Jul 30, 2021
    Posts:
    61
    I'll DM you the code in a couple of minutes :)

    I'll remove
    goto
    and
    i++
    at the beginning of the loop, and only increment
    i
    it if it successfully adds a new Vector3 to the array.