/* ************************************************************************** description -------------------- copyright : (C) 2002 by Andreas Zehender email : zehender@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. * * * **************************************************************************/ #include "pmlathe.h" #include "pmxmlhelper.h" #include "pmlatheedit.h" #include "pmmemento.h" #include "pmviewstructure.h" #include "pm2dcontrolpoint.h" #include "pmsplinememento.h" #include "pmsplinesegment.h" #include "pmdefaults.h" #include "pmenumproperty.h" #include "pmobjectaction.h" #include const int defaultNumberOfPoints = 4; const PMVector defaultPoint[defaultNumberOfPoints] = { PMVector( 0.0, 1.0 ), PMVector( 0.5, 0.7 ), PMVector( 0.5, 0.3 ), PMVector( 0.0, 0.0 ) }; const bool defaultSturm = false; const PMLathe::SplineType defaultSplineType = PMLathe::LinearSpline; PMDefinePropertyClass( PMLathe, PMLatheProperty ); PMDefineEnumPropertyClass( PMLathe, PMLathe::SplineType, PMSplineTypeProperty ); PMMetaObject* PMLathe::s_pMetaObject = 0; PMObject* createNewLathe( PMPart* part ) { return new PMLathe( part ); } class PMPointProperty : public PMPropertyBase { public: PMPointProperty( ) : PMPropertyBase( "splinePoints", PMVariant::Vector ) { m_index = 0; } virtual int dimensions( ) const { return 1; } virtual void setIndex( int /*dimension*/, int index ) { m_index = index; } virtual int size( PMObject* object, int /*dimension*/ ) const { return ( ( PMLathe* ) object )->numberOfPoints( ); } protected: virtual bool setProtected( PMObject* obj, const PMVariant& var ) { PMLathe* p = ( PMLathe* ) obj; TQValueList list = p->points( ); TQValueList::Iterator it = list.begin( ); int i; PMVector v = var.vectorData( ); v.resize( 2 ); for( i = 0; i < m_index && it != list.end( ); ++i ) ++it; // expand the list if necessary for( ; i < m_index; ++i ) list.insert( it, v ); if( it == list.end( ) ) it = list.insert( it, v ); else *it = v; p->setPoints( list ); return true; } virtual PMVariant getProtected( const PMObject* obj ) { PMLathe* p = ( PMLathe* ) obj; TQValueList list = p->points( ); TQValueList::ConstIterator it = list.at( m_index ); if( it == list.end( ) ) { kdError( PMArea ) << "Range error in PMLathe::PointProperty::get" << endl; return PMVariant( ); } return PMVariant( *it ); } private: int m_index; }; int PMLathe::s_rSteps = c_defaultLatheRSteps; int PMLathe::s_sSteps = c_defaultLatheSSteps; int PMLathe::s_parameterKey = 0; PMLathe::PMLathe( PMPart* part ) : Base( part ) { int i; for( i = 0; i < defaultNumberOfPoints; ++i ) m_points.append( defaultPoint[i] ); m_splineType = defaultSplineType; m_sturm = defaultSturm; } PMLathe::PMLathe( const PMLathe& l ) : Base( l ) { m_points = l.m_points; m_splineType = l.m_splineType; m_sturm = l.m_sturm; } PMLathe::~PMLathe( ) { } TQString PMLathe::description( ) const { return i18n( "lathe" ); } void PMLathe::serialize( TQDomElement& e, TQDomDocument& doc ) const { TQDomElement data = doc.createElement( "extra_data" ); TQDomElement p; e.setAttribute( "spline_type", m_splineType ); e.setAttribute( "sturm", m_sturm ); TQValueList::ConstIterator it; for( it = m_points.begin( ); it != m_points.end( ); ++it ) { p = doc.createElement( "point" ); p.setAttribute( "vector", ( *it ).serializeXML( ) ); data.appendChild( p ); } e.appendChild( data ); Base::serialize( e, doc ); } void PMLathe::readAttributes( const PMXMLHelper& h ) { m_splineType = ( SplineType ) h.intAttribute( "spline_type", defaultSplineType ); m_sturm = h.boolAttribute( "sturm", defaultSturm ); m_points.clear( ); PMVector v( 2 ); TQDomElement e = h.extraData( ); if( !e.isNull( ) ) { TQDomNode c = e.firstChild( ); while( !c.isNull( ) ) { if( c.isElement( ) ) { TQDomElement ce = c.toElement( ); if( ce.tagName( ) == "point" ) { TQString str = ce.attribute( "vector" ); if( !str.isNull( ) ) { v.loadXML( str ); m_points.append( v ); } } } c = c.nextSibling( ); } } Base::readAttributes( h ); } PMMetaObject* PMLathe::metaObject( ) const { if( !s_pMetaObject ) { s_pMetaObject = new PMMetaObject( "Lathe", Base::metaObject( ), createNewLathe ); s_pMetaObject->addProperty( new PMLatheProperty( "sturm", &PMLathe::setSturm, &PMLathe::sturm ) ); PMSplineTypeProperty* p = new PMSplineTypeProperty( "splineType", &PMLathe::setSplineType, &PMLathe::splineType ); p->addEnumValue( "LinearSpline", LinearSpline ); p->addEnumValue( "QuadraticSpline", QuadraticSpline ); p->addEnumValue( "CubicSpline", CubicSpline ); p->addEnumValue( "BezierSpline", BezierSpline ); s_pMetaObject->addProperty( p ); s_pMetaObject->addProperty( new PMPointProperty( ) ); } return s_pMetaObject; } void PMLathe::cleanUp( ) const { if( s_pMetaObject ) { delete s_pMetaObject; s_pMetaObject = 0; } Base::cleanUp( ); } void PMLathe::setSplineType( PMLathe::SplineType t ) { if( m_splineType != t ) { if( m_pMemento ) m_pMemento->addData( s_pMetaObject, PMSplineTypeID, ( int ) m_splineType ); setViewStructureChanged( ); m_splineType = t; } } void PMLathe::setSturm( bool s ) { if( m_sturm != s ) { if( m_pMemento ) m_pMemento->addData( s_pMetaObject, PMSturmID, m_sturm ); m_sturm = s; } } void PMLathe::setPoints( const TQValueList& points ) { if( m_points != points ) { if( m_pMemento ) ( ( PMSplineMemento* ) m_pMemento )->setSplinePoints( m_points ); setViewStructureChanged( ); m_points = points; } } PMDialogEditBase* PMLathe::editWidget( TQWidget* parent ) const { return new PMLatheEdit( parent ); } void PMLathe::createMemento( ) { if( m_pMemento ) delete m_pMemento; m_pMemento = new PMSplineMemento( this ); } void PMLathe::restoreMemento( PMMemento* s ) { PMSplineMemento* m = ( PMSplineMemento* ) s; PMMementoDataIterator it( s ); PMMementoData* data; for( ; it.current( ); ++it ) { data = it.current( ); if( data->objectType( ) == s_pMetaObject ) { switch( data->valueID( ) ) { case PMSplineTypeID: setSplineType( ( SplineType ) data->intData( ) ); break; case PMSturmID: setSturm( data->boolData( ) ); break; default: kdError( PMArea ) << "Wrong ID in PMLathe::restoreMemento\n"; break; } } } if( m->splinePointsSaved( ) ) setPoints( m->splinePoints( ) ); Base::restoreMemento( s ); } void PMLathe::createViewStructure( ) { if( s_sSteps == 0 ) s_sSteps = c_defaultLatheSSteps; if( s_rSteps == 0 ) s_rSteps = c_defaultLatheRSteps; int rSteps = (int)( ( (float)s_rSteps / 2 ) * ( displayDetail( ) + 1 ) ); int sSteps = (int)( ( (float)s_sSteps / 2 ) * ( displayDetail( ) + 1 ) ); int np = m_points.count( ); int ns = 0; int i, j, r, si; // calculate number of segments switch( m_splineType ) { case LinearSpline: ns = np - 1; break; case QuadraticSpline: ns = np - 2; break; case CubicSpline: ns = np - 3; break; case BezierSpline: ns = np / 4; break; } // calculate number of points and lines of the view structure int vsp = 0; if( m_splineType != BezierSpline ) vsp = ns * sSteps + 1; else vsp = ns * ( sSteps + 1 ); int vsl = 0; if( m_splineType != BezierSpline ) vsl = ( 2 * vsp - 1 ) * rSteps; else vsl = ns * ( ( 2 * sSteps + 1 ) * rSteps ); vsp *= rSteps; if( m_pViewStructure ) { if( m_pViewStructure->points( ).size( ) != ( unsigned ) vsp ) m_pViewStructure->points( ).resize( vsp ); if( m_pViewStructure->lines( ).size( ) != ( unsigned ) vsl ) m_pViewStructure->lines( ).resize( vsl ); } else m_pViewStructure = new PMViewStructure( vsp, vsl ); // calculate the spline segments TQValueList segments; TQValueList::Iterator it1, it2, it3, it4; it1 = m_points.begin( ); it2 = it1; ++it2; it3 = it2; ++it3; it4 = it3; ++it4; PMSplineSegment s; for( i = 0; i < ns; ++i ) { switch( m_splineType ) { case LinearSpline: s.calculateLinear( *it1, *it2 ); ++it1; ++it2; break; case QuadraticSpline: s.calculateQuadratic( *it1, *it2, *it3 ); ++it1; ++it2; ++it3; break; case CubicSpline: s.calculateCubic( *it1, *it2, *it3, *it4 ); ++it1; ++it2; ++it3; ++it4; break; case BezierSpline: s.calculateBezier( *it1, *it2, *it3, *it4 ); for( j = 0; j < 4; ++j ) { ++it1; ++it2; ++it3; ++it4; } break; } segments.append( s ); } // create the line array if( m_splineType != BezierSpline ) { PMLineArray& lines = m_pViewStructure->lines( ); int vl = ns * sSteps; int lb = 0; for( i = 0; i < vl + 1; ++i ) { for( j = 0; j < rSteps - 1; ++j ) lines[lb+j] = PMLine( lb + j, lb + j + 1 ); lines[lb+rSteps-1] = PMLine( lb, lb + rSteps - 1 ); lb += rSteps; } int pi = 0; for( i = 0; i < vl; ++i ) { for( j = 0; j < rSteps; ++j ) { lines[lb] = PMLine( pi, pi + rSteps ); ++pi; ++lb; } } } else { PMLineArray& lines = m_pViewStructure->lines( ); int lb = 0; int pi = 0; for( si = 0; si < ns; ++si ) { for( i = 0; i < sSteps + 1; ++i ) { for( j = 0; j < rSteps - 1; ++j ) lines[lb+j] = PMLine( lb + j, lb + j + 1 ); lines[lb+rSteps-1] = PMLine( lb, lb + rSteps - 1 ); lb += rSteps; } } for( si = 0; si < ns; ++si ) { for( i = 0; i < sSteps; ++i ) { for( j = 0; j < rSteps; ++j ) { lines[lb] = PMLine( pi, pi + rSteps ); ++pi; ++lb; } } pi += rSteps; } } // calculate the points PMVector point2, point3; TQValueList::Iterator sit = segments.begin( ); int pi = 0; double poffset = 1.0 / sSteps; PMMatrix rot = PMMatrix::rotation( 0.0, M_PI * 2.0 / rSteps, 0.0 ); PMPointArray& points = m_pViewStructure->points( ); if( m_splineType != BezierSpline ) { for( i = 0; i < ns; ++i, ++sit ) { for( j = 0; j < sSteps; ++j ) { point2 = ( *sit ).point( poffset * j ); point3[0] = point2[0]; point3[1] = point2[1]; point3[2] = 0.0; for( r = 0; r < rSteps; ++r ) { points[pi] = PMPoint( point3 ); if( r != rSteps - 1 ) point3.transform( rot ); ++pi; } } if( i == ns - 1 ) { point2 = ( *sit ).point( 1.0 ); point3[0] = point2[0]; point3[1] = point2[1]; point3[2] = 0.0; for( r = 0; r < rSteps; ++r ) { points[pi] = PMPoint( point3 ); if( r != rSteps - 1 ) point3.transform( rot ); ++pi; } } } } else { for( i = 0; i < ns; ++i, ++sit ) { for( j = 0; j < sSteps + 1; ++j ) { point2 = ( *sit ).point( poffset * j ); point3[0] = point2[0]; point3[1] = point2[1]; point3[2] = 0.0; for( r = 0; r < rSteps; ++r ) { points[pi] = PMPoint( point3 ); if( r != rSteps - 1 ) point3.transform( rot ); ++pi; } } } } } void PMLathe::controlPoints( PMControlPointList& list ) { TQValueList::Iterator it; int i, d; PM2DControlPoint* cp = 0; TQPtrList tmp[2]; for( d = 0; d < 2; ++d ) { if( m_splineType != BezierSpline ) { PM2DControlPoint* firstPoint = 0; PM2DControlPoint* lastPoint = 0; for( it = m_points.begin( ), i = 0; it != m_points.end( ); ++it, ++i ) { lastPoint = cp; if( d == 0 ) cp = new PM2DControlPoint( *it, PM2DControlPoint::PM2DXY, i, i18n( "Point %1 (xy)" ).arg( i + 1 ) ); else cp = new PM2DControlPoint( *it, PM2DControlPoint::PM2DZY, i, i18n( "Point %1 (xy)" ).arg( i + 1 ) ); if( i == 0 ) firstPoint = cp; if( ( i == 1 ) && ( m_splineType != LinearSpline ) ) firstPoint->setBasePoint( cp ); tmp[d].append( cp ); } if( m_splineType == CubicSpline ) cp->setBasePoint( lastPoint ); } else { PM2DControlPoint* helpPoint = 0; for( it = m_points.begin( ), i = 0; it != m_points.end( ); ++it, ++i ) { int imod4 = i % 4; if( d == 0 ) cp = new PM2DControlPoint( *it, PM2DControlPoint::PM2DXY, i, i18n( "Point %1 (xy)" ).arg( i + 1 ) ); else cp = new PM2DControlPoint( *it, PM2DControlPoint::PM2DZY, i, i18n( "Point %1 (xy)" ).arg( i + 1 ) ); switch( imod4 ) { case 0: helpPoint = cp; break; case 1: cp->setBasePoint( helpPoint ); break; case 2: helpPoint = cp; break; case 3: helpPoint->setBasePoint( cp ); break; default: break; } tmp[d].append( cp ); } } } TQPtrListIterator cit1( tmp[0] ), cit2( tmp[1] ); for( ; cit1.current( ) && cit2.current( ); ++cit1, ++cit2 ) { ( *cit1 )->setLatheLink( *cit2 ); ( *cit2 )->setLatheLink( *cit1 ); } for( cit1.toFirst( ); cit1.current( ); ++cit1 ) list.append( *cit1 ); for( cit2.toFirst( ); cit2.current( ); ++cit2 ) list.append( *cit2 ); } void PMLathe::controlPointsChanged( PMControlPointList& list ) { PMControlPointListIterator it1( list ), it2( list ); TQValueList::Iterator pit = m_points.begin( ); PM2DControlPoint* p1; PM2DControlPoint* p2; bool firstChange = true; for( it2 += list.count( ) / 2; it2.current( ); ++it1, ++it2, ++pit ) { p1 = ( PM2DControlPoint* ) it1.current( ); p2 = ( PM2DControlPoint* ) it2.current( ); if( p1->changed( ) ) { if( firstChange ) { if( m_pMemento ) { PMSplineMemento* m = ( PMSplineMemento* ) m_pMemento; if( !m->splinePointsSaved( ) ) m->setSplinePoints( m_points ); } firstChange = false; setViewStructureChanged( ); } p2->setPoint( p1->point( ) ); ( *pit ) = p1->point( ); } else if( p2->changed( ) ) { if( firstChange ) { if( m_pMemento ) { PMSplineMemento* m = ( PMSplineMemento* ) m_pMemento; if( !m->splinePointsSaved( ) ) m->setSplinePoints( m_points ); } firstChange = false; setViewStructureChanged( ); } p1->setPoint( p2->point( ) ); ( *pit ) = p2->point( ); } } } void PMLathe::addObjectActions( const PMControlPointList& /*cp*/, TQPtrList& actions ) { PMObjectAction* a; a = new PMObjectAction( s_pMetaObject, PMSplitSegmentID, i18n( "Add Point" ) ); actions.append( a ); a = new PMObjectAction( s_pMetaObject, PMJoinSegmentsID, i18n( "Remove Point" ) ); int np = m_points.count( ); int minp = 3; switch( m_splineType ) { case LinearSpline: minp = 3; break; case QuadraticSpline: minp = 4; break; case CubicSpline: minp = 5; break; case BezierSpline: minp = 8; break; } if( np < minp ) a->setEnabled( false ); actions.append( a ); } void PMLathe::objectActionCalled( const PMObjectAction* action, const PMControlPointList& cp, const TQPtrList& cpViewPosition, const PMVector& clickPosition ) { if( action->objectType( ) == s_pMetaObject ) { switch( action->actionID( ) ) { case PMSplitSegmentID: splitSegment( cp, cpViewPosition, clickPosition ); break; case PMJoinSegmentsID: joinSegments( cp, cpViewPosition, clickPosition ); break; default: kdError( PMArea ) << "Wrong ID in PMLathe::objectActionCalled\n"; break; } } else Base::objectActionCalled( action, cp, cpViewPosition, clickPosition ); } void PMLathe::splitSegment( const PMControlPointList& /*cp*/, const TQPtrList& cpViewPosition, const PMVector& clickPosition ) { // find nearest segment int nump = cpViewPosition.count( ) / 2 - 1; double abs = 0.0, minabs = 1e10; int ns = -1; int i, j; PMVector mid( 3 ), dist( 2 ); TQPtrListIterator it1( cpViewPosition ); TQPtrListIterator it2( cpViewPosition ); ++it2; for( j = 0; j < 2; ++j ) { for( i = 0; i < nump; ++i ) { bool skip = false; switch( m_splineType ) { case LinearSpline: case BezierSpline: break; case QuadraticSpline: if( i == 0 ) skip = true; break; case CubicSpline: if( ( i == 0 ) || ( i == ( nump - 1 ) ) ) skip = true; break; } if( !skip ) { mid = ( **it1 + **it2 ) / 2.0; dist[0] = mid[0]; dist[1] = mid[1]; dist -= clickPosition; abs = dist.abs( ); if( ( minabs > abs ) || ( ns < 0 ) ) { minabs = abs; ns = i; } } ++it1; ++it2; } ++it1; ++it2; } // add a new segment TQValueList newPoints = m_points; if( m_splineType == BezierSpline ) { ns /= 4; ns *= 4; } TQValueList::Iterator it = newPoints.at( ( unsigned ) ns ); PMVector p[4]; TQValueList::Iterator hit = it; // calculate the spline segment PMSplineSegment segment; switch( m_splineType ) { case LinearSpline: for( i = 0; i < 2; ++i, ++hit ) p[i] = *hit; segment.calculateLinear( p[0], p[1] ); break; case QuadraticSpline: --hit; for( i = 0; i < 3; ++i, ++hit ) p[i] = *hit; segment.calculateQuadratic( p[0], p[1], p[2] ); break; case CubicSpline: --hit; for( i = 0; i < 4; ++i, ++hit ) p[i] = *hit; segment.calculateCubic( p[0], p[1], p[2], p[3] ); break; case BezierSpline: for( i = 0; i < 4; ++i, ++hit ) p[i] = *hit; segment.calculateBezier( p[0], p[1], p[2], p[3] ); break; } mid = segment.point( 0.5 ); if( m_splineType != BezierSpline ) { ++it; newPoints.insert( it, mid ); } else { PMVector end = *it; ++it; *it = end + ( *it - end ) / 2.0; ++it; PMVector grad = segment.gradient( 0.5 ) / 4.0; newPoints.insert( it, mid - grad ); newPoints.insert( it, mid ); newPoints.insert( it, mid ); newPoints.insert( it, mid + grad ); ++it; end = *it; --it; *it = end + ( *it - end ) / 2.0; } setPoints( newPoints ); } void PMLathe::joinSegments( const PMControlPointList& /*cp*/, const TQPtrList& cpViewPosition, const PMVector& clickPosition ) { // find nearest point int nump = cpViewPosition.count( ) / 2; int minp = 0; switch( m_splineType ) { case LinearSpline: minp = 3; break; case QuadraticSpline: minp = 4; break; case CubicSpline: minp = 5; break; case BezierSpline: minp = 8; break; } if( nump < minp ) { kdError( PMArea ) << "Not enough points in PMLathe::joinSegments\n"; return; } double abs = 0.0, minabs = 1e10; int ns = -1; int i, j; PMVector* p; PMVector dist( 2 ); TQPtrListIterator it1( cpViewPosition ); for( j = 0; j < 2; ++j ) { for( i = 0; i < nump; ++i ) { p = *it1; dist[0] = (*p)[0]; dist[1] = (*p)[1]; dist -= clickPosition; abs = dist.abs( ); if( ( minabs > abs ) || ( ns < 0 ) ) { minabs = abs; ns = i; } ++it1; } } // join two segments TQValueList newPoints = m_points; TQValueList::Iterator it; if( m_splineType != BezierSpline ) { // never remove the first or last point if( ns == 0 ) ++ns; if( ns == ( nump - 1 ) ) --ns; it = newPoints.at( ns ); newPoints.remove( it ); } else { ns = ( ns - 2 ) / 4; if( ns < 0 ) ns = 0; if( ns >= ( nump / 4 - 1 ) ) ns = nump / 4 - 2; it = newPoints.at( ns * 4 + 2 ); for( i = 0; i < 4; ++i ) it = newPoints.remove( it ); } setPoints( newPoints ); } void PMLathe::setRSteps( int r ) { if( r >= 4 ) s_rSteps = r; else kdDebug( PMArea ) << "PMLathe::setRSteps: R must be greater than 3\n"; ++s_parameterKey; } void PMLathe::setSSteps( int s ) { if( s >= 1 ) s_sSteps = s; else kdDebug( PMArea ) << "PMLathe::setSSteps: S must be greater than 0\n"; ++s_parameterKey; }