diff options
Diffstat (limited to 'kig/objects/cubic_imp.cc')
-rw-r--r-- | kig/objects/cubic_imp.cc | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/kig/objects/cubic_imp.cc b/kig/objects/cubic_imp.cc new file mode 100644 index 00000000..51bb5d9f --- /dev/null +++ b/kig/objects/cubic_imp.cc @@ -0,0 +1,437 @@ +// Copyright (C) 2003 Dominique Devriese <devriese@kde.org> + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301, USA. + +#include "cubic_imp.h" + +#include "bogus_imp.h" + +#include "../misc/kigpainter.h" +#include "../misc/screeninfo.h" +#include "../misc/kignumerics.h" +#include "../misc/common.h" +#include "../kig/kig_view.h" + +#include <math.h> +#include <klocale.h> + +CubicImp::CubicImp( const CubicCartesianData& data ) + : CurveImp(), mdata( data ) +{ +} + +CubicImp::~CubicImp() +{ +} + +ObjectImp* CubicImp::transform( const Transformation& t ) const +{ + bool valid = true; + CubicCartesianData d = calcCubicTransformation( data(), t, valid ); + if ( valid ) return new CubicImp( d ); + else return new InvalidImp; +} + +void CubicImp::draw( KigPainter& p ) const +{ + p.drawCurve( this ); +} + +bool CubicImp::contains( const Coordinate& o, int width, const KigWidget& w ) const +{ + return internalContainsPoint( o, w.screenInfo().normalMiss( width ) ); +} + +bool CubicImp::inRect( const Rect&, int, const KigWidget& ) const +{ + // TODO ? + return false; +} + +CubicImp* CubicImp::copy() const +{ + return new CubicImp( mdata ); +} + +double CubicImp::getParam( const Coordinate& p, const KigDocument& ) const +{ + double x = p.x; + double y = p.y; + double t; + + double a000 = mdata.coeffs[0]; + double a001 = mdata.coeffs[1]; + double a002 = mdata.coeffs[2]; + double a011 = mdata.coeffs[3]; + double a012 = mdata.coeffs[4]; + double a022 = mdata.coeffs[5]; + double a111 = mdata.coeffs[6]; + double a112 = mdata.coeffs[7]; + double a122 = mdata.coeffs[8]; + double a222 = mdata.coeffs[9]; + + /* + * first project p onto the cubic. This is done by computing the + * line through p in the direction of the gradient + */ + + double f = a000 + a001*x + a002*y + a011*x*x + a012*x*y + a022*y*y + + a111*x*x*x + a112*x*x*y + a122*x*y*y + a222*y*y*y; + if ( f != 0 ) + { + double fx = a001 + 2*a011*x + a012*y + 3*a111*x*x + 2*a112*x*y + a122*y*y; + double fy = a002 + 2*a022*y + a012*x + 3*a222*y*y + 2*a122*x*y + a112*x*x; + Coordinate v = Coordinate (fx, fy); + if ( f < 0 ) v = -v; // the line points away from the intersection + double a, b, c, d; + calcCubicLineRestriction ( mdata, p, v, a, b, c, d ); + if ( a < 0 ) + { + a *= -1; + b *= -1; + c *= -1; + d *= -1; + } + + // computing the coefficients of the Sturm sequence + double p1a = 2*b*b - 6*a*c; + double p1b = b*c - 9*a*d; + double p0a = c*p1a*p1a + p1b*(3*a*p1b - 2*b*p1a); + // compute the number of roots for negative lambda + int variations = calcCubicVariations ( 0, a, b, c, d, p1a, p1b, p0a ); + bool valid; + int numroots; + double lambda = calcCubicRoot ( -1e10, 1e10, a, b, c, d, variations, valid, + numroots ); + if ( valid ) + { + Coordinate pnew = p + lambda*v; + x = pnew.x; + y = pnew.y; + } + } + + if (x > 0) t = x/(1+x); + else t = x/(1-x); + t = 0.5*(t + 1); + t /= 3; + + Coordinate p1 = getPoint ( t ); + Coordinate p2 = getPoint ( t + 1.0/3.0 ); + Coordinate p3 = getPoint ( t + 2.0/3.0 ); + + double mint = t; + double mindist = p1.valid() ? fabs ( y - p1.y ) : double_inf; + if ( p2.valid() && fabs ( y - p2.y ) < mindist ) + { + mint = t + 1.0/3.0; + mindist = fabs ( y - p2.y ); + } + if ( p3.valid() && fabs ( y - p3.y ) < mindist ) + { + mint = t + 2.0/3.0; + } + + return mint; +} + +const Coordinate CubicImp::getPoint( double p, const KigDocument& ) const +{ + return getPoint( p ); +} + +const Coordinate CubicImp::getPoint( double p ) const +{ + /* + * this isn't really elegant... + * the magnitude of p tells which one of the maximum 3 intersections + * of the vertical line with the cubic to take. + */ + + p *= 3; + int root = (int) p; + assert ( root >= 0 ); + assert ( root <= 3 ); + if ( root == 3 ) root = 2; + + p -= root; + + assert ( 0 <= p && p <= 1 ); + if ( p <= 0. ) p = 1e-6; + if ( p >= 1. ) p = 1 - 1e-6; + root++; + p = 2*p - 1; + double x; + if (p > 0) x = p/(1 - p); + else x = p/(1 + p); + + // calc the third degree polynomial: + // compute the third degree polinomial: +// double a000 = mdata.coeffs[0]; +// double a001 = mdata.coeffs[1]; +// double a002 = mdata.coeffs[2]; +// double a011 = mdata.coeffs[3]; +// double a012 = mdata.coeffs[4]; +// double a022 = mdata.coeffs[5]; +// double a111 = mdata.coeffs[6]; +// double a112 = mdata.coeffs[7]; +// double a122 = mdata.coeffs[8]; +// double a222 = mdata.coeffs[9]; +// +// // first the y^3 coefficient, it coming only from a222: +// double a = a222; +// // next the y^2 coefficient (from a122 and a022): +// double b = a122*x + a022; +// // next the y coefficient (from a112, a012 and a002): +// double c = a112*x*x + a012*x + a002; +// // finally the constant coefficient (from a111, a011, a001 and a000): +// double d = a111*x*x*x + a011*x*x + a001*x + a000; + +// commented out, since the bound is already computed when passing a huge +// interval; the normalization is not needed also + + // renormalize: positive a +// if ( a < 0 ) +// { +// a *= -1; +// b *= -1; +// c *= -1; +// d *= -1; +// } +// +// const double small = 1e-7; +// int degree = 3; +// if ( fabs(a) < small*fabs(b) || +// fabs(a) < small*fabs(c) || +// fabs(a) < small*fabs(d) ) +// { +// degree = 2; +// if ( fabs(b) < small*fabs(c) || +// fabs(b) < small*fabs(d) ) +// { +// degree = 1; +// } +// } + +// and a bound for all the real roots: + +// double bound; +// switch (degree) +// { +// case 3: +// bound = fabs(d/a); +// if ( fabs(c/a) + 1 > bound ) bound = fabs(c/a) + 1; +// if ( fabs(b/a) + 1 > bound ) bound = fabs(b/a) + 1; +// break; +// +// case 2: +// bound = fabs(d/b); +// if ( fabs(c/b) + 1 > bound ) bound = fabs(c/b) + 1; +// break; +// +// case 1: +// default: +// bound = fabs(d/c) + 1; +// break; +// } + + int numroots; + bool valid = true; + double y = calcCubicYvalue ( x, -double_inf, double_inf, root, mdata, valid, + numroots ); + if ( ! valid ) return Coordinate::invalidCoord(); + else return Coordinate(x,y); +// if ( valid ) return Coordinate(x,y); +// root--; if ( root <= 0) root += 3; +// y = calcCubicYvalue ( x, -bound, bound, root, mdata, valid, +// numroots ); +// if ( valid ) return Coordinate(x,y); +// root--; if ( root <= 0) root += 3; +// y = calcCubicYvalue ( x, -bound, bound, root, mdata, valid, +// numroots ); +// assert ( valid ); +// return Coordinate(x,y); +} + +const uint CubicImp::numberOfProperties() const +{ + return Parent::numberOfProperties() + 1; +} + +const QCStringList CubicImp::propertiesInternalNames() const +{ + QCStringList l = Parent::propertiesInternalNames(); + l << "cartesian-equation"; + assert( l.size() == CubicImp::numberOfProperties() ); + return l; + +} + +/* + * cartesian equation property contributed by Carlo Sardi + */ + +const QCStringList CubicImp::properties() const +{ + QCStringList l = Parent::properties(); + l << I18N_NOOP( "Cartesian Equation" ); + assert( l.size() == CubicImp::numberOfProperties() ); + return l; + +} + +const ObjectImpType* CubicImp::impRequirementForProperty( uint which ) const +{ + if ( which < Parent::numberOfProperties() ) + return Parent::impRequirementForProperty( which ); + else return CubicImp::stype(); +} + +const char* CubicImp::iconForProperty( uint which ) const +{ + int pnum = 0; + if ( which < Parent::numberOfProperties() ) + return Parent::iconForProperty( which ); + if ( which == Parent::numberOfProperties() + pnum++ ) + return "kig_text"; // cartesian equation string + else + assert( false ); + return ""; +} + +ObjectImp* CubicImp::property( uint which, const KigDocument& w ) const +{ + int pnum = 0; + + if ( which < Parent::numberOfProperties() ) + return Parent::property( which, w ); + if ( which == Parent::numberOfProperties() + pnum++ ) + return new StringImp( cartesianEquationString( w ) ); + else + assert( false ); + return new InvalidImp; +} + +const CubicCartesianData CubicImp::data() const +{ + return mdata; +} + +void CubicImp::visit( ObjectImpVisitor* vtor ) const +{ + vtor->visit( this ); +} + +bool CubicImp::equals( const ObjectImp& rhs ) const +{ + return rhs.inherits( CubicImp::stype() ) && + static_cast<const CubicImp&>( rhs ).data() == data(); +} + +const ObjectImpType* CubicImp::type() const +{ + return CubicImp::stype(); +} + +const ObjectImpType* CubicImp::stype() +{ + static const ObjectImpType t( + Parent::stype(), "cubic", + I18N_NOOP( "cubic curve" ), + I18N_NOOP( "Select this cubic curve" ), + I18N_NOOP( "Select cubic curve %1" ), + I18N_NOOP( "Remove a Cubic Curve" ), + I18N_NOOP( "Add a Cubic Curve" ), + I18N_NOOP( "Move a Cubic Curve" ), + I18N_NOOP( "Attach to this cubic curve" ), + I18N_NOOP( "Show a Cubic Curve" ), + I18N_NOOP( "Hide a Cubic Curve" ) + ); + return &t; +} + +bool CubicImp::containsPoint( const Coordinate& p, const KigDocument& ) const +{ + return internalContainsPoint( p, test_threshold ); +} + +bool CubicImp::internalContainsPoint( const Coordinate& p, double threshold ) const +{ + double a000 = mdata.coeffs[0]; + double a001 = mdata.coeffs[1]; + double a002 = mdata.coeffs[2]; + double a011 = mdata.coeffs[3]; + double a012 = mdata.coeffs[4]; + double a022 = mdata.coeffs[5]; + double a111 = mdata.coeffs[6]; + double a112 = mdata.coeffs[7]; + double a122 = mdata.coeffs[8]; + double a222 = mdata.coeffs[9]; + + double x = p.x; + double y = p.y; + + double f = a000 + a001*x + a002*y + a011*x*x + a012*x*y + a022*y*y + + a111*x*x*x + a112*x*x*y + a122*x*y*y + a222*y*y*y; + double fx = a001 + 2*a011*x + a012*y + 3*a111*x*x + 2*a112*x*y + a122*y*y; + double fy = a002 + a012*x + 2*a022*y + a112*x*x + 2*a122*x*y + 3*a222*y*y; + + double dist = fabs(f)/(fabs(fx) + fabs(fy)); + + return dist <= threshold; +} + +bool CubicImp::isPropertyDefinedOnOrThroughThisImp( uint which ) const +{ + return Parent::isPropertyDefinedOnOrThroughThisImp( which ); +} + +Rect CubicImp::surroundingRect() const +{ + // it's probably possible to calculate this if it exists, but for + // now we don't. + return Rect::invalidRect(); +} + +QString CubicImp::cartesianEquationString( const KigDocument& ) const +{ + /* + * unfortunately QStrings.arg method is limited to %1, %9, so we cannot + * treat all 10 arguments! Let's split the equation in two parts... + * Now this ends up also in the translation machinery, is this really + * necessary? Otherwise we could do a little bit of tidy up on the + * the equation (removal of zeros, avoid " ... + -1234 x ", etc.) + */ + + QString ret = i18n( "%6 x³ + %9 y³ + %7 x²y + %8 xy² + %5 y² + %3 x² + %4 xy + %1 x + %2 y" ); + ret = ret.arg( mdata.coeffs[1], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[2], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[3], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[4], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[5], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[6], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[7], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[8], 0, 'g', 3 ); + ret = ret.arg( mdata.coeffs[9], 0, 'g', 3 ); + + ret.append( i18n( " + %1 = 0" ) ); + ret = ret.arg( mdata.coeffs[0], 0, 'g', 3 ); + + // we should find a common place to do this... + ret.replace( "+ -", "- " ); + ret.replace( "+-", "-" ); + return ret; +} |