Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice
  2. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  3. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

VHACD Normals bad / non-standard? Lacking vertex triplication

Discussion in 'Robotics' started by XANTOMEN, Oct 6, 2022.

  1. XANTOMEN

    XANTOMEN

    Joined:
    Dec 18, 2012
    Posts:
    41
    Hi, I realized that the normals from Meshes generated through Unity's VHACD plugin are quite unusable. I wonder if this is intended or not, and if it's just me. I have the last version from the Package Manager.

    Here is the difference between a default Cube, and a Convex Hull generated with VHACD of that Cube. Left side is VHACD, right side is default Cube:

    Vertex Normals:
    VHACDVertexNormals.png

    Face Normals:
    VHACDNormals.png

    By the looks of it, the vertices have not been generated in the way that would produce usable normals (unless one wants rounded corners), so VHACD is generating shared vertices instead of unique vertices Reference

    VHACDVertexCounts.png

    The process of generating the mesh was simply calling vhacd.GenerateConvexMeshes(); and assigning the resulting mesh to a MeshFilter, plus calling RecalculateNormals as the normals array comes out empty by default.

    The code to generate the colored rays is as follows:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class CheckNormalsAndVertices : MonoBehaviour
    6. {
    7.     MeshFilter meshFilter;
    8.     Mesh mesh;
    9.  
    10.     public bool displayVertexNormals = false;
    11.     public bool displayFaceNormals = true;
    12.  
    13.     // Start is called before the first frame update
    14.     void OnEnable()
    15.     {
    16.         mesh = GetComponent<MeshFilter>().sharedMesh;
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update()
    21.     {
    22.         if (mesh.normals.Length == 0)
    23.             mesh.RecalculateNormals();
    24.  
    25.         var triangleIndices = mesh.triangles;
    26.         var vertices = mesh.vertices;
    27.         var normals = mesh.normals;
    28.  
    29.         Debug.Log($"{transform.name} : TRIANGLE COUNT {triangleIndices.Length} : VERTEX COUNT {vertices.Length} : NORMALS COUNT {normals.Length}");
    30.  
    31.         for (var i = 0; i < triangleIndices.Length; i += 3)
    32.         {
    33.             var p1 = vertices[triangleIndices[i]];
    34.             var p2 = vertices[triangleIndices[i + 1]];
    35.             var p3 = vertices[triangleIndices[i + 2]];
    36.  
    37.             var pCenter = (p1 + p2 + p3) / 3f;
    38.  
    39.             var n1 = normals[triangleIndices[i]];
    40.             var n2 = normals[triangleIndices[i + 1]];
    41.             var n3 = normals[triangleIndices[i + 2]];
    42.  
    43.             var normal = (n1 + n2 + n3) / 3f;
    44.  
    45.             if (displayVertexNormals)
    46.             {
    47.                 var trP1 = transform.TransformPoint(p1);
    48.                 var trP2 = transform.TransformPoint(p2);
    49.                 var trP3 = transform.TransformPoint(p3);
    50.  
    51.                 var trN1 = transform.TransformVector(n1);
    52.                 var trN2 = transform.TransformVector(n2);
    53.                 var trN3 = transform.TransformVector(n3);
    54.  
    55.                 Debug.DrawRay(trP1, trN1.normalized, Color.red);
    56.                 Debug.DrawRay(trP2, trN2.normalized, Color.green);
    57.                 Debug.DrawRay(trP3, trN3.normalized, Color.cyan);
    58.             }
    59.  
    60.             var transformedVertex = transform.TransformPoint(pCenter);
    61.             var transformedNormal = transform.TransformVector(normal);
    62.  
    63.             if (displayFaceNormals)
    64.                 Debug.DrawRay(transformedVertex, transformedNormal.normalized, Color.yellow);
    65.         }
    66.     }
    67. }
    68.  
    Is this all intended or something that slipped? If intended, why? I imagine it is to reduce the number of vertices as the assumed use-case is for non-visual stuff, but in my case I actually do need correct face normals for non-visual stuff. Maybe a setting to decide if getting shared or unique vertices would be nice?

    Thank you!

    [EDIT: Managed to get the right normals with this workaround, but it would be much nicer if I could get the unique vertices with their correct normals in the mesh data, and I'm not wrapping my head around a function to do that automatically right now)]


    var n1 = Vector3.Cross(p2 - p1, p3 - p1);
    var n2 = Vector3.Cross(p3 - p2, p1 - p2);
    var n3 = Vector3.Cross(p1 - p3, p2 - p3);
     
    Last edited: Oct 6, 2022
  2. LaurieUnity

    LaurieUnity

    Unity Technologies

    Joined:
    Oct 23, 2020
    Posts:
    77
    Hi, yes, this package was built to use on non-visual stuff (specifically, splitting a mesh into convex mesh colliders). What's your reason to use it for a visual mesh?
     
  3. XANTOMEN

    XANTOMEN

    Joined:
    Dec 18, 2012
    Posts:
    41
    Hi @LaurieUnity , thanks for the answer.

    In actuality, I'm not using it for visuals, the visuals just helped me visualize what was wrong with my code. My usecase is that I needed to check if a point is inside or outside a Concave or Convex Mesh.

    My method to approximate for Concave check is to use VHACD to make multiple Convex Meshes that together more or less make up the Concave shape, and then use a function to check if the point is outside ANY of the Convex Meshes, in which case it's outside of the Concave too.

    I do have to make it that way because I'm querying for this information in real time while outside of the main thread, so for example Raycast is not available to me at that time... Anyway, that's beyond the scope of the question, the thing is, I've come up with three of four different ways to tell if point is inside a convex or not, but they all need to know the unique face normals, and not the shared one.

    Naturally, now that I came up with the workaround of computing the normals myself later with the Vector3.Cross as mentioned above, I can functionally get by without it, but it is still the case that it would be more ideal for me if there was the option to generate the VHACD hulls with unique vertices (or second best, if I knew a function to reshuffle and rebuild the mesh automatically with unique vertices associated with the right triangles myself afterwards, which my brain is struggling with).

    As an extra boon, users that might have other reasons to need the Face Normals or users that actually do want the convex hulls to be used for visuals would get to choose if they prefer that or the cost-efficiency of shared vertices.

    My main reasons to want it that way are so I can store the normal info in the mesh and not go around recalculating it whenever I need it with my workaround, plus this is meant to be in an asset that I release publicly, and the users could be either bringing their own Convex Mesh/es or using VHACD to generate one, and I would prefer to not have to have (possibly faulty) controls to tell if a convex mesh has shared vertices / been generated with VHACD or not.

    I do understand if this is not brought in as a option, so let's say at this point my post is less about "I think this is broken" and more a "I would love if this was possible or hopefully be pointed at a clean alternative".

    Thank you
     
    Last edited: Oct 14, 2022
  4. LaurieUnity

    LaurieUnity

    Unity Technologies

    Joined:
    Oct 23, 2020
    Posts:
    77
    Hmm, given that the mesh is convex, the simplest way to check whether a point is inside your mesh would be to pick a point outside the mesh bounds, and call Collider.Raycast between the two points in both directions. If neither collides, or both collide, it's outside. If only the inbound ray collides, it's inside. (And if only the outbound ray collides, your mesh is inside out! Which... probably can't happen?)

    Also, the general solution to this problem (for a mesh that might be concave or convex) is just to do the same raycast, not stopping at the first hit, and count how many hits you get: an even number = your point is outside. Unfortunately Collider.Raycast doesn't have that option, but Physics.RaycastAll does exist and should be way faster than running VHACD.
     
    Last edited: Oct 14, 2022
  5. XANTOMEN

    XANTOMEN

    Joined:
    Dec 18, 2012
    Posts:
    41
    Thank you but as I mentioned, I can't use Raycast. I have to make the inside/outside mesh test inside the Physics.ContactModifyEvent callback, and therefore, I'm not allowed to use Raycast, or OverlapSphere, or any useful functions that sadly will give an exception when used outside of the main thread.

    The reason for that is that (as far as I understand the ContactModifyEvent), I need to know before coming out of that callback if I want to ignore the contact pair or not (and for my specific case, one of the rules I use to make this decision is if the contact point is or isn't inside a list of concave meshes / list of convex hulls)

    I did see a ray of hope (pun intended) when I saw the RaycastCommand class, but unless I have used it wrong (which might well be the case), it also complains if I try to use it inside the ContactModify callback, so I accepted that I won't be able to test for Concave, and the next best thing is to test multiple Convexes, as that I can figure out based on my own math as long as I know the correct face normals beforehand (which I'm able to achieve with the workaround I mentioned).
     
    Last edited: Oct 14, 2022
  6. LaurieUnity

    LaurieUnity

    Unity Technologies

    Joined:
    Oct 23, 2020
    Posts:
    77
    I see. Ok, so after rereading your earlier message:
    It's pretty trivial to generate a version of a mesh that has unique vertices per triangle. Would look something like this (untested but should work):

    Code (CSharp):
    1. Mesh MakeVerticesUnique(Mesh input)
    2. {
    3.   List<Vector3> vertices = new List<Vector3>();
    4.   List<int> triangles = new List<int>();
    5.   for(int triIdx = 0; triIdx < input.triangles.Length; triIdx++)
    6.   {
    7.     vertices.Add(input.vertices[input.triangles[triIdx]]);
    8.     triangles.Add(triIdx);
    9.   }
    10.   Mesh result = new Mesh();
    11.   result.vertices = vertices.ToArray();
    12.   result.triangles = triangles.ToArray();
    13.   return result;
    14. }
     
  7. XANTOMEN

    XANTOMEN

    Joined:
    Dec 18, 2012
    Posts:
    41
    [EDIT!! Nevermind! I screwed up! It works well and the vertices are on the right spot! I had the wrong MeshRenderer enabled! Thanks a ton! Have a lovely day!]

    Woaah, thanks a ton @LaurieUnity . I did myself try to make that function but I was really misunderstanding how and thought was much harder. :D :D

    The normals look great now, altho the vertices to triangle assignation order or something seems a bit off, as my centroid finding operation ( (p1+p2+p3) / 3 ) sometimes finds the centroid correctly and sometimes show ups at the face diagonal, as if the triangles were built differently to what the wireframe is showing.

    (Left is vhacd + unique vertices function, right is normal cube. Same code for showing Debug Rays as in the first post)
    upload_2022-10-14_21-56-33.png
     
    Last edited: Oct 14, 2022