├── .gitignore ├── images ├── cad1.png ├── cad2.png ├── cad3.png └── bunny.png ├── CMakeLists.txt ├── LICENSE ├── run_suite.py ├── README.md ├── source └── remesher_main.cpp ├── meshes ├── shaft_support_8mm.obj ├── phone_mount.obj ├── hub.obj ├── anti_backlash_nut.obj ├── delta_arm2.obj ├── bearing_plate_608.obj └── axis_end.obj └── include ├── mesh.h └── remesher.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | bin 4 | output -------------------------------------------------------------------------------- /images/cad1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesgregson/remesher/HEAD/images/cad1.png -------------------------------------------------------------------------------- /images/cad2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesgregson/remesher/HEAD/images/cad2.png -------------------------------------------------------------------------------- /images/cad3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesgregson/remesher/HEAD/images/cad3.png -------------------------------------------------------------------------------- /images/bunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesgregson/remesher/HEAD/images/bunny.png -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 2.6 ) 2 | 3 | project( remesher ) 4 | 5 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) 6 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/bin ) 7 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/bin ) 8 | 9 | find_package( utilities ) 10 | 11 | include_directories( 12 | ${UTILITIES_INCLUDE_DIRS} 13 | ${CMAKE_SOURCE_DIR}/include 14 | ) 15 | 16 | set( REMESHER_HEADERS 17 | include/mesh.h 18 | include/remesher.h 19 | ) 20 | 21 | add_executable( remesher source/remesher_main.cpp ${REMESHER_HEADERS} ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 James Gregson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /run_suite.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | def remesh( obj, size, feat, iters=10 ): 4 | # create the output path 5 | if not os.path.exists('output'): 6 | os.mkdir('output') 7 | # generate the remesher command line 8 | cmd = 'bin/remesher -input meshes/%s.obj -output output/%s_remesh.obj -size %f -feat %f -iters %d' % (obj,obj,size,feat,iters) 9 | print cmd 10 | os.system(cmd) 11 | 12 | #remesh( 'u_joint_link', 0.02, 80.0, 20 ) 13 | #remesh( 'axis_end', 0.02, 30.0, 20 ) 14 | #remesh( 'bunny', 0.01, 180.0, 20 ) 15 | #remesh('phone_mount', 0.02, 30.0, 40 ) 16 | #remesh( 'lever_arm', 0.01, 30.0, 20 ) 17 | #remesh( 'delta_arm_end_effector', 0.02, 30.0, 20 ) 18 | #remesh( 'chain_fixed', 0.03, 30.0, 20 ) 19 | #remesh('anti_backlash_nut', 0.02, 30.0, 20 ) 20 | #remesh( 'cap_thing', 0.02, 30.0, 20 ) 21 | remesh( 'delta_arm2', 0.015, 30.0, 20 ) 22 | 23 | sys.exit() 24 | 25 | #remesh( 'axis_end', 0.01, 30.0, 5 ) 26 | #remesh( 'bearing_plate_608', 0.05, 30.0 ) 27 | #remesh( 'carriage', 0.05, 30.0 ) 28 | #remesh( 'delta_arm_base', 0.05, 30.0 ) 29 | #remesh( 'gear2', 0.02, 30.0 ) 30 | #remesh('phone_mount', 0.01, 30.0 ) 31 | sys.exit() 32 | remesh( 'shaft_support_8mm', 0.05, 30.0 ) 33 | remesh( 'u_joint_link', 0.05, 30.0 ) 34 | remesh( 'delta_robot_linear_axis', 0.05, 30.0 ) 35 | 36 | remesh('9x1_ratio_plate', 0.03, 30.0 ) 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | remesher 2 | ======== 3 | 4 | A simple feature-preserving isotropic remesher based on edge flips, edge collapses and vertex repositioning. 5 | 6 | This is pre-alpha code! Use at your own risk. I cannot support it! 7 | 8 | ## Requirements 9 | 10 | My utilities code for string handling, some geometry functions and command line arguments, download from: https://github.com/jamesgregson/utilities 11 | 12 | ## Building 13 | 14 | Generate makefiles using CMake from the build/ subdirectory then build with make. 15 | 16 | ``` 17 | /remesher$ cd build 18 | /remesher/build$ cmake .. -G Xcode 19 | /remesher/build$ make 20 | ``` 21 | ## Meshing 22 | 23 | After a successful build, run from bin/ subdirectory or play with the run_suite.py 24 | 25 | Run with no options to see command line arguments, e.g.: 26 | 27 | ``` 28 | /remesher/bin$ ./remesher 29 | option -size defaulted to 0.02 30 | option -feat defaulted to 45.0087 31 | option -iters defaulted to 10 32 | ================================================== 33 | Usage: ./remesher [parameters] 34 | ================================================== 35 | Parameters: 36 | -input [REQUIRED] input file in .obj format 37 | -output [REQUIRED] output file in .obj format 38 | Optional parameters: 39 | -size [OPTIONAL] edge length as fraction of bounding box longest side, default: 0.02 40 | -feat [OPTIONAL] feature threshold, degrees, default: 45.0087 41 | -iters [OPTIONAL] number of remeshing iterations to perform, default: 10 42 | ================================================== 43 | Errors: 44 | required option -input was not specified! 45 | required option -output was not specified! 46 | 47 | command line was: 48 | ./remesher 49 | ``` 50 | 51 | ## Example outputs 52 | 53 | ![bunny](https://github.com/jamesgregson/remesher/blob/master/images/bunny.png) 54 | 55 | ![cad model](https://github.com/jamesgregson/remesher/blob/master/images/cad1.png) 56 | 57 | ![cad model](https://github.com/jamesgregson/remesher/blob/master/images/cad2.png) 58 | 59 | ![cad model](https://github.com/jamesgregson/remesher/blob/master/images/cad3.png) 60 | -------------------------------------------------------------------------------- /source/remesher_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | std::vector tokenize( std::string &in ){ 13 | std::vector tokens; 14 | std::istringstream iss(in); 15 | std::copy(std::istream_iterator(iss), 16 | std::istream_iterator(), 17 | std::back_inserter >(tokens)); 18 | return tokens; 19 | } 20 | 21 | template< typename real > 22 | bool load_obj( const char *filename, std::vector& coords, std::vector& tris ){ 23 | 24 | std::ifstream in(filename); 25 | if( !in.is_open() ) 26 | return false; 27 | 28 | std::string line; 29 | while( std::getline(in,line) ){ 30 | if( line.empty() ) 31 | continue; 32 | std::istringstream iss(line); 33 | std::string token; 34 | 35 | iss >> token; 36 | if( token == "v" ){ 37 | real x, y, z; 38 | iss >> x >> y >> z; 39 | coords.push_back( x ); 40 | coords.push_back( y ); 41 | coords.push_back( z ); 42 | } else if( token == "f" ){ 43 | std::vector tokens = tokenize(line); 44 | if( tokens.size() != 4 ){ 45 | std::cout << "input not a triangle mesh!" << std::endl; 46 | return false; 47 | } 48 | for( int i=1; i(tokens[i])-1; 51 | tris.push_back( id ); 52 | } 53 | } 54 | } 55 | 56 | return true; 57 | } 58 | 59 | int main( int argc, const char **argv ){ 60 | char input_file[1024]; 61 | char output_file[1024]; 62 | double edge_len = 1.0/50.0; 63 | double feat = acos(0.707)*180.0/M_PI; 64 | int iters=10; 65 | 66 | utilities::command_line_options clopts; 67 | clopts.add_required_parameter( "-input", ARGUMENT_STRING, input_file, 1, "input file in .obj format" ); 68 | clopts.add_required_parameter( "-output", ARGUMENT_STRING, output_file, 1, "output file in .obj format" ); 69 | clopts.add_optional_parameter( "-size", ARGUMENT_DOUBLE, &edge_len, 1, "edge length as fraction of bounding box longest side" ); 70 | clopts.add_optional_parameter( "-feat", ARGUMENT_DOUBLE, &feat, 1, "feature threshold, degrees" ); 71 | clopts.add_optional_parameter( "-iters", ARGUMENT_INT, &iters, 1, "number of remeshing iterations to perform"); 72 | if( !clopts.parse( argc, argv ) ){ 73 | return 1; 74 | } 75 | 76 | std::vector coords, rm_coords; 77 | std::vector tris, rm_tris; 78 | if( !load_obj( input_file, coords, tris ) ) 79 | return 2; 80 | 81 | // compute the longest edge of the 82 | // input bounding box 83 | vec3d minim( coords[0], coords[1], coords[2] ); 84 | vec3d maxim = minim; 85 | for( int i=3; i 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace trimesh { 14 | 15 | template< typename real > 16 | bool save_obj( const char *filename, std::vector& coords, std::vector& tris ){ 17 | std::ofstream out(filename); 18 | for( int i=0; i vec3d; 29 | 30 | class mesh; 31 | class vertex; 32 | class triangle; 33 | 34 | vec3d triangle_angles( vec3d A, vec3d B, vec3d C ){ 35 | double a = (B-C).length(); 36 | double b = (A-C).length(); 37 | double c = (A-B).length(); 38 | double cosalpha = std::max( -1.0, std::min( 1.0, (b*b+c*c-a*a)/(2.0*b*c) ) ); 39 | double cosbeta = std::max( -1.0, std::min( 1.0, (a*a+c*c-b*b)/(2.0*a*c) ) ); 40 | double cosgamma = std::max( -1.0, std::min( 1.0, (a*a+b*b-c*c)/(2.0*a*b) ) ); 41 | vec3d ret( acos(cosalpha),acos(cosbeta),acos(cosgamma)); 42 | if( fabs(ret[0]+ret[1]+ret[2]-M_PI) > 1e-5 ){ 43 | std::cout << a << ", " << b << ", " << c << ", " << cosalpha << ", " << cosbeta << ", " << cosgamma << ", " << ret[0]+ret[1]+ret[2]-M_PI << std::endl; 44 | //throw "triangle angle formula error"; 45 | return vec3d(0,0,0); 46 | } 47 | return ret; 48 | } 49 | 50 | class vertex { 51 | public: 52 | typedef std::set::iterator triangle_iterator; 53 | 54 | vertex( mesh *m, vec3d p ) : m_mesh(m), m_pos(p) { 55 | m_is_feature=false; 56 | } 57 | 58 | bool& is_feature(){ return m_is_feature; } 59 | 60 | int &feature_edge_count(){ return m_feat_count; } 61 | 62 | void add_triangle( triangle* t ){ 63 | m_tris.insert( t ); 64 | } 65 | 66 | void remove_triangle( triangle* t ){ 67 | m_tris.erase(t); 68 | } 69 | 70 | bool has_triangle( triangle* t ){ 71 | return m_tris.find(t) != m_tris.end(); 72 | } 73 | 74 | int valence(){ 75 | return m_tris.size(); 76 | } 77 | 78 | triangle_iterator triangles_begin(){ 79 | return m_tris.begin(); 80 | } 81 | triangle_iterator triangles_end(){ 82 | return m_tris.end(); 83 | } 84 | 85 | vec3d& pos(){ 86 | return m_pos; 87 | } 88 | private: 89 | mesh* m_mesh; 90 | vec3d m_pos; 91 | std::set m_tris; 92 | bool m_is_feature; 93 | int m_feat_count; 94 | }; 95 | 96 | class triangle { 97 | public: 98 | triangle( mesh *m, vertex* a, vertex *b, vertex* c ) : m_mesh(m) { 99 | m_vtx[0]=a; m_vtx[1]=b; m_vtx[2]=c; 100 | } 101 | 102 | virtual ~triangle(){ 103 | } 104 | 105 | bool has_vertex( vertex* v ){ 106 | return m_vtx[0]==v || m_vtx[1] == v || m_vtx[2] == v; 107 | } 108 | 109 | vertex* vtx( int id ){ 110 | return m_vtx[id]; 111 | } 112 | 113 | vertex* next( vertex* v ){ 114 | if( m_vtx[0] == v ) return m_vtx[1]; 115 | if( m_vtx[1] == v ) return m_vtx[2]; 116 | if( m_vtx[2] == v ) return m_vtx[0]; 117 | return NULL; 118 | } 119 | 120 | vertex* other( vertex* a, vertex* b ){ 121 | int cnt=0; 122 | vertex* oth=NULL; 123 | if( m_vtx[0] == a || m_vtx[0] == b ) cnt++; 124 | else oth=m_vtx[0]; 125 | if( m_vtx[1] == a || m_vtx[1] == b ) cnt++; 126 | else oth=m_vtx[1]; 127 | if( m_vtx[2] == a || m_vtx[2] == b ) cnt++; 128 | else oth=m_vtx[2]; 129 | return cnt == 2 ? oth : NULL; 130 | } 131 | 132 | bool shares_edge( triangle* t ){ 133 | return (t->has_vertex( vtx(0) ) && t->has_vertex( vtx(1) )) 134 | || (t->has_vertex( vtx(1) ) && t->has_vertex( vtx(2) )) 135 | || (t->has_vertex( vtx(2) ) && t->has_vertex( vtx(0) )); 136 | } 137 | private: 138 | mesh* m_mesh; 139 | vertex* m_vtx[3]; 140 | }; 141 | 142 | class mesh { 143 | public: 144 | typedef std::unordered_set vertex_set; 145 | typedef std::unordered_set triangle_set; 146 | typedef typename vertex_set::iterator vertex_iterator; 147 | typedef typename triangle_set::iterator triangle_iterator; 148 | 149 | vertex* add_vertex( vec3d pos ){ 150 | vertex* v = new vertex(this,pos); 151 | m_verts.insert(v); 152 | return v; 153 | } 154 | 155 | void remove_vertex( vertex *v ){ 156 | if( v->valence() != 0 ){ 157 | fail_and_die("tried to remove a connected vertex"); 158 | } 159 | m_verts.erase(v); 160 | delete v; 161 | } 162 | 163 | bool has_vertex( vertex* v ){ 164 | return m_verts.find(v) != m_verts.end(); 165 | } 166 | 167 | triangle* add_triangle( vertex* a, vertex *b, vertex *c ){ 168 | if( tri_for_edge(a,b) || tri_for_edge(b,c) || tri_for_edge(c,a) ){ 169 | //fail_and_die("add_triangle: new triangle would create non manifold edge"); 170 | return NULL; 171 | } 172 | if( a == b || a == c || b == c ) 173 | fail_and_die("add_triangle: triangle has duplicated vertices"); 174 | 175 | if( !has_vertex(a) || !has_vertex(b) || !has_vertex(c) ) 176 | fail_and_die("add_triangle: triangle references unknown vertices" ); 177 | 178 | for( vertex::triangle_iterator it=a->triangles_begin(); it!=a->triangles_end(); ++it ){ 179 | triangle *t=*it; 180 | if( t->has_vertex(b) && t->has_vertex(c) ) 181 | fail_and_die("add_triangle: triangle would be duplicated" ); 182 | } 183 | for( vertex::triangle_iterator it=b->triangles_begin(); it!=b->triangles_end(); ++it ){ 184 | triangle *t=*it; 185 | if( t->has_vertex(a) && t->has_vertex(c) ) 186 | fail_and_die("add_triangle: triangle would be duplicated" ); 187 | } 188 | for( vertex::triangle_iterator it=c->triangles_begin(); it!=c->triangles_end(); ++it ){ 189 | triangle *t=*it; 190 | if( t->has_vertex(a) && t->has_vertex(b) ) 191 | fail_and_die("add_triangle: triangle would be duplicated" ); 192 | } 193 | 194 | triangle* t = new triangle(this,a,b,c); 195 | m_tris.insert(t); 196 | a->add_triangle(t); 197 | b->add_triangle(t); 198 | c->add_triangle(t); 199 | m_tri_for_edge[ halfedge(a,b) ] = t; 200 | m_tri_for_edge[ halfedge(b,c) ] = t; 201 | m_tri_for_edge[ halfedge(c,a) ] = t; 202 | if( !check_mesh_pointers(t) ) 203 | fail_and_die("mesh pointers incorrect"); 204 | 205 | return t; 206 | } 207 | 208 | void remove_triangle( triangle* t ){ 209 | vertex *a,*b,*c; 210 | a = t->vtx(0); 211 | b = t->vtx(1); 212 | c = t->vtx(2); 213 | 214 | m_tris.erase( t ); 215 | a->remove_triangle(t); 216 | b->remove_triangle(t); 217 | c->remove_triangle(t); 218 | m_tri_for_edge.erase( halfedge(a,b) ); 219 | m_tri_for_edge.erase( halfedge(b,c) ); 220 | m_tri_for_edge.erase( halfedge(c,a) ); 221 | delete t; 222 | if( tri_for_edge(a,b) || tri_for_edge(b,c) || tri_for_edge(c,a) ) 223 | fail_and_die("remove_triangle: local error in mesh"); 224 | } 225 | 226 | bool has_triangle( triangle* t ){ 227 | return m_tris.find(t) != m_tris.end(); 228 | } 229 | 230 | vertex_iterator vertices_begin(){ 231 | return m_verts.begin(); 232 | } 233 | vertex_iterator vertices_end(){ 234 | return m_verts.end(); 235 | } 236 | triangle_iterator triangles_begin(){ 237 | return m_tris.begin(); 238 | } 239 | triangle_iterator triangles_end(){ 240 | return m_tris.end(); 241 | } 242 | 243 | triangle* tri_for_edge( vertex* a, vertex* b ){ 244 | std::map< halfedge, triangle*>::iterator it=m_tri_for_edge.find( halfedge(a,b) ); 245 | if( it != m_tri_for_edge.end() ){ 246 | triangle* t = it->second; 247 | if( m_tris.find(t) != m_tris.end() ) 248 | return t; 249 | } 250 | return NULL; 251 | } 252 | 253 | void flip_edge( vertex* a, vertex* b ){ 254 | if( a->valence() == 3 || b->valence() == 3 ) 255 | return; 256 | 257 | if( !check_local_mesh(a) || !check_local_mesh(b) ) 258 | fail_and_die("flip_edge: local error in mesh"); 259 | 260 | triangle* T1 = tri_for_edge(a,b); 261 | triangle* T2 = tri_for_edge(b,a); 262 | if( !T1 || !T2 ) return; 263 | vertex* c = T1->other(a,b); 264 | vertex* d = T2->other(a,b); 265 | if( !check_local_mesh(c) || !check_local_mesh(d) ) 266 | fail_and_die("flip_edge: local error in mesh"); 267 | 268 | remove_triangle( T1 ); 269 | remove_triangle( T2 ); 270 | T1 = add_triangle( c, a, d ); 271 | T2 = add_triangle( d, b, c ); 272 | if( !T1 || !T2 ){ 273 | if( T1 ) remove_triangle(T1); 274 | if( T2 ) remove_triangle(T2); 275 | add_triangle( a, b, c ); 276 | add_triangle( b, a, d ); 277 | } 278 | 279 | if( !check_local_mesh(a) || !check_local_mesh(b) || !check_local_mesh(c) || !check_local_mesh(d) ) 280 | fail_and_die("flip_edge: local error in mesh"); 281 | } 282 | 283 | void extract_triangles_near_edge( vertex* a, vertex* b, std::vector& coord, std::vector& tris ){ 284 | int cnt=0; 285 | for( vertex::triangle_iterator it=a->triangles_begin(); it!=a->triangles_end(); ++it ){ 286 | triangle *t = *it; 287 | for( int i=0; i<3; i++ ){ 288 | vertex* v = t->vtx(i); 289 | coord.push_back( v->pos()[0] ); 290 | coord.push_back( v->pos()[1] ); 291 | coord.push_back( v->pos()[2] ); 292 | tris.push_back(cnt++); 293 | } 294 | } 295 | for( vertex::triangle_iterator it=b->triangles_begin(); it!=b->triangles_end(); ++it ){ 296 | triangle* t = *it; 297 | if( !t->has_vertex(a) ){ 298 | for( int i=0; i<3; i++ ){ 299 | vertex* v = t->vtx(i); 300 | coord.push_back( v->pos()[0] ); 301 | coord.push_back( v->pos()[1] ); 302 | coord.push_back( v->pos()[2] ); 303 | tris.push_back(cnt++); 304 | } 305 | } 306 | } 307 | } 308 | 309 | void collapse_edge( vertex* a, vertex* b, bool allow_features ){ 310 | // do not collapse if the edge is a feature edge 311 | if( edge_is_feature(a, b) && !allow_features ) 312 | return; 313 | // if the edge is not a feature edge, but b is a feature 314 | // vertex, do not collapse because this will change 315 | // features associated with b 316 | if( !edge_is_feature(a,b) && (b->is_feature() || a->is_feature()) ) 317 | return; 318 | 319 | bool feat_edge = edge_is_feature(a,b); 320 | int feat_edge_count=0; 321 | 322 | 323 | triangle* T1 = tri_for_edge(a,b); 324 | triangle* T2 = tri_for_edge(b,a); 325 | if( !T1 || !T2 ) 326 | return; 327 | 328 | // needed? 329 | //if( a->valence()-2+b->valence()-2 <= 3 || b->valence() == 3 || T1->other(a,b)->valence() == 3 || T2->other(a,b)->valence() == 3 ) 330 | // return; 331 | 332 | vec3d newpos = a->pos(); 333 | if( (feat_edge && feat_edge_count < 6) || (!a->is_feature() && !b->is_feature()) ) 334 | newpos = (a->pos()+b->pos())/2.0; 335 | 336 | 337 | std::vector remtri; 338 | std::vector newtri; 339 | std::vector newtrivtx; 340 | std::vector oldtrivtx; 341 | vertex* v = a; //add_vertex( ( a->pos() + b->pos() )/2.0 ); 342 | for( vertex::triangle_iterator it=a->triangles_begin(); it!=a->triangles_end(); ++it ){ 343 | triangle* t = *it; 344 | vertex* v1 = t->next(a); 345 | vertex* v2 = t->next(v1); 346 | 347 | feat_edge_count += edge_is_feature(a,v1); 348 | feat_edge_count += edge_is_feature(a,v2); 349 | 350 | vec3d nstart = (v1->pos()-a->pos()).cross(v2->pos()-a->pos()); 351 | vec3d nend = (v1->pos()-newpos).cross(v2->pos()-newpos); 352 | if( nstart.dot(nend) <= 0.0 ) 353 | return; 354 | 355 | if( !t->has_vertex(b) ){ 356 | if( v1 == a || v1 == b ) 357 | fail_and_die("uhoh"); 358 | if( v2 == a || v2 == b || v2 == v1 ) 359 | fail_and_die("uhoh"); 360 | 361 | //newtrivtx.push_back(v); 362 | //newtrivtx.push_back(v1); 363 | //newtrivtx.push_back(v2); 364 | } else { 365 | oldtrivtx.push_back(a); 366 | oldtrivtx.push_back(v1); 367 | oldtrivtx.push_back(v2); 368 | remtri.push_back(t); 369 | } 370 | } 371 | std::vector< halfedge > newfeat; 372 | for( vertex::triangle_iterator it=b->triangles_begin(); it!=b->triangles_end(); ++it ){ 373 | triangle *t = *it; 374 | if( !t->has_vertex(a) ){ 375 | vertex* v1 = t->next(b); 376 | vertex* v2 = t->next(v1); 377 | newtrivtx.push_back(v); 378 | newtrivtx.push_back(v1); 379 | newtrivtx.push_back(v2); 380 | 381 | vec3d nstart = (v1->pos()-b->pos()).cross(v2->pos()-b->pos()); 382 | vec3d nend = (v1->pos()-newpos).cross(v2->pos()-newpos); 383 | if( nstart.dot(nend) <= 0.0 ) 384 | return; 385 | 386 | if( edge_is_feature(b,v1) ) 387 | newfeat.push_back( halfedge(v,v1) ); 388 | if( edge_is_feature(b,v2) ) 389 | newfeat.push_back( halfedge(v,v2) ); 390 | if( edge_is_feature(a,v1) ) 391 | newfeat.push_back( halfedge(v,v1) ); 392 | if( edge_is_feature(a,v2) ) 393 | newfeat.push_back( halfedge(v,v2) ); 394 | 395 | oldtrivtx.push_back(b); 396 | oldtrivtx.push_back(v1); 397 | oldtrivtx.push_back(v2); 398 | remtri.push_back(t); 399 | } 400 | } 401 | for( int i=0; iis_feature() && !b->is_feature()) ) 416 | a->pos() = (a->pos()+b->pos())/2.0; 417 | 418 | for( int i=0; i dtris; 443 | std::vector oldtris; 444 | std::vector tris; 445 | std::vector newtris; 446 | for( vertex::triangle_iterator it=b->triangles_begin(); it!=b->triangles_end(); ++it ){ 447 | triangle* t = *it; 448 | if( !t->has_vertex(a) ){ 449 | vertex *v1 = t->next(b); 450 | vertex *v2 = t->next(v1); 451 | tris.push_back(a); 452 | tris.push_back(v1); 453 | tris.push_back(v2); 454 | dtris.push_back(t); 455 | oldtris.push_back( t->vtx(0) ); 456 | oldtris.push_back( t->vtx(1) ); 457 | oldtris.push_back( t->vtx(2) ); 458 | } else { 459 | dtris.push_back(t); 460 | oldtris.push_back( t->vtx(0) ); 461 | oldtris.push_back( t->vtx(1) ); 462 | oldtris.push_back( t->vtx(2) ); 463 | } 464 | } 465 | 466 | // remove the old triangles 467 | for( int i=0; ivtx(0),t->vtx(1) ) != t ) 505 | fail_and_die("uhoh"); 506 | if( tri_for_edge( t->vtx(1),t->vtx(2) ) != t ) 507 | fail_and_die("uhoh"); 508 | if( tri_for_edge( t->vtx(2),t->vtx(0) ) != t ) 509 | fail_and_die("uhoh"); 510 | if( !tri_for_edge( t->vtx(1),t->vtx(0) ) ) 511 | fail_and_die("uhoh"); 512 | if( !tri_for_edge( t->vtx(2),t->vtx(1) ) ) 513 | fail_and_die("uhoh"); 514 | if( !tri_for_edge( t->vtx(0),t->vtx(2) ) ) 515 | fail_and_die("uhoh"); 516 | if( !check_local_mesh( t->vtx(0) ) ) 517 | fail_and_die("uhoh"); 518 | if( !check_local_mesh( t->vtx(1) ) ) 519 | fail_and_die("uhoh"); 520 | } 521 | } 522 | 523 | //remove_vertex(b); 524 | 525 | if( !check_local_mesh(a) ) 526 | fail_and_die("error in local mesh"); 527 | */ 528 | } 529 | 530 | void split_edge( vertex* a, vertex* b ){ 531 | triangle* T1 = tri_for_edge(a,b); 532 | triangle* T2 = tri_for_edge(b,a); 533 | if( !T1 || !T2 ) return; 534 | vertex *m = add_vertex( (a->pos()+b->pos())/2 ); 535 | vertex *c = T1->other(a,b); 536 | vertex *d = T2->other(a,b); 537 | remove_triangle(T1); 538 | remove_triangle(T2); 539 | add_triangle( a, m, c ); 540 | add_triangle( m, b, c ); 541 | add_triangle( b, m, d ); 542 | add_triangle( m, a, d ); 543 | if( edge_is_feature( a, b ) ){ 544 | mark_as_features( a, m ); 545 | mark_as_features( m, b ); 546 | } 547 | } 548 | 549 | void mark_as_features( vertex* a, vertex* b ){ 550 | a->is_feature() = true; 551 | b->is_feature() = true; 552 | m_features.insert( halfedge(std::min(a,b),std::max(a,b)) ); 553 | } 554 | 555 | bool edge_is_feature( vertex* a, vertex* b ){ 556 | return m_features.find( halfedge(std::min(a,b),std::max(a,b)) ) != m_features.end(); 557 | } 558 | 559 | void get_mesh( std::vector& coord, std::vector& tris ){ 560 | int nextvid=0; 561 | std::map< vertex*, int > vid; 562 | for( mesh::vertex_iterator it=vertices_begin(); it!=vertices_end(); ++it ){ 563 | vertex *v = *it; 564 | vid[v] = nextvid++; 565 | coord.push_back( v->pos()[0] ); 566 | coord.push_back( v->pos()[1] ); 567 | coord.push_back( v->pos()[2] ); 568 | } 569 | for( mesh::triangle_iterator it=triangles_begin(); it!=triangles_end(); ++it ){ 570 | triangle* t = *it; 571 | tris.push_back( vid[t->vtx(0)] ); 572 | tris.push_back( vid[t->vtx(1)] ); 573 | tris.push_back( vid[t->vtx(2)] ); 574 | } 575 | } 576 | 577 | bool check_local_mesh( vertex* v ){ 578 | bool ret=true; 579 | 580 | // first check that the vertex exists in the mesh 581 | if( !has_vertex(v) ){ 582 | ret = false; 583 | } 584 | 585 | // now loop over the adjacent triangles 586 | for( vertex::triangle_iterator it=v->triangles_begin(); it!=v->triangles_end(); ++it ){ 587 | triangle *t = *it; 588 | 589 | // check if the triangle exists in the mesh 590 | if( !has_triangle(t) ){ 591 | ret = false; 592 | } 593 | // check that the triangle actually uses the vertex 594 | if( !t->has_vertex(v) ){ 595 | ret = false; 596 | } 597 | 598 | // now loop over the edges 599 | for( int i=0; i<3; i++ ){ 600 | // check that the edge pointers point to the current triangle 601 | if( tri_for_edge( t->vtx(i), t->vtx((i+1)%3)) != t ){ 602 | ret = false; 603 | } 604 | 605 | // check that a neigboring triangle exists 606 | if( !tri_for_edge( t->vtx((i+1)%3), t->vtx(i) ) ){ 607 | ret = false; 608 | } 609 | } 610 | } 611 | return ret; 612 | } 613 | 614 | bool check_mesh(){ 615 | for( vertex_iterator it=vertices_begin(); it!=vertices_end(); ++it ){ 616 | if(!check_local_mesh(*it) ) return false; 617 | } 618 | return true; 619 | } 620 | 621 | bool check_mesh_pointers( vertex* v ){ 622 | bool ret = true; 623 | for( vertex::triangle_iterator it=v->triangles_begin(); it!=v->triangles_end(); ++it ){ 624 | triangle* t = *it; 625 | ret &= check_mesh_pointers( t ); 626 | } 627 | return ret; 628 | } 629 | 630 | bool check_mesh_pointers( triangle* t ){ 631 | if( tri_for_edge(t->vtx(0),t->vtx(1)) != t || tri_for_edge(t->vtx(1),t->vtx(2)) != t || tri_for_edge(t->vtx(2),t->vtx(0)) != t ){ 632 | return false; 633 | } 634 | for( int i=0; i<3; i++ ){ 635 | if( !t->vtx(0)->has_triangle(t) ) 636 | return false; 637 | } 638 | return true; 639 | } 640 | 641 | void fail_and_die( const char *msg ){ 642 | std::vector coord; 643 | std::vector tri; 644 | get_mesh( coord, tri ); 645 | save_obj( "remesher_dump.obj", coord, tri ); 646 | std::cout << "FATAL ERROR: " << msg << std::endl; 647 | throw msg; 648 | } 649 | 650 | void fail_and_die( const char *msg, std::vector& coord, std::vector& tri ){ 651 | save_obj( "remesher_error.obj", coord, tri ); 652 | std::cout << "FATAL ERROR: " << msg << std::endl; 653 | throw msg; 654 | } 655 | 656 | void check_closed_manifold(){ 657 | for( triangle_iterator it=triangles_begin(); it!=triangles_end(); ++it ){ 658 | triangle* t = *it; 659 | if( !check_mesh_pointers(t) || !tri_for_edge(t->vtx(1), t->vtx(0)) || !tri_for_edge(t->vtx(2),t->vtx(1)) || !tri_for_edge(t->vtx(0),t->vtx(2)) ){ 660 | fail_and_die( "mesh is non_manifold" ); 661 | } 662 | } 663 | } 664 | 665 | private: 666 | 667 | vertex_set m_verts; 668 | triangle_set m_tris; 669 | 670 | typedef std::pair halfedge; 671 | std::map< halfedge, triangle*> m_tri_for_edge; 672 | 673 | std::set< halfedge > m_features; 674 | }; 675 | 676 | }; 677 | 678 | #endif -------------------------------------------------------------------------------- /include/remesher.h: -------------------------------------------------------------------------------- 1 | #ifndef REMESHER_H 2 | #define REMESHER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include"mesh.h" 14 | using namespace trimesh; 15 | 16 | typedef std::pair edge; 17 | edge make_edge( vertex* a, vertex *b ){ 18 | return edge( std::min(a,b), std::max(a,b) ); 19 | } 20 | 21 | void label_features( mesh* m, double thresh ){ 22 | for( mesh::triangle_iterator it=m->triangles_begin(); it!=m->triangles_end(); ++it ){ 23 | triangle* t = *it; 24 | for( int i=0; i<3; i++ ){ 25 | vertex *a=t->vtx(i); 26 | vertex *b=t->vtx((i+1)%3); 27 | triangle* n = m->tri_for_edge(b,a); 28 | if( !n ){ 29 | a->is_feature() = true; 30 | b->is_feature() = true; 31 | m->mark_as_features( a, b ); 32 | } else { 33 | vec3d n1 = (t->vtx(1)->pos()-t->vtx(0)->pos()).cross(t->vtx(2)->pos()-t->vtx(0)->pos()); 34 | vec3d n2 = (n->vtx(1)->pos()-n->vtx(0)->pos()).cross(n->vtx(2)->pos()-n->vtx(0)->pos()); 35 | n1.normalize(); 36 | n2.normalize(); 37 | if( n1.dot(n2) < thresh ){ 38 | a->is_feature()=true; 39 | b->is_feature()=true; 40 | m->mark_as_features( a, b ); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | double min_triangle_angle( vec3d a, vec3d b, vec3d c ){ 48 | vec3d ang = triangle_angles( a, b, c ); 49 | return std::min( ang[0], std::min( ang[1], ang[2] ) ); 50 | } 51 | 52 | enum { 53 | FLIP_VALENCE, 54 | FLIP_MIN_ANGLE, 55 | }; 56 | 57 | bool edge_should_flip( mesh* m, vertex* a, vertex *b, int mode ){ 58 | triangle* T1 = m->tri_for_edge(a,b); 59 | triangle* T2 = m->tri_for_edge(b,a); 60 | if( !T1 || !T2 || m->edge_is_feature(a,b) /*|| a->is_feature() || b->is_feature()*/ ) 61 | return false; 62 | vertex* c = T1->next(b); 63 | vertex* d = T2->next(a); 64 | //if( a->is_feature() || b->is_feature() ) 65 | // return false; 66 | 67 | try { 68 | vec3d ang1 = triangle_angles( a->pos(), b->pos(), c->pos() ); 69 | vec3d ang2 = triangle_angles( a->pos(), b->pos(), d->pos() ); 70 | double theta_a = ang1[0]+ang2[0]; 71 | double theta_b = ang1[1]+ang2[1]; 72 | if( theta_a >= 0.8*M_PI || theta_b >= 0.8*M_PI ) 73 | return false; 74 | 75 | if( mode == FLIP_MIN_ANGLE || a->is_feature() || b->is_feature() ){ 76 | double min_angle1 = std::min( ang1[0], std::min(ang1[1],ang1[2]) ); 77 | double min_angle2 = std::min( ang2[0], std::min(ang2[1],ang2[2]) ); 78 | double min_angle = std::min(min_angle1,min_angle2); 79 | 80 | ang1 = triangle_angles( a->pos(), c->pos(), d->pos() ); 81 | ang2 = triangle_angles( b->pos(), d->pos(), c->pos() ); 82 | min_angle1 = std::min( ang1[0], std::min(ang1[1],ang1[2]) ); 83 | min_angle2 = std::min( ang2[0], std::min(ang2[1],ang2[2]) ); 84 | double tmp = std::min(min_angle1,min_angle2); 85 | return tmp > min_angle; 86 | } 87 | 88 | // valence flipping strategy 89 | int va = a->valence()-6; 90 | int vb = b->valence()-6; 91 | int vc = c->valence()-6; 92 | int vd = d->valence()-6; 93 | int init = va*va + vb*vb + vc*vc + vd*vd; 94 | va--; 95 | vb--; 96 | vc++; 97 | vd++; 98 | int final = va*va + vb*vb + vc*vc +vd*vd; 99 | return final < init; 100 | } catch( const char *err ){ 101 | return false; 102 | } 103 | } 104 | 105 | void check_closed_manifold( mesh* m ){ 106 | m->check_closed_manifold(); 107 | } 108 | 109 | void flip_edges( mesh* m, int mode ){ 110 | if( !m->check_mesh() ) 111 | throw "uh oh"; 112 | 113 | std::vector edges; 114 | for( mesh::triangle_iterator it=m->triangles_begin(); it!=m->triangles_end(); ++it ){ 115 | triangle *t = *it; 116 | edges.push_back( make_edge(t->vtx(0),t->vtx(1)) ); 117 | edges.push_back( make_edge(t->vtx(1),t->vtx(2)) ); 118 | edges.push_back( make_edge(t->vtx(2),t->vtx(0)) ); 119 | } 120 | for( int i=0; iflip_edge( edges[i].first, edges[i].second ); 123 | } 124 | } 125 | if( !m->check_mesh() ) 126 | throw "uh oh"; 127 | 128 | } 129 | 130 | template< typename proj > 131 | void split_edges( mesh* m, proj& cp, double minsize, double maxsize, double max_error, bool allow_features ){ 132 | if( !m->check_mesh() ) 133 | throw "uh oh"; 134 | 135 | std::vector edges; 136 | for( mesh::triangle_iterator it=m->triangles_begin(); it!=m->triangles_end(); ++it ){ 137 | triangle *t = *it; 138 | edges.push_back( make_edge(t->vtx(0),t->vtx(1)) ); 139 | edges.push_back( make_edge(t->vtx(1),t->vtx(2)) ); 140 | edges.push_back( make_edge(t->vtx(2),t->vtx(0)) ); 141 | } 142 | for( int i=0; ipos()-v0->pos()).length(); 147 | bool grade_split = false; 148 | 149 | bool adapt_split = false; 150 | 151 | bool feat_split = !m->edge_is_feature(v0, v1) && (v0->is_feature() && v1->is_feature()); 152 | 153 | if( max_error > 0.0 ){ 154 | vec3d mid = (v0->pos()+v1->pos())/2.0; 155 | vec3d n, p = cp( mid, L, n ); 156 | adapt_split = L > minsize && (p-mid).length() > max_error; 157 | } 158 | if( L > maxsize || adapt_split || grade_split || feat_split ){ 159 | if( m->edge_is_feature(edges[i].first, edges[i].second) && !allow_features ) 160 | continue; 161 | m->split_edge(edges[i].first,edges[i].second); 162 | } 163 | } 164 | 165 | if( !m->check_mesh() ) 166 | throw "uh oh"; 167 | } 168 | 169 | template< typename proj > 170 | void collapse_edges( mesh* m, proj& cp, double minsize, double maxsize, double max_error, bool allow_features ){ 171 | if( !m->check_mesh() ) 172 | throw "uh oh"; 173 | 174 | std::vector edges; 175 | for( mesh::triangle_iterator it=m->triangles_begin(); it!=m->triangles_end(); ++it ){ 176 | triangle *t = *it; 177 | edges.push_back( make_edge(t->vtx(0),t->vtx(1)) ); 178 | edges.push_back( make_edge(t->vtx(1),t->vtx(2)) ); 179 | edges.push_back( make_edge(t->vtx(2),t->vtx(0)) ); 180 | } 181 | for( int i=0; ipos()-v0->pos()).length(); 186 | 187 | if( L < minsize || v1->valence() <= 4 ){ 188 | m->collapse_edge(v0,v1,allow_features); 189 | } 190 | } 191 | 192 | if( !m->check_mesh() ) 193 | throw "uh oh"; 194 | } 195 | 196 | double smooth_d = 2.0; 197 | 198 | template< typename proj > 199 | void smooth_vertex( mesh* m, proj& prj, vertex* v, bool project ){ 200 | if( v->is_feature() ) return; 201 | 202 | vec3d pos(0,0,0); 203 | vec3d nrm(0.0,0.0,0.0); 204 | double w = 0.0; 205 | double min_angle = 180.0; 206 | for( vertex::triangle_iterator it=v->triangles_begin(); it!=v->triangles_end(); ++it ){ 207 | triangle* t = *it; 208 | 209 | min_angle = std::min( min_angle, min_triangle_angle(t->vtx(0)->pos(),t->vtx(1)->pos(),t->vtx(2)->pos()) ); 210 | 211 | vec3d tnrm = (t->vtx(1)->pos()-t->vtx(0)->pos()).cross(t->vtx(2)->pos()-t->vtx(0)->pos()); 212 | nrm += tnrm; 213 | double a = tnrm.length(); 214 | for( int j=0; j<3; j++ ){ 215 | 216 | 217 | pos += t->vtx(j)->pos(); 218 | w += 1.0; 219 | 220 | } 221 | } 222 | //pos += 0.001*w*v->pos(); 223 | //w += w*0.001; 224 | vec3d delta = pos/w - v->pos(); 225 | if( project ) 226 | delta -= nrm*nrm.dot(delta)/(nrm.dot(nrm)); 227 | 228 | vec3d oldpos = v->pos(); 229 | 230 | vec3d n; 231 | double L = 2.0*delta.length(); 232 | double alpha = 1.0; 233 | for( int i=0; i<10; i++ ){ 234 | v->pos() += delta*alpha; 235 | if( project ) 236 | v->pos() = prj( v->pos(), smooth_d, n ); 237 | 238 | 239 | bool valid = true; 240 | for( vertex::triangle_iterator it=v->triangles_begin(); it!=v->triangles_end(); ++it ){ 241 | triangle *t = *it; 242 | vertex *v0 = t->next(v); 243 | vertex *v1 = t->next(v0); 244 | vec3d oldn = (v0->pos()-oldpos).cross(v1->pos()-oldpos); 245 | vec3d newn = (v0->pos()-v->pos()).cross(v1->pos()-v->pos()); 246 | if( oldn.dot(newn) <= 0.0 ){ 247 | valid = false; 248 | break; 249 | } 250 | } 251 | if( valid ){ 252 | break; 253 | } else { 254 | v->pos() = oldpos; 255 | alpha *= 0.75; 256 | } 257 | 258 | 259 | /* 260 | double tminangle = 180.0; 261 | for( vertex::triangle_iterator it=v->triangles_begin(); it!=v->triangles_end(); ++it ){ 262 | triangle* t = *it; 263 | tminangle = std::min( tminangle, min_triangle_angle( t->vtx(0)->pos(), t->vtx(1)->pos(), t->vtx(2)->pos() ) ); 264 | } 265 | if( tminangle > min_angle || tminangle > 20.0*M_PI/180 ){ 266 | break; 267 | } else { 268 | v->pos() = oldpos; 269 | alpha *= 0.75; 270 | } 271 | */ 272 | } 273 | } 274 | 275 | template< typename proj > 276 | void smooth_vertices( mesh* m, proj& prj, bool project ){ 277 | for( mesh::vertex_iterator it=m->vertices_begin(); it!=m->vertices_end(); ++it ){ 278 | vertex *v = *it; 279 | smooth_vertex(m,prj,v,project); 280 | } 281 | } 282 | 283 | typedef std::map remesher_options; 284 | 285 | /* 286 | template< typename T > 287 | T from_str( std::string& tmp ){ 288 | std::istringstream iss(tmp); 289 | T val; 290 | iss >> val; 291 | return val; 292 | } 293 | 294 | template 295 | std::string to_str( T val ){ 296 | std::ostringstream oss; 297 | oss << val; 298 | return oss.str(); 299 | } 300 | */ 301 | 302 | 303 | class remesher { 304 | public: 305 | remesher( remesher_options opts, const std::vector& icoord, std::vector& itris ) : m_opts(opts), m_icoord(icoord), m_itris(itris) { 306 | // initialize the remesher 307 | initialize(); 308 | } 309 | 310 | void remesh(){ 311 | for( int i=0; i& coords, std::vector& tris ){ 320 | m_mesh->get_mesh( coords, tris ); 321 | } 322 | 323 | private: 324 | 325 | double target_size( vec3d pos ){ 326 | return m_target_edge_length; 327 | } 328 | 329 | bool update_mesh_guarded( std::vector& rmtri, std::vector& addtrivtx ){ 330 | // remove the triangles from the mesh, but store the 331 | // vertex indices that define them 332 | std::vector rmtrivtx; 333 | for( int i=0; ivtx(0) ); 335 | rmtrivtx.push_back( rmtri[i]->vtx(1) ); 336 | rmtrivtx.push_back( rmtri[i]->vtx(2) ); 337 | m_mesh->remove_triangle( rmtri[i] ); 338 | } 339 | 340 | // now attempt to add the triangles, one by one, 341 | // checking to see if they are successfully added 342 | triangle *t; 343 | bool failed = false; 344 | std::vector addtri; 345 | for( int i=0; iadd_triangle( addtrivtx[i+0], addtrivtx[i+1], addtrivtx[i+2] ); 347 | if( !t ){ 348 | failed=true; 349 | break; 350 | } else { 351 | addtri.push_back(t); 352 | } 353 | } 354 | if( failed ){ 355 | for( int i=0; iremove_triangle( addtri[i] ); 357 | } 358 | for( int i=0; iadd_triangle( rmtrivtx[i+0], rmtrivtx[i+1], rmtrivtx[i+2] ); 360 | } 361 | return false; 362 | } 363 | return true; 364 | } 365 | 366 | void smooth(bool proj){ 367 | for( mesh::vertex_iterator it=m_mesh->vertices_begin(); it!=m_mesh->vertices_end(); ++it ){ 368 | vertex* v = *it; 369 | if( v->is_feature() ) 370 | continue; 371 | vec3d vpos(0.0,0.0,0.0); 372 | double wgt=0.0; 373 | for( vertex::triangle_iterator tit=v->triangles_begin(); tit!=v->triangles_end(); ++tit ){ 374 | triangle* t = *tit; 375 | double w = (t->vtx(1)->pos()-t->vtx(0)->pos()).cross(t->vtx(2)->pos()-t->vtx(0)->pos()).length(); 376 | vpos += w*(t->vtx(0)->pos()+t->vtx(1)->pos()+t->vtx(2)->pos())/3.0; 377 | wgt += w; 378 | } 379 | vec3d nrm; 380 | vpos /= wgt; 381 | if( proj ) 382 | vpos = m_proj(vpos,m_closest_point_search_radius,nrm); 383 | 384 | // project vpos to the surface 385 | bool do_it=true; 386 | for( vertex::triangle_iterator tit=v->triangles_begin(); tit!=v->triangles_end(); ++tit ){ 387 | triangle* t = *tit; 388 | vertex *a = v; 389 | vertex *b = t->next(v); 390 | vertex *c = t->next(b); 391 | vec3d curnrm = (b->pos()-a->pos()).cross(c->pos()-a->pos()); 392 | vec3d newnrm = (b->pos()-vpos).cross(c->pos()-vpos); 393 | if( curnrm.dot(newnrm) > 0.0 ){ 394 | double oldang = min_triangle_angle( a->pos(), b->pos(), c->pos() ); 395 | double newang = min_triangle_angle( vpos, b->pos(), c->pos() ); 396 | if( newang < oldang ){ 397 | //do_it = false; 398 | //break; 399 | } 400 | } 401 | } 402 | if( do_it ){ 403 | v->pos() = vpos; 404 | } 405 | } 406 | } 407 | 408 | void update_edges(){ 409 | int e[][2] = { {0,1},{1,2},{2,0} }; 410 | // build a list of edges 411 | /* 412 | std::set< std::pair > edge_set; 413 | for( mesh::triangle_iterator it=m_mesh->triangles_begin(); it!=m_mesh->triangles_end(); ++it ){ 414 | triangle* tri = *it; 415 | for( int j=0; j<3; j++ ){ 416 | if( tri->vtx(e[j][0]) < tri->vtx(e[j][1]) ){ 417 | edge_set.insert( std::pair((tri->vtx(e[j][0])->pos()-tri->vtx(e[j][1])->pos()).length(),edge(tri->vtx(e[j][0]),tri->vtx(e[j][1]) ) ) ); 418 | } 419 | } 420 | } 421 | */ 422 | std::vector< edge > edge_list; 423 | //for( std::set< std::pair >::iterator it=edge_set.begin(); it!=edge_set.end(); ++it ){ 424 | // edge_list.push_back( (*it).second ); 425 | //} 426 | for( mesh::triangle_iterator it=m_mesh->triangles_begin(); it!=m_mesh->triangles_end(); ++it ){ 427 | triangle* tri = *it; 428 | for( int j=0; j<3; j++ ){ 429 | if( tri->vtx(e[j][0]) < tri->vtx(e[j][1]) ){ 430 | edge_list.push_back( edge(tri->vtx(e[j][0]),tri->vtx(e[j][1]) ) ); 431 | } 432 | } 433 | } 434 | 435 | // now loop over the edges and decide what to do 436 | for( int i=0; ihas_vertex(a) || !m_mesh->has_vertex(b) ) 443 | continue; 444 | 445 | // now get the adjacent triangles and check 446 | // that they are also still in the mesh 447 | triangle* t0 = m_mesh->tri_for_edge(a,b); 448 | triangle* t1 = m_mesh->tri_for_edge(b,a); 449 | if( !m_mesh->has_triangle(t0) || !m_mesh->has_triangle(t1) ) 450 | continue; 451 | 452 | // now get the opposing vertices for the 453 | // triangles in order to check if a flip 454 | // should be performed 455 | vertex *c = t0->other(a,b); 456 | vertex *d = t1->other(a,b); 457 | 458 | // compute the angles in the triangle to 459 | // make sure that the unfolded quad formed 460 | // by t0 and t1 is convex 461 | vec3d t0_angles = triangle_angles( a->pos(), b->pos(), c->pos() ); 462 | vec3d t1_angles = triangle_angles( a->pos(), b->pos(), d->pos() ); 463 | 464 | // check if the edge is a feature edge 465 | bool is_feature_edge = m_mesh->edge_is_feature(a,b); 466 | 467 | // check for convexity, and if so, consider 468 | // flipping the edge ab -> cd 469 | double convex_threshold = 0.9; 470 | if( t0_angles[0]+t1_angles[0] < convex_threshold*M_PI && t0_angles[1]+t1_angles[1] < convex_threshold*M_PI ){ 471 | // sum of angles at vertex a and b are less than 472 | // 180 degrees, so [a,b] is a flip candidate 473 | 474 | int va = a->valence(); 475 | int vb = b->valence(); 476 | int vc = c->valence(); 477 | int vd = d->valence(); 478 | int init_L2 = va*va + vb*vb + vc*vc + vd*vd; 479 | va--; 480 | vb--; 481 | vc++; 482 | vd++; 483 | int final_L2 = va*va + vb*vb + vc*vc + vd*vd; 484 | 485 | vec3d newangles1 = triangle_angles( a->pos(), d->pos(), c->pos() ); 486 | vec3d newangles2 = triangle_angles( b->pos(), c->pos(), d->pos() ); 487 | newangles1 = newangles1.min(newangles2); 488 | double newminangle = std::min( newangles1[0], std::min(newangles1[1],newangles1[2])); 489 | newangles1 = t0_angles.min(t1_angles); 490 | double oldminangle = std::min( newangles1[0], std::min(newangles1[2],newangles1[2])); 491 | 492 | // check if the edge should flip, default condition is that 493 | // the edge is not a feature edge 494 | bool edge_should_flip = !is_feature_edge; 495 | 496 | // minimum angle should improve as a result of an edge flip 497 | edge_should_flip &= init_L2 > final_L2 && newminangle > 5*M_PI/180.0; 498 | if( a->is_feature() || b->is_feature() ) 499 | edge_should_flip &= newminangle > oldminangle /*&& newminangle > 5*M_PI/180.0*/; 500 | 501 | if( edge_should_flip ){ 502 | std::vector addtrivtx; 503 | std::vector rmtri; 504 | // remove the two triangles 505 | rmtri.push_back( t0 ); 506 | rmtri.push_back( t1 ); 507 | 508 | // add the flipped triangles 509 | addtrivtx.push_back(a); addtrivtx.push_back(d); addtrivtx.push_back(c); 510 | addtrivtx.push_back(b); addtrivtx.push_back(c); addtrivtx.push_back(d); 511 | 512 | // try to update the mesh 513 | if( !update_mesh_guarded( rmtri, addtrivtx ) ){ 514 | } else { 515 | } 516 | 517 | // if the edge flipped then don't bother 518 | // trying the rest of the options for 519 | // the edge 520 | continue; 521 | } 522 | } 523 | 524 | // compute the squared length of the edge 525 | // and its midpoint in preparation for 526 | // deciding whether to collapse or 527 | // subdivide the edge, also retrieve the 528 | // target size from the sizing field. 529 | double L = (a->pos()-b->pos()).length_squared(); 530 | vec3d mid = (a->pos()+b->pos())/2.0; 531 | double ts = target_size(mid); 532 | 533 | double max_edge_length_sq = (2.0*ts)*(2.0*ts); 534 | if( L > max_edge_length_sq ){ 535 | // edge is longer than the maximum edge length, see if it 536 | // can be subdivided 537 | bool edge_should_subdivide = true; 538 | 539 | // do not subdivide feature edges unless explicitly permitted 540 | edge_should_subdivide &= !is_feature_edge || (is_feature_edge && m_split_features); 541 | 542 | // now try to subdivide the 543 | if( edge_should_subdivide ){ 544 | 545 | // generate the new midpoint vertex 546 | vec3d nrm; 547 | vertex *m = m_mesh->add_vertex( (a->pos()+b->pos())*0.5 ); 548 | m->pos() = m_proj( m->pos(), m_closest_point_search_radius, nrm ); 549 | 550 | // plan the mesh modifications 551 | std::vector rmtri; 552 | std::vector addtrivtx; 553 | rmtri.push_back( t0 ); 554 | rmtri.push_back( t1 ); 555 | addtrivtx.push_back( a ); addtrivtx.push_back( m ); addtrivtx.push_back( c ); 556 | addtrivtx.push_back( a ); addtrivtx.push_back( d ); addtrivtx.push_back( m ); 557 | addtrivtx.push_back( b ); addtrivtx.push_back( m ); addtrivtx.push_back( d ); 558 | addtrivtx.push_back( b ); addtrivtx.push_back( c ); addtrivtx.push_back( m ); 559 | 560 | vec3d angles = triangle_angles( a->pos(), m->pos(), c->pos() ); 561 | angles = angles.min( triangle_angles( a->pos(), d->pos(), m->pos() ) ); 562 | angles = angles.min( triangle_angles( b->pos(), m->pos(), d->pos() ) ); 563 | angles = angles.min( triangle_angles( b->pos(), c->pos(), m->pos() ) ); 564 | double min_angle = std::min(angles[0],std::min(angles[1],angles[2])); 565 | 566 | // perform the mesh update and delete 567 | // the new vertex if it fails 568 | if( /*min_angle > 1.0*M_PI/180.0 &&*/ update_mesh_guarded( rmtri, addtrivtx ) ){ 569 | if( is_feature_edge ){ 570 | m_mesh->mark_as_features( a, m ); 571 | m_mesh->mark_as_features( m, b ); 572 | } 573 | } else { 574 | m_mesh->remove_vertex(m); 575 | } 576 | 577 | continue; 578 | } 579 | } 580 | 581 | // check if the edge is too short, in which case 582 | // it should be collapsed, pending validity checks 583 | double min_edge_length_sq = (0.5*ts)*(0.5*ts); 584 | if( L < min_edge_length_sq ){ 585 | // edge is shorter than the minimum edge length, see 586 | // if it should be collapsed. 587 | bool edge_should_collapse = true; 588 | 589 | // check if edge is a feature and collapsible, or not a feature 590 | edge_should_collapse &= !is_feature_edge || (is_feature_edge && m_coarsen_features); 591 | 592 | if( !is_feature_edge && b->is_feature() ) 593 | edge_should_collapse = false; 594 | 595 | if( a->valence() <= 3 || b->valence() <= 3 ) 596 | edge_should_collapse &= true; 597 | 598 | if( a->valence() == 5 && b->valence() == 5 ) 599 | edge_should_collapse &= true; 600 | 601 | if( edge_should_collapse ){ 602 | bool would_flip_tri = false; 603 | std::vector rmtri; 604 | std::vector addtrivtx; 605 | std::vector newfeature; 606 | for( vertex::triangle_iterator it=b->triangles_begin(); it!=b->triangles_end(); ++it ){ 607 | triangle* t = *it; 608 | // if the triangle uses vertex a, it will be 609 | // deleted in the edge collapse, add it to 610 | // the removed triangles and continue 611 | if( t->has_vertex(a) ){ 612 | rmtri.push_back( t ); 613 | continue; 614 | } 615 | // triangle touches vertex b only, check the 616 | // normal before and after the collapse and 617 | // make sure the triangle would not flip, also 618 | // check that the minimum angle is larger than 619 | // 10 degrees, since we don't want to generate 620 | // degenerate triangles 621 | vertex* t0 = b; 622 | vertex* t1 = t->next(b); 623 | vertex* t2 = t->next(t1); 624 | vec3d curnrm = (t1->pos()-t0->pos()).cross(t2->pos()-t0->pos()); 625 | vec3d newnrm = (t1->pos()-a->pos()).cross(t2->pos()-a->pos()); 626 | vec3d newang = triangle_angles( a->pos(), t1->pos(), t2->pos() ); 627 | double minang = std::min(newang[0],std::min(newang[1],newang[2])); 628 | if( newnrm.dot(curnrm) <= 0.0 /*|| minang < 1.0*M_PI/180.0*/ ){ 629 | would_flip_tri = true; 630 | break; 631 | } 632 | 633 | if( m_mesh->edge_is_feature(b,t1) ){ 634 | newfeature.push_back(a); 635 | newfeature.push_back(t1); 636 | } 637 | if( m_mesh->edge_is_feature(b,t2) ){ 638 | newfeature.push_back(a); 639 | newfeature.push_back(t2); 640 | } 641 | 642 | // add the triangle to the removed list and 643 | // add the new triangle vertices to the 644 | // new triangles vertex list 645 | rmtri.push_back( t ); 646 | addtrivtx.push_back( a ); 647 | addtrivtx.push_back( t1 ); 648 | addtrivtx.push_back( t2 ); 649 | } 650 | 651 | if(!would_flip_tri){ 652 | if( update_mesh_guarded( rmtri, addtrivtx) ){ 653 | m_mesh->remove_vertex(b); 654 | for( int j=0; jmark_as_features( newfeature[j+0], newfeature[j+1] ); 656 | } 657 | } 658 | } 659 | 660 | continue; 661 | } 662 | } 663 | } 664 | } 665 | 666 | void label_features(){ 667 | double thresh = cos(m_feature_threshold); 668 | for( mesh::triangle_iterator it=m_mesh->triangles_begin(); it!=m_mesh->triangles_end(); ++it ){ 669 | triangle* t = *it; 670 | for( int i=0; i<3; i++ ){ 671 | vertex *a=t->vtx(i); 672 | vertex *b=t->vtx((i+1)%3); 673 | triangle* n = m_mesh->tri_for_edge(b,a); 674 | if( !n ){ 675 | a->is_feature() = true; 676 | b->is_feature() = true; 677 | m_mesh->mark_as_features( a, b ); 678 | } else { 679 | vec3d n1 = (t->vtx(1)->pos()-t->vtx(0)->pos()).cross(t->vtx(2)->pos()-t->vtx(0)->pos()); 680 | vec3d n2 = (n->vtx(1)->pos()-n->vtx(0)->pos()).cross(n->vtx(2)->pos()-n->vtx(0)->pos()); 681 | n1.normalize(); 682 | n2.normalize(); 683 | if( n1.dot(n2) < thresh ){ 684 | a->is_feature()=true; 685 | b->is_feature()=true; 686 | m_mesh->mark_as_features( a, b ); 687 | } 688 | } 689 | } 690 | } 691 | } 692 | 693 | void initialize(){ 694 | // handle the input options and perform checking 695 | if( m_opts["REMESHER_FEATURE_THRESHOLD"] == "" ) m_opts["REMESHER_FEATURE_THRESHOLD"] = to_str(0.5); 696 | if( m_opts["REMESHER_COARSEN_FEATURES"] == "" ) m_opts["REMESHER_COARSEN_FEATURES"] = "TRUE"; 697 | if( m_opts["REMESHER_REFINE_FEATURES"] == "" ) m_opts["REMESHER_REFINE_FEATURES"] = "TRUE"; 698 | if( m_opts["REMESHER_TARGET_EDGE_LENGTH"] == "" ) m_opts["REMESHER_TARGET_EDGE_LENGTH"] = to_str(1.0); 699 | if( m_opts["REMESHER_ITERATIONS"] == "" ) m_opts["REMESHER_ITERATIONS"] = to_str(10); 700 | 701 | m_feature_threshold = from_str(m_opts["REMESHER_FEATURE_THRESHOLD"]); 702 | m_target_edge_length = from_str(m_opts["REMESHER_TARGET_EDGE_LENGTH"]); 703 | m_num_iterations = from_str(m_opts["REMESHER_ITERATIONS"]); 704 | m_split_features = m_opts["REMESHER_REFINE_FEATURES"] == "TRUE"; 705 | m_coarsen_features = m_opts["REMESHER_COARSEN_FEATURES"] == "TRUE"; 706 | 707 | // create the mesh and store a list of vertex pointers 708 | // and positions to build the triangles and closest point 709 | // search structure 710 | m_mesh = new mesh(); 711 | std::vector vtx; 712 | std::vector vtxpos; 713 | for( int i=0; iadd_vertex( pos ) ); 717 | if( i == 0 ){ 718 | m_mesh_minim = pos; 719 | m_mesh_maxim = pos; 720 | } else { 721 | m_mesh_minim = m_mesh_minim.min( pos ); 722 | m_mesh_maxim = m_mesh_maxim.max( pos ); 723 | } 724 | } 725 | m_proj.initialize( vtxpos, m_itris ); 726 | m_closest_point_search_radius = 2.0*m_target_edge_length; 727 | 728 | // now add the mesh triangles 729 | for( int i=0; iadd_triangle( vtx[m_itris[i+0]], vtx[m_itris[i+1]], vtx[m_itris[i+2]] ); 731 | } 732 | label_features(); 733 | } 734 | 735 | remesher_options m_opts; 736 | const std::vector& m_icoord; 737 | const std::vector& m_itris; 738 | geom::closest_point_on_mesh m_proj; 739 | 740 | double m_feature_threshold; 741 | double m_target_edge_length; 742 | int m_num_iterations; 743 | bool m_split_features; 744 | bool m_coarsen_features; 745 | 746 | mesh* m_mesh; 747 | vec3d m_mesh_minim; 748 | vec3d m_mesh_maxim; 749 | double m_closest_point_search_radius; 750 | }; 751 | 752 | 753 | void remesh( remesher_options opts, const std::vector& icoord, std::vector& itris, std::vector&ocoord, std::vector& otris ){ 754 | // set up the input options, setting defaults if none are specified 755 | if( opts["REMESHER_FEATURE_THRESHOLD"] == "" ) opts["REMESHER_FEATURE_THRESHOLD"] = to_str(0.5); 756 | if( opts["REMESHER_COARSEN_FEATURES"] == "" ) opts["REMESHER_COARSEN_FEATURES"] = "TRUE"; 757 | if( opts["REMESHER_REFINE_FEATURES"] == "" ) opts["REMESHER_REFINE_FEATURES"] = "TRUE"; 758 | if( opts["REMESHER_MIN_EDGE_LENGTH"] == "" ) opts["REMESHER_MIN_EDGE_LENGTH"] = to_str(1.0); 759 | if( opts["REMESHER_MAX_EDGE_LENGTH"] == "" ) opts["REMESHER_MAX_EDGE_LENGTH"] = to_str(2.0); 760 | if( opts["REMESHER_RELATIVE_EDGE_ERROR"] == "" ) opts["REMESHER_RELATIVE_EDGE_ERROR"] = to_str(-1000.0); 761 | if( opts["REMESHER_ITERATIONS"] == "" ) opts["REMESHER_ITERATIONS"] = to_str(10); 762 | double feature_thresh = from_str(opts["REMESHER_FEATURE_THRESHOLD"]); 763 | double min_edge_length = from_str(opts["REMESHER_MIN_EDGE_LENGTH"]); 764 | double max_edge_length = from_str(opts["REMESHER_MAX_EDGE_LENGTH"]); 765 | double rel_edge_error = from_str(opts["REMESHER_RELATIVE_EDGE_ERROR"]); 766 | int iterations = from_str(opts["REMESHER_ITERATIONS"]); 767 | bool split_features = opts["REMESHER_REFINE_FEATURES"] == "TRUE"; 768 | bool coarsen_features = opts["REMESHER_COARSEN_FEATURES"] == "TRUE"; 769 | 770 | mesh m; 771 | vec3d minim( icoord[0], icoord[1], icoord[2] ); 772 | vec3d maxim( icoord[0], icoord[1], icoord[2] ); 773 | std::vector v; 774 | std::vector tv; 775 | for( int i=0; i cp; 794 | cp.initialize( tv, itris ); 795 | 796 | label_features( &m, feature_thresh); 797 | 798 | for( int i=0; i<10; i++ ){ 799 | flip_edges( &m, FLIP_MIN_ANGLE ); 800 | } 801 | 802 | for( int i=0; i