Search Unity

How to procedurally add objects to surface of mesh?

Discussion in 'Scripting' started by TheoKain, Oct 31, 2018.

  1. TheoKain

    TheoKain

    Joined:
    Nov 20, 2016
    Posts:
    14
    I am trying to figure out the best way to procedurally attach a series of objects around the surface of a mesh at runtime. Each object should be perpendicular to the surface it sits on. For example, something like a bunch of trees sticking out the irregular surface of an asteroid...

    Currently, I imagine there are two ways to do this, access the normal and vertex data from the mesh class of the object and somehow parent each objects transform to that vertex/normal or create a mesh collider of the parent object and do something with that....

    Any help or ideas would be appreciated!
     
  2. Hosnkobf

    Hosnkobf

    Joined:
    Aug 23, 2016
    Posts:
    1,096
    The mesh collider is the easier way because then you can use built in methods to find the location where to put the object (Do a raycast from the camera to the direction of the mouse. multiply the returned distance with the direction vector and add it to the camera position).

    About the normal I am not so sure right now... here you would need to get the points of the triangle you hit with the ray somehow...
     
    TheoKain likes this.
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Your second option is by far the easier one: create a mesh collider, then raycast at it (probably from a random position to a point inside the mesh), and set the position to the raycast hit point, and use hit.normal to align it along the surface. This does mean that you'll be placing things only on places that get hit by the raycast, so if you have concave surfaces things won't be able to be randomly placed there.

    The other option is a little more "complete", but will indeed involve getting nitty-gritty with the Mesh class. The basic idea will be:
    1. pick a triangle from mesh.triangles[] at random (be sure to use something like Random.Range(0,mesh.triangles.Length/3)*3 since triangles is sets of 3 ints)
    2. pick a point within that triangle. Getting this randomness to be evenly spaced will be tricky, but you could use an algorithm like this to pick a point within a "normalized triangle" (e.g. a triangle of (0,0), (1,0), (0,1) ), and then use that to find the position and normal.
    Code (csharp):
    1. Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
    2. int rndTriStart = Random.Range(0,mesh.triangles.Length/3)*3; //pick a triangle
    3. Vector2 rndNorm = new Vector2(Random.value, Random.value); //find random point in normalized square
    4. if (rndNorm.X + rndNorm.y >= 1f) {
    5. rndNorm = new Vector2(1f - rndNorm.x, 1f - rndNorm.y); //cut the square diagonally in half by reflecting points not in the normalized triangle
    6. }
    7. Vector3 position = mesh.vertices[rndTriStart+0] + (rndNorm.x * mesh.vertices[rndTriStart+1]) + (rndNorm.y * mesh.vertices[rndTriStart+2]);
    8. Vector3 normal = mesh.normals[rndTriStart+0] + (rndNorm.x * mesh.normals[rndTriStart+1]) + (rndNorm.y * mesh.normals[rndTriStart+2]);
    9. someNewObject.transform.position = position;
    10. someNewObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, normal);
    Be advised that this is complex and untested :)
    One caveat with this technique is that it pretends all triangles are the same size, so the random distribution will appear to favor smaller triangles. If your triangles in your mesh are roughly the same size this is a non-issue, but if not, you may want to consider weighting the selection of rndTriStart somehow based on the triangles' surface area.
     
    TheoKain likes this.
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Hosnkobf likes this.
  5. TheoKain

    TheoKain

    Joined:
    Nov 20, 2016
    Posts:
    14
    @StarManta @Hosnkobf

    So I decided to implement the easier collider option and I'm satisfied with the results, thanks for the help!

    For anyone interested, here's an overview of the method I used, all done via script at runtime:
    1. Placed a random mesh from a list into meshfilter.mesh
    2. Attached a meshcollider component to that gameobject (what's nice is that unity conforms the meshcollider to the new mesh automatically)
    3. Get the new collider.bounds.max.magnitude to use as radius
    4. Random.onUnitSphere * radius to get a random point on the surface of a sphere surrounding the object to shoot the rays from down into it's center point
    5. Take the resulting raycasthit.point and raycasthit.normal
    6. Use the point to instantiate the new object to
    7. Use the normal with Quaternion.FromToRotation(Vector3.up, normal) to orient the object

    Here's a gif of the result
     
    Hubbard_Stephen likes this.
  6. TheoKain

    TheoKain

    Joined:
    Nov 20, 2016
    Posts:
    14
    I found that the distribution of objects was skewed towards the center of the mesh since I was raycasting there and I really wanted a more uniform distribution. To do this I would need to raycast at random points on the mesh using collider.closestpoint... but that function didn't like CONCAVE mesh colliders which was a pain...

    So to find a random point on any mesh, I used a modified version of the code posted above by StarManta and this formula to get a random point of a triangle in 3D space.
    [ https://math.stackexchange.com/questions/538458/triangle-point-picking-in-3d ]

    x = v1 + a(v2 − v1) + b(v3 − v1)
    (x is the new vector, v1, v2, v3 are vertices and a, b are random nums between [0, 1])

    Code (CSharp):
    1.  
    2. Vector3[] meshPoints = transform.GetComponent<MeshFilter>().mesh.vertices;
    3. int[] tris = transform.GetComponent<MeshFilter>().mesh.triangles;
    4. int triStart = Random.Range(0, meshPoints.Length / 3) * 3; // get first index of each triangle
    5.  
    6. float a = Random.value;
    7. float b = Random.value;
    8.          
    9. if(a + b >= 1){ // reflect back if > 1
    10.     a = 1 - a;
    11.     b = 1 - b;
    12. }
    13.  
    14. Vector3 newPointOnMesh = meshPoints[triStart] + (a * (meshPoints[triStart + 1] - meshPoints[triStart])) + (b * (meshPoints[triStart + 2] - meshPoints[triStart])); // apply formula to get new random point inside triangle
    15.  
    16. newPointOnMesh = transform.TransformPoint(newPointOnMesh); // convert back to worldspace
    17.  
    18. Vector3 rayOrigin = ((Random.onUnitSphere * r) + transform.position); // put the ray randomly around the transform
    19.  
    20. RaycastHit hitPoint;
    21. Physics.Raycast(rayOrigin, newPointOnMesh - rayOrigin, out hitPoint, 100f, layermask)
    The results are much nicer distribution over all mesh shapes

     
    BGP001-02, NotaNaN, Kamiikam and 3 others like this.
  7. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    Hi @agreeablegames I hope you don't mind me asking this. I've been trying to spawn objects around a spherical mesh and have been trying to follow what you have above, but can't seem to get it working - the object appears on a point rather than the center of a mesh face and is just off position wise, with its rotation not quite right either. I'm still intermediate at best with coding and hope you could help? Would really appreciate it. Here's what I have so far:

    Code (csharp):
    1.  
    2.  mesh = gameObject.GetComponent<Mesh>();
    3.         Vector3[] meshPoints = transform.GetComponent<MeshFilter>().mesh.vertices;
    4.         MeshCollider col = gameObject.GetComponent<MeshCollider>();
    5.         float radius = col.bounds.max.magnitude;
    6.  
    7.         int[] tris = transform.GetComponent<MeshFilter>().mesh.triangles;
    8.         int triStart = Random.Range(0, meshPoints.Length / 3) * 3; // get first index of each triangle
    9.  
    10.         float a = Random.value;
    11.         float b = Random.value;
    12.  
    13.         if (a + b >= 1)
    14.         { // reflect back if > 1
    15.             a = 1 - a;
    16.             b = 1 - b;
    17.         }
    18.  
    19.         Vector3 newPointOnMesh = meshPoints[triStart] + (a * (meshPoints[triStart + 1] - meshPoints[triStart])) + (b * (meshPoints[triStart + 2] - meshPoints[triStart])); // apply formula to get new random point inside triangle
    20.  
    21.         newPointOnMesh = transform.TransformPoint(newPointOnMesh); // convert back to worldspace
    22.  
    23.         Vector3 rayOrigin = ((Random.onUnitSphere * radius) + transform.position); // put the ray randomly around the transform
    24.  
    25.         RaycastHit hitPoint;
    26.         Physics.Raycast(rayOrigin, newPointOnMesh - rayOrigin, out hitPoint, 200f);
    27.         GameObject newObj = Instantiate(spawnObject, hitPoint.transform); //spawn & parent
    28.         newObj.transform.position = newPointOnMesh;
    29.         newObj.transform.localRotation = Quaternion.FromToRotation(Vector3.up, hitPoint.normal);
    30.  
     
    KimberleyC likes this.
  8. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    Fixed rotation issue with
    Code (csharp):
    1.  
    2.   newObj.transform.localRotation = Quaternion.FromToRotation(Vector3.zero, hitPoint.normal);
    3.  
     
    iambrettstar likes this.
  9. Eysimir

    Eysimir

    Joined:
    Oct 2, 2013
    Posts:
    17
    Hey,
    I just noticed on TheoKains latest post a couple of things that should be corrected.
    The selection of triangle index should be made from the triangles and not the vertices, like so:
    Code (CSharp):
    1. int triStart = Random.Range(0, tris.Length / 3) * 3; // get first index of each triangle
    And then the formula for calculating the random point within the selected triangle should reference the verticies of the selected triangle, like so:
    Code (CSharp):
    1. Vector3 newPointOnMesh = meshPoints[tris[triStart]] + (a * (meshPoints[tris[triStart + 1]] - meshPoints[tris[triStart]])) + (b * (meshPoints[tris[triStart + 2]] - meshPoints[tris[triStart]])); // apply formula to get new random point inside triangle
     
    BGP001-02 likes this.
  10. Artpen

    Artpen

    Joined:
    Jan 24, 2015
    Posts:
    291
    This is not really random. Using just this code will place too many objects in smaller triangles
    Code (CSharp):
    1. int triStart = Random.Range(0, tris.Length / 3) * 3;
    I found a potential solution, but will appreciate if someone can give a hint on how to update existing code to this:
    https://dev.to/bogdanalexandru/generating-random-points-within-a-polygon-in-unity-nce

    Basically, add random based on an area of a triangle
     
  11. Artpen

    Artpen

    Joined:
    Jan 24, 2015
    Posts:
    291
  12. Artpen

    Artpen

    Joined:
    Jan 24, 2015
    Posts:
    291
    Anyone? Still can't find a way
     
  13. Artpen

    Artpen

    Joined:
    Jan 24, 2015
    Posts:
    291
    Ok if anyone will need it here is a more evenly distributed version. Works only in 2D. I removed all Raycasting.

    Code (CSharp):
    1.  // Get object position on the mesh. Based on
    2.     // https://mathworld.wolfram.com/TrianglePointPicking.html
    3.     // https://dev.to/bogdanalexandru/generating-random-points-within-a-polygon-in-unity-nce
    4.     private Vector3 GetRandomPositionOnMesh()
    5.     {
    6.         Vector3[] meshPoints = mesh.vertices;
    7.         int[] tris = mesh.triangles;
    8.  
    9.         // Get random index in triangles array based on triangle area
    10.         float range = Random.Range(0f, Utils.CalculateSurfaceArea(mesh));
    11.  
    12.         for(int i = 0; i < tris.Length / 3; ++i)
    13.         {
    14.             if (range < Utils.CalculateTriangleArea(mesh, i * 3))
    15.             {
    16.                 triStart = i * 3;
    17.                 break;
    18.             }
    19.             range -= Utils.CalculateTriangleArea(mesh, i * 3);
    20.         }
    21.  
    22.         // Apply formula to get new random point inside triangle
    23.         // P = (1 - sqr R1)*A + (sqr R1 * (1 - R2)) * B + (R2 * sqr R1) * C;
    24.         // R1 and R2 random point [0; 1];
    25.         // A, B, C triangle points
    26.         float r1 = Mathf.Sqrt(Random.Range(0f, 1f));
    27.         float r2 = Random.Range(0f, 1f);
    28.         float m1 = 1 - r1;
    29.         float m2 = r1 * (1 - r2);
    30.         float m3 = r2 * r1;
    31.  
    32.         // Triangle points
    33.         Vector3 a = meshPoints[tris[triStart]];
    34.         Vector3 b = meshPoints[tris[triStart + 1]];
    35.         Vector3 c = meshPoints[tris[triStart + 2]];
    36.  
    37.         return (m1 * a) + (m2 * b) + (m3 * c);
    38.     }