Search Unity

  1. New Unity Live Help updates. Check them out here!

    Dismiss Notice

Procedural mesh. Make faces point towards geometry

Discussion in 'Scripting' started by ptgodz, Mar 26, 2020.

  1. ptgodz

    ptgodz

    Joined:
    Aug 4, 2016
    Posts:
    99
    I've made a script which procedurally generates a room. There can be any number of points in the room. I am successfully generating the mesh walls for the room as well as the floor and cieling using the triangulator script found on the unity wiki.

    I'm having a little trouble when it comes to making the wall faces point in the right direction. I want them to point towards the floor geometery.

    I've been getting close with this however my method is flawed.

    I'm currently calculating the direction of the wall - the room center and then finding the dot product of this direction against the normals of each wall

    Code (CSharp):
    1. for(int i = 0; i < wall.Count; i++){
    2.     Vector3 directionToCenter = roomCenter -
    3.     wall[i].GetCenterOfWall();
    4.  
    5.     float dotProd = Vector3.Dot(directionToCenter,
    6.     wall[i].mesh.normals[0]);
    7. }
    if the dot product is greater than 0, then I know the wall is facing roughly towards the room center and I leave the wall as is. If it's less than 0, then I flip the mesh around to face the other way.

    This obviously doesn't work if the room was an L shape or a U shape.

    My method only works if there are 4 walls or less.

    Can anyone help me with what I need to be looking at to solve this problem?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    4,616
    This should probably be happening when you put down the wall, ie. earlier in the process.

    How are you doing the procedural generation? Are you generating floor tiles and then figuring out the outline, or?
     
  3. juiced12

    juiced12

    Joined:
    Nov 8, 2018
    Posts:
    28
    Edit: Posting this from a different account by accident. OP here

    Hi Baste. So the wall generation is just happening by dragging a line on a 2d plane. I take the start and end positions as the length of the wall, then I add a wall height which is pre determined. I end up with 4 positions which are

    StartPositon
    StartPostion + height
    EndPositon
    EndPostion + height

    I then create the mesh and triangles from these points.

    The reason that I'm struggling with the faces is that when I create the mesh, I create the triangles from these positions. However in my game, it's possible for walls to be drawn in the opposite direction.

    So you can click and drag from left to right, aswell as click and drag from right to left and both create a wall, but it affects which way its facing

    I thought as a solution I could just draw the positions on the 2d "floor". Then create all the wall meshes. And then progrmatically make them face the correct direction, but I don't know where to start with this.
     
    Last edited: Mar 26, 2020
  4. juiced12

    juiced12

    Joined:
    Nov 8, 2018
    Posts:
    28
    I forgot to mention a crucial point. The wall only has a single side. So it's only visible from the front
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    662
    triangle A-B-C and triangle A-C-B are different triangles, congruent, but flipped differently.
    it matters how you make your geometry initially, as Baste suggested.

    yes, you can simply rotate the objects if your walls are separate, but that's not exactly an efficient geometry, if you're thinking 'well every piece of geometry will be a separate object'. it will run extremely slow if you have so many objects on the scene.

    in other words, you have to make a system that is smart enough to create the proper mesh very early. that's called baking, you need a mesh that is baked altogether, with floors, ceilings, walls, and doors, already looking good.

    so your system has to be aware of the actual directions very early, and take that into account when creating walls.

    when you draw new walls, you inform a model in memory that this and this wall was drawn here and here, then you throw the old mesh away, and finally call a mesh generator that can translate your model in memory into a new mesh that looks good, compositing this new wall information on the fly.

    now maybe your particular case is different and you have featureless, low-poly and chunky walls that are few in number, so you might slip by with having only wall objects. but then you shouldn't care about making meshes at all, you just make a wall prefab and instantiate that all over the place.

    also you might consider having baked walls, but keeping floors and ceilings as separate objects. etc. some sort of object separation may be desirable.

    I can only suggest giving us a screenshot or two of what is desired and what's going on. it is very hard to understand you from the text alone. procedural meshing can be really hard btw. I don't know what your doing exactly, but sounds like you're just taking baby steps while being completely unaware of the nearby abyss.
     
    Last edited: Mar 26, 2020
  6. ptgodz

    ptgodz

    Joined:
    Aug 4, 2016
    Posts:
    99
    Hi @orionsyndrome.

    Thanks for responding. I'll attach a image of what I'm trying to achieve. I understand what you are saying regarding the ordering of the mesh triangles. The problem is that as my code currently stands, the winding order is affected depending on which direction the user creates a wall.

    The walls are created by a user dragging the mouse in the direction they want the direction of the wall. This is in 2D. The user can view the room in 3D once the walls are created. It is a single sided mesh so that it is visible from behind.

    I've thought of a solution regarding this, I could get the user to create all wall points in 2d. Then I could loop through all wall points and create the walls in succession

    Code (CSharp):
    1. for(int i = 0; i < wallPoints.length; i++){
    2. Wall wall = createWall(wallPoints[i].position, wallPoints[i].position);
    3. }
    This would ensure that the winding order would be the same for every wall. However I've still got the problem that depending on the ordering of the walll point array. My winding order could be reversed, and thus the faces would be "outside".

    What I want to achieve

     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    5,510
    Why not just wind all the triangles both ways?

    Or you can have an indication in 2D which way is the "in" way (left or right from the direction of drag), and give the user a key to toggle that direction until it suits them.
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    662
    well, I don't know if that helps you but you can always check if the winding order is correct by doing the cross of the edges (B-A x C-A), and then flip triangle ABC to ACB if the order was wrong. if you compare this cross result to some ground truth (let's say your expected surface normals), you can dot the two to see whether they're winded the same (because if not, dot will equal -1).

    and you can determine your ground truth because you're absolutely positive that your surface normals should point toward the inside of the room.

    considering this image you shared: let's consider the forward wall (across that room). you know your surface normal is (0, 0, -1), and lets say you erroneously made a wall whose surface normal points to the north, instead of south, so you've used points (1, 0, 1) (1, 1, 1) (0, 1, 1) for its upper right triangle. the cross (B-A x C-A) will show you a vector (0, 0, 1) which is directly opposite of (0, 0, -1) so you now know you have to build a triangle (1, 0, 1) (0, 1, 1) (1, 1, 1) instead.

    edit:
    changed the north wall to be at +1 Z, not 0
     
    Last edited: Mar 26, 2020
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    662
    in code it should look something like this
    Code (csharp):
    1. Vector3 triCross(Vector3 a, Vector3 b, Vector3 c) => Vector3.Cross(b - a, c - a);
    2. bool sameSide(Vector3 a, Vector3 b) => Vector3.Dot(a, b) > 0f;
    3. bool checkWinding(Vector3 a, Vector3 b, Vector3 c, Vector3 normal) => sameSide(triCross(a, b, c), normal);
    4.  
    5. // also a couple of irrelevant utilities you might find useful
    6. Vector3 surfaceNormal(Vector3 a, Vector3 b, Vector3 c) => triCross(a, b, c).normalized;
    7. float triArea(Vector3 a, Vector3 b, Vector3 c) => triCross(a, b, c).magnitude / 2f;
    8.  
    9. // definitely bundle these together to minimize computing that cross as much as possible
    10. (Vector3 normal, float area) computeNormalAndArea(Vector3 a, Vector3 b, Vector3 c) {
    11.   var cross = triCross(a, b, c);
    12.   return (cross.normalized, cross.magnitude / 2f);
    13. }
    14.  
    15. // use it like this (look up value tuple deconstruction)
    16. var (normal, area) = computeNormalAndArea(a, b, c);
    17. Debug.Log($"normal: {normal} area: {area}");
    18. // or
    19. var tuple = computeNormalAndArea(a, b, c);
    20. Debug.Log($"normal: {tuple.normal} area: {tuple.area}");
    21.  
    22. // you can also use in like this, to minimize unnecessary copying structs all over the place
    23. Vector3 triCross(in Vector3 a, in Vector3 b, in Vector3 c) => Vector3.Cross(b - a, c - a);
    so as I said before
    Code (csharp):
    1. Debug.Log(checkWinding(new Vector3(1f, 0f, 1f), new Vector3(1f, 1f, 1f), new Vector3(0f, 1f, 1f), new Vector3(0f, 0f, -1f)));
    2. // the last vector is a normal, not a point
    should give you
    false
    , assuming I did everything correctly here.

    (obviously, the actual code invocation should be prettier and more useful than this blatant hammering of vectors. hopefully you get the point)
     
    Last edited: Mar 27, 2020
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    662
    Also note that your ground truth doesn't have to be a strictly orthogonal vector, you can simply subtract the triangle centroid ((A+B+C)/3) from the room center (I think I already saw you do this), and declare that as your "normal" for the purposes of checking the winding order. As long as that dot works properly, you should be fine (unless you have super wide or super thin wall triangles, which you shouldn't do anyway).

    you can also always hardcode your expected normals anyway. you know your wall types, east, west, north, south, so you can hardcode their corresponding normals as well, you know east wall's normal should be Vector3.left, north wall's normal should be Vector3.back and so on. (in case you find the above paragraph confusing, just ignore it and go with this)

    feel free to ask me anything.
    I've been accused of being hard to understand, and even for using 'esoteric' teachings, a couple of times now, so I'll try and use a different explanation if something's not easy to understand.
     
    Last edited: Mar 26, 2020
  11. ptgodz

    ptgodz

    Joined:
    Aug 4, 2016
    Posts:
    99
    Hi @orionsyndrome

    I just wanted to say thankyou for your answers. I read them this morning and its sent me in the right direction and I've managed to come up with a solution using your advice.

    I followed your advice regarding finding the cross product of the edges to determine the winding order. I did this on the floor polygon. Once I'd established whether it was CW or CCW. I could then get the correct winding order for the wall meshes.

    It's working flawlessly. Thankyou again!
     
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    662
    I'm glad it worked, you're welcome
     
unityunity