Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added preserve_border argument to lossless method. #37

Merged
merged 8 commits into from
Oct 23, 2024
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,14 @@ Parameters of the **`simplify\_mesh\_lossless`** method that can be tuned.
Maximal error after which a vertex is not deleted.
- **max\_iterations**
Maximum number of iterations.
- **preserve\_border**
Flag for preserving the vertices situated on open borders. Applies the method described in `this issue <https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/issues/14>`__.

Note
~~~~

- The **`simplify\_mesh\_lossless`** method is different from the **`simplify\_mesh`** method with the lossless flag enabled, and should be prefered when quality is the aim and not a precise number of target triangles.
- Tests have shown that the **threshold\_lossless** argument has little to no influence on the reduction of the meshes.
- On the other hand, only the basic algorithm has the option (yet) to preserve open borders.


Implications of the parameters for the threshold growth rate :
Expand Down
10 changes: 7 additions & 3 deletions pyfqmr/Simplify.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ namespace Simplify
compact_mesh();
} //simplify_mesh()

void simplify_mesh_lossless(void (*log)(char*, int)=NULL, double epsilon=1e-3, int max_iterations = 9999)
void simplify_mesh_lossless(void (*log)(char*, int)=NULL, double epsilon=1e-3, int max_iterations = 9999, bool preserve_border = false)
{
// init
loopi(0,triangles.size())
Expand Down Expand Up @@ -499,8 +499,12 @@ namespace Simplify
int i0=t.v[ j ]; Vertex &v0 = vertices[i0];
int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];

// Border check
if(v0.border != v1.border) continue;
// Border check //Added preserve_border method from issue 14 for lossless
if(preserve_border){
if (v0.border || v1.border) continue; // should keep border vertices
}
else
if (v0.border != v1.border) continue; // base behaviour

// Compute vertex to collapse to
vec3f p;
Expand Down
37 changes: 32 additions & 5 deletions pyfqmr/Simplify.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ cdef extern from "Simplify.h" namespace "Simplify" :
void simplify_mesh( int target_count, int update_rate, double aggressiveness,
void (*log)(char*, int), int max_iterations,double alpha, int K,
bool lossless, double threshold_lossless, bool preserve_border)
void simplify_mesh_lossless(void (*log)(char*, int), double epsilon, int max_iterations)
void simplify_mesh_lossless(void (*log)(char*, int), double epsilon, int max_iterations, bool preserve_border)
void setMeshFromExt(vector[vector[double]] vertices, vector[vector[int]] faces)
vector[vector[int]] getFaces()
vector[vector[double]] getVertices()
Expand Down Expand Up @@ -107,7 +107,12 @@ cdef class Simplify :
self.triangles_cpp.clear()
self.vertices_cpp.clear()
self.normals_cpp.clear()

# Here we will need some checks, just to make sure the right objets are passed
# Only checks on the faces is possible, as they are necessarily integers.
# Maybe other checks like max(faces)==len(vertices) could be added
assert is_integer_array(faces), "Faces must contain only integers, check function argument order"
bwoodsend marked this conversation as resolved.
Show resolved Hide resolved

self.faces_mv = faces.astype(dtype="int32", subok=False, copy=False)
self.vertices_mv = vertices.astype(dtype="float64", subok=False, copy=False)
self.triangles_cpp = setFacesNogil(self.faces_mv, self.triangles_cpp)
Expand Down Expand Up @@ -171,7 +176,7 @@ cdef class Simplify :
round(t_end-t_start,4), N_start, N_end)
)

cpdef void simplify_mesh_lossless(self, bool verbose = True, double epsilon=1e-3, int max_iterations=9999):
cpdef void simplify_mesh_lossless(self, bool verbose = True, double epsilon=1e-3, int max_iterations=9999, bool preserve_border = False):
"""Simplify mesh using lossless method

Parameters
Expand All @@ -191,7 +196,7 @@ cdef class Simplify :

N_start = self.faces_mv.shape[0]
t_start = _time()
simplify_mesh_lossless(log, epsilon, max_iterations)
simplify_mesh_lossless(log, epsilon, max_iterations, preserve_border)
t_end = _time()
N_end = getFaces().size()

Expand All @@ -205,7 +210,7 @@ cdef class Simplify :
@cython.boundscheck(False)
@cython.wraparound(False) # turn off negative index wrapping for entire function
@cython.nonecheck(False)
cdef vector[vector[double]] setVerticesNogil(double[:,:] vertices, vector[vector[double]] vector_vertices )nogil:
cdef vector[vector[double]] setVerticesNogil(double[:,:] vertices, vector[vector[double]] vector_vertices )nogil noexcept:
"""nogil function for filling the vector of vertices, "vector_vertices",
with the data found in the memory view of the array "vertices"
"""
Expand All @@ -224,7 +229,7 @@ cdef vector[vector[double]] setVerticesNogil(double[:,:] vertices, vector[vector
@cython.boundscheck(False)
@cython.wraparound(False) # turn off negative index wrapping for entire function
@cython.nonecheck(False)
cdef vector[vector[int]] setFacesNogil(int[:,:] faces, vector[vector[int]] vector_faces )nogil:
cdef vector[vector[int]] setFacesNogil(int[:,:] faces, vector[vector[int]] vector_faces )nogil noexcept:
"""nogil function for filling the vector of faces, "vector_faces",
with the data found in the memory view of the array "faces"
"""
Expand All @@ -239,3 +244,25 @@ cdef vector[vector[int]] setFacesNogil(int[:,:] faces, vector[vector[int]] vecto
triangle.push_back(faces[i,j])
vector_faces.push_back(triangle)
return vector_faces


# For faster type checking
def is_integer_array(arr):
# Check if the dtype is integer before calling Cython function
if np.issubdtype(arr.dtype, np.integer):
return True
return is_integer_array_cython(arr)
bwoodsend marked this conversation as resolved.
Show resolved Hide resolved

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def is_integer_array_cython(np.ndarray arr):
cdef Py_ssize_t i, j
cdef Py_ssize_t n_rows = arr.shape[0]
cdef Py_ssize_t n_cols = arr.shape[1]

for i in range(n_rows):
for j in range(n_cols):
if arr[i, j] % 1 != 0:
return False
return True
Loading