/* $Id$ * * Copyright (c) 2010 Anders Wallin (anders.e.e.wallin "at" gmail.com). * * This file is part of OpenCAMlib * (see https://github.com/aewallin/opencamlib). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include "ellipse.hpp" #include "numeric.hpp" #include "brent_zero.hpp" #include "fiber.hpp" namespace ocl { //******** Ellipse ********************** */ Ellipse::Ellipse(Point& centerin, double ain, double bin, double offsetin) { center = centerin; a = ain; b = bin; assert( b > 0.0 ); eccen = a/b; offset = offsetin; target = Point(0,0,0); } AlignedEllipse::AlignedEllipse(Point& centerin, double ain, double bin, double offsetin, Point& major, Point& minor) { center = centerin; a = ain; // major axis of ellipse b = bin; // minor axis of ellipse assert( b > 0.0 ); eccen = a/b; offset = offsetin; major_dir=major; minor_dir=minor; } Point Ellipse::ePoint1() const { return this->ePoint(EllipsePosition1); } Point Ellipse::ePoint2() const { return this->ePoint(EllipsePosition2); } Point Ellipse::oePoint1() const { return this->oePoint(EllipsePosition1); } Point Ellipse::oePoint2() const { return this->oePoint(EllipsePosition2); } Point Ellipse::ePoint(const EllipsePosition& pos) const { // (s, t) where: s^2 + t^2 = 1 // a and b are the orthogonal axes of the ellipse // point of ellipse is: center + a s + b t s=cos(theta) t=sin(theta) // tangent at point is: -a t + b s // normal at point is: b s + a t // point on offset-ellipse: point on ellipse + offset*normal Point p(center); p.x += a*pos.s; // a is in X-direction p.y += b*pos.t; // b is in Y-direction return p; } Point AlignedEllipse::ePoint(const EllipsePosition& pos) const { Point p = center + a*pos.s*major_dir + b*pos.t*minor_dir; // point of ellipse is: center + a s + b t return p; } Point Ellipse::oePoint(const EllipsePosition& pos) const { return ePoint(pos) + offset * normal(pos); // offset-point = ellipse-point + offset*normal } Point AlignedEllipse::oePoint(const EllipsePosition& pos) const { return ePoint(pos) + offset * normal(pos); // offset-point = ellipse-point + offset*normal } Point Ellipse::normal(const EllipsePosition& pos) const { assert( pos.isValid() ); Point n = Point( b*pos.s, a*pos.t, 0); n.normalize(); return n; } Point AlignedEllipse::normal(const EllipsePosition& pos) const { // normal at point is: b s + a t Point n = pos.s*b*major_dir + pos.t*a*minor_dir; n.normalize(); return n; } #define OE_ERROR_TOLERANCE 1e-10 /// \todo magic number tolerance // #define DEBUG_SOLVER // // given a known EllipsePosition1, look for the other symmetric // solution EllipsePosition2 bool Ellipse::find_EllipsePosition2() { // a horrible horrible function... :( assert( EllipsePosition1.isValid() ); double err1 = fabs(this->error(this->EllipsePosition1)); this->EllipsePosition2.s = this->EllipsePosition1.s; // plus this->EllipsePosition2.t = -this->EllipsePosition1.t; // minus if ( fabs(this->error(this->EllipsePosition2)) < err1+OE_ERROR_TOLERANCE) { if ( (fabs(this->EllipsePosition2.s - this->EllipsePosition1.s) > 1E-8) || (fabs(this->EllipsePosition2.t - this->EllipsePosition1.t) > 1E-8) ) { #ifdef DEBUG_SOLVER std::cout << "2nd: (s, t)= " << this->EllipsePosition2 << " oePoint()= " << this->oePoint(this->EllipsePosition2) << " e=" << this->error(this->EllipsePosition2) << "\n"; #endif return true; } } this->EllipsePosition2.s = -this->EllipsePosition1.s; this->EllipsePosition2.t = this->EllipsePosition1.t; if ( fabs(this->error(this->EllipsePosition2)) < err1+OE_ERROR_TOLERANCE) { if ( (fabs(this->EllipsePosition2.s - this->EllipsePosition1.s) > 1E-8) || (fabs(this->EllipsePosition2.t - this->EllipsePosition1.t) > 1E-8) ) { #ifdef DEBUG_SOLVER std::cout << "2nd: (s, t)= " << this->EllipsePosition2 << " oePoint()= " << this->oePoint(this->EllipsePosition2) << " e=" << this->error(this->EllipsePosition2) << "\n"; #endif return true; } } this->EllipsePosition2.s = -this->EllipsePosition1.s; this->EllipsePosition2.t = -this->EllipsePosition1.t; if ( fabs(this->error(this->EllipsePosition2)) < err1+OE_ERROR_TOLERANCE) { if ( (fabs(this->EllipsePosition2.s - this->EllipsePosition1.s) > 1E-8) || (fabs(this->EllipsePosition2.t - this->EllipsePosition1.t) > 1E-8) ) { #ifdef DEBUG_SOLVER std::cout << "2nd: (s, t)= " << this->EllipsePosition2 << " oePoint()= " << this->oePoint(this->EllipsePosition2) << " e=" << this->error(this->EllipsePosition2) << "\n"; #endif return true; } } // last desperate attempt is identical solutions this->EllipsePosition2.s = this->EllipsePosition1.s; this->EllipsePosition2.t = this->EllipsePosition1.t; if ( fabs(this->error(this->EllipsePosition2)) < err1+OE_ERROR_TOLERANCE) { // DON'T require solutions to differ // if ( (fabs(this->EllipsePosition2.s - this->EllipsePosition1.s) > 1E-8) || (fabs(this->EllipsePosition2.t - this->EllipsePosition1.t) > 1E-8) ) { #ifdef DEBUG_SOLVER std::cout << "2nd: (s, t)= " << this->EllipsePosition2 << " oePoint()= " << this->oePoint(this->EllipsePosition2) << " e=" << this->error(this->EllipsePosition2) << "\n"; #endif //std::cout << " find_EllipsePosition2() desperate-mode\n"; return true; //} } std::cout << "Ellipse::find_EllipsePosition2 cannot find EllipsePosition2! \n"; std::cout << "ellipse= "<< *this << "\n"; print_solutions(); assert(0); // serious error if we get here! return false; } /// offfset-ellipse solver using Brent's method /// find the EllipsePosition that makes the offset-ellipse point be at p /// this is a zero of Ellipse::error() /// returns number of iterations /// /// called (only?) by BullCutter::singleEdgeDropCanonical() int Ellipse::solver_brent() { int iters = 1; EllipsePosition apos, bpos; // Brent's method requires bracketing the root in [apos.diangle, bpos.diangle] apos.setDiangle( 0.0 ); assert( apos.isValid() ); bpos.setDiangle( 3.0 ); assert( bpos.isValid() ); if ( fabs( error(apos) ) < OE_ERROR_TOLERANCE ) { // if we are lucky apos is the solution EllipsePosition1 = apos; // and we do not need to search further. find_EllipsePosition2(); return iters; } else if ( fabs( error(bpos) ) < OE_ERROR_TOLERANCE ) { // or bpos might be the solution? EllipsePosition1 = bpos; find_EllipsePosition2(); return iters; } // neither apos nor bpos is the solution // but root is now bracketed, so we can use brent_zero assert( error(apos) * error(bpos) < 0.0 ); // this looks for the diangle that makes the offset-ellipse point y-coordinate zero double dia_sln = brent_zero( apos.diangle, bpos.diangle , 3E-16, OE_ERROR_TOLERANCE, this ); // brent_zero.hpp EllipsePosition1.setDiangle( dia_sln ); assert( EllipsePosition1.isValid() ); // because we only work with the y-coordinate of the offset-ellipse-point, there are two symmetric solutions find_EllipsePosition2(); return iters; } // used by BullCutter pushcutter edge-test bool AlignedEllipse::aligned_solver( const Fiber& f ) { error_dir = f.dir.xyPerp(); // now calls to error(diangle) will give the right error assert( error_dir.xyNorm() > 0.0 ); target = f.p1; // target is either x or y-coord of f.p1 // find position(s) where ellipse tangent is parallel to fiber. Here error() will be minimized/maximized. // tangent at point is: -a t + b s = -a*major_dir*t + b*minor_dir*s // -at ma.y + bs mi.y = 0 for X-fiber // s = sqrt(1-t^2) // -a*ma.y * t + b*mi.y* sqrt(1-t^2) = 0 // => t^2 = b^2 / (a^2 + b^2) double t1(0.0); if (f.p1.y == f.p2.y) t1 = sqrt( square( b*minor_dir.y ) / ( square( a*major_dir.y ) + square( b*minor_dir.y ) ) ); else if (f.p1.x == f.p2.x) t1 = sqrt( square( b*minor_dir.x ) / ( square( a*major_dir.x ) + square( b*minor_dir.x ) ) ); else assert(0); // bracket root EllipsePosition tmp,apos,bpos; double s1 = sqrt(1.0-square(t1)); bool found_positive=false; bool found_negative=false; tmp.setDiangle( xyVectorToDiangle(s1,t1) ); double err = error(tmp.diangle); if (err > 0) { found_positive = true; apos = tmp; } else if (err < 0) { found_negative = true; bpos = tmp; } tmp.setDiangle( xyVectorToDiangle(s1,-t1) ); err = error(tmp.diangle); if (err > 0) { found_positive = true; apos = tmp; } else if (err < 0) { found_negative = true; bpos = tmp; } tmp.setDiangle( xyVectorToDiangle(-s1,t1) ); err = error(tmp.diangle); if (err > 0) { found_positive = true; apos = tmp; } else if (err < 0) { found_negative = true; bpos = tmp; } tmp.setDiangle( xyVectorToDiangle(-s1,-t1) ); err = error(tmp.diangle); if (err > 0) { found_positive = true; apos = tmp; } else if (err < 0) { found_negative = true; bpos = tmp; } if (found_positive) { if (found_negative) { assert( this->error(apos.diangle) * this->error(bpos.diangle) < 0.0 ); // root is now bracketed. double lolim(0.0), hilim(0.0); if (apos.diangle > bpos.diangle ) { lolim = bpos.diangle; hilim = apos.diangle; } else if (bpos.diangle > apos.diangle) { hilim = bpos.diangle; lolim = apos.diangle; } double dia_sln = brent_zero( lolim, hilim , 3E-16, OE_ERROR_TOLERANCE, this ); double dia_sln2 = brent_zero( hilim-4.0, lolim , 3E-16, OE_ERROR_TOLERANCE, this ); EllipsePosition1.setDiangle( dia_sln ); EllipsePosition2.setDiangle( dia_sln2 ); assert( EllipsePosition1.isValid() ); assert( EllipsePosition2.isValid() ); /* // FIXME. This assert fails in some cases (30sphere.stl z=0, for example) // FIXME. The allowed error should probably be in proportion to the difficulty of the case. if (!isZero_tol( error(EllipsePosition1.diangle) )) { std::cout << "AlignedEllipse::aligned_solver() ERROR \n"; std::cout << "error(EllipsePosition1.diangle)= "<< error(EllipsePosition1.diangle) << " (expected zero)\n"; } assert( isZero_tol( error(EllipsePosition1.diangle) ) ); assert( isZero_tol( error(EllipsePosition2.diangle) ) ); */ return true; } } return false; } double AlignedEllipse::error(double diangle) const { EllipsePosition tmp; tmp.setDiangle( diangle ); Point p = this->oePoint(tmp); Point errorVec = target-p; return errorVec.dot(error_dir); } double Ellipse::error(double diangle ) const { EllipsePosition tmp; tmp.setDiangle( diangle ); return error(tmp); } double Ellipse::error(EllipsePosition& pos) const { Point p1 = oePoint(pos); return p1.y; } /// given the two solutions EllipsePosition1 and EllipsePosition2 and the edge up1-up2 /// locate the ellipse center correctly Point Ellipse::calcEcenter(const Point& up1, const Point& up2, int sln) { Point cle = ( sln == 1 ? oePoint1() : oePoint2() ); double xoffset = - cle.x; // x-coord on line is x = up1.x + t*(up2.x-up1.x) = center.x+offset double t = (center.x + xoffset - up1.x) / (up2.x - up1.x); return up1 + t*(up2-up1); // return a point on the line } Point Ellipse::ePointHi() const { return ePoint( EllipsePosition_hi ); } void Ellipse::setEllipsePositionHi(const Point& u1, const Point& u2) { Point ecen1 = calcEcenter( u1, u2, 1); Point ecen2 = calcEcenter( u1, u2, 2); if (ecen1.z >= ecen2.z) { // we want the higher center EllipsePosition_hi = EllipsePosition1; center = ecen1; } else { EllipsePosition_hi = EllipsePosition2; center = ecen2; } } void Ellipse::print_solutions() { std::cout << "1st: (s, t)= " << EllipsePosition1 << " oePoint()= " << oePoint(EllipsePosition1) << " e=" << error(EllipsePosition1) << "\n"; std::cout << "2nd: (s, t)= " << EllipsePosition2 << " oePoint()= " << oePoint(EllipsePosition2) << " e=" << error(EllipsePosition2) << "\n"; } /// Ellipse string output std::ostream& operator<<(std::ostream &stream, const Ellipse& e) { stream << "Ellipse: cen=" << e.center << " a=" << e.a << " b=" << e.b << " ofs=" << e.offset ; return stream; } } // end namespace // end of file oellipse.cpp