////////////////////////////////////////////////////////////////////// // LibFile: triangulation.scad // Functions to triangulate polyhedron faces. // To use, add the following lines to the beginning of your file: // ``` // include // include // ``` ////////////////////////////////////////////////////////////////////// // Section: Functions // Function: face_normal() // Description: // Given an array of vertices (`points`), and a list of indexes into the // vertex array (`face`), returns the normal vector of the face. // Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function face_normal(points, face) = let(count=len(face)) unit( sum( [ for(i=[0:1:count-1]) cross( points[face[(i+1)%count]]-points[face[0]], points[face[(i+2)%count]]-points[face[(i+1)%count]] ) ] ) ) ; // Function: find_convex_vertex() // Description: // Returns the index of a convex point on the given face. // Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. // facenorm = The normal vector of the face. function find_convex_vertex(points, face, facenorm, i=0) = let(count=len(face), p0=points[face[i]], p1=points[face[(i+1)%count]], p2=points[face[(i+2)%count]] ) (len(face)>i)? ( (cross(p1-p0, p2-p1)*facenorm>0)? (i+1)%count : find_convex_vertex(points, face, facenorm, i+1) ) : //This should never happen since there is at least 1 convex vertex. undef ; // Function: point_in_ear() // Description: Determine if a point is in a clipable convex ear. // Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function point_in_ear(points, face, tests, i=0) = (iprev[0])? [test, i] : prev : [_check_point_in_ear(points[face[i]], tests), i] ; // Internal non-exposed function. function _check_point_in_ear(point, tests) = let( result=[ (point*tests[0][0])-tests[0][1], (point*tests[1][0])-tests[1][1], (point*tests[2][0])-tests[2][1] ] ) (result[0]>0 && result[1]>0 && result[2]>0)? result[0] : -1 ; // Function: normalize_vertex_perimeter() // Description: Removes the last item in an array if it is the same as the first item. // Arguments: // v = The array to normalize. function normalize_vertex_perimeter(v) = let(lv = len(v)) (lv < 2)? v : (v[lv-1] != v[0])? v : [for (i=[0:1:lv-2]) v[i]] ; // Function: is_only_noncolinear_vertex() // Description: // Given a face in a polyhedron, and a vertex in that face, returns true // if that vertex is the only non-colinear vertex in the face. // Arguments: // points = Array of vertices for the polyhedron. // facelist = The face, given as a list of indices into the vertex array `points`. // vertex = The index into `facelist`, of the vertex to test. function is_only_noncolinear_vertex(points, facelist, vertex) = let( face=select(facelist, vertex+1, vertex-1), count=len(face) ) 0==sum( [ for(i=[0:1:count-1]) norm( cross( points[face[(i+1)%count]]-points[face[0]], points[face[(i+2)%count]]-points[face[(i+1)%count]] ) ) ] ) ; // Function: triangulate_face() // Description: // Given a face in a polyhedron, subdivides the face into triangular faces. // Returns an array of faces, where each face is a list of three vertex indices. // Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function triangulate_face(points, face) = let( face = deduplicate_indexed(points,face), count = len(face) ) (count < 3)? [] : (count == 3)? [face] : let( facenorm=face_normal(points, face), cv=find_convex_vertex(points, face, facenorm) ) assert(!is_undef(cv), "Cannot triangulate self-crossing face perimeters.") let( pv=(count+cv-1)%count, nv=(cv+1)%count, p0=points[face[pv]], p1=points[face[cv]], p2=points[face[nv]], tests=[ [cross(facenorm, p0-p2), cross(facenorm, p0-p2)*p0], [cross(facenorm, p1-p0), cross(facenorm, p1-p0)*p1], [cross(facenorm, p2-p1), cross(facenorm, p2-p1)*p2] ], ear_test=point_in_ear(points, face, tests), clipable_ear=(ear_test[0]<0), diagonal_point=ear_test[1] ) (clipable_ear)? // There is no point inside the ear. is_only_noncolinear_vertex(points, face, cv)? // In the point&line degeneracy clip to somewhere in the middle of the line. flatten([ triangulate_face(points, select(face, cv, (cv+2)%count)), triangulate_face(points, select(face, (cv+2)%count, cv)) ]) : // Otherwise the ear is safe to clip. flatten([ [select(face, pv, nv)], triangulate_face(points, select(face, nv, pv)) ]) : // If there is a point inside the ear, make a diagonal and clip along that. flatten([ triangulate_face(points, select(face, cv, diagonal_point)), triangulate_face(points, select(face, diagonal_point, cv)) ]); // Function: triangulate_faces() // Description: // Subdivides all faces for the given polyhedron that have more than three vertices. // Returns an array of faces where each face is a list of three vertex array indices. // Arguments: // points = Array of vertices for the polyhedron. // faces = Array of faces for the polyhedron. Each face is a list of 3 or more indices into the `points` array. function triangulate_faces(points, faces) = [ for (face=faces) each len(face)==3? [face] : triangulate_face(points, normalize_vertex_perimeter(face)) ]; // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap