33

2D に 3 つの点があり、それらを通る二次ベジエ曲線を描きたいと考えています。x1中央のコントロール ポイント (およびy1quadTo の場合)を計算するにはどうすればよいですか? 私は大学で線形代数を知っていますが、これについて簡単な助けが必要です。

曲線が通過するように中央の制御点を計算するにはどうすればよいですか?

4

4 に答える 4

4

JavaFXアプリケーションでNemosの回答を使用しましたが、私の目標は曲線を描くことでした。これにより、曲線の視覚的なターニングポイントが常に選択された固定のもの(CP)に適合します。

CP = コントロールポイント
SP =スタートポイント
EP =
エンドポイント BP(t) = BeziérCurve 上の可変ポイント (t は 0 ~ 1 の範囲)

これを達成するために、変数tを作成しました(0.5を修正しません)。選択したポイント CP が SP と EP の中間にない場合は、t を少し上下に変更する必要があります。最初のステップとして、CP が SP または EP に近いかどうかを知る必要があります。distanceSP を CP と SP の間の距離、distanceEP を CP と EP の間の距離とすると、比率を次のように定義します。

ratio = (distanceSP - distanceEP) / (distanceSP + distanceEP);

これを使用して t を上下に変化させます。

ratio = 0.5 - (1/3) * ratio;

注: これはまだ概算であり、試行錯誤によって 1/3 が選択されます。

これが私のJava関数です:(Point2DはJavaFXのクラスです)

private Point2D adjustControlPoint(Point2D start, Point2D end, Point2D visualControlPoint) {
    // CP = ControlPoint, SP = StartPoint, EP = EndPoint, BP(t) = variable Point on BeziérCurve where t is between 0 and 1
    // BP(t) = SP*t^2 + CP*2*t*(1-t) + EP*(1-t)^2
    // CP = (BP(t) - SP*t^2 - EP*(1-t)^2) / ( 2*t*(1-t) )
    // but we are missing t the goal is to approximate t
    double distanceStart  = visualControlPoint.distance(start);
    double distanceEnd    = visualControlPoint.distance(end);
    double ratio          = (distanceStart - distanceEnd) / (distanceStart + distanceEnd);
    // now approximate ratio to be t
    ratio = 0.5 - (1.0 / 3) * ratio;

    double ratioInv = 1 - ratio;
    Point2D term2 = start.multiply( ratio * ratio );
    Point2D term3 = end.multiply( ratioInv * ratioInv );
    double  term4 = 2 * ratio * ratioInv;

    return visualControlPoint.subtract(term2).subtract(term3).multiply( 1 / term4);
}

これが役立つことを願っています。

于 2014-11-23T22:27:14.743 に答える
4

取得したいクワッド ベジェを P(t) = P1 t^2 + PC 2 t (1-t) + P2*(1-t)^2 とし、そのクワッド ベジェが P1,Pt,P2 を渡すとします。

3 つのポイント P1、Pt、P3 を通過する最適なクワッド ベジエは、曲線の接線の垂線に向けられた張力を持つコントロール ポイント PC を持ちます。その点は、そのベジエの二等分線でもあります。throw PC と Pt を通過する直線上の PC で P1 を開始し、P3 を終了するベジェは、同じパラメトリック t 値でクワッド ベジェをカットします。

PC ポイントは、ベジエのパラメトリック位置 t=.5 では達成されません。一般に、任意の P1、Pt、P2 について、次の式で説明するように Pc を取得します。

ここに画像の説明を入力

結果の PC は、そのベジエの Pt への近点でもあり、Pt と Pc を通る直線は、三角形 P1、Pt、Pc の二等分線です。

ここに定理と式が記述されている論文があります - それは私のウェブサイトにあります

また、ここにコードがあります

