円弧を描く

ガイドラインを利用して円弧を描きます。

ガイドラインの片方の余った長さの部分は直線になります。

円弧の計算についてはここを参考に
円弧の半径と角度を求める

・ガイドライン2辺のうち短い方の長さを選ぶ
・ガイドライン同士の成す角から円弧の中心角θが求まる
・角度θとガイドラインから三平方の定理で半径と中心点が求まる
・中心点、角度、半径から円弧を描く

ガイドラインの始点を軸とした行列をつくり、その空間で計算をしてから元の空間に戻しています。

//
// プリミティブごとに円弧を描く
// Run Over: Primitives
// input0: ガイドカーブ
//
float step = 1.0;

int npts[] = primpoints(0, @primnum);
//
// 行列
//
vector world_p0 = point(0, "P", npts[0]);
vector world_p1 = point(0, "P", npts[1]);
vector world_p2 = point(0, "P", npts[2]);

// XY平面へ変換する行列をつくる
vector tangent = normalize(world_p1 - world_p0);
vector normal = normalize(cross(tangent, world_p2 - world_p1));
vector binormal = cross(normal, tangent);
matrix worldPlane = maketransform(normal, binormal, world_p0);

matrix inversePlane = invert(worldPlane);

vector local_p0 = world_p0 * inversePlane;
vector local_p1 = world_p1 * inversePlane;
vector local_p2 = world_p2 * inversePlane;

vector v0 = normalize(local_p1 - local_p0);
vector v1 = normalize(local_p1 - local_p2);

float I = acos(dot(v0, v1*-1));
//
// 円弧が描けるか判定
//
if(I <= radians(1.0))
{
    // 角度が浅い場合はベジェ曲線を描画する
    int prim = addprim(0, "polyline");

    int num = 100;
    for(int i = 0; i < num; i++)
    {
        float t = i / float(num-1);
        vector pos = (1-t)*(1-t)*world_p0 + 2*(1-t)*t*world_p1 + t*t*world_p2;
        int pt = addpoint(0, pos);
        addvertex(0, prim, pt);
    }
}
else
{
    // 2辺の交点座標
    vector cross = local_p1;
    
    // 開始位置を保存
    vector old_p0 = local_p0;
    vector old_p2 = local_p2;

    // 2辺の成す角度
    float theta = acos(dot(v0, -v1));
    
    // 交点から円弧開始地点までの距離(短い方を選ぶ)
    float dist_p0_cross = length(cross - local_p0);
    float dist_p2_cross = length(cross - local_p2);
    float length = min(dist_p0_cross, dist_p2_cross);
    
    // 開始位置をスライドさせる
    local_p0 = cross - v0 * length;
    local_p2 = cross - v1 * length;
    
    // 弦の長さ
    float chord_length = length((cross - v1 * length) - (cross - v0 * length));
    
    // 半径
    float radius = chord_length / 2 / (sin(theta / 2));
    
    // 円弧の長さ
    float arc_length = radius * theta;
    
    vector left = cross(set(0, 0, 1), v1);
    // 円の外側と内側の反転
    int sign = 1;
    if(dot(left, v0) > 0)
        sign *= -1;
    
    // 円の中心
    vector arc_center = local_p2 + normalize(left) * sign * radius; 
    
    // 円弧にポイントを配置していく
    int num = (int)(arc_length / step) + 1;
    float per_angle = theta / (float)(num - 1);
    
    vector center_to_p0 = local_p0 - arc_center;
    float start_angle = atan2(center_to_p0.y, center_to_p0.x);
    
    vector point_pos[];
    for(int i = 0; i < num; i++)
    {
        float angle = start_angle + per_angle * i * sign * -1;
        point_pos[i] = set(cos(angle), sin(angle), 0) * radius + arc_center;
    }
    
    //
    // ポリラインを生成する
    //
    int prim = addprim(0, "polyline");
    
    // 始点
    if(dist_p0_cross > dist_p2_cross)
    {
        int pt = addpoint(0, old_p0 * worldPlane);
        addvertex(0, prim, pt);
    }
    // 円弧
    for(int i = 0; i < num; i++)
    {
        int pt = addpoint(0, point_pos[i] * worldPlane);
        addvertex(0, prim, pt);
    }
    // 終点
    if(dist_p2_cross > dist_p0_cross)
    {
        int pt = addpoint(0, old_p2 * worldPlane);
        addvertex(0, prim, pt);
    }
}

removeprim(0, @primnum, 1);
タイトルとURLをコピーしました