From 44331302efeba18c38777396ae407f54f414fdc5 Mon Sep 17 00:00:00 2001 From: paulhoux Date: Wed, 27 Dec 2023 13:21:39 +0100 Subject: [PATCH 1/5] Added convenience functions to Path2d and Shape2d, allowing easy creation of several shapes and smooth curves. --- include/cinder/Path2d.h | 57 ++++++- include/cinder/Shape2d.h | 27 +++ src/cinder/Path2d.cpp | 355 +++++++++++++++++++++++++++++++++++++-- src/cinder/Shape2d.cpp | 87 ++++++++++ 4 files changed, 504 insertions(+), 22 deletions(-) diff --git a/include/cinder/Path2d.h b/include/cinder/Path2d.h index 3ebb9f6f22..492b92fd26 100644 --- a/include/cinder/Path2d.h +++ b/include/cinder/Path2d.h @@ -45,18 +45,54 @@ class CI_API Path2d { void lineTo( float x, float y ) { lineTo( vec2( x, y ) ); } void quadTo( const vec2 &p1, const vec2 &p2 ); void quadTo( float x1, float y1, float x2, float y2 ) { quadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } + void smoothQuadTo( const vec2 &p2 ); + void smoothQuadTo( float x2, float y2 ) { smoothQuadTo( vec2( x2, y2 ) ); } void curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ); void curveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { curveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } + void smoothCurveTo( const vec2 &p2, const vec2 &p3 ); + void smoothCurveTo( float x2, float y2, float x3, float y3 ) { smoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } void arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward = true ); void arc( float centerX, float centerY, float radius, float startRadians, float endRadians, bool forward = true ) { arc( vec2( centerX, centerY ), radius, startRadians, endRadians, forward ); } void arcTo( const vec2 &p, const vec2 &t, float radius ); void arcTo( float x, float y, float tanX, float tanY, float radius) { arcTo( vec2( x, y ), vec2( tanX, tanY ), radius ); } + void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); //! Closes the path, by drawing a straight line from the first to the last point. This is only legal as the last command. void close() { mSegments.push_back( CLOSE ); } - bool isClosed() const { return ( mSegments.size() > 1 ) && mSegments.back() == CLOSE; } + bool isClosed() const { return ! mSegments.empty() && mSegments.back() == CLOSE; } + + //! Creates a circle with given \a center and \a radius and returns it as a Path2d. + static Path2d circle( const vec2 ¢er, float radius ); + //! Creates an ellipse with given \a center and \a radiusX and \a radiusY and returns it as a Path2d. + static Path2d ellipse( const vec2 ¢er, float radiusX, float radiusY ); + //! Creates a line with given start point \a p0 and end point \a p1 and returns it as a Path2d. + static Path2d line( const vec2 &p0, const vec2 &p1 ); + //! Creates a polygon from the given \a points and returns it as a Path2d. + static Path2d polygon( const std::vector &points, bool closed = true ); + //! Creates a rectangle with given \a bounds and returns it as a Path2d. + static Path2d rectangle( const Rectf &bounds ) { return rectangle( bounds.x1, bounds.y1, bounds.getWidth(), bounds.getHeight() ); } + //! Creates a rectangle with given origin \a x, \a y and size \a width, \a height and returns it as a Path2d. + static Path2d rectangle( float x, float y, float width, float height ); + //! Creates a rounded rectangle with given \a bounds and corner radius \a r and returns it as a Path2d. + static Path2d roundedRectangle( const Rectf &bounds, float r ) { return roundedRectangle( bounds, r, r ); } + //! Creates a rounded rectangle with given \a bounds and corner radii \a rx and \a ry and returns it as a Path2d. + static Path2d roundedRectangle( const Rectf &bounds, float rx, float ry ) { return roundedRectangle( bounds.x1, bounds.y1, bounds.getWidth(), bounds.getHeight(), rx, ry ); } + //! Creates a rounded rectangle with given origin \a x, \a y and size \a width, \a height and corner radius \a r returns it as a Path2d. + static Path2d roundedRectangle( float x, float y, float width, float height, float r ) { return roundedRectangle( x, y, width, height, r, r ); } + //! Creates a rounded rectangle with given origin \a x, \a y and size \a width, \a height and corner radii \a rx and \a ry and returns it as a Path2d. + static Path2d roundedRectangle( float x, float y, float width, float height, float rx, float ry ); + //! Creates a star with the given \a center and number of \a points and returns it as a Path2d. + static Path2d star( const vec2 ¢er, int points, float largeRadius, float smallRadius, float rotation = 0 ); + //! Creates an arrow from start point \a p0 to end point \a p1 and returns it as a Path2d. The arrow head can be shaped with parameters \a thickness, \a width, \a length and \a concavity. + static Path2d arrow( const vec2 &p0, const vec2 &p1, float thickness, float width = 4, float length = 4, float concavity = 0 ); + //! Creates an arrow from start point \a x0, \a y0 to end point \a x1, \a y1 and returns it as a Path2d. The arrow head can be shaped with parameters \a thickness, \a width, \a length and \a concavity. + static Path2d arrow( float x0, float y0, float x1, float y1, float thickness, float width = 4, float length = 4, float concavity = 0 ) { return arrow( vec2( x0, y0 ), vec2( x1, y1 ), thickness, width, length, concavity ); } + //! Creates an Archimedean spiral at \a center and returns it as a Path2d. The spiral runs from \a innerRadius to \a outerRadius and the radius will increase by \a spacing every full revolution. + //! You can provide an optional radial \a offset. + static Path2d spiral( const vec2 ¢er, float innerRadius, float outerRadius, float spacing, float offset = 0 ); - //! Reverses the order of the path's points, inverting its winding order + //! Reverses the orientation of the path, changing CW to CCW and vice versa. void reverse(); bool empty() const { return mPoints.empty(); } @@ -94,8 +130,12 @@ class CI_API Path2d { const std::vector& getPoints() const { return mPoints; } std::vector& getPoints() { return mPoints; } - const vec2& getPoint( size_t point ) const { return mPoints[point]; } - vec2& getPoint( size_t point ) { return mPoints[point]; } + const vec2& getPoint( size_t point ) const { return mPoints[ point % mPoints.size() ]; } + vec2& getPoint( size_t point ) { return mPoints[ point % mPoints.size() ]; } + const vec2& getPointBefore( size_t point ) const { return getPoint( point + mPoints.size() - 1 ); } + vec2& getPointBefore( size_t point ) { return getPoint( point + mPoints.size() - 1 ); } + const vec2& getPointAfter( size_t point ) const { return getPoint( point + 1 ); } + vec2& getPointAfter( size_t point ) { return getPoint( point + 1 ); } const vec2& getCurrentPoint() const { return mPoints.back(); } void setPoint( size_t index, const vec2 &p ) { mPoints[index] = p; } @@ -115,6 +155,11 @@ class CI_API Path2d { //! Returns the precise bounding box around the curve itself. Slower to calculate than calcBoundingBox(). Rectf calcPreciseBoundingBox() const; + //! Returns whether the path is defined in clockwise order. + bool calcClockwise() const; + //! Returns whether the path is defined in counter-clockwise order. + bool calcCounterClockwise() const { return !calcClockwise(); } + //! Returns whether the point \a pt is contained within the boundaries of the Path2d. If \a evenOddFill is \c true (the default) then Even-Odd fill rule is used, otherwise, the Winding fill rule is applied. bool contains( const vec2 &pt, bool evenOddFill = true ) const; @@ -158,7 +203,9 @@ class CI_API Path2d { private: void arcHelper( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ); void arcSegmentAsCubicBezier( const vec2 ¢er, float radius, float startRadians, float endRadians ); - + + float angleHelper( const vec2 &u, const vec2 &v ) const; + //! Returns the minimum distance from point \a pt to segment \a segment. The \a firstPoint parameter can be used as an optimization if known, otherwise pass 0. float calcDistance( const vec2 &pt, size_t segment, size_t firstPoint ) const; diff --git a/include/cinder/Shape2d.h b/include/cinder/Shape2d.h index 8b79957ecc..cbfccef6d3 100644 --- a/include/cinder/Shape2d.h +++ b/include/cinder/Shape2d.h @@ -35,17 +35,44 @@ class CI_API Shape2d { public: void moveTo( const vec2 &p ); void moveTo( float x, float y ) { moveTo( vec2( x, y ) ); } + void relativeMoveTo( const vec2 &p ); + void relativeMoveTo( float dx, float dy ) { relativeMoveTo( vec2( dx, dy ) ); } void lineTo( const vec2 &p ); void lineTo( float x, float y ) { lineTo( vec2( x, y ) ); } + void relativeLineTo( const vec2 &p ); + void relativeLineTo( float dx, float dy ) { relativeLineTo( vec2( dx, dy ) ); } + void horizontalLineTo( float x ); + void relativeHorizontalLineTo( float dx ); + void verticalLineTo( float y ); + void relativeVerticalLineTo( float dy ); void quadTo( const vec2 &p1, const vec2 &p2 ); void quadTo( float x1, float y1, float x2, float y2 ) { quadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } + void relativeQuadTo( const vec2 &p1, const vec2 &p2 ); + void relativeQuadTo( float x1, float y1, float x2, float y2 ) { relativeQuadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } + void smoothQuadTo( const vec2 &p2 ); + void smoothQuadTo( float x2, float y2 ) { smoothQuadTo( vec2( x2, y2 ) ); } + void relativeSmoothQuadTo( const vec2 &p2 ); + void relativeSmoothQuadTo( float x2, float y2 ) { relativeSmoothQuadTo( vec2( x2, y2 ) ); } void curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ); void curveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { curveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } + void relativeCurveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ); + void relativeCurveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { relativeCurveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } + void smoothCurveTo( const vec2 &p2, const vec2 &p3 ); + void smoothCurveTo( float x2, float y2, float x3, float y3 ) { smoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } + void relativeSmoothCurveTo( const vec2 &p2, const vec2 &p3 ); + void relativeSmoothCurveTo( float x2, float y2, float x3, float y3 ) { relativeSmoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } void arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward = true ); void arc( float centerX, float centerY, float radius, float startRadians, float endRadians, bool forward = true ) { arc( vec2( centerX, centerY ), radius, startRadians, endRadians, forward ); } void arcTo( const vec2 &p, const vec2 &t, float radius ); void arcTo( float x, float y, float tanX, float tanY, float radius) { arcTo( vec2( x, y ), vec2( tanX, tanY ), radius ); } + void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float px, float py ) { arcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( px, py ) ); } + void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float px, float py ) { relativeArcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( px, py ) ); } + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); void close(); + + //! Reverses the orientation of the shape's contours, changing CW to CCW and vice versa. + void reverse(); bool empty() const { return mContours.empty(); } void clear() { mContours.clear(); } diff --git a/src/cinder/Path2d.cpp b/src/cinder/Path2d.cpp index 1df6515042..1431c9509d 100644 --- a/src/cinder/Path2d.cpp +++ b/src/cinder/Path2d.cpp @@ -178,6 +178,24 @@ void Path2d::quadTo( const vec2 &p1, const vec2 &p2 ) mSegments.push_back( QUADTO ); } +void Path2d::smoothQuadTo( const vec2 &p2 ) +{ + if( mPoints.empty() ) + throw Path2dExc(); // can only smoothQuadTo as non-first point + + vec2 p1 = getCurrentPoint(); + + if( ! mSegments.empty() && mSegments.back() == QUADTO ) { + const vec2 &c = getPointBefore( mPoints.size() - 1 ); + p1.x = 2 * p1.x - c.x; + p1.y = 2 * p1.y - c.y; + } + + mSegments.emplace_back( QUADTO ); + mPoints.emplace_back( p1.x, p1.y ); + mPoints.emplace_back( p2.x, p2.y ); +} + void Path2d::curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) { if( mPoints.empty() ) @@ -189,6 +207,25 @@ void Path2d::curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) mSegments.push_back( CUBICTO ); } +void Path2d::smoothCurveTo( const vec2 &p2, const vec2 &p3 ) +{ + if( mPoints.empty() ) + throw Path2dExc(); // can only smoothCurveTo as non-first point + + vec2 p1 = getCurrentPoint(); + + if( ! mSegments.empty() && mSegments.back() == CUBICTO ) { + const vec2 &c = getPointBefore( mPoints.size() - 1 ); + p1.x = 2 * p1.x - c.x; + p1.y = 2 * p1.y - c.y; + } + + mSegments.emplace_back( CUBICTO ); + mPoints.emplace_back( p1.x, p1.y ); + mPoints.emplace_back( p2.x, p2.y ); + mPoints.emplace_back( p3.x, p3.y ); +} + void Path2d::arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ) { if( forward ) { @@ -264,6 +301,14 @@ void Path2d::arcSegmentAsCubicBezier( const vec2 ¢er, float radius, float st center.y + r_sin_B - h * r_cos_B, center.x + r_cos_B, center.y + r_sin_B ); } +float Path2d::angleHelper( const vec2 &u, const vec2 &v ) const +{ + // See: equation 5.4 of https://www.w3.org/TR/SVG/implnote.html + const float c = u.x * v.y - u.y * v.x; + const float d = glm::dot( glm::normalize( u ), glm::normalize( v ) ); + return c < 0 ? -math::acos( d ) : math::acos( d ); +} + // Implementation courtesy of Lennart Kudling void Path2d::arcTo( const vec2 &p1, const vec2 &t, float radius ) { @@ -338,26 +383,285 @@ void Path2d::arcTo( const vec2 &p1, const vec2 &t, float radius ) } } -void Path2d::reverse() +void Path2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) { - // The path is empty: nothing to do. - if( empty() ) - return; + // See: https://www.w3.org/TR/SVG/implnote.html - // Reverse all points. - std::reverse( mPoints.begin(), mPoints.end() ); + if( approxZero( rx ) || approxZero( ry ) ) { + return lineTo( p2 ); + } - // Reverse the segments, but skip the "moveto" and "close": - if( isClosed() ) { - // There should be at least 4 segments: "moveto", "close" and two other segments. - if( mSegments.size() > 3 ) - std::reverse( mSegments.begin() + 1, mSegments.end() - 1 ); - } - else { - // There should be at least 3 segments: "moveto" and two other segments. - if( mSegments.size() > 2 ) - std::reverse( mSegments.begin() + 1, mSegments.end() ); - } + const vec2 p1 = mPoints.back(); + + const float sinPhi = math::sin( phi ); + const float cosPhi = math::cos( phi ); + + // Step 1: move ellipse so origin will be the midpoint between p1 and p2. + vec2 mid = ( p1 - p2 ) * 0.5f; // midpoint + + const float x1p = mid.x * cosPhi + mid.y * sinPhi; // equation 5.1 + const float y1p = mid.y * cosPhi - mid.x * sinPhi; + if( approxZero( x1p ) && approxZero( y1p ) ) + return lineTo( p2 ); + + const float x1pSquared = x1p * x1p; + const float y1pSquared = y1p * y1p; + + float rxSquared = rx * rx; + float rySquared = ry * ry; + + float lambda = x1pSquared / rxSquared + y1pSquared / rySquared; // equation 6.2 + if( lambda > 1.0f ) { // equation 6.3 + lambda = math::sqrt( lambda ); + rx *= lambda; + ry *= lambda; + rxSquared = rx * rx; + rySquared = ry * ry; + } + + // Step 2: compute coordinates of the center of the ellipse. + const float x = rySquared * x1pSquared; + const float y = rxSquared * y1pSquared; + + float r = roundToZero( ( rxSquared * rySquared - y - x ) / ( y + x ) ); + r = largeArcFlag == sweepFlag ? -sqrtf( r ) : sqrtf( r ); + + float cxp = r * ( rx * y1p ) / ry; // equation 5.2 + float cyp = r * -( ry * x1p ) / rx; + + // Step 3: transform back to original coordinate system. + mid = ( p1 + p2 ) * 0.5f; + vec2 c{ cxp * cosPhi - cyp * sinPhi + mid.x, cyp * cosPhi + cxp * sinPhi + mid.y }; // equation 5.3 + + // Step 4: compute angles and number of segments. + vec2 v1{ ( x1p - cxp ) / rx, ( y1p - cyp ) / ry }; + vec2 v2{ ( -x1p - cxp ) / rx, ( -y1p - cyp ) / ry }; + + float theta = angleHelper( { 1, 0 }, v1 ); + float deltaTheta = angleHelper( v1, v2 ); + + if( !sweepFlag && deltaTheta > 0 ) + deltaTheta -= float( 2 * M_PI ); + else if( sweepFlag && deltaTheta < 0 ) + deltaTheta += float( 2 * M_PI ); + + float segments = glm::max( 1.0f, math::ceil( math::abs( deltaTheta ) / float( M_PI / 2 ) ) ); + deltaTheta /= segments; + + float h = 4.0f / 3.0f * math::tan( deltaTheta / 4 ); + + // Step 5: generate cubic bezier curve segments. + for( int i = 0; i < int( segments ); ++i ) { + float x1 = roundToZero( math::cos( theta ) ); + float y1 = roundToZero( math::sin( theta ) ); + + theta += deltaTheta; + + float x2 = roundToZero( math::cos( theta ) ); + float y2 = roundToZero( math::sin( theta ) ); + + vec2 c1{ rx * ( x1 - y1 * h ), ry * ( y1 + x1 * h ) }; + vec2 c2{ rx * ( x2 + y2 * h ), ry * ( y2 - x2 * h ) }; + vec2 c3{ rx * x2, ry * y2 }; + + c1 = glm::rotate( c1, phi ) + c; + c2 = glm::rotate( c2, phi ) + c; + c3 = glm::rotate( c3, phi ) + c; + + curveTo( c1, c2, c3 ); + } +} + +void Path2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +{ + const auto &pt = getCurrentPoint(); + arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + p2 ); +} + +Path2d Path2d::circle( const vec2 ¢er, float radius ) +{ + Path2d shape; + shape.moveTo( center.x + radius, center.y ); + shape.relativeArcTo( radius, radius, 0, false, true, vec2( -( radius + radius ), 0 ) ); + shape.relativeArcTo( radius, radius, 0, false, true, vec2( +( radius + radius ), 0 ) ); + return shape; +} + +Path2d Path2d::ellipse( const vec2 ¢er, float radiusX, float radiusY ) +{ + Path2d shape; + shape.moveTo( center.x + radiusX, center.y ); + shape.relativeArcTo( radiusX, radiusY, 0, false, true, vec2( -( radiusX + radiusX ), 0 ) ); + shape.relativeArcTo( radiusX, radiusY, 0, false, true, vec2( +( radiusX + radiusX ), 0 ) ); + return shape; +} + +Path2d Path2d::line( const vec2 &p0, const vec2 &p1 ) +{ + Path2d shape; + shape.moveTo( p0 ); + shape.lineTo( p1 ); + return shape; +} + +Path2d Path2d::polygon( const std::vector &points, bool closed ) +{ + if( points.size() < 2 ) + throw Path2dExc(); // + + Path2d shape; + + auto itr = points.begin(); + shape.moveTo( *itr++ ); + while( itr != points.end() ) + shape.lineTo( *itr++ ); + + if( closed ) + shape.close(); + + return shape; +} + +Path2d Path2d::rectangle( float x, float y, float width, float height ) +{ + Path2d shape; + shape.moveTo( x, y ); + shape.lineTo( x + width, y ); + shape.lineTo( x + width, y + height ); + shape.lineTo( x, y + height ); + shape.close(); + return shape; +} + +Path2d Path2d::roundedRectangle( float x, float y, float width, float height, float rx, float ry ) +{ + if( approxZero( rx ) || approxZero( ry ) ) + return rectangle( x, y, width, height ); + + Path2d shape; + shape.moveTo( x + rx, y ); + shape.lineTo( x + width - rx, y ); + shape.arcTo( rx, ry, 0, false, true, vec2( x + width, y + ry ) ); + shape.lineTo( x + width, y + height - ry ); + shape.arcTo( rx, ry, 0, false, true, vec2( x + width - rx, y + height ) ); + shape.lineTo( x + rx, y + height ); + shape.arcTo( rx, ry, 0, false, true, vec2( x, y + height - ry ) ); + shape.lineTo( x, y + ry ); + shape.arcTo( rx, ry, 0, false, true, vec2( x + rx, y ) ); + return shape; +} + +Path2d Path2d::star( const vec2 ¢er, int points, float largeRadius, float smallRadius, float rotation ) +{ + const float step = glm::radians( 180.0f / float( points ) ); + + Path2d shape; + for( int i = 0; i < 2 * points; i += 2 ) { + float x = center.x + largeRadius * glm::sin( rotation + float( i + 0 ) * step ); + float y = center.y - largeRadius * glm::cos( rotation + float( i + 0 ) * step ); + if( i == 0 ) + shape.moveTo( x, y ); + else + shape.lineTo( x, y ); + x = center.x + smallRadius * glm::sin( rotation + float( i + 1 ) * step ); + y = center.y - smallRadius * glm::cos( rotation + float( i + 1 ) * step ); + shape.lineTo( x, y ); + } + shape.close(); + return shape; +} + +Path2d Path2d::arrow( const vec2 &p0, const vec2 &p1, float thickness, float width, float length, float concavity ) +{ + const float distance = glm::distance( p1, p0 ); + const vec2 direction = ( p1 - p0 ) / distance; + const vec2 normal{ 0.5f * thickness * direction.y, -0.5f * thickness * direction.x }; + + vec2 base = p0 + direction * glm::max( 0.0f, distance - thickness * length ); + + Path2d shape; + shape.moveTo( p0 - normal ); + shape.lineTo( base - normal + direction * thickness * length * concavity ); + shape.lineTo( base - normal * width ); + shape.lineTo( p1 ); + shape.lineTo( base + normal * width ); + shape.lineTo( base + normal + direction * thickness * length * concavity ); + shape.lineTo( p0 + normal ); + shape.close(); + return shape; +} + +Path2d Path2d::spiral( const vec2 ¢er, float innerRadius, float outerRadius, float spacing, float offset ) +{ + // Helper struct + struct Point { + float x; + float y; + float theta; + float tangent; + + explicit Point( float theta, float offset = 0 ) + : theta( theta ) + { + float c = math::cos( theta + offset ); + float s = math::sin( theta + offset ); + x = theta * c; + y = theta * s; + tangent = math::atan2( s + x, c - y ); + } + + std::pair generate( const Point &previous ) const + { + const auto offset = 4 * math::tan( ( theta - previous.theta ) / 4 ) / 3; + const auto p1 = vec2( math::cos( previous.tangent ) * offset * previous.theta + previous.x, math::sin( previous.tangent ) * offset * previous.theta + previous.y ); + const auto p2 = vec2( math::cos( tangent - float( M_PI ) ) * offset * theta + x, math::sin( tangent - float( M_PI ) ) * offset * theta + y ); + return std::make_pair( p1, p2 ); + } + }; + + const auto step = spacing / ( 2.0f * float( M_PI ) ); + const auto radiansStart = glm::radians( 360 * innerRadius / spacing ); + const auto radiansEnd = glm::radians( 360 * outerRadius / spacing ); + + Point p0( radiansStart, offset - radiansStart ); + + Path2d shape; + shape.moveTo( center.x + p0.x * step, center.y + p0.y * step ); + + float radians = radiansStart + glm::radians( clamp( radiansStart * step, 3.0f, 90.0f ) ); // Adaptive step size. + while( radians < radiansEnd ) { + const auto p3 = Point( radians, offset - radiansStart ); + const auto controls = p3.generate( p0 ); + shape.curveTo( center.x + controls.first.x * step, center.y + controls.first.y * step, center.x + controls.second.x * step, center.y + controls.second.y * step, center.x + p3.x * step, center.y + p3.y * step ); + + p0 = p3; + + radians += glm::radians( glm::clamp( radians * step, 3.0f, 90.0f ) ); // Adaptive step size. + } + + const auto p3 = Point( radiansEnd, offset - radiansStart ); + const auto controls = p3.generate( p0 ); + + shape.curveTo( center.x + controls.first.x * step, center.y + controls.first.y * step, center.x + controls.second.x * step, center.y + controls.second.y * step, center.x + p3.x * step, center.y + p3.y * step ); + + return shape; +} + +void Path2d::reverse() +{ + // The path is empty: nothing to do. + if( empty() ) + return; + + // Reverse all points. + std::reverse( mPoints.begin(), mPoints.end() ); + + if( isClosed() && mSegments.size() > 2 ) { + std::reverse( mSegments.begin(), mSegments.end() - 1 ); + } + else if( ! isClosed() && mSegments.size() > 1 ) { + std::reverse( mSegments.begin(), mSegments.end() ); + } } void Path2d::appendSegment( SegmentType segmentType, const vec2 *points ) @@ -1004,6 +1308,23 @@ Rectf Path2d::calcPreciseBoundingBox() const return result; } +bool Path2d::calcClockwise() const +{ + // See: https://en.wikipedia.org/wiki/Curve_orientation + size_t index = 0; + for( size_t i = 1; i < mPoints.size(); ++i ) { + if( mPoints.at( i ).x < mPoints.at( index ).x || ( approxEqual( mPoints.at( i ).x, mPoints.at( index ).x ) && mPoints.at( i ).y < mPoints.at( index ).y ) ) + index = i; + } + + const auto &a = getPoint(index); + const auto &b = getPointBefore(index); + const auto &c = getPointAfter(index); + const auto sign = glm::sign( (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y) ); + + return sign < 0; +} + namespace { float calcCubicBezierSpeed( const vec2 p[3], float t ) { diff --git a/src/cinder/Shape2d.cpp b/src/cinder/Shape2d.cpp index cf10fa6f9d..14ff2d8f9e 100644 --- a/src/cinder/Shape2d.cpp +++ b/src/cinder/Shape2d.cpp @@ -34,21 +34,91 @@ void Shape2d::moveTo( const vec2 &p ) mContours.back().moveTo( p ); } +void Shape2d::relativeMoveTo( const vec2 &p ) +{ + const auto &pt = getCurrentPoint(); + moveTo( pt + p ); +} + void Shape2d::lineTo( const vec2 &p ) { mContours.back().lineTo( p ); } +void Shape2d::relativeLineTo( const vec2 &p ) +{ + const auto &pt = getCurrentPoint(); + lineTo( pt + p ); +} + +void Shape2d::horizontalLineTo( float x ) +{ + const auto &pt = getCurrentPoint(); + lineTo( x, pt.y ); +} + +void Shape2d::relativeHorizontalLineTo( float dx ) +{ + const auto &pt = getCurrentPoint(); + lineTo( pt.x + dx, pt.y ); +} + +void Shape2d::verticalLineTo( float y ) +{ + const auto &pt = getCurrentPoint(); + lineTo( pt.x, y ); +} + +void Shape2d::relativeVerticalLineTo( float dy ) +{ + const auto &pt = getCurrentPoint(); + lineTo( pt.x, pt.y + dy ); +} + void Shape2d::quadTo( const vec2 &p1, const vec2 &p2 ) { mContours.back().quadTo( p1, p2 ); } +void Shape2d::relativeQuadTo( const vec2 &p1, const vec2 &p2 ) +{ + const auto &pt = getCurrentPoint(); + quadTo( pt + p1, pt + p2 ); +} + +void Shape2d::smoothQuadTo( const vec2 &p2 ) +{ + mContours.back().smoothQuadTo( p2 ); +} + +void Shape2d::relativeSmoothQuadTo( const vec2 &p2 ) +{ + const auto &pt = getCurrentPoint(); + smoothQuadTo( pt + p2 ); +} + void Shape2d::curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) { mContours.back().curveTo( p1, p2, p3 ); } +void Shape2d::relativeCurveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) +{ + const auto &pt = getCurrentPoint(); + curveTo( pt + p1, pt + p2, pt + p3 ); +} + +void Shape2d::smoothCurveTo( const vec2 &p2, const vec2 &p3 ) +{ + mContours.back().smoothCurveTo( p2, p3 ); +} + +void Shape2d::relativeSmoothCurveTo( const vec2 &p2, const vec2 &p3 ) +{ + const auto &pt = getCurrentPoint(); + smoothCurveTo( pt + p2, pt + p3 ); +} + void Shape2d::arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ) { if( mContours.empty() ) @@ -61,12 +131,29 @@ void Shape2d::arcTo( const vec2 &p, const vec2 &t, float radius ) mContours.back().arcTo( p, t, radius ); } +void Shape2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +{ + mContours.back().arcTo( rx, ry, phi, largeArcFlag, sweepFlag, p2 ); +} + +void Shape2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +{ + const auto &pt = getCurrentPoint(); + arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + p2 ); +} + void Shape2d::close() { if( ! mContours.empty() ) mContours.back().close(); } +void Shape2d::reverse() +{ + for( auto &contour : mContours ) + contour.reverse(); +} + void Shape2d::append( const Shape2d &shape ) { for( vector::const_iterator pathIt = shape.getContours().begin(); pathIt != shape.getContours().end(); ++pathIt ) From cfbd3aff31d9a1d288c5860e45485bfa0fb0eef9 Mon Sep 17 00:00:00 2001 From: paulhoux Date: Tue, 2 Jan 2024 13:01:06 +0100 Subject: [PATCH 2/5] Added relative functions to Path2d and refactored things. --- include/cinder/Path2d.h | 21 ++++++++- include/cinder/Shape2d.h | 33 +++++++------- src/cinder/Path2d.cpp | 66 ++++++++++++++++++++++++++-- src/cinder/Shape2d.cpp | 92 ++++++++++++++++++++-------------------- 4 files changed, 146 insertions(+), 66 deletions(-) diff --git a/include/cinder/Path2d.h b/include/cinder/Path2d.h index 492b92fd26..828c881566 100644 --- a/include/cinder/Path2d.h +++ b/include/cinder/Path2d.h @@ -43,6 +43,8 @@ class CI_API Path2d { void moveTo( float x, float y ) { moveTo( vec2( x, y ) ); } void lineTo( const vec2 &p ); void lineTo( float x, float y ) { lineTo( vec2( x, y ) ); } + void horizontalLineTo( float x ); + void verticalLineTo( float y ); void quadTo( const vec2 &p1, const vec2 &p2 ); void quadTo( float x1, float y1, float x2, float y2 ) { quadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } void smoothQuadTo( const vec2 &p2 ); @@ -56,7 +58,24 @@ class CI_API Path2d { void arcTo( const vec2 &p, const vec2 &t, float radius ); void arcTo( float x, float y, float tanX, float tanY, float radius) { arcTo( vec2( x, y ), vec2( tanX, tanY ), radius ); } void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); - void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); + + //! + void relativeMoveTo( float dx, float dy ) { relativeMoveTo( vec2( dx, dy ) ); } + void relativeMoveTo( const vec2 &delta ); + void relativeLineTo( float dx, float dy ) { relativeLineTo( vec2( dx, dy ) ); } + void relativeLineTo( const vec2 &delta ); + void relativeHorizontalLineTo( float dx ); + void relativeVerticalLineTo( float dy ); + void relativeQuadTo( float dx1, float dy1, float dx2, float dy2 ) { relativeQuadTo( vec2( dx1, dy1 ), vec2( dx2, dy2 ) ); } + void relativeQuadTo( const vec2 &delta1, const vec2 &delta2 ); + void relativeSmoothQuadTo( float dx, float dy ) { relativeSmoothQuadTo( vec2( dx, dy ) ); } + void relativeSmoothQuadTo( const vec2 &delta ); + void relativeCurveTo( float dx1, float dy1, float dx2, float dy2, float dx3, float dy3 ) { relativeCurveTo( vec2( dx1, dy1 ), vec2( dx2, dy2 ), vec2( dx3, dy3 ) ); } + void relativeCurveTo( const vec2 &delta1, const vec2 &delta2, const vec2 &delta3 ); + void relativeSmoothCurveTo( float dx2, float dy2, float dx3, float dy3 ) { relativeSmoothCurveTo( vec2( dx2, dy2 ), vec2( dx3, dy3 ) ); } + void relativeSmoothCurveTo( const vec2 &delta2, const vec2 &delta3 ); + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float dx, float dy ) { relativeArcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( dx, dy ) ); } + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &delta ); //! Closes the path, by drawing a straight line from the first to the last point. This is only legal as the last command. void close() { mSegments.push_back( CLOSE ); } diff --git a/include/cinder/Shape2d.h b/include/cinder/Shape2d.h index cbfccef6d3..f26f07e91a 100644 --- a/include/cinder/Shape2d.h +++ b/include/cinder/Shape2d.h @@ -35,42 +35,43 @@ class CI_API Shape2d { public: void moveTo( const vec2 &p ); void moveTo( float x, float y ) { moveTo( vec2( x, y ) ); } - void relativeMoveTo( const vec2 &p ); - void relativeMoveTo( float dx, float dy ) { relativeMoveTo( vec2( dx, dy ) ); } void lineTo( const vec2 &p ); void lineTo( float x, float y ) { lineTo( vec2( x, y ) ); } - void relativeLineTo( const vec2 &p ); - void relativeLineTo( float dx, float dy ) { relativeLineTo( vec2( dx, dy ) ); } void horizontalLineTo( float x ); - void relativeHorizontalLineTo( float dx ); void verticalLineTo( float y ); - void relativeVerticalLineTo( float dy ); void quadTo( const vec2 &p1, const vec2 &p2 ); void quadTo( float x1, float y1, float x2, float y2 ) { quadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } - void relativeQuadTo( const vec2 &p1, const vec2 &p2 ); - void relativeQuadTo( float x1, float y1, float x2, float y2 ) { relativeQuadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } void smoothQuadTo( const vec2 &p2 ); void smoothQuadTo( float x2, float y2 ) { smoothQuadTo( vec2( x2, y2 ) ); } - void relativeSmoothQuadTo( const vec2 &p2 ); - void relativeSmoothQuadTo( float x2, float y2 ) { relativeSmoothQuadTo( vec2( x2, y2 ) ); } void curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ); void curveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { curveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } - void relativeCurveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ); - void relativeCurveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { relativeCurveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } void smoothCurveTo( const vec2 &p2, const vec2 &p3 ); void smoothCurveTo( float x2, float y2, float x3, float y3 ) { smoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } - void relativeSmoothCurveTo( const vec2 &p2, const vec2 &p3 ); - void relativeSmoothCurveTo( float x2, float y2, float x3, float y3 ) { relativeSmoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } void arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward = true ); void arc( float centerX, float centerY, float radius, float startRadians, float endRadians, bool forward = true ) { arc( vec2( centerX, centerY ), radius, startRadians, endRadians, forward ); } void arcTo( const vec2 &p, const vec2 &t, float radius ); void arcTo( float x, float y, float tanX, float tanY, float radius) { arcTo( vec2( x, y ), vec2( tanX, tanY ), radius ); } void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float px, float py ) { arcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( px, py ) ); } void arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); - void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float px, float py ) { relativeArcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( px, py ) ); } - void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ); void close(); + void relativeMoveTo( const vec2 &p ); + void relativeMoveTo( float dx, float dy ) { relativeMoveTo( vec2( dx, dy ) ); } + void relativeLineTo( const vec2 &delta ); + void relativeLineTo( float dx, float dy ) { relativeLineTo( vec2( dx, dy ) ); } + void relativeHorizontalLineTo( float dx ); + void relativeVerticalLineTo( float dy ); + void relativeQuadTo( const vec2 &delta1, const vec2 &delta2 ); + void relativeQuadTo( float x1, float y1, float x2, float y2 ) { relativeQuadTo( vec2( x1, y1 ), vec2( x2, y2 ) ); } + void relativeSmoothQuadTo( const vec2 &delta ); + void relativeSmoothQuadTo( float x2, float y2 ) { relativeSmoothQuadTo( vec2( x2, y2 ) ); } + void relativeCurveTo( const vec2 &delta1, const vec2 &delta2, const vec2 &delta3 ); + void relativeCurveTo( float x1, float y1, float x2, float y2, float x3, float y3 ) { relativeCurveTo( vec2( x1, y1 ), vec2( x2, y2 ), vec2( x3, y3 ) ); } + void relativeSmoothCurveTo( const vec2 &delta2, const vec2 &delta3 ); + void relativeSmoothCurveTo( float x2, float y2, float x3, float y3 ) { relativeSmoothCurveTo( vec2( x2, y2 ), vec2( x3, y3 ) ); } + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, float px, float py ) { relativeArcTo( rx, ry, phi, largeArcFlag, sweepFlag, vec2( px, py ) ); } + void relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &delta ); + //! Reverses the orientation of the shape's contours, changing CW to CCW and vice versa. void reverse(); diff --git a/src/cinder/Path2d.cpp b/src/cinder/Path2d.cpp index 1431c9509d..760368bb2f 100644 --- a/src/cinder/Path2d.cpp +++ b/src/cinder/Path2d.cpp @@ -168,6 +168,18 @@ void Path2d::lineTo( const vec2 &p ) mSegments.push_back( LINETO ); } +void Path2d::horizontalLineTo( float x ) +{ + const vec2 &pt = getCurrentPoint(); + lineTo( x, pt.y ); +} + +void Path2d::verticalLineTo( float y ) +{ + const vec2 &pt = getCurrentPoint(); + lineTo( pt.x, y ); +} + void Path2d::quadTo( const vec2 &p1, const vec2 &p2 ) { if( mPoints.empty() ) @@ -472,10 +484,58 @@ void Path2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweep } } -void Path2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) -{ +void Path2d::relativeMoveTo( const vec2 &delta ) +{ + const auto &pt = getCurrentPoint(); + moveTo( pt + delta ); +} + +void Path2d::relativeLineTo( const vec2 &delta ) +{ + const auto &pt = getCurrentPoint(); + lineTo( pt + delta ); +} + +void Path2d::relativeHorizontalLineTo( float dx ) +{ + const auto &pt = getCurrentPoint(); + horizontalLineTo( pt.x + dx ); +} + +void Path2d::relativeVerticalLineTo( float dy ) +{ + const auto &pt = getCurrentPoint(); + verticalLineTo( pt.y + dy ); +} + +void Path2d::relativeQuadTo( const vec2 &delta1, const vec2 &delta2 ) +{ + const auto &pt = getCurrentPoint(); + quadTo( pt + delta1, pt + delta2 ); +} + +void Path2d::relativeSmoothQuadTo( const vec2 &delta ) +{ + const auto &pt = getCurrentPoint(); + smoothQuadTo( pt + delta ); +} + +void Path2d::relativeCurveTo( const vec2 &delta1, const vec2 &delta2, const vec2 &delta3 ) +{ + const auto &pt = getCurrentPoint(); + curveTo( pt + delta1, pt + delta2, pt + delta3 ); +} + +void Path2d::relativeSmoothCurveTo( const vec2 &delta2, const vec2 &delta3 ) +{ + const auto &pt = getCurrentPoint(); + smoothCurveTo( pt + delta2, pt + delta3 ); +} + +void Path2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &delta ) +{ const auto &pt = getCurrentPoint(); - arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + p2 ); + arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + delta ); } Path2d Path2d::circle( const vec2 ¢er, float radius ) diff --git a/src/cinder/Shape2d.cpp b/src/cinder/Shape2d.cpp index 14ff2d8f9e..47601860ef 100644 --- a/src/cinder/Shape2d.cpp +++ b/src/cinder/Shape2d.cpp @@ -34,112 +34,112 @@ void Shape2d::moveTo( const vec2 &p ) mContours.back().moveTo( p ); } -void Shape2d::relativeMoveTo( const vec2 &p ) -{ - const auto &pt = getCurrentPoint(); - moveTo( pt + p ); -} - void Shape2d::lineTo( const vec2 &p ) { mContours.back().lineTo( p ); } -void Shape2d::relativeLineTo( const vec2 &p ) +void Shape2d::horizontalLineTo( float x ) { const auto &pt = getCurrentPoint(); - lineTo( pt + p ); + lineTo( x, pt.y ); } -void Shape2d::horizontalLineTo( float x ) +void Shape2d::verticalLineTo( float y ) { const auto &pt = getCurrentPoint(); - lineTo( x, pt.y ); + lineTo( pt.x, y ); } -void Shape2d::relativeHorizontalLineTo( float dx ) +void Shape2d::quadTo( const vec2 &p1, const vec2 &p2 ) { - const auto &pt = getCurrentPoint(); - lineTo( pt.x + dx, pt.y ); + mContours.back().quadTo( p1, p2 ); } -void Shape2d::verticalLineTo( float y ) +void Shape2d::smoothQuadTo( const vec2 &p2 ) { - const auto &pt = getCurrentPoint(); - lineTo( pt.x, y ); + mContours.back().smoothQuadTo( p2 ); } -void Shape2d::relativeVerticalLineTo( float dy ) +void Shape2d::curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) { - const auto &pt = getCurrentPoint(); - lineTo( pt.x, pt.y + dy ); + mContours.back().curveTo( p1, p2, p3 ); } -void Shape2d::quadTo( const vec2 &p1, const vec2 &p2 ) +void Shape2d::smoothCurveTo( const vec2 &p2, const vec2 &p3 ) { - mContours.back().quadTo( p1, p2 ); + mContours.back().smoothCurveTo( p2, p3 ); } -void Shape2d::relativeQuadTo( const vec2 &p1, const vec2 &p2 ) +void Shape2d::arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ) { - const auto &pt = getCurrentPoint(); - quadTo( pt + p1, pt + p2 ); + if( mContours.empty() ) + mContours.push_back( Path2d() ); + mContours.back().arc( center, radius, startRadians, endRadians, forward ); } -void Shape2d::smoothQuadTo( const vec2 &p2 ) +void Shape2d::arcTo( const vec2 &p, const vec2 &t, float radius ) { - mContours.back().smoothQuadTo( p2 ); + mContours.back().arcTo( p, t, radius ); } -void Shape2d::relativeSmoothQuadTo( const vec2 &p2 ) +void Shape2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +{ + mContours.back().arcTo( rx, ry, phi, largeArcFlag, sweepFlag, p2 ); +} + +void Shape2d::relativeMoveTo( const vec2 &p ) { const auto &pt = getCurrentPoint(); - smoothQuadTo( pt + p2 ); + moveTo( pt + p ); } -void Shape2d::curveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) +void Shape2d::relativeLineTo( const vec2 &delta ) { - mContours.back().curveTo( p1, p2, p3 ); + const auto &pt = getCurrentPoint(); + lineTo( pt + delta ); } -void Shape2d::relativeCurveTo( const vec2 &p1, const vec2 &p2, const vec2 &p3 ) +void Shape2d::relativeHorizontalLineTo( float dx ) { const auto &pt = getCurrentPoint(); - curveTo( pt + p1, pt + p2, pt + p3 ); + lineTo( pt.x + dx, pt.y ); } -void Shape2d::smoothCurveTo( const vec2 &p2, const vec2 &p3 ) +void Shape2d::relativeVerticalLineTo( float dy ) { - mContours.back().smoothCurveTo( p2, p3 ); + const auto &pt = getCurrentPoint(); + lineTo( pt.x, pt.y + dy ); } -void Shape2d::relativeSmoothCurveTo( const vec2 &p2, const vec2 &p3 ) +void Shape2d::relativeQuadTo( const vec2 &delta1, const vec2 &delta2 ) { const auto &pt = getCurrentPoint(); - smoothCurveTo( pt + p2, pt + p3 ); + quadTo( pt + delta1, pt + delta2 ); } -void Shape2d::arc( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ) +void Shape2d::relativeSmoothQuadTo( const vec2 &delta ) { - if( mContours.empty() ) - mContours.push_back( Path2d() ); - mContours.back().arc( center, radius, startRadians, endRadians, forward ); + const auto &pt = getCurrentPoint(); + smoothQuadTo( pt + delta ); } -void Shape2d::arcTo( const vec2 &p, const vec2 &t, float radius ) +void Shape2d::relativeCurveTo( const vec2 &delta1, const vec2 &delta2, const vec2 &delta3 ) { - mContours.back().arcTo( p, t, radius ); + const auto &pt = getCurrentPoint(); + curveTo( pt + delta1, pt + delta2, pt + delta3 ); } -void Shape2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +void Shape2d::relativeSmoothCurveTo( const vec2 &delta2, const vec2 &delta3 ) { - mContours.back().arcTo( rx, ry, phi, largeArcFlag, sweepFlag, p2 ); + const auto &pt = getCurrentPoint(); + smoothCurveTo( pt + delta2, pt + delta3 ); } -void Shape2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) +void Shape2d::relativeArcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &delta ) { const auto &pt = getCurrentPoint(); - arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + p2 ); + arcTo( rx, ry, phi, largeArcFlag, sweepFlag, pt + delta ); } void Shape2d::close() From 0a0e8957d3d812d4ea9e9db76d76029cfeaea2a2 Mon Sep 17 00:00:00 2001 From: paulhoux Date: Tue, 2 Jan 2024 13:26:19 +0100 Subject: [PATCH 3/5] Closed the rounded rectangle in Path2d. --- src/cinder/Path2d.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cinder/Path2d.cpp b/src/cinder/Path2d.cpp index 760368bb2f..acc94737e9 100644 --- a/src/cinder/Path2d.cpp +++ b/src/cinder/Path2d.cpp @@ -608,6 +608,7 @@ Path2d Path2d::roundedRectangle( float x, float y, float width, float height, fl shape.arcTo( rx, ry, 0, false, true, vec2( x, y + height - ry ) ); shape.lineTo( x, y + ry ); shape.arcTo( rx, ry, 0, false, true, vec2( x + rx, y ) ); + shape.close(); return shape; } From 8b713369278566bcc5edcfc0e4d62b6c876a6d62 Mon Sep 17 00:00:00 2001 From: paulhoux Date: Wed, 3 Jan 2024 15:01:09 +0100 Subject: [PATCH 4/5] Tweaked the Path2d::spiral to get rid of a wobble. --- src/cinder/Path2d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cinder/Path2d.cpp b/src/cinder/Path2d.cpp index acc94737e9..83a1ff8a84 100644 --- a/src/cinder/Path2d.cpp +++ b/src/cinder/Path2d.cpp @@ -689,7 +689,7 @@ Path2d Path2d::spiral( const vec2 ¢er, float innerRadius, float outerRadius, Path2d shape; shape.moveTo( center.x + p0.x * step, center.y + p0.y * step ); - float radians = radiansStart + glm::radians( clamp( radiansStart * step, 3.0f, 90.0f ) ); // Adaptive step size. + float radians = radiansStart + glm::radians( clamp( radiansStart * step, 3.0f, 60.0f ) ); // Adaptive step size. while( radians < radiansEnd ) { const auto p3 = Point( radians, offset - radiansStart ); const auto controls = p3.generate( p0 ); @@ -697,7 +697,7 @@ Path2d Path2d::spiral( const vec2 ¢er, float innerRadius, float outerRadius, p0 = p3; - radians += glm::radians( glm::clamp( radians * step, 3.0f, 90.0f ) ); // Adaptive step size. + radians += glm::radians( glm::clamp( radians * step, 3.0f, 60.0f ) ); // Adaptive step size. } const auto p3 = Point( radiansEnd, offset - radiansStart ); From 76ddae2d3695eeb71205dc024552b3c23976300d Mon Sep 17 00:00:00 2001 From: paulhoux Date: Mon, 8 Jan 2024 16:25:39 +0100 Subject: [PATCH 5/5] Path2d::angleHelper() is now an anonymous namespace function. --- include/cinder/Path2d.h | 4 +--- src/cinder/Path2d.cpp | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/cinder/Path2d.h b/include/cinder/Path2d.h index 828c881566..5efb2cb601 100644 --- a/include/cinder/Path2d.h +++ b/include/cinder/Path2d.h @@ -222,9 +222,7 @@ class CI_API Path2d { private: void arcHelper( const vec2 ¢er, float radius, float startRadians, float endRadians, bool forward ); void arcSegmentAsCubicBezier( const vec2 ¢er, float radius, float startRadians, float endRadians ); - - float angleHelper( const vec2 &u, const vec2 &v ) const; - + //! Returns the minimum distance from point \a pt to segment \a segment. The \a firstPoint parameter can be used as an optimization if known, otherwise pass 0. float calcDistance( const vec2 &pt, size_t segment, size_t firstPoint ) const; diff --git a/src/cinder/Path2d.cpp b/src/cinder/Path2d.cpp index 83a1ff8a84..4f231db7ca 100644 --- a/src/cinder/Path2d.cpp +++ b/src/cinder/Path2d.cpp @@ -313,14 +313,6 @@ void Path2d::arcSegmentAsCubicBezier( const vec2 ¢er, float radius, float st center.y + r_sin_B - h * r_cos_B, center.x + r_cos_B, center.y + r_sin_B ); } -float Path2d::angleHelper( const vec2 &u, const vec2 &v ) const -{ - // See: equation 5.4 of https://www.w3.org/TR/SVG/implnote.html - const float c = u.x * v.y - u.y * v.x; - const float d = glm::dot( glm::normalize( u ), glm::normalize( v ) ); - return c < 0 ? -math::acos( d ) : math::acos( d ); -} - // Implementation courtesy of Lennart Kudling void Path2d::arcTo( const vec2 &p1, const vec2 &t, float radius ) { @@ -395,6 +387,18 @@ void Path2d::arcTo( const vec2 &p1, const vec2 &t, float radius ) } } +namespace { + +float angleHelper( const vec2 &u, const vec2 &v ) +{ + // See: equation 5.4 of https://www.w3.org/TR/SVG/implnote.html + const float c = u.x * v.y - u.y * v.x; + const float d = glm::dot( glm::normalize( u ), glm::normalize( v ) ); + return c < 0 ? -math::acos( d ) : math::acos( d ); +} + +} // namespace + void Path2d::arcTo( float rx, float ry, float phi, bool largeArcFlag, bool sweepFlag, const vec2 &p2 ) { // See: https://www.w3.org/TR/SVG/implnote.html