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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

procedural object placement and problems with overlapping meshes

Discussion in 'Scripting' started by brizzelsprout, May 19, 2015.

  1. brizzelsprout

    brizzelsprout

    Joined:
    Mar 26, 2015
    Posts:
    7
    First off, I'm sorry if this end up being really long. I've been working on this issue for a while now.

    I'm randomly generating a forest. Each tree is one of 5 prefabs that are then scaled to 1,2, or 3 times it's size via transform.localScale. For testing purposes I'm calling CheckZOverlap() in Update but that was just where I last put it in the process of seeing what was going on.


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class generation4 : MonoBehaviour {
    5.  
    6.     public GameObject[] trees;
    7.     private GameObject[,] forest;
    8.     private int width;
    9.     private int length;
    10.     private int scale;
    11.     private float size;
    12.  
    13.     // Use this for initialization
    14.     void Start () {
    15.  
    16.         width = 10;
    17.         length = 20;
    18.         scale = 20;
    19.         forest = new GameObject[width, length];
    20.         GenerateLayout ();
    21.     }
    22.  
    23.     void GenerateLayout()
    24.     {
    25.         for (int x = 0; x <= width-1; x++) {
    26.             for (int z = 0; z <= length-1; z++) {
    27.              
    28.                 size = Random.Range (1, 3);
    29.              
    30.                 var loc = new Vector3 (Random.Range (x * scale, (x * scale) + (scale)), 0, Random.Range (z * scale, (z * scale) + (scale)));
    31.              
    32.                 forest [x, z] = Instantiate (trees [Random.Range (0, 5)], loc, Quaternion.Euler (0, Random.Range (0, 360), 0)) as GameObject;
    33.  
    34.                 forest [x, z].transform.localScale = new Vector3 (size, size, size);
    35.                 if(z>0)
    36.                 {
    37.                     CheckZOverlap(forest[x,z], forest[x,z]);[
    38.                 }
    39.             }
    40.         }
    41.     }
    42.  
    43.     void CheckZOverlap(GameObject tree1, GameObject tree2)
    44.     {
    45.         Bounds b1 = tree1.GetComponent<MeshFilter> ().mesh.bounds;
    46.         Bounds b2 = tree2.GetComponent<MeshFilter> ().mesh.bounds;
    47.         Bounds r2 = tree2.GetComponent<Renderer> ().bounds; //debug
    48.  
    49.         while (b1.Intersects(b2)) {
    50.                 tree2.transform.Translate(new Vector3(0.0f,0.0f,1.0f), Space.World);
    51.                 tree2.GetComponent<MeshFilter>().mesh.RecalculateBounds();
    52.         }
    53.     }
    Everything seems to run just dandy up untill I detect the overlapping bounds and move the tree. The while loop in CheckZOverlap(); causes an infinite loop because the bounds of the object are not being updated even though I'm calling RecalculateBounds()

    I've tried this a number of different ways including colliders and raycasting ect.. ect...

    Obviously nothing I've tried has worked as needed and I think it's because I'm calling most of the code before anything is drawn on the screen causing the rendering/physics engines to not update the bound or colliders. (I could be wrong though)

    So why doesn't RecalcuateBounds() work?
    Also, this can't be the best way at doing this. What are your guys suggestions on methods for generating the layout?
     
  2. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    I don't have too much experience in working with mesh filters and mesh bounds, but your logic seems solid so it's likely you're misinterpreting what this code is doing.

    I think when you get your b1 and b2 in CheckZOverlap() you're getting a copy instead of a reference to your bounds. If that is the case, you ARE correctly moving your trees and eventually they won't overlap, but you are still checking b1 and b2, which are equal to the bounds BEFORE you moved the trees.

    This is just a guess, I hope you figure it out!
     
    brizzelsprout likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,806
    Yes, what Gambit MSplitz said: Bounds are structures (value types) so those b1/b2 objects are actually copies of what it was at the time you set it and will never be updated in your while loop.

    One handy way to debug procedural code in Unity that I use all the time is to make the functions that generate my stuff into CoRoutines, and then yield every time one object is created. That way when you run it, you can watch the procedural forest come together "live," and it can give you insight as to what exactly is going on.
     
    brizzelsprout likes this.
  4. brizzelsprout

    brizzelsprout

    Joined:
    Mar 26, 2015
    Posts:
    7
    Ahh yes, that makes perfect sense. I've never used coroutines in unity before so I'll need to look into that. It's been on my list but I just haven't gotten to it yet. Do you have any good tutorials to suggest?
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,806
    I don't have any specific suggestions for coroutine learning but there are plenty of simple (and complex) tutorials out there. Google is your friend.

    Unity chose a very clever way of doing it: by making your coroutine actually be a "generator" function that yields individual values every time Unity calls (on your behalf) the IEnumerator that it returns. Unity inspects the things you return from your coroutine (various types of objects) in order to determine how to run your coroutine "for the next step," as it were.

    It's a very simple system but takes a moment to wrap your head around. If you know how to write custom enumerators (sometimes called Generators in other languages), you'll understand right away.

    You can even make your Start() function into a coroutine and do your entire logic in an infinite while loop (with an appropriate "yield return null;" call inside the loop) within that Start() function. It's really handy if you're used to making a "main game loop" type structured game (oldschool).
     
  6. brizzelsprout

    brizzelsprout

    Joined:
    Mar 26, 2015
    Posts:
    7
    So I changed ...
    Code (CSharp):
    1. void CheckZOverlap(GameObject tree1, GameObject tree2)
    2.     {
    3.         Bounds b1 = tree1.GetComponent<MeshFilter> ().mesh.bounds;
    4.         Bounds b2 = tree2.GetComponent<MeshFilter> ().mesh.bounds;
    5.         Bounds r2 = tree2.GetComponent<Renderer> ().bounds; //debug
    6.         while (b1.Intersects(b2)) {
    7.                 tree2.transform.Translate(new Vector3(0.0f,0.0f,1.0f), Space.World);
    8.                 tree2.GetComponent<MeshFilter>().mesh.RecalculateBounds();
    9.         }
    10.     }
    to...
    Code (CSharp):
    1.     void CheckYOverlap(GameObject tree1, GameObject tree2) //check rendere.bounds for overlap.
    2.     {
    3.         while (tree1.GetComponent<MeshFilter>().mesh.bounds.Intersects(tree2.GetComponent<MeshFilter>().mesh.bounds))
    4.         {
    5.             tree1.transform.Translate(new Vector3(0.0f,0.0f,1.0f), Space.World);
    6.             tree1.GetComponent<MeshFilter>().mesh.RecalculateBounds();
    7.             Bounds b1 = tree1.GetComponent<MeshFilter>().mesh.bounds; //debug
    8.         }
    and it still ends up in the same infinite loop. When checked what the mesh bounds are (the assignment to b1) the bounds.center is always the same.

    I fixed the issue by using the following adjustment.

    Code (CSharp):
    1.     void CheckZOverlap(GameObject tree1, GameObject tree2)
    2.     {
    3.         while (tree1.GetComponent<Renderer>().bounds.Intersects(tree2.GetComponent<Renderer>().bounds))
    4.         {
    5.             tree1.transform.Translate(new Vector3(0.0f,0.0f,1.0f), Space.World);
    6.             tree1.GetComponent<MeshFilter>().mesh.RecalculateBounds();
    7.             Bounds b1 = tree1.GetComponent<Renderer>().bounds; //debug
    8.         }
    9.     }
    The cause of the problem was due to mesh.bounds being in local space not world space. Switching to the renderer bounds does what we need because it is in world space.

    It's not perfect though.

    I still have some overlap happening but that is due to the code only checking against the previous tree placed, not all trees. I'll update that and we SHOULD be good to go.

    Below is the full script as it is right now. I'll update this thread with the final version when I have it done.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4.  
    5. public class generation4 : MonoBehaviour {
    6.  
    7.     public GameObject[] trees;
    8.  
    9.     private GameObject[,] forest;
    10.     private int width;
    11.     private int length;
    12.     private int scale;
    13.     private float size;
    14.  
    15.     // Use this for initialization
    16.     void Start () {
    17.  
    18.         width = 1;
    19.         length = 20;
    20.         scale = 20;
    21.         forest = new GameObject[width, length];
    22.         GenerateLayout ();
    23.     }
    24.  
    25.     void GenerateLayout()
    26.     {
    27.         for (int x = 0; x <= width-1; x++)
    28.         { //loop along the x axis
    29.             for (int z = 0; z <= length-1; z++)
    30.             { //loop along the z axis
    31.              
    32.                 size = Random.Range (1, 3); //used for scaling trees
    33.              
    34.                 var loc = new Vector3 (Random.Range (x * scale, (x * scale) + (scale)), 0, Random.Range (z * scale, (z * scale) + (scale))); //create location of tree
    35.              
    36.                 forest [x, z] = Instantiate (trees [Random.Range (0, 5)], loc, Quaternion.Euler (0, Random.Range (0, 360), 0)) as GameObject; //create tree in array
    37.  
    38.                 forest [x, z].transform.localScale = new Vector3 (size, size, size); //resize tree to random scale.
    39.  
    40.                 if(z>0)
    41.                 {
    42.                     CheckZOverlap(forest[x, z], forest[x, z-1]);
    43.                 }
    44.             }
    45.         }
    46.     }
    47.  
    48.     void CheckZOverlap(GameObject tree1, GameObject tree2) //check renderer.bounds for overlap.
    49.     {
    50.         while (tree1.GetComponent<Renderer>().bounds.Intersects(tree2.GetComponent<Renderer>().bounds))
    51.         {
    52.             tree1.transform.Translate(new Vector3(0.0f,0.0f,1.0f), Space.World);
    53.             tree1.GetComponent<MeshFilter>().mesh.RecalculateBounds();
    54.             Bounds b1 = tree1.GetComponent<Renderer>().bounds; //debug
    55.         }
    56.     }
    57. }
    58.  
    If anyone has any suggestions or improvements feel free to let me know. More updates soon.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,806
    Hey, very good catch. I didn't think about that possibility, but now that you discovered it, it totally makes sense.