Search Unity

Extruding a 2D mesh collider

Discussion in 'Scripting' started by bbvrdev, Apr 26, 2011.

  1. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Hey guys,

    I'm developing a pseudo 2D game, and I've got a 2d mesh that is procedurally generated, but I need to extrude it on the z axis so that it can collide with various objects. Can anyone give me some pointers as to an easy way to accomplish this? I was thinking, I don't specifically have to extrude the mesh itself, only the collider, whichever one is easier. I'm currently using the triangulator script on the wiki to create the triangles, which is sort of limiting me to the 2d, though if there was a straightforward way to procedurally create triangles that might solve my problem as well.

    Thanks for any suggestions!!
     
  2. reissgrant

    reissgrant

    Joined:
    Aug 20, 2009
    Posts:
    726
    Have you tried the procedural demos given by Unity?

    Click Here

    But, the collider has a "bounds" property (collider.bounds) which is a struct that contains some values that you can manipulate rather than modifying the mesh triangles and vertices.

    See Here:
    Click Here
     
  3. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    That would be really useful, reissgrant! I'd rather not have to mess with the mesh itself if possible. Do you have any examples of changing the bounds of a mesh collider? I'd be extruding a flat 2d shape, so hopefully that would simplify it a bit, but I'm still having problems visualizing it :)
     
  4. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Bounds.Expand doesn't seem to do anything, unless I'm using it wrong? It seems like this is the answer to my problem...

    Code (csharp):
    1. collider.bounds.Expand(new Vector3(0,0,50));
     
  5. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Here is a simple illustration of what I'm trying to accomplish with the collider. Anyone?
     

    Attached Files:

    • $-1.jpg
      $-1.jpg
      File size:
      25.3 KB
      Views:
      4,044
  6. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Since this is a simple floor for the characters to interact with, perhaps I could somehow position primitive colliders on each of the upper planes instead?
     
  7. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Here is a link to the corresponding UA thread (just so people don't duplicate their efforts).

    You said the source mesh was procedurally generated; I assume there's some regularity to how it's constructed. Can you post an example screenshot, or perhaps describe how the mesh is built? (What you describe can be done fairly easily, but the details will depend on how the vertices in the source mesh are organized.)
     
  8. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Thanks Jesse,

    The source mesh is similar to the illustration above, but the top edge is a little more complicated. Essentially, it's built from a simple array in which the vertexes are arranged on the x-axis, beginning in the top left corner and working its way around. There are no concave areas.
    Then I run the Triangulator script on the wiki to create the triangles, and add a mesh collider to it.

    Does that help? I'm not allowed to post a screenshot, but I can draw a picture if you'd like. :)
     
  9. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Actually, a drawing or picture would probably help; I'm not quite being able to visualize exactly how the vertices are organized. (I know you posted a drawing above - if you could do something like that, but more closely matching the actual layout, that would probably help.)
     
  10. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Jesse, I really appreciate your help! Here's a simplified image of how the verts are laid out. The actual object is something like 180 points along the top, but this should give you the gist of it.

    The image on the right is of what I actually need in a collider (or series of colliders). The sides and bottom of my object are for rendering only, I don't need them to collide with anything, just the top edge.

    As far as I can tell, my choices at the moment are:
    - Extrude the entire shape and re-attach a new mesh collider
    - Attach a collider first and extrude it, leaving the object 2D
    - Trace the top edge in a series of box colliders (is this even possible?)
     

    Attached Files:

    • $-2.jpg
      $-2.jpg
      File size:
      32.6 KB
      Views:
      3,896
  11. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Ok, the first step will be to take all of the vertices, duplicate them, move the duplicates along the desired axis, and then combine the original vertices and the duplicates into one container. Using a List would probably be easiest for this. Just make sure that order is preserved; that is, the first N vertices in the list are the original vertex positions (unmodified), and then the next N are the extruded vertex positions, in the same order.

    If there are N original vertices, there will be N * 2 triangles, and N * 6 triangle indices.

    You'll want to build the triangles two at a time (that is, you'll want to build one quad at a time). I can't absolutely guarantee I'll get this right off the top of my head, but here's how to build the indices for a single quad ('i' is the integer index for this quad, and 'N' is the number of original vertices, which is also the number of quads):

    Code (csharp):
    1. int i1 = i;
    2. int i2 = (i1 + 1) % N;
    3. int i3 = i1 + N;
    4. int i4 = i2 + N;
    5. // Assuming that 'indices' is of type List<int>:
    6. indices.Add(i1, i3, i4);
    7. indices.Add(i1, i4, i2);
    The method I've described ignores the fact that only the upward-facing quads are of interest. That can be addressed though by eliminating quads for which the normal does not point upwards (with respect to some threshold). This complicates things somewhat in that you'd probably also want to eliminate the unused vertex positions, which in turn would alter the indices as well. The details will depend somewhat on the topology of the mesh, and whether it's possible for faces to 'alternate' between upward-facing and non-upward-facing.

    Feel free to ask if you have further questions about this.
     
  12. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Thanks for the detailed answer, Jesse! I'll jump into it right now. One question quickly, is List supported on iPhone? Last I heard it wasn't, but this should work with ArrayList, right?
     
  13. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I can never remember, so I'll let someone else field that one. But yes, you could use another container type.

    One other thing I'll mention is the possibility of writing an editor script to precompute and compile the collision meshes as part of the development process (which would then eliminate the need to do the work at run time).
     
  14. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    You know, if I could do that it'd be great :) But this is for deformable terrain, so it needs to recalculate some of the verts and redraw the mesh when a projectile hits it.
     
  15. johot

    johot

    Joined:
    Apr 11, 2011
    Posts:
    201
  16. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Thanks a lot johot!

    Would you be willing to post some code? I am working on it now, but it's a long job, and I'd rather not reinvent the wheel if you'd be willing to share.
     
  17. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Here's my implementation, but it doesn't display anything... Jesse, if you're still around, would you take a quick gander at it and tell me what I'm doing wrong? My vertices3D is an array set in the inspector with 12 points, and after this is run the indices array is 72 in length, so it seems like it should be working.

    Code (csharp):
    1.  
    2.  
    3.                 List<Vector3> verticesList = new List<Vector3>(vertices3D);
    4.         List<Vector3> verticesExtrudedList = new List<Vector3>();
    5.         List<int> indices = new List<int>();
    6.        
    7.         for (int i = 0; i < verticesList.Count; i++) {
    8.             verticesExtrudedList.Add(new Vector3(verticesList[i].x, verticesList[i].y, 50));
    9.         }
    10.        
    11.         //add the extruded parts to the end of verteceslist
    12.         verticesList.AddRange(verticesExtrudedList);
    13.        
    14.         for (int i = 0; i < verticesList.Count; i++) {
    15.            
    16.             int N = verticesList.Count;
    17.             int i1 = i;
    18.             int i2 = (i1 + 1) % N;
    19.             int i3 = i1 + N;
    20.             int i4 = i2 + N;
    21.            
    22.             indices.Add(i1);
    23.             indices.Add(i3);
    24.             indices.Add(i4);
    25.            
    26.             indices.Add(i1);
    27.             indices.Add(i4);
    28.             indices.Add(i2);
    29.            
    30.         }
    31.        
    32.         mesh.vertices = new Vector3[verticesList.Count];
    33.         verticesList.CopyTo(mesh.vertices);
    34.         mesh.triangles = new int[indices.Count];
    35.         indices.CopyTo(mesh.triangles);
    36.        
    37.         mesh.RecalculateNormals();
    38.         mesh.RecalculateBounds();
    39.         mesh.Optimize();
    40.        
    41.         renderer.material = meshMat;
    42.         gameObject.AddComponent("MeshCollider");
     
  18. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    This:

    Code (csharp):
    1. mesh.vertices = new Vector3[verticesList.Count];
    2. verticesList.CopyTo(mesh.vertices);
    3. mesh.triangles = new int[indices.Count];
    4. indices.CopyTo(mesh.triangles);
    Looks a bit suspicious. Mesh.vertices and Mesh.indices return copies of the corresponding data, so copying to the returned array probably won't have the desired effect.

    Assuming that List works as expected on the iPhone, you should be able to do this instead:

    Code (csharp):
    1. mesh.vertices = verticesList.ToArray();
    2. mesh.triangles = indices.ToArray();
     
  19. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Thanks Jesse! Well, it's obviously finding the array, because now I'm getting:
    Failed setting triangles. Some indices are referencing out of bounds vertices. :)
     
  20. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Here:

    Code (csharp):
    1. for (int i = 0; i < verticesList.Count; i++) {
    2.            
    3.     int N = verticesList.Count;
    That should be the original number of vertices, e.g.:

    Code (csharp):
    1. for (int i = 0; i < originalVertexCount; ++i) {
    2.     int N = originalVertexCount;
    3.     // ...
     
  21. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Oh mama! It's calculating the sides perfectly! :) Thanks a million for working through this with me, Jesse!
    One question before I implement this into my game, if I add a mesh collider to this object, would it need to be checked "convex"?
     
  22. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Only if it's convex :)

    Based on the images you posted though, no, it shouldn't be marked as convex.
     
  23. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Thanks again, really appreciate it!
     
  24. johot

    johot

    Joined:
    Apr 11, 2011
    Posts:
    201
    Ah good you made it :)

    You really only need to check the convex stuff If your meshes should be movable and can collide with other mesh colliders. Then you will need to convex stuff checked. In my game it's only part of the landscape so I don't have to think about it.
     
  25. bbvrdev

    bbvrdev

    Joined:
    Aug 11, 2009
    Posts:
    221
    Yeah, thanks johot! :) It's working splendidly now. Appreciate the help!
     
  26. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Mesh colliders should only be marked as convex if they're actually convex. If a mesh is non-convex, marking it as 'convex' is unlikely to yield the correct results.
     
  27. johot

    johot

    Joined:
    Apr 11, 2011
    Posts:
    201
    Isn't it simply given a convex hull if it's not convex?

    By the way, I saw a neat trick where zero height box colliders was used along the edges of a mesh to make it work with physics even though it was non-convex. I guess you could also use multiple convex parts to create a non-convex body that works with physics by parenting them? Thats what we did in Box2D i remember :)
     
  28. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I wasn't under that impression, but I've never actually tried it.

    Does it say in the docs somewhere that a convex hull will be built if the mesh is non-convex? (I don't remember reading that anywhere. Also, the 255-triangle limit would imply that the convex hull would have to come in at 255 triangles or fewer as well.)
     
  29. oleyb

    oleyb

    Joined:
    Nov 8, 2012
    Posts:
    15
    Hello everyone! So I'm currently attempting to use the code Sandworm posted (along with Jesse's corrections) for taking my 2d mesh and extruding it to 3d. The problem is that my triangles seem to be being drawn between the wrong verts.

    Here is what the generated mesh ends up looking like:


    and here is what my original 2d mesh looked like:


    If it would help for me to provide the code I used to draw the original 2d mesh, I can provide that as well. To be honest, I'm new to working with 3d at this level, so I may be in a little over my head. I appreciate any help though! Thank you!
     
  30. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
  31. oleyb

    oleyb

    Joined:
    Nov 8, 2012
    Posts:
    15
    Fantastic! That's a neat tool you made, I can definitely see that coming in useful for some things in my project. This code I'm working on currently needs to be generating meshes in realtime during gameplay though.

    The code is based on Prime31's 2d terrain generation video, though he doesn't go over all the code so I've had to fill in some blanks. I've posted the code for the terrain generation class below, the "drawMesh()" method is the one of interest. In the comments I point out where I'd added Sandworm's code and where that code ends.

    Again, thanks a lot for your help!

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class Terrain {
    7.     public TerrainGenerator terrainGenerator;
    8.     public List<Vector3> borderVertices = new List<Vector3>();
    9.     public float startX;
    10.     public float endX;
    11.  
    12.     // keeps track of what we are rendering in the terrain
    13.     int _fromKeyPointI;
    14.     int _toKeyPointI;
    15.     int prevFromKeyPointI = -1;
    16.     int prevToKeyPointI = -1;
    17.  
    18.     // not sure about these
    19.     public int terrainSegmentWidth = 20;
    20.     public int textureSize = 1024;
    21.  
    22.     private GameObject _originGameObject;
    23.  
    24.     public Terrain(GameObject originGameObject){
    25.         _originGameObject = originGameObject;
    26.  
    27.         terrainGenerator = new TerrainGenerator();
    28.     }
    29.  
    30.     public void generateMeshWithWidth(float width, MeshFilter meshFilter){
    31.         terrainGenerator.resetToLastUserIndex(_toKeyPointI);
    32.  
    33.         _fromKeyPointI = 0;
    34.         _toKeyPointI = 0;
    35.  
    36.         while(terrainGenerator[++_toKeyPointI].x < width){}
    37.  
    38.         drawMesh(meshFilter);
    39.     }
    40.  
    41.     private void drawMesh(MeshFilter meshFilter){
    42.         borderVertices.Clear();
    43.  
    44.         var terrainVertices = new List<Vector3>();
    45.         var terrainTexCoords = new List<Vector2>();
    46.         var triangles = new List<int>();
    47.         var triangleIndex = -2;
    48.  
    49.         Vector3 keyPoint0, keyPoint1, pt0, pt1 = new Vector3(0, 0, terrainGenerator.zPositionOfTerrain);
    50.         keyPoint0 = terrainGenerator[_fromKeyPointI];
    51.  
    52.         for(int i = _fromKeyPointI + 1; i <= _toKeyPointI; i++) {
    53.             keyPoint1 = terrainGenerator[i];
    54.  
    55.             //triangle strip between p0 and p1
    56.             int totalSegments = Mathf.CeilToInt((keyPoint1.x - keyPoint0.x) / terrainSegmentWidth);
    57.             float segmentWidth = (keyPoint1.x - keyPoint0.x) / totalSegments; // actual, calculated width of each segment
    58.             float da = Mathf.PI / totalSegments;
    59.             float ymid = (keyPoint0.y + keyPoint1.y)/2;
    60.             float amplitude = (keyPoint0.y - keyPoint1.y)/2;
    61.             pt0 = keyPoint0;
    62.  
    63.             // add 1 on the last loop to ensure the keypoint itself gets a vert pair to match up with the next mesh
    64.             if(i == _toKeyPointI){
    65.                 totalSegments++;
    66.             }
    67.  
    68.             for(var j = 0; j <= totalSegments; j++){
    69.                 pt1.x = keyPoint0.x + j * segmentWidth;
    70.                 pt1.y = ymid + amplitude * Mathf.Cos(da*j);
    71.  
    72.                 var topVert = new Vector3(pt0.x, pt0.y, terrainGenerator.zPositionOfTerrain);
    73.  
    74.                 //we only need the top vert for the border
    75.                 borderVertices.Add(topVert);
    76.  
    77.                 terrainVertices.Add(topVert);
    78.                 terrainTexCoords.Add(new Vector2(pt0.x / textureSize, 1));
    79.                 terrainVertices.Add(new Vector3(pt0.x, pt0.y - textureSize, terrainGenerator.zPositionOfTerrain)); //bottom
    80.                 terrainTexCoords.Add(new Vector2(pt0.x / textureSize, 0));
    81.  
    82.                 // This next section is not used when we use Sandworm's Extrusion code.
    83.                 // no tris to add for the first 2 verts
    84.                 if(triangleIndex >= 0) {
    85.                     triangles.Add(triangleIndex + 2);
    86.                     triangles.Add(triangleIndex + 1);
    87.                     triangles.Add(triangleIndex + 0);
    88.                     triangles.Add(triangleIndex + 3);
    89.                     triangles.Add(triangleIndex + 1);
    90.                     triangles.Add(triangleIndex + 2);
    91.                 }
    92.                 triangleIndex += 2;
    93.  
    94.                 pt0 = pt1;
    95.             }
    96.  
    97.             keyPoint0 = keyPoint1;
    98.         }
    99.  
    100.         //Start Sandworm's Code
    101.  
    102.         List<Vector3> verticesList = new List<Vector3>(terrainVertices);
    103.         List<Vector3> verticesExtrudedList = new List<Vector3>();
    104.         List<int> indices = new List<int>();
    105.  
    106.         for (int i = 0; i < verticesList.Count; i++) {
    107.             verticesExtrudedList.Add(new Vector3(verticesList[i].x, verticesList[i].y, 100));
    108.         }
    109.  
    110.         //add the extruded parts to the end of verteceslist
    111.         verticesList.AddRange(verticesExtrudedList);
    112.  
    113.         for (int i = 0; i < terrainVertices.Count; i++) {
    114.             int N = terrainVertices.Count;
    115.             int i1 = i;
    116.             int i2 = (i1 + 1) % N;
    117.             int i3 = i1 + N;
    118.             int i4 = i2 + N;
    119.  
    120.             indices.Add(i1);
    121.             indices.Add(i3);
    122.             indices.Add(i4);
    123.  
    124.             indices.Add(i1);
    125.             indices.Add(i4);
    126.             indices.Add(i2);
    127.         }
    128.        
    129.         var mesh = meshFilter.mesh;
    130.         mesh.Clear();
    131.         mesh.vertices = verticesList.ToArray();
    132.         mesh.triangles = indices.ToArray();
    133.  
    134.         mesh.RecalculateNormals();
    135.         mesh.RecalculateBounds();
    136.         mesh.Optimize();
    137.  
    138.         // End Sandworm's Code
    139.  
    140.  
    141.         /* My old 2d mesh building code
    142.  
    143.         var mesh = meshFilter.mesh;
    144.         mesh.Clear();
    145.         mesh.vertices = terrainVertices.ToArray();
    146.         mesh.uv = terrainTexCoords.ToArray();
    147.         mesh.triangles = triangles.ToArray();
    148.  
    149.         mesh.Optimize();
    150.         mesh.RecalculateBounds();
    151.        
    152.         //addMeshCollider(meshFilter, borderVertices);
    153.  
    154.         End of my 2d mesh building code */
    155.     }
    156.  
    157.     private void addMeshCollider(MeshFilter meshFilter, List<Vector3> borderVerts){
    158.         // I will need to do something here.
    159.     }
    160.  
    161.     // figures out which hill points are visible to the camera
    162.     private bool calculateVisibleVertices(Camera camera){
    163.         // Not sure what to do here yet.
    164.         return true;
    165.     }
    166.  
    167.     public void renderTerrainVisibleToCamera(Camera camera, MeshFilter meshFilter){
    168.         // Same as above.  
    169.     }
    170. }
    171.  
     
  32. AniketKayande

    AniketKayande

    Joined:
    Sep 9, 2014
    Posts:
    1
    Hey guys, This discussion has really helped me in my game. the only problem that i am facing now is when i am generating a mesh that has structure as shown in the screenshot. the Mesh collider doesnt exactly follow the geometry of mesh.. can you guys help me with this problem
    2015_09_18_16_52_15_Start.png

    I have used the same code snippet from last post.

    Vector3[] vertices = m_PlatformMesh.sharedMesh.vertices;
    List<Vector3> terrainVertices = new List<Vector3>();
    foreach(var vert in vertices)
    {
    terrainVertices.Add(vert);
    }

    List<Vector3> verticesList = new List<Vector3>(terrainVertices);
    List<Vector3> verticesExtrudedList = new List<Vector3>();
    List<int> indices = new List<int>();

    for (int i = 0; i < verticesList.Count; i++) {
    verticesExtrudedList.Add(new Vector3(verticesList.x, verticesList.y, 100));
    }

    //add the extruded parts to the end of verteceslist
    verticesList.AddRange(verticesExtrudedList);

    for (int i = 0; i < terrainVertices.Count; i++) {
    int N = terrainVertices.Count;
    int i1 = i;
    int i2 = (i1 + 1) % N;
    int i3 = i1 + N;
    int i4 = i2 + N;

    indices.Add(i1);
    indices.Add(i3);
    indices.Add(i4);

    indices.Add(i1);
    indices.Add(i4);
    indices.Add(i2);
    }




    //var mesh = meshFilter.mesh;
    Mesh mesh = new Mesh();
    mesh.Clear();
    mesh.vertices = verticesList.ToArray();
    mesh.triangles = indices.ToArray();

    mesh.RecalculateNormals();
    mesh.RecalculateBounds();
    mesh.Optimize();

    m_ColliderObject.sharedMesh = mesh;