void CanvasCairo::pathAltArcTo(float rx, float ry, float angle, bool largeArc, bool sweep, float x, float y) { double _x, _y; cairo_get_current_point (_context, &_x, &_y); rx = fabsf(rx); ry = fabsf(ry); Mat4 transform(Mat4::IDENTITY); Mat4::createRotationZ(angle, &transform); Mat4 inverse = transform; inverse.transpose(); Vec2 vDash(cocos2d::PointApplyTransform(Vec2((_x - x) / 2, (_y - y) / 2), inverse)); float lambda = (vDash.x * vDash.x) / (rx * rx) + (vDash.y * vDash.y) / (ry * ry); if (lambda > 1.0f) { rx = sqrtf(lambda) * rx; ry = sqrtf(lambda) * ry; vDash = Vec2(cocos2d::PointApplyTransform(Vec2((_x - x) / 2, (_y - y) / 2), inverse)); } float rx_y1_ = (rx * rx * vDash.y * vDash.y); float ry_x1_ = (ry * ry * vDash.x * vDash.x); float cst = sqrtf(((rx * rx * ry * ry) - rx_y1_ - ry_x1_) / (rx_y1_ + ry_x1_)); Vec2 cDash((largeArc != sweep ? 1.0f : -1.0f) * cst * rx * vDash.y / ry, (largeArc != sweep ? 1.0f : -1.0f) * - cst * ry * vDash.x / rx); float cx = cDash.x + (_x + x) / 2; float cy = cDash.y + (_y + y) / 2; float startAngle = Vec2::angle(Vec2(1.0f, 0.0f), Vec2((vDash.x - cDash.x) / rx, (vDash.y - cDash.y) / ry)); float sweepAngle = Vec2::angle(Vec2((vDash.x - cDash.x) / rx, (vDash.y - cDash.y) / ry), Vec2((-vDash.x - cDash.x) / rx, (-vDash.y - cDash.y) / ry)); pathArcTo(cx, cy, rx, ry, startAngle, sweepAngle, angle); }
// Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius void spiralArcTo( QPainterPath& path, const QPointF& center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction ) { // start point QPointF A = circlePoint( center, startRadius, startAngle ); // end point QPointF B = circlePoint( center, endRadius, endAngle ); // middle points qreal deltaAngle; deltaAngle = endAngle - startAngle; if ( direction * deltaAngle < 0.0 ) deltaAngle = deltaAngle + direction * 2 * M_PI; QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle ); QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle ); QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle ); qreal cRadius; QPointF cCenter; // first circle arc if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) ) { // aligned points => draw a straight line path.lineTo( I2 ); } else { // angles in the new circle qreal a1 = atan2( cCenter.y() - A.y(), A.x() - cCenter.x() ); qreal a2 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() ); pathArcTo( path, cCenter, cRadius, a1, a2, direction ); } // second circle arc if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) ) { // aligned points => draw a straight line path.lineTo( B ); } else { // angles in the new circle qreal a1 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() ); qreal a2 = atan2( cCenter.y() - B.y(), B.x() - cCenter.x() ); pathArcTo( path, cCenter, cRadius, a1, a2, direction ); } }
QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd, qreal startWidth, qreal width, qreal headSize, QgsArrowSymbolLayer::HeadType headType, qreal offset ) { qreal circleRadius; QPointF circleCenter; if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) || circleRadius > 10000.0 ) { // aligned points => draw a straight arrow return straightArrow( po, pd, startWidth, width, headSize, headType, offset ); } // angles of each point qreal angle_o = clampAngle( atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) ); qreal angle_m = clampAngle( atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) ); qreal angle_d = clampAngle( atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) ); // arc direction : 1 = counter-clockwise, -1 = clockwise int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1; qreal deltaAngle = angle_d - angle_o; if ( direction * deltaAngle < 0.0 ) deltaAngle = deltaAngle + direction * 2 * M_PI; qreal length = euclidian_distance( po, pd ); // for close points and deltaAngle < 180, draw a straight line if ( fabs( deltaAngle ) < M_PI && ((( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headSize ) ) || (( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headSize ) ) || (( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2*headSize ) ) ) ) { return straightArrow( po, pd, startWidth, width, headSize, headType, offset ); } // ajust coordinates to include offset circleRadius += offset; po = circlePoint( circleCenter, circleRadius, angle_o ); pm = circlePoint( circleCenter, circleRadius, angle_m ); pd = circlePoint( circleCenter, circleRadius, angle_d ); qreal headAngle = direction * atan( headSize / circleRadius ); QPainterPath path; if ( headType == QgsArrowSymbolLayer::HeadDouble ) { // the first head path.moveTo( po ); path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_o + headAngle ) ); pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction ); // the second head path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_d - headAngle ) ); path.lineTo( pd ); path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_d - headAngle ) ); pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction ); // the end of the first head path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_o + headAngle ) ); path.lineTo( po ); } else if ( headType == QgsArrowSymbolLayer::HeadSingle ) { path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) ); spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction ); // the arrow head path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_d - headAngle ) ); path.lineTo( pd ); path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_d - headAngle ) ); spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction ); path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) ); } else if ( headType == QgsArrowSymbolLayer::HeadReversed ) { path.moveTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) ); spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction ); path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) ); spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction ); path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_o + headAngle ) ); path.lineTo( po ); path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_o + headAngle ) ); path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) ); } return path.toSubpathPolygons().at( 0 ); }