2018年4月1日日曜日

cairo_arc_to() #2

cairo_arc_to() #1

一応動作確認をした、自家製版コードを以下に示す。
cairo_arc_to()はすでに存在しているようなので、自分で作ったものはpseudo_arc_to()とした。
実際に、円弧ではなくベジエ曲線なので、擬似円弧だ。
#include <gtk/gtk.h>
#include <glib.h>
#include <math.h>

/*----------------------------------------------*/
/*   Get difference of angle between angles.    */
/* Arguments:                                   */
/*    st:  start angle. -π 〜 +π              */
/*    ed:  end angle.   -π 〜 +π              */
/*    dir: direction.                           */
/*          0: Normal direction.                */
/*          1: Reverse direction.               */
/* Return value:                                */
/*   Difference of angle.                       */
/*   if dir is zero, result will be positive    */
/*   value, otherwise, result will be negative. */
/*----------------------------------------------*/
static double get_angle_difference( double st, double ed, int dir )
{
    double angle = ed -st;

    if( dir == 0 ) {
        if( angle < 0.0 ) {
            angle += (M_PI *2.0);
        }
    }
    else {
        if( 0.0 < angle ) {
            angle -= (M_PI *2.0);
        }
    }
    return( angle );
}

/*--------------------------------------------------------------*/
/*      pseudo arc to                                           */
/* Arguments:                                                   */
/*    cr:  a cairo context                                      */
/*    edX: the X coordinate of the end of the new line          */
/*    edY: the Y coordinate of the end of the new line          */
/*    ox:  X position of the center of the arc                  */
/*    oy:  Y position of the center of the arc                  */
/*    dir: direction of drawing arc                             */
/*           0: Normal direction                                */
/*           1: Reverse direction                               */
/*                                                              */
/*--------------------------------------------------------------*/
void pseudo_arc_to( cairo_t *cr, 
        double edX, double edY, // End point
        double oX,  double oY,  // Origin of arc
        int    dirFlag )        // Direction of rotation
                                //  0: Normal direction
                                //  1: Counter direction
{
    double stX, stY;    // Start point.
    double st,  ed;     // Angle of start point and end point.
    double r;           // Radius of arc
    double angle;       // Angle from st to ed. 

    int    n, i;
    double l;           // Distance to Ctrl point for each divided arc.
    double prvX, prvY;

    // Prepare each value.
    cairo_get_current_point( cr, &stX, &stY );  // Get start point.
    st    = atan2( (stY -oY), (stX -oX) );      // Angle of start point.
    ed    = atan2( (edY -oY), (edX -oX) );      // Angle of end point.
    r     = sqrt( ((edX -oX) *(edX -oX)) +((edY -oY) *(edY -oY)) ); // Radius of arc
    angle = get_angle_difference( st, ed, dirFlag );    // Angle from st to ed.

    // Draw divided arc.
    n    = ceil( fabs(angle / (M_PI*0.5)) );    // Number of division.
    l    = fabs( r *4.0 *tan((angle/n)/4.0) /3.0 );   // Distance to Ctrl point.
    prvX = stX;
    prvY = stY;
    for( i=1; i<=n; i++ ) {
        double c1X, c1Y;                // Ctrl1 point.
        double c2X, c2Y;                // Ctrl2 point.
        double a = st +(angle *i /n);
        double nowX = r *cos(a) +oX;
        double nowY = r *sin(a) +oY;

        if( i == n ) {                  // Last divided arc.
            // Only last part of arcs will be exactly drawn
            // with given end point.
            nowX = edX;
            nowY = edY;
        }

        if( dirFlag == 0 ) {
            c1X = prvX + (l * (-(prvY -oY)/r)); // cos(θ+π/2) = -sin(θ)
            c1Y = prvY + (l * ( (prvX -oX)/r)); // sin(θ+π/2) =  cos(θ)
            c2X = nowX + (l * ( (nowY -oY)/r)); // cos(θ-π/2) =  sin(θ)
            c2Y = nowY + (l * (-(nowX -oX)/r)); // sin(θ-π/2) = -cos(θ)
        }
        else {
            c1X = prvX + (l * ( (prvY -oY)/r)); // cos(θ-π/2) =  sin(θ)
            c1Y = prvY + (l * (-(prvX -oX)/r)); // sin(θ-π/2) = -cos(θ)
            c2X = nowX + (l * (-(nowY -oY)/r)); // cos(θ+π/2) = -sin(θ)
            c2Y = nowY + (l * ( (nowX -oX)/r)); // sin(θ+π/2) =  cos(θ)
        }

        cairo_curve_to( cr, c1X, c1Y, c2X, c2Y, nowX, nowY );
        prvX = nowX;
        prvY = nowY;
    }

    // 半径は、原点と終点の距離から計算される。
    // 回転方向は解りにくい。
    // 数学的には左回り(CCW:時計と逆)が順方向であるが、コンピュータの座標系は
    // Y軸が反転しているため、右回り(CW:時計方向)が順方向になる。
    // 回転方向は、表示装置の座標系のとり方に依存するもので、計算や処理に
    // 依存するものではない。
}
コメントにも書いてあるが、回転方向には注意が必要だ。
多くの処理系、表示デバイスでは、下方向がY軸の増加になり、時計回りが順方向になる。
この向きは、cairo_arc()の描画方向に一致している。

実装はしたものの、迷っている部分がある。
始点と終点が一致している場合、全く書かないのが良いのか?全周を描くのが良いのか?
これについては、描画方向も考慮するのが良いかもしれない。
現在は、一番シンプルな形で実装している。

0 件のコメント:

コメントを投稿