vector<ofVec3f> ofApp::offsetCell(list<int> & crv, float amt) { float scaling = 10; ClipperOffset co; Path P; Paths offsetP; float offset = amt; for (auto index : crv) { ofVec3f v = linesMesh.getVertex(index); P.push_back(IntPoint(v.x*scaling, v.y*scaling)); } co.AddPath(P, jtRound, etClosedPolygon); co.Execute(offsetP, -offset*scaling); vector<ofVec3f> offsetPts; if (offsetP.size() > 0) { //visual offset for etching CleanPolygons(offsetP); if (doEtchOffset) { co.Clear(); co.AddPaths(offsetP, jtRound, etClosedPolygon); co.Execute(offsetP, -etchOffset*scaling); } Path & oP = offsetP[0]; for (int i = 0; i < oP.size(); i++) { ofVec3f pt3D(oP[i].X / scaling, oP[i].Y / scaling); offsetPts.push_back(pt3D); } } return offsetPts; }
Vector<Vector<Point2> > Geometry::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) { using namespace ClipperLib; JoinType jt = jtSquare; switch (p_join_type) { case JOIN_SQUARE: jt = jtSquare; break; case JOIN_ROUND: jt = jtRound; break; case JOIN_MITER: jt = jtMiter; break; } EndType et = etClosedPolygon; switch (p_end_type) { case END_POLYGON: et = etClosedPolygon; break; case END_JOINED: et = etClosedLine; break; case END_BUTT: et = etOpenButt; break; case END_SQUARE: et = etOpenSquare; break; case END_ROUND: et = etOpenRound; break; } ClipperOffset co; Path path; // Need to scale points (Clipper's requirement for robust computation) for (int i = 0; i != p_polypath.size(); ++i) { path << IntPoint(p_polypath[i].x * SCALE_FACTOR, p_polypath[i].y * SCALE_FACTOR); } co.AddPath(path, jt, et); Paths paths; co.Execute(paths, p_delta * SCALE_FACTOR); // inflate/deflate // Have to scale points down now Vector<Vector<Point2> > polypaths; for (Paths::size_type i = 0; i < paths.size(); ++i) { Vector<Vector2> polypath; const Path &scaled_path = paths[i]; for (Paths::size_type j = 0; j < scaled_path.size(); ++j) { polypath.push_back(Point2( static_cast<real_t>(scaled_path[j].X) / SCALE_FACTOR, static_cast<real_t>(scaled_path[j].Y) / SCALE_FACTOR)); } polypaths.push_back(polypath); } return polypaths; }
void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount ) { ClipperOffset c; BOOST_FOREACH( const POLYGON& poly, m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), jtRound, etClosedPolygon ); } PolyTree solution; c.ArcTolerance = fabs( (double) aFactor ) / M_PI / aCircleSegmentsCount; c.Execute( solution, aFactor ); importTree( &solution ); }
void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount ) { // A static table to avoid repetitive calculations of the coefficient // 1.0 - cos( M_PI/aCircleSegmentsCount) // aCircleSegmentsCount is most of time <= 64 and usually 8, 12, 16, 32 #define SEG_CNT_MAX 64 static double arc_tolerance_factor[SEG_CNT_MAX+1]; ClipperOffset c; for( const POLYGON& poly : m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), jtRound, etClosedPolygon ); } PolyTree solution; // Calculate the arc tolerance (arc error) from the seg count by circle. // the seg count is nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aFactor)) // see: // www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm if( aCircleSegmentsCount < 6 ) // avoid incorrect aCircleSegmentsCount values aCircleSegmentsCount = 6; double coeff; if( aCircleSegmentsCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegmentsCount] == 0 ) { coeff = 1.0 - cos( M_PI/aCircleSegmentsCount); if( aCircleSegmentsCount <= SEG_CNT_MAX ) arc_tolerance_factor[aCircleSegmentsCount] = coeff; } else coeff = arc_tolerance_factor[aCircleSegmentsCount]; c.ArcTolerance = std::abs( aFactor ) * coeff; c.Execute( solution, aFactor ); importTree( &solution ); }
vector<ofVec3f> ofApp::offsetCell(list<int> & crv, AnisoPoint2f & pt) { float scaling = 1000; ClipperOffset co; co.ArcTolerance = 1; Path P; Paths offsetP; float offset = ofClamp(offsetPercent / sqrt(pt.jacobian->determinant()), minThick*0.5, maxThick*0.5); if (doSmooth) { for (auto index : crv) { ofVec3f v = linesMesh.getVertex(index); IntPoint iPt(v.x * scaling, v.y * scaling); P.push_back(iPt); } co.AddPath(P, jtRound, etClosedPolygon); co.Execute(offsetP, -offset*scaling); if(offsetP.size() == 0) return vector<ofVec3f>(); P.clear(); ofVec2f center; for (auto & v : offsetP[0]) { //ofVec3f v = linesMesh.getVertex(index); Vector2f p(v.X, v.Y); p = (*pt.jacobian)*p; IntPoint iPt(p.coeff(0), p.coeff(1)); P.push_back(iPt); center += ofVec2f(iPt.X, iPt.Y); } center /= crv.size(); CleanPolygon(P); co.Clear(); co.AddPath(P, jtRound, etClosedPolygon); float radius = 9e20; //get exact radius from straight skeleton Polygon_2 poly; for (auto & pt : P) { poly.push_back(Point_2(pt.X, pt.Y)); } boost::shared_ptr<Ss> iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end()); radius = 0; for (Ss::Vertex_handle vh = iss->vertices_begin(); vh != iss->vertices_end(); vh++) { if (!vh->has_infinite_time()) { radius = max(radius, (float)vh->time()); } } //estimate radius //for (auto & iPt : P) { // radius = min(radius, (iPt.X - center.x)*(iPt.X - center.x) + (iPt.Y - center.y)*(iPt.Y - center.y)); //} //radius = sqrt(radius); //co.Execute(offsetP, -radius); //int tries = 0; //while (offsetP.size() == 0 && tries < 50) { // radius *= .95; // co.Execute(offsetP, -radius); // tries++; //} //cout << tries << endl; radius *= filletPercent; co.Execute(offsetP, -radius); //radius = min(radius,(radius - offset*scaling)*filletPercent + offset*scaling); //co.Execute(offsetP, -offset*scaling); vector<ofVec3f> offsetPts; if (offsetP.size() > 0) { //visual offset for etching co.Clear(); CleanPolygons(offsetP); co.AddPaths(offsetP, jtRound, etClosedPolygon); co.Execute(offsetP, radius); CleanPolygons(offsetP); Path longestP; int pLen = 0; for (auto & oP : offsetP) { if (oP.size() > pLen) { pLen = oP.size(); longestP = oP; } } Matrix2f inverse = pt.jacobian->inverse(); if (doEtchOffset) { co.Clear(); for (auto & lPt : longestP) { Vector2f anisoPt(lPt.X, lPt.Y); anisoPt = inverse*anisoPt; lPt.X = anisoPt[0]; lPt.Y = anisoPt[1]; } co.AddPath(longestP, jtRound, etClosedPolygon); co.Execute(offsetP, etchOffset*scaling); longestP = offsetP[0]; } for (int i = 0; i < longestP.size(); i++) { //ofVec3f pt3D(oP[i].X / scaling, oP[i].Y/ scaling); Vector2f anisoPt(longestP[i].X / scaling, longestP[i].Y / scaling); if(!doEtchOffset)anisoPt = inverse*anisoPt; offsetPts.push_back(ofVec3f(anisoPt.coeff(0), anisoPt.coeff(1))); } } return offsetPts; } else { for (auto index : crv) { ofVec3f v = linesMesh.getVertex(index); IntPoint iPt(v.x * scaling, v.y * scaling); P.push_back(iPt); } CleanPolygon(P); //Paths simplerP; //SimplifyPolygon(P, simplerP); //CleanPolygons(simplerP); //P = simplerP[0]; //CleanPolygon(P); //Polygon_2 poly; //for (auto & pt : P) { // poly.push_back(Point_2(pt.X, pt.Y)); //} //boost::shared_ptr<Ss> iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end()); //float radius = 0; //for (Ss::Vertex_handle vh = iss->vertices_begin(); vh != iss->vertices_end(); vh++) { // if (!vh->has_infinite_time()) { // radius = max(radius, (float)vh->time()); // } //} //Paths simplerP; //SimplifyPolygon(P, simplerP); //co.AddPaths(simplerP, jtRound, etClosedPolygon); //radius = ofClamp(radius*offsetPercent, minThick*0.5*scaling, maxThick*0.5*scaling); co.AddPath(P, jtRound, etClosedPolygon); co.Execute(offsetP, -offset*scaling); vector<ofVec3f> offsetPts; if (offsetP.size() > 0) { CleanPolygons(offsetP); if (doEtchOffset) { co.Clear(); co.AddPaths(offsetP, jtRound, etClosedPolygon); co.Execute(offsetP, etchOffset*scaling); } else { co.Clear(); co.AddPaths(offsetP, jtRound, etClosedPolygon); co.Execute(offsetP, 1); } Path longestP; int pLen = 0; for (auto & oP : offsetP) { if (oP.size() > pLen) { pLen = oP.size(); longestP = oP; } } Path & oP = longestP; for (int i = 0; i < oP.size(); i++) { ofVec3f pt3D(oP[i].X / scaling, oP[i].Y/ scaling); offsetPts.push_back(pt3D); } } return offsetPts; } }
float Polygon3D::computeInset(std::vector<float> &offsetDistances, Loop3D &pgonInset, bool computeArea) { Loop3D cleanPgon; double tol = 0.01f; cleanPgon = this->contour; int prev, next; int cSz = cleanPgon.size(); if(cSz < 3){ return 0.0f; } if(reorientFace(cleanPgon)){ std::reverse(offsetDistances.begin(), offsetDistances.end() - 1); } //if offsets are zero, add a small epsilon just to avoid division by zero for(size_t i=0; i<offsetDistances.size(); ++i){ if(fabs(offsetDistances[i]) < tol){ offsetDistances[i] = tol; } } //pgonInset.resize(cSz); QVector3D intPt; /* // GEN CODE--> It leads to self-intersection very often with non-convex polygons for(int cur=0; cur<cSz; ++cur){ //Some geometry and trigonometry //point p1 is the point with index cur prev = (cur-1+cSz)%cSz; //point p0 next = (cur+1)%cSz; //point p2 if (Util::diffAngle(cleanPgon[prev] - cleanPgon[cur], cleanPgon[next] - cleanPgon[cur]) < 0.1f) { // For deanend edge QVector3D vec = cleanPgon[cur] - cleanPgon[prev]; QVector3D vec2(-vec.y(), vec.x(), 0); float angle = atan2f(vec2.y(), vec2.x()); for (int i = 0; i <= 10; ++i) { float a = angle - (float)i * M_PI / 10.0f; intPt = QVector3D(cleanPgon[cur].x() + cosf(a) * offsetDistances[cur], cleanPgon[cur].y() + sinf(a) * offsetDistances[cur], cleanPgon[cur].z()); pgonInset.push_back(intPt); } } else { Util::getIrregularBisector(cleanPgon[prev], cleanPgon[cur], cleanPgon[next], offsetDistances[prev], offsetDistances[cur], intPt); // For acute angle if (pgonInset.size() >= 2) { if (Util::diffAngle(pgonInset[pgonInset.size() - 2] - pgonInset[pgonInset.size() - 1], intPt - pgonInset[pgonInset.size() - 1]) < 0.1f) { pgonInset.erase(pgonInset.begin() + pgonInset.size() - 1); } } pgonInset.push_back(intPt); } }*/ // Old Code pgonInset.resize(cSz); for(int cur=0; cur<cSz; ++cur){ //Some geometry and trigonometry //point p1 is the point with index cur prev = (cur-1+cSz)%cSz; //point p0 next = (cur+1)%cSz; //point p2 getIrregularBisector(cleanPgon[prev], cleanPgon[cur], cleanPgon[next], offsetDistances[prev], offsetDistances[cur], intPt); pgonInset[cur] = intPt; } //temp //Compute inset area if(computeArea){ boost::geometry::ring_type<Polygon3D>::type bg_contour; boost::geometry::ring_type<Polygon3D>::type bg_contour_inset; float contArea; float contInsetArea; if(pgonInset.size()>0){ boost::geometry::assign(bg_contour_inset, pgonInset); boost::geometry::correct(bg_contour_inset); if(boost::geometry::intersects(bg_contour_inset)){ //printf("INSET: intersects\n"); pgonInset.clear(); //return 0.0f; } else { boost::geometry::assign(bg_contour, cleanPgon); boost::geometry::correct(bg_contour); //if inset is not within polygon if( !is2DRingWithin2DRing(bg_contour_inset, bg_contour) ){ pgonInset.clear(); //printf("INSET: ringWithRing\n"); //return 0.0f; } else { contArea = fabs(boost::geometry::area(bg_contour)); contInsetArea = fabs(boost::geometry::area(bg_contour_inset)); if(contInsetArea < contArea){// OK EXIT //return boost::geometry::area(bg_contour_inset); return contInsetArea; } else { //printf("INSET: contInsetArea < contArea\n"); pgonInset.clear(); //return 0.0f; } } } } else { //printf("INSET: sides <0\n"); pgonInset.clear(); //return 0.0f; } // IT FAILED TRY SECOND METHOD { Path subj; Paths solution; for(int cur=0; cur<cSz; ++cur){ subj << IntPoint(cleanPgon[cur].x()*1000,cleanPgon[cur].y()*1000); } /*subj << ClipperLib::IntPoint(348,257) << IntPoint(364,148) << IntPoint(362,148) << IntPoint(326,241) << IntPoint(295,219) << IntPoint(258,88) << IntPoint(440,129) << IntPoint(370,196) << IntPoint(372,275);*/ ClipperOffset co; co.AddPath(subj, jtSquare, etClosedPolygon); co.Execute(solution, -1000*7.5); pgonInset.resize(solution[0].size()); for(int sN=0;sN<solution[0].size();sN++){ pgonInset[sN]=QVector3D(solution[0][sN].X/1000.0f,solution[0][sN].Y/1000.0f,0); } //printf("Solutions %d\n",solution.size()); return Area(solution[0]); } } return 0.0f; }