void test(std::string fname, std::size_t expected_duplicated_vertices) { std::vector<K::Point_3> points; std::vector< std::vector<std::size_t> > polygons; std::ifstream input(fname.c_str()); if (!input) { std::cerr << "Cannot open file " << fname << "\n"; exit(EXIT_FAILURE); } if (!CGAL::read_OFF(input, points, polygons)) { std::cerr << "Error parsing the OFF file " << fname << "\n"; exit(EXIT_FAILURE); } std::size_t initial_nb_points = points.size(); CGAL::orient_polygon_soup(points, polygons); assert(expected_duplicated_vertices == points.size()-initial_nb_points); Polyhedron P; CGAL::polygon_soup_to_polyhedron_3(P, points, polygons); assert(P.is_valid()); std::cout << fname << " OK\n"; }
void transformMesh( Polyhedron & poly, Transformation3 & map ) { for ( Vertex_iterator vi = poly.vertices_begin(); vi != poly.vertices_end(); ++vi ) { Point3 store = vi->point().transform( map ); vi->point() = store; } }
/// See http://paulbourke.net/geometry/platonic/ Polyhedron Polyhedron::Tetrahedron(const float3 ¢erPos, float scale, bool ccwIsFrontFacing) { const float3 vertices[4] = { float3(1,1,1), float3(-1,1,-1), float3(1,-1,-1), float3(-1,-1,1) }; const int faces[4][3] = { { 0, 1, 2 }, { 1, 3, 2 }, { 0, 2, 3 }, { 0, 3, 1 } }; scale /= 2.f; Polyhedron p; for(int i = 0; i < 4; ++i) p.v.push_back(vertices[i]*scale + centerPos); for(int i = 0; i < 4; ++i) { Face f; for(int j = 0; j < 3; ++j) f.v.push_back(faces[i][j]); p.f.push_back(f); } if (!ccwIsFrontFacing) p.FlipWindingOrder(); return p; }
int main() { // Generated points are in that vector std::vector<Point> points; // Create input polyhedron Polyhedron polyhedron; polyhedron.make_tetrahedron(Point(-1,0,0), Point(0,1,0), Point(1,0,0), Point(0,0,-1)); // Create domain Mesh_domain domain(polyhedron); using namespace CGAL::parameters; // Mesh criteria (no cell_size set) Mesh_criteria criteria(facet_angle=25, facet_size=0.15, facet_distance=0.008, cell_radius_edge_ratio=3); // Mesh generation C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria, no_perturb(), no_exude()); // Create the generator, input is the C3t3 c3t3 Random_points_in_tetrahedral_mesh_3<C3t3> g(c3t3); // Get 100 random points in cdt CGAL::cpp11::copy_n( g, 100, std::back_inserter(points)); // Check that we have really created 100 points. assert( points.size() == 100); // test if the generated points are close std::cout << points[0] << std::endl; return 0; }
/// Computes the Minkowski sum of two convex polyhedra PolyhedronPtr Polyhedron::minkowski(Polyhedron& p1, const Matrix4& T1, Polyhedron& p2, const Matrix4& T2, bool reflect_p2) { // verify that both polyhedra are convex if (!p1.is_convex() || !p2.is_convex()) throw std::runtime_error("Polyhedron::minkowski() only operates on convex polyhedra"); // we'll transform p2 to p1's frame Matrix4 T2_to_T1 = Matrix4::inverse_transform(T1) * T2; Polyhedron p2_copy = p2; p2_copy.transform(T2_to_T1); // compute the minkowski sum std::list<Vector3> points; for (unsigned i=0; i< p1.get_vertices().size(); i++) for (unsigned j=0; j< p2_copy.get_vertices().size(); j++) { // add vertex from p1 to vertex from p2 Vector3 v = p1.get_vertices()[i]; if (reflect_p2) v -= p2_copy.get_vertices()[j]; else v += p2_copy.get_vertices()[j]; points.push_back(v); } // compute the convex hull of the points return CompGeom::calc_convex_hull(points.begin(), points.end()); }
void Polyhedron::move(Polyhedron** polyhedronArray, int size) { for(int i = 0; i < size; i++) { Polyhedron* other = polyhedronArray[i]; if(this != other) { //Check to see if the polyhedrons collided using AABB bool isCollided = aabb.checkAABB(other->getAABB()); //If collided then set the velocity to its inverse if(isCollided) { this->setVelocity(-1 * (this->getVelocity().x), -1 * (this->getVelocity().y), -1 * (this->getVelocity().z)); acceleration.x *= -1; acceleration.y *= -1; } } } //Update the position using euler integration eulerIntegrationUpdatePosition(); //Update AABB aabb.centerPoint = vec3(centerX + offsetX, centerY + offsetY, centerZ + offsetZ); }
bool PolyhedronIntersectsAABB_OBB(const Polyhedron &p, const T &obj) { if (p.Contains(obj.CenterPoint())) return true; if (obj.Contains(p.Centroid())) return true; // Test for each edge of the AABB/OBB whether this polyhedron intersects it. for(int i = 0; i < obj.NumEdges(); ++i) if (p.Intersects(obj.Edge(i))) return true; // Test for each edge of this polyhedron whether the AABB/OBB intersects it. for(size_t i = 0; i < p.f.size(); ++i) { assert(!p.f[i].v.empty()); // Cannot have degenerate faces here, and for performance reasons, don't start checking for this condition in release mode! int v0 = p.f[i].v.back(); float3 l0 = p.v[v0]; for(size_t j = 0; j < p.f[i].v.size(); ++j) { int v1 = p.f[i].v[j]; float3 l1 = p.v[v1]; if (v0 < v1 && obj.Intersects(LineSegment(l0, l1))) // If v0 < v1, then this line segment is the canonical one. return true; l0 = l1; v0 = v1; } } return false; }
/// Sends this polyhedron to the specified stream using VRML void Polyhedron::to_vrml(std::ostream& out, const Polyhedron& p, Vector3 diffuse_color, bool wireframe) { const unsigned X = 0, Y = 1, Z = 2; // get the vertices and the facets const std::vector<Vector3>& vertices = p.get_vertices(); const std::vector<IndexedTri>& facets = p.get_facets(); out << "Shape {" << std::endl; out << " appearance Appearance { material Material { diffuseColor " << diffuse_color[0] << " " << diffuse_color[1] << " " << diffuse_color[2] << " } }" << std::endl; out << " geometry "; if (!wireframe) out << "IndexedFaceSet {" << std::endl; else out << "IndexedLineSet {" << std::endl; out << " coord Coordinate { point [ "; for (unsigned i=0; i< vertices.size(); i++) out << vertices[i][X] << " " << vertices[i][Y] << " " << vertices[i][Z] << ", "; out << " ] }" << std::endl; out << " coordIndex [ "; if (!wireframe) for (unsigned i=0; i< facets.size(); i++) out << facets[i].a << " " << facets[i].b << " " << facets[i].c << " -1, "; else for (unsigned i=0; i< facets.size(); i++) out << facets[i].a << " " << facets[i].b << " " << facets[i].c << " " << facets[i].a << " -1, "; out << " ] } }" << std::endl; }
void test_extraPlane() { Vector< PlaneF > planes; // Build planes for a unit cube centered at the origin. // Note that the normals must be facing inwards. planes.push_back( PlaneF( Point3F( -0.5f, 0.f, 0.f ), Point3F( 1.f, 0.f, 0.f ) ) ); planes.push_back( PlaneF( Point3F( 0.5f, 0.f, 0.f ), Point3F( -1.f, 0.f, 0.f ) ) ); planes.push_back( PlaneF( Point3F( 0.f, -0.5f, 0.f ), Point3F( 0.f, 1.f, 0.f ) ) ); planes.push_back( PlaneF( Point3F( 0.f, 0.5f, 0.f ), Point3F( 0.f, -1.f, 0.f ) ) ); planes.push_back( PlaneF( Point3F( 0.f, 0.f, -0.5f ), Point3F( 0.f, 0.f, 1.f ) ) ); planes.push_back( PlaneF( Point3F( 0.f, 0.f, 0.5f ), Point3F( 0.f, 0.f, -1.f ) ) ); // Add extra plane that doesn't contribute a new edge. planes.push_back( PlaneF( Point3F( 0.5f, 0.5f, 0.5f ), Point3F( -1.f, -1.f, -1.f ) ) ); // Turn it into a polyhedron. Polyhedron polyhedron; polyhedron.buildFromPlanes( PlaneSetF( planes.address(), planes.size() ) ); // Check if we got a cube back. TEST( polyhedron.getNumPoints() == 8 ); TEST( polyhedron.getNumPlanes() == 6 ); TEST( polyhedron.getNumEdges() == 12 ); }
Polyhedron* Polyhedron::Translate( float OffsetX, float OffsetY, int FlipX, float Rotation ) { float cosR = cos(-Rotation * (M_PI / 180.0f)); float sinR = sin(-Rotation * (M_PI / 180.0f)); Polyhedron* newPoly = new Polyhedron; for( int idx = 0; idx < Points->count; idx++ ) { Vector2* working = new Vector2( Points->ItemAt<Vector2*>(idx)->X * ( FlipX != 0 ? -1 : 1 ), Points->ItemAt<Vector2*>(idx)->Y ); if( Rotation != 0.0f ) { float vx = (cosR * working->X) - (sinR * working->Y); float vy = (sinR * working->X) + (cosR * working->Y); working->X = vx; working->Y = vy; } working->X += OffsetX; working->Y += OffsetY; newPoly->Points->AddToEnd( (void*)working ); } newPoly->Compute(); return newPoly; }
int main() { Polyhedron P; Build_triangle<HalfedgeDS> triangle; P.delegate( triangle); CGAL_assertion( P.is_triangle( P.halfedges_begin())); return 0; }
bool is_weakly_convex(Polyhedron const& p) { for (typename Polyhedron::Edge_const_iterator i = p.edges_begin(); i != p.edges_end(); ++i) { typename Polyhedron::Plane_3 p(i->opposite()->vertex()->point(), i->vertex()->point(), i->next()->vertex()->point()); if (p.has_on_positive_side(i->opposite()->next()->vertex()->point()) && CGAL::squared_distance(p, i->opposite()->next()->vertex()->point()) > 1e-8) { return false; } } // Also make sure that there is only one shell: boost::unordered_set<typename Polyhedron::Facet_const_handle, typename CGAL::Handle_hash_function> visited; // c++11 // visited.reserve(p.size_of_facets()); std::queue<typename Polyhedron::Facet_const_handle> to_explore; to_explore.push(p.facets_begin()); // One arbitrary facet visited.insert(to_explore.front()); while (!to_explore.empty()) { typename Polyhedron::Facet_const_handle f = to_explore.front(); to_explore.pop(); typename Polyhedron::Facet::Halfedge_around_facet_const_circulator he, end; end = he = f->facet_begin(); CGAL_For_all(he,end) { typename Polyhedron::Facet_const_handle o = he->opposite()->facet(); if (!visited.count(o)) { visited.insert(o); to_explore.push(o); } } }
void flip_edge( Polyhedron& P, Halfedge_handle e) { if ( e->is_border_edge()) return; Halfedge_handle h = e->next(); P.join_facet( e); P.split_facet( h, h->next()->next()); }
void initWeights( Polyhedron & poly ) { // assign the same IDs to the identical/ opposite halfedges for ( Halfedge_iterator hi = poly.halfedges_begin(); hi != poly.halfedges_end(); ++hi ) { hi->fixed() = false; } }
static void remove_edge(Polyhedron& p, typename Polyhedron::Halfedge_handle& edge) { // // FIXME: Is it possible to do this in a smarter way than a linear scan // for (csg::Polyhedron_3::Facet_iterator facet = p.facets_begin(); // facet != p.facets_end(); facet++) // { // if ( facet_is_degenerate<csg::Polyhedron_3>(facet, threshold) ) // { // //print_facet(facet); // // Find a short edge // csg::Polyhedron_3::Halfedge::Halfedge_handle shortest_edge = facet->facet_begin(); // csg::Polyhedron_3::Facet::Halfedge_around_facet_circulator current_edge = facet->facet_begin(); // double min_length = get_edge_length(current_edge); // for (int i = 0; i < 2; i++) // { // current_edge++; // if (get_edge_length(current_edge) < min_length) // { // shortest_edge = current_edge; // min_length = get_edge_length(current_edge); // } // } // Join small triangles with neighbor facets edge = p.join_facet(edge->next()); p.join_facet(edge->opposite()->prev()); // The joined facets are now quads // Join the two close vertices p.join_vertex(edge); }
void geometryUtils::subdivide(Polyhedron& P) { if (P.size_of_facets() == 0) return; // We use that new vertices/halfedges/facets are appended at the end. std::size_t nv = P.size_of_vertices(); Vertex_iterator last_v = P.vertices_end(); --last_v; // the last of the old vertices Edge_iterator last_e = P.edges_end(); --last_e; // the last of the old edges Facet_iterator last_f = P.facets_end(); --last_f; // the last of the old facets Facet_iterator f = P.facets_begin(); // create new center vertices do { geometryUtils::subdivide_create_center_vertex(P, f); } while (f++ != last_f); std::vector<Point_3> pts; // smooth the old vertices pts.reserve(nv); // get intermediate space for the new points ++last_v; // make it the past-the-end position again std::transform(P.vertices_begin(), last_v, std::back_inserter(pts), Smooth_old_vertex()); std::copy(pts.begin(), pts.end(), P.points_begin()); Edge_iterator e = P.edges_begin(); // flip the old edges ++last_e; // make it the past-the-end position again while (e != last_e) { Halfedge_handle h = e; ++e; // careful, incr. before flip since flip destroys current edge geometryUtils::subdivide_flip_edge(P, h); }; CGAL_postcondition(P.is_valid()); };
int main() { Point p(1.0, 0.0, 0.0); Point q(0.0, 1.0, 0.0); Point r(0.0, 0.0, 1.0); Point s(0.0, 0.0, 0.0); Polyhedron polyhedron; polyhedron.make_tetrahedron(p, q, r, s); // constructs the AABB tree and the internal search tree for // efficient distance queries. Tree tree( CGAL::edges(polyhedron).first, CGAL::edges(polyhedron).second, polyhedron); tree.accelerate_distance_queries(); // counts #intersections with a triangle query Triangle triangle_query(p,q,r); std::cout << tree.number_of_intersected_primitives(triangle_query) << " intersections(s) with triangle" << std::endl; // computes the closest point from a query point Point point_query(2.0, 2.0, 2.0); Point closest = tree.closest_point(point_query); std::cerr << "closest point is: " << closest << std::endl; return EXIT_SUCCESS; }
int main() { Polyhedron P; Point a(1,0,0); Point b(0,1,0); Point c(0,0,1); Point d(0,0,0); P.make_tetrahedron(a,b,c,d); // associate indices to the vertices using the "id()" field of the vertex. vertex_iterator vb, ve; int index = 0; // boost::tie assigns the first and second element of the std::pair // returned by boost::vertices to the variables vit and ve for(boost::tie(vb,ve)=vertices(P); vb!=ve; ++vb ){ vertex_descriptor vd = *vb; vd->id() = index++; } kruskal(P); return 0; }
IGL_INLINE bool igl::mesh_to_polyhedron( const Eigen::MatrixXd & V, const Eigen::MatrixXi & F, Polyhedron & poly) { typedef typename Polyhedron::HalfedgeDS HalfedgeDS; // Postcondition: hds is a valid polyhedral surface. CGAL::Polyhedron_incremental_builder_3<HalfedgeDS> B(poly.hds()); B.begin_surface(V.rows(),F.rows()); typedef typename HalfedgeDS::Vertex Vertex; typedef typename Vertex::Point Point; assert(V.cols() == 3 && "V must be #V by 3"); for(int v = 0;v<V.rows();v++) { B.add_vertex(Point(V(v,0),V(v,1),V(v,2))); } assert(F.cols() == 3 && "F must be #F by 3"); for(int f=0;f<F.rows();f++) { B.begin_facet(); for(int c = 0;c<3;c++) { B.add_vertex_to_facet(F(f,c)); } B.end_facet(); } if(B.error()) { B.rollback(); return false; } B.end_surface(); return poly.is_valid(); }
void test_write_read() { typedef CGAL::Nef_polyhedron_3< Kernel > Nef_polyhedron; typedef CGAL::Polyhedron_3< Kernel > Polyhedron; typedef typename Kernel::Point_3 Point; typename Kernel::RT n( std::string("6369051672525773")); typename Kernel::RT d( std::string("4503599627370496")); Point p(n, 0, 0, d); Point q(0, n, 0, d); Point r(0, 0, n, d); Point s(0, 0, 0, 1); std::cout << " build...\n"; Polyhedron P; P.make_tetrahedron( p, q, r, s); Nef_polyhedron nef_1( P ); std::cout << " write...\n"; std::ofstream out ("temp.nef"); out << nef_1; out.close(); std::cout << " read...\n"; std::ifstream in ("temp.nef"); Nef_polyhedron nef_2; in >> nef_2; in.close(); std::cout << " check...\n"; assert( nef_1 == nef_2); }
void Polyhedron_demo_convex_hull_plugin::on_actionConvexHull_triggered() { const Scene_interface::Item_id index = scene->mainSelectionIndex(); Scene_polyhedron_item* poly_item = qobject_cast<Scene_polyhedron_item*>(scene->item(index)); Scene_points_with_normal_item* pts_item = qobject_cast<Scene_points_with_normal_item*>(scene->item(index)); Scene_polylines_item* lines_item = qobject_cast<Scene_polylines_item*>(scene->item(index)); if(poly_item || pts_item || lines_item) { // wait cursor QApplication::setOverrideCursor(Qt::WaitCursor); QTime time; time.start(); std::cout << "Convex hull..."; // add convex hull as new polyhedron Polyhedron *pConvex_hull = new Polyhedron; if ( poly_item ){ Polyhedron* pMesh = poly_item->polyhedron(); CGAL::convex_hull_3(pMesh->points_begin(),pMesh->points_end(),*pConvex_hull); } else{ if (pts_item) CGAL::convex_hull_3(pts_item->point_set()->begin(),pts_item->point_set()->end(),*pConvex_hull); else{ std::size_t nb_points=0; for(std::list<std::vector<Kernel::Point_3> >::const_iterator it = lines_item->polylines.begin(); it != lines_item->polylines.end(); ++it) nb_points+=it->size(); std::vector<Kernel::Point_3> all_points; all_points.reserve( nb_points ); for(std::list<std::vector<Kernel::Point_3> >::const_iterator it = lines_item->polylines.begin(); it != lines_item->polylines.end(); ++it) std::copy(it->begin(), it->end(),std::back_inserter( all_points ) ); CGAL::convex_hull_3(all_points.begin(),all_points.end(),*pConvex_hull); } } std::cout << "ok (" << time.elapsed() << " ms)" << std::endl; Scene_polyhedron_item* new_item = new Scene_polyhedron_item(pConvex_hull); new_item->setName(tr("%1 (convex hull)").arg(scene->item(index)->name())); new_item->setColor(Qt::magenta); new_item->setRenderingMode(FlatPlusEdges); scene->addItem(new_item); // default cursor QApplication::restoreOverrideCursor(); } }
/// See http://paulbourke.net/geometry/platonic/ Polyhedron Polyhedron::Dodecahedron(const float3 ¢erPos, float scale, bool ccwIsFrontFacing) { float phi = (1.f + Sqrt(5.f)) / 2.f; float b = 1.f / phi; float c = 2.f - phi; const float3 vertices[20] = { float3( c, 0, 1), float3(-c, 0, 1), float3(-b, b, b), float3( 0, 1, c), float3( b, b, b), float3( b, -b, b), float3( 0, -1, c), float3(-b, -b, b), float3( 0, -1, -c), float3( b, -b, -b), float3(-c, 0, -1), float3( c, 0, -1), float3(-b, -b, -b), float3( b, b, -b), float3( 0, 1, -c), float3(-b, b, -b), float3( 1, c, 0), float3(-1, c, 0), float3(-1, -c, 0), float3( 1, -c, 0) }; const int faces[12][5] = { { 0, 1, 2, 3, 4 }, { 1, 0, 5, 6, 7 }, { 11, 10, 12, 8, 9 }, { 10, 11, 13, 14, 15 }, { 13, 16, 4, 3, 14 }, // Note: The winding order of this face was flipped from PBourke's original representation. { 2, 17, 15, 14, 3 }, // Winding order flipped. { 12, 18, 7, 6, 8 }, // Winding order flipped. { 5, 19, 9, 8, 6 }, // Winding order flipped. { 16, 19, 5, 0, 4 }, { 19, 16, 13, 11, 9 }, { 17, 18, 12, 10, 15 }, { 18, 17, 2, 1, 7 } }; scale /= 2.f; Polyhedron p; for(int i = 0; i < 20; ++i) p.v.push_back(vertices[i]*scale + centerPos); for(int i = 0; i < 12; ++i) { Face f; for(int j = 0; j < 5; ++j) f.v.push_back(faces[i][j]); p.f.push_back(f); } if (!ccwIsFrontFacing) p.FlipWindingOrder(); return p; }
// Add semantics to Interior room polyhedra void set_semantic_InteriorLoD4(Polyhedron& polyhe) { std::transform( polyhe.facets_begin(), polyhe.facets_end(),polyhe.planes_begin(),Plane_Newel_equation()); for (Polyhedron::Facet_iterator fIt = polyhe.facets_begin(); fIt != polyhe.facets_end(); ++fIt) { // Iterate over faces Kernel::FT z = fIt->plane().orthogonal_vector().z(); if (z <= -HORIZONTAL_ANGLE_RANGE) fIt->semanticBLA = "FloorSurface"; else if (z >= HORIZONTAL_ANGLE_RANGE) fIt->semanticBLA = "CeilingSurface"; else fIt->semanticBLA = "InteriorWallSurface"; } }
void test_impl(Tree& tree, Polyhedron& p, const double duration) { tree.accelerate_distance_queries(p.points_begin(),p.points_end()); typedef Tree_vs_naive<Tree, Polyhedron, K, Type> Tester; Tester tester(tree, p); tester.test_all_distance_methods(duration); }
/** * Set the label on all edges to be CHANNEL, RIDGE, or TRANSVERSE. */ void label_all_edges(Polyhedron& p) { for (Halfedge_iterator i = p.halfedges_begin(); i != p.halfedges_end(); ++i) { // type is not initialized by the constructor, so we initialize it here. i->type = NO_TYPE; i->type = edge_type(i); } }
bool Sphere::Contains(const Polyhedron &polyhedron) const { assume(polyhedron.IsClosed()); for(int i = 0; i < polyhedron.NumVertices(); ++i) if (!Contains(polyhedron.Vertex(i))) return false; return true; }
void PolyController::init(GLuint program) { // Init polyhedrons for(int i = 0; i < sizeOfPolyArray; i++) { Polyhedron* polyhedron = polyArray[i]; polyhedron->init(program); } }
Polyhedron generate_polyhedron( const MatrixFr& vertices, const MatrixIr& faces) { Polyhedron P; PolyhedronBuilder<HalfedgeDS> triangle(vertices, faces); P.delegate(triangle); assert(vertices.rows() == P.size_of_vertices()); assert(faces.rows() == P.size_of_facets()); return P; }
void PolyController::display() { // Render polyhedrons for(int i = 0; i < sizeOfPolyArray; i++) { Polyhedron* polyhedron = polyArray[i]; polyhedron->display(); } }
/// See http://paulbourke.net/geometry/platonic/ Polyhedron Polyhedron::Icosahedron(const float3 ¢erPos, float scale, bool ccwIsFrontFacing) { float a = 0.5f; float phi = (1.f + Sqrt(5.f)) / 2.f; float b = 1.f / (2.f * phi); const float3 vertices[12] = { float3( 0, b, -a), float3( b, a, 0), float3(-b, a, 0), float3( 0, b, a), float3( 0, -b, a), float3(-a, 0, b), float3( a, 0, b), float3( 0, -b, -a), float3(-a, 0, -b), float3(-b, -a, 0), float3( b, -a, 0), float3( a, 0, -b) }; const int faces[20][3] = { { 0, 1, 2 }, { 3, 2, 1 }, { 3, 4, 5 }, { 3, 6, 4 }, { 0, 7, 11 }, { 0, 8, 7 }, { 4, 10, 9 }, { 7, 9, 10 }, { 2, 5, 8 }, { 9, 8, 5 }, { 1, 11, 6 }, { 10, 6, 11 }, { 3, 5, 2 }, { 3, 1, 6 }, { 0, 2, 8 }, { 0, 11, 1 }, { 7, 8, 9 }, { 7, 10, 11 }, { 4, 9, 5 }, { 4, 6, 10 } }; Polyhedron p; for(int i = 0; i < 12; ++i) p.v.push_back(vertices[i]*scale + centerPos); for(int i = 0; i < 20; ++i) { Face f; for(int j = 0; j < 3; ++j) f.v.push_back(faces[i][j]); p.f.push_back(f); } if (!ccwIsFrontFacing) p.FlipWindingOrder(); return p; }