(function() {

    var canvas, ctx, point, style, drag = null, dPoint;

    // define initial points
    function Init() {
        point = {
            p1: { x:200, y:350 },
            p2: { x:600, y:350 }
        };
        point.cp1 = { x: 500, y: 200 };
        
        // default styles
        style = {
            curve:  { width: 2, color: "#333" },
            cpline: { width: 1, color: "#C00" },
            curve1: { width: 1, color: "#2f94e2" },
            curve2: { width: 1, color: "#2f94e2" },
            point: { radius: 10, width: 2, color: "#2f94e2", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
        }
        
        // line style defaults
        ctx.lineCap = "round";
        ctx.lineJoin = "round";

        // event handlers
        canvas.onmousedown = DragStart;
        canvas.onmousemove = Dragging;
        canvas.onmouseup = canvas.onmouseout = DragEnd;
        
        DrawCanvas();
    }
    
    
    // draw canvas
    function DrawCanvas() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // control lines
        ctx.lineWidth = style.cpline.width;
        ctx.strokeStyle = style.cpline.color;
        ctx.beginPath();
        ctx.moveTo(point.p1.x, point.p1.y);
        ctx.lineTo(point.cp1.x, point.cp1.y);
        ctx.lineTo(point.p2.x, point.p2.y);
        ctx.stroke();
        
        // curve
        ctx.lineWidth = style.curve.width;
        ctx.strokeStyle = style.curve.color;
        ctx.beginPath();
        ctx.moveTo(point.p1.x, point.p1.y);

        through = !document.getElementById("cbThrough").checked;
        if(through)
        {
            tmpx1 = point.p1.x-point.cp1.x;
            tmpx2 = point.p2.x-point.cp1.x;
            tmpy1 = point.p1.y-point.cp1.y;
            tmpy2 = point.p2.y-point.cp1.y;
            dist1 = Math.sqrt(tmpx1*tmpx1+tmpy1*tmpy1);
            dist2 = Math.sqrt(tmpx2*tmpx2+tmpy2*tmpy2);
            tmpx = point.cp1.x-Math.sqrt(dist1*dist2)*(tmpx1/dist1+tmpx2/dist2)/2;
            tmpy = point.cp1.y-Math.sqrt(dist1*dist2)*(tmpy1/dist1+tmpy2/dist2)/2;
            ctx.quadraticCurveTo(tmpx, tmpy, point.p2.x, point.p2.y);
        }
        else
        {
            ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y);
        }
        
        ctx.stroke();
        
        //new, t range is [0, 1]
        ctx.beginPath();
        ctx.lineWidth = style.curve1.width;
        ctx.strokeStyle = style.curve1.color;
        
        ctx.moveTo(point.p1.x, point.p1.y);
        
        // control points
        for (var p in point) {
            ctx.lineWidth = style.point.width;
            ctx.strokeStyle = style.point.color;
            ctx.fillStyle = style.point.fill;
            ctx.beginPath();
            ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
            ctx.fill();
            ctx.stroke();
        }
    }
    
    // start dragging
    function DragStart(e) {
        e = MousePos(e);
        var dx, dy;
        for (var p in point) {
            dx = point[p].x - e.x;
            dy = point[p].y - e.y;
            if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) {
                drag = p;
                dPoint = e;
                canvas.style.cursor = "move";
                return;
            }
        }
    }
    
    
    // dragging
    function Dragging(e) {
        if (drag) {
            e = MousePos(e);
            point[drag].x += e.x - dPoint.x;
            point[drag].y += e.y - dPoint.y;
            dPoint = e;
            DrawCanvas();
        }
    }
    
    
    // end dragging
    function DragEnd(e) {
        drag = null;
        canvas.style.cursor = "default";
        DrawCanvas();
    }

    
    // event parser
    function MousePos(event) {
        event = (event ? event : window.event);
        return {
            x: event.pageX - canvas.offsetLeft,
            y: event.pageY - canvas.offsetTop
        }
    }
    
    
    // start
    canvas = document.getElementById("canvas");
    if (canvas.getContext) {
        ctx = canvas.getContext("2d");
        Init();
    }
    
})();

 
html, body { background-color: #DDD;font-family: sans-serif; height: 100%; margin:0;
padding:10px;
}
canvas { display:block;}
#btnControl { font-size:1em; position: absolute; top: 10px; left: 10px; }
#btnSplit { font-size:1em; position: absolute; top: 35px; left: 10px; }
#text { position: absolute; top: 75px; left: 10px; }
a {
  text-decoration: none;
  font-weight:700;
  color: #2f94e2;
}
#little { font-size:.7em; color:#a0a0a0; position: absolute; top: 775px; left: 10px; }
<h1>Quadratic bezier throw 3 points</h1>
<div>
  
  Also take a look the the math paper <a target="_blank" href="https://microbians.com/mathcode">Quadratic bezier through three points! →&lt;/a> <br/><br/>
  Gabriel Suchowolski (<a href="https://microbians.com" target="_blank">microbians</a>), December, 2012
</div>
<div id="little">Thanks to 艾蔓草 xhhjin for the code (that I fork) implementing my math paper.</div>
  <br/>
</div>
<input type="checkbox" id="cbThrough" name="through"/>Primitive quadratic Bezier (as control points)</input><br/><br/>
<canvas id="canvas" height="500" width="800" class="through" style="cursor: default; background-color: #FFF;"></canvas>

楽しみ

于 2021-03-03T18:48:25.727 に答える
2

正確な中間点が必要ではなく、t の任意の値 (0 から 1) が必要な場合、方程式は次のようになります。

controlX = pointToPassThroughX/t - startX*t - endX*t;
controlY = pointToPassThroughY/t - startY*t - endY*t;

もちろん、これは中間点でも機能します。t を 0.5 に設定するだけです。単純!:-)

于 2014-07-10T06:53:02.860 に答える