mlivesu / cinolib

A generic programming header only C++ library for processing polygonal and polyhedral meshes

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cut a volume mesh based on embedded level set

f-p-b opened this issue · comments

commented

Hi, I would like to cut a volume mesh based a level set embedded on its vertices (at isovalue zero). To do so I have tried implementing it as seem bellow. While it compiles and runs without issues the mesh is not being split. I am probably missing something pretty trivial but I can’t seem figure out what it is. Hopefully you can shed some light on it.

std::vector<double> isovalues;
for (uint vid=1; vid<MyTetMesh.num_verts(); ++vid) {
    // Get coordinates of current point
    vec3d point = MyTetMesh.vert(vid);
    Point p(point.x(), point.y(), point.z());

    // Compute isovalue for current point
    double isovalue = my_function(p);
    isovalues.push_back(isovalue);
}

// Copy the field on the mesh?
ScalarField f = ScalarField(isovalues);
f.copy_to_mesh(MyTetMesh);

// Slice the mesh?
MeshSlicer ss;
ss.Z_thresh = 0;
ss.slice(MyTetMesh);

Hi, slicing with MeshSlicer is just a visual thing. It is meant to show/hide mesh elements, but these are not conforming with your scalar field. If you want to cut mesh elements along a user defined scalar field you must construct a cinolib::Isosurface object, which takes in input a mesh with a field embedded in its vertices and an iso value. This class has a tessellate() method which does the splitting you want. Note that this covers only the splitting part. If you want to throw away elements at one side of the field you can for example compute the average field value for each element and decide whether you want to keep it or not depending on which side of your reference iso value it stays. Discarding can be either a visual thing (e.g. you hide the element) or something more permanent (e.g. you remove that element from the mesh). Your choice...

You might want to take a look at this example, which does something very similar to what you mean

commented

Hi, that did the job. Thank you!

I have a couple of extra questions related to this. First one is whether there is an inbuilt way to retrieve the coordinates of an element center? I tried to find it but failed to do so.
Also, I was looking inside the tetmesh and abstract_mesh classes and was expecting to find some function that allows to automatically remove elements based on their idx or something like that. Is there a way to accomplish this automatically (ensuring all data inside the mesh class is updated accordingly) or do I have to remove them manually myself and ensure all other relevant stuff in the class is also updated?
Finally, I was also looking for a way to add polylabels to the elements of an existing mesh in order to differentiate the different areas of the sliced mesh and though something along the lines of the following should work (of course the 1 would be a variable that is 0 or 1 depending on the side):

  std::vector<int> poly_labels;
  for (uint pid=0; pid<m_tet.num_polys(); ++pid) {
    poly_labels.push_back(1);
  }
  m_tet.poly_apply_labels(poly_labels);

However, the number assigned to the volume, 0 by default, remains unchanged. What is the correct way to achieve what I am trying to do?

  • the coordinates of the centroid can be retrieved with poly_centroid()
  • elements can be removed with poly_remove() if it is one, polys_remove() if there are many. They do all the dirty job for you
  • for the labels, what you did should do the job (assuming I understood your intent). Alternatively, you could access labels directly with poly_data(pid).label
commented

Thanks again! In principle I am getting what I want now but I have noticed an unexpected behavior. For the most part the mesh is cut correctly but I have noticed that occasionally it does not cut an element as expected. See the 'spikes' I have encircled in red in the two images bellow. These should not be there since the underlying level-set is continuous and smooth.

image image

mmmm, this smells like a bug. Can you share the mesh, field and isovalue so that I can reproduce the issue and verify what's going on?

commented

Sure, you will find the files in the attached ZIP. I have stored the initial mesh as a MESH file and dumped the field in a TXT file since I am not sure what format you use for this. The isovalue I used was 0.

test.zip

Thanks a lot. I have a few upcoming deadlines, but I will try to look into it asap

commented

Hi, have you been able to look into it? :)

sorry but not yet, it's been a busy period :)
I'll try my best in the following weeks...

commented

No worries!

I have tried to reproduce your issue but I cannot find the exact location of the flawed mesh elements.
I see that you applied some clustering algorithm and then cut along a level set. Would you mind sharing the whole code with me so that I can do the same cut, filter by label and then inspect it better?

Also, the text file you sent me had 9764 per vertex values, but the mesh contains 9765 vertices. I shifted all values by assigning 0 to the first mesh vertex and (at least visually) things make sense, but chances are that what I am looking at is not exactly what you were looking at....

For the record, if in the code you have a cinolib::ScalarField that contains your field values, you can export it to a text file with the serialize method

commented

Hi, apologies for the late reaction. The attached file contains a minimal example. In the output mesh it generates you will see elements sticking out at different locations of what should otherwise be smooth surface.
cinolib_test.zip

Thanks, this is exactly what I needed. I've already seen that the issue is in the mesh refinement and not in the level set tracing. In fact, if I just visualize the zero level set without splitting tets it looks just fine. Now I just need to chase this bug in the splitting. I'll keep you posted

hi, it turned out there was no bug in the library :)

Your issue depended on a non fully robust filtering of the tetrahedral elements. Basically, you were evaluating your function at the centroid of each tetrahedron, discarding any elements for which levelSetFucntion(tet_centroid)<=0

But levelSetFucntion is full of transcendental functions and is not very precise, therefore evaluating levelSetFucntion(tet_centroid) on almost degenerate tetrahedra created by marching tets occasionally gives you the wrong answer.

I just had to change the way you filter tets, removing from the mesh all tets for which at least one vertex is < 0. This is both safer and cheaper, because the value of levelSetFucntion at mesh vertices is already known. Here is the new code for mesh filtering:

    std::vector<uint> pids_remove;
    for(uint pid=0; pid<m.num_polys(); ++pid)
    {
        for(uint vid : m.adj_p2v(pid))
        {
            if(m.vert_data(vid).uvw[0]<0)
            {
                pids_remove.push_back(pid);
                break;
            }
        }
    }
    m.polys_remove(pids_remove);
commented

I completely overlooked that could be an issue, my bad. Thank you so much for the help!