/* $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