Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Trimming a mesh by changing its vertices &/ triangles based on a spline path

Discussion in 'Scripting' started by xezrunner, Oct 10, 2020.

  1. xezrunner

    xezrunner

    Joined:
    Jul 5, 2020
    Posts:
    12
    I would like to trim a track model by changing its mesh.
    So far, I have tried iterating through the vertices and triangles, getting their Vector3 points, checking them against the cutoff Vector3 point (basically a point ahead of the current points on a spline path), and only letting those points into the array that aren't beyond the cutoff point.

    I have tried multiple implementations, some were more successful than others:

    Full-length deformation:


    Cutoff deformation attempts:




    Sample, experimentation code trying to trim the mesh (note: I had multiple variations, this is the last one pictured)
    Code (CSharp):
    1. void TrimMesh()
    2.     {
    3.         // VERTS
    4.         Vector3[] verts2 = new Vector3[vertices.Length];
    5.         for (int i = 0; i < vertices.Length; i++)
    6.         {
    7.             // Get vertex point on spline
    8.             Vector3 meshVertex = vertices[i];
    9.  
    10.             Vector3 splinePoint = path.GetPointAtDistance(meshVertex.z + targetObject.transform.position.z);
    11.             Vector3 futureSplinePoint = path.GetPointAtDistance(meshVertex.z + targetObject.transform.position.z + 0.01f);
    12.             Vector3 forwardVector = futureSplinePoint - splinePoint;
    13.             Quaternion imaginaryPlaneRotation = Quaternion.LookRotation(forwardVector, Vector3.up);
    14.             Vector3 pointWithinPlane = new Vector3(meshVertex.x, meshVertex.y, 0f);
    15.  
    16.             Vector3 result = splinePoint + (imaginaryPlaneRotation * pointWithinPlane);
    17.  
    18.             // Check vertex point against cutoff point
    19.             if (Vector3.Distance(result, path.GetPointAtDistance(DesiredLength)) > DesiredLength) continue;
    20.             verts2[i] = vertices[i];
    21.             mesh.vertices = verts2;
    22.         }
    23.  
    24.         // TRIS
    25.         int[] triangles2 = new int[triangles.Count];
    26.         for (int i = 0; i < triangles.Count; i += 1)
    27.         {
    28.             // Get vertex point on spline
    29.             Vector3 meshVertex = vertices[triangles[i]];
    30.  
    31.             Vector3 splinePoint = path.GetPointAtDistance(meshVertex.z + targetObject.transform.position.z);
    32.             Vector3 futureSplinePoint = path.GetPointAtDistance(meshVertex.z + targetObject.transform.position.z + 0.01f);
    33.             Vector3 forwardVector = futureSplinePoint - splinePoint;
    34.             Quaternion imaginaryPlaneRotation = Quaternion.LookRotation(forwardVector, Vector3.up);
    35.             Vector3 pointWithinPlane = new Vector3(meshVertex.x, meshVertex.y, 0f);
    36.  
    37.             Vector3 result = splinePoint + (imaginaryPlaneRotation * pointWithinPlane);
    38.  
    39.             // Check vertex point against cutoff point
    40.             if (Vector3.Distance(result, path.GetPointAtDistance(DesiredLength)) > DesiredLength) continue;
    41.             triangles2[i] = triangles[i];
    42.             mesh.triangles = triangles2;
    43.         }
    44.     }

    Lengthening is not required, although I would be interested in knowing whether that would also be possible (perhaps through repeating the triangle/vertex pattern?)

    I would also accept recommendations if there's possibly a better way to trim models.
    I've been looking into clip shaders, but looking into the future, I would need to trim the model from the other way around as well - clip shaders can only be used once.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    First minor note, line 21 and line 42 don't need to be inside the loop. You're hammering the entire array in again and again each time you step. But that would not (necessarily) be a logic problem, just spammy.

    Second of all, these verts are not necessarily in any order you think they are, unless you put them in there procedurally, and even then I'm not sure that's guaranteed. They might be in any order. Your routine is assuming they start at the beginning of the track, and the instant any one is outside the track, it abandons everything.

    Talking about the logic of your approach however, you don't want to just copy verts and copy tris through (lines 20 and 41) one-to-one indexes... there's no point to that.

    Essentially what must happen, IF you want to genuinely not have all the vertices in there, is you need to select which vertices are within your limit, copying them to a fresh list (without leaving blanks obviously, just an ever-growing list) and noting how each one renumbers:

    each old vertex index ---> new vertex index in new list

    You need to probably store all that in a Dictionary for rapid access in a moment.

    Then you need to copy ONLY triangles referencing the original triangle that corresponds to something actually fully present in the new offset list, as in all referenced verts are "kept."

    Ya with me? It's a little tricky but if you draw two triangles with 6 verts on a piece of paper, and step through, considering triangle 1 good and triangle 2 bad, you'll get how the numbers have to be looked up. Work the two-triangle problem from both directions and you'll see the importance of renumbering.

    Finally when you decide you WANT to keep that triangle based on the above bookkeeping, you now need to renumber each of the vertex indices in that triangle according to how you compressed the verts, and add it to your kept triangle list.

    You probably COULD skip the Vertex copying / renumbering by just iterating Triangles and copying over ONLY the ones that reference Vertices that you determine are good in the first step, just keeping a list of who is within.

    Then you'd have the same number of verts, but way fewer tris.
     
    xezrunner likes this.
  3. xezrunner

    xezrunner

    Joined:
    Jul 5, 2020
    Posts:
    12
    Yep, I wrote all this for experimentation purposes only. I had a variant of this code where it would wait a few milliseconds and set the mesh so I could see what's happening in real time.

    Honestly, I'm a bit new to vertices and triangles. I understand vertices, I kind-of understand how triangles theoretically work, but when it comes to manipulating the triangles by their values, I'm a bit lost on how their numbers correspond to the way things are drawn.

    From what I'm gathering from your reply, it sounds like the biggest issue here is that the vertices / triangles aren't quite in order and that I would also require to re-assign the triangle numbers when their order changes.

    Is it possible that I would require the vertices / triangles to be in order for this to work optimally?
    If trimming the length from the end of the model is possible, I would assume it's trivial that it would also be able to change it from the start - I would like to trim them in both directions.

    I've been trying to think of other ways to solve this, two come to mind - one would be clip planes, the other is a screen space boolean effect, but both are "fake" in that they're graphical shaders and not properly changing the mesh.

    Do you see a better option here? If not, could you please give me more insight on how I could continue with this?
    Thanks for your time!
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    If you leave all the vertices in place, just trim the triangles:

    -For each triangle, look up the 3 verts, take the average of their position

    -Check if their average position is acceptable to include

    -IF it is, add the new triangle indices (3 integers) to a fresh list of triangles

    -When you are done, assign it back to the mesh.

    If you're gonna do procgen geometry, Unity is like the easiest place to start, but you gotta understand the fundamentals. You can look around my MakeGeo project if you're curious for more examples:

    MakeGeo is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/makegeo

    https://github.com/kurtdekker/makegeo

    https://gitlab.com/kurtdekker/makegeo

    https://sourceforge.net/p/makegeo
     
    xezrunner and Bunny83 like this.
  5. xezrunner

    xezrunner

    Joined:
    Jul 5, 2020
    Posts:
    12
    Sounds awesome! Thank you for your help!
    I'll check your project for sure - procedurally generated meshes definitely interest me and would even come in handy for other ideas as well!
    If I succeed, I'll update the thread with screenshots and implementation details.
     
    Kurt-Dekker likes this.
  6. xezrunner

    xezrunner

    Joined:
    Jul 5, 2020
    Posts:
    12
    Progress / partial success!
    It looks like it's somewhat working, but there are some small quirks that I've been spending a few hours fixing, without much luck...

    Notice how in the following screenshots, some triangles at the edges don't get cut off:
    spheres - edges of triangles (the 3 vertices of each triangle)
    thin wall - the cut-off point




    I've tried two different methods of checking whether a triangle should be cut off:
    - Screenshot 1: Calculating the distance between the average of triangle vector points and the cutoff point using Vector3.Distance() - this seems to yield weird behavior, as not only does it calculate the desired length incorrectly (it seems to double), but it also affects vertices before the cutoff point, due to the distance being an absolute unit.

    - Screenshot 2: Subtracting the Z axes of the two vectors and using that in the condition check - this works best in terms of accuracy, but unfortunately, still has some issues at the edges.

    Here's the code I came up with (includes both cut-off checking methods):
    Code (CSharp):
    1.  
    2. ...
    3. Mesh mesh; // the mesh being modified
    4. int[] triangles = mesh.triangles; // original mesh triangles
    5. int[] vertices = mesh.vertices; // original mesh vertices
    6. ...
    7.  
    8. void SelectTriangles()
    9.     {
    10.         // empty list for triangles to be included
    11.         List<int> tris = new List<int>();
    12.  
    13.         // Get cutoff point
    14.         Vector3 cutoff = path.GetPointAtDistance(meshObject.transform.position.z + DesiredLength); // account for the object's Z position along the spline
    15.  
    16.         // Go through the original triangles
    17.         for (int t = 0; t < triangles.Length; t += 3)
    18.         {
    19.             // Calculate the average vector for 3 triangles
    20.             Vector3 average = Vector3.zero;
    21.             Vector3 tempVector = Vector3.zero;
    22.             for (int i = 0; i < 3; i++)
    23.                 tempVector += vertices[triangles[t + i]];
    24.             average = tempVector / 3;
    25.  
    26.             // Calculate the distance between the triangle average and the cutoff point
    27.             // Only used with TRY 1!
    28.             float distance = Vector3.Distance(average, cutoff);
    29.  
    30.             // If we're behind the cutoff point, add triangles
    31.             //if (distance < DesiredLength) // TRY 1: DISTANCE BETWEEN TWO VECTORS (messes with vectors behind cutoff point as well - distance is in absolute unit!)
    32.             if (average.z < cutoff.z)       // TRY 2: ONLY CHECK Z AXIS. Seems to work better, although some triangles are beyond or behind the cutoff and it messes with them
    33.             {
    34.                 for (int i = 0; i < 3; i++)
    35.                 {
    36.                     tris.Add(triangles[t + i]);
    37.                     // debug spheres for each vector
    38.                 }
    39.             }
    40.  
    41.  
    42.             // debug wall at cutoff point
    43.         }
    44.  
    45.         // Set mesh triangles
    46.         mesh.triangles = tris.ToArray();
    47.  
    48.         mesh.RecalculateNormals();
    49.         mesh.RecalculateTangents();
    50.         mesh.RecalculateBounds();
    51. }
    52.  
    My guess would be that we somehow have to check if the overall area of the triangle is beyond/touching the cut-off point and base the inclusion condition off that.
    Perhaps there's a better way?

    Thanks for any help in advance!
     
    Last edited: Oct 22, 2020
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    what's trimming in general? are you drawing a line and expecting all triangles on one side of that line to be deleted?

    if that's the definition, then the next question is do you want to splice the triangles as well? or do you only need them filtered through?

    finally how do you supply the trimming path? is it all in 3D and you're drawing from a 2D projection?
     
  8. xezrunner

    xezrunner

    Joined:
    Jul 5, 2020
    Posts:
    12
    That's eye opening actually, as I might be better off adjusting the last vertex points closest to the cut-off wall to match the cut-off wall position.
    I guess it's impossible to just delete the triangles and end up with a nicely trimmed model.

    I'm working in 3D. I have a spline system that lets me get a point (and rotation) on the given path. From the above code, you might get a better idea on how I'm trying to work with this.

    Thanks for the reply!