将贝塞尔曲线分成两个相等的部分

10

我有两点之间的贝塞尔曲线。我想将所有曲线切成两半。 我的一个想法是,如果我可以控制“t”值,我将通过t = [0,0.5]和t = [0.5,1]绘制2条曲线,但我不知道如何做到这一点。以下是我的代码。我不介意任何其他想法或建议

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>D3 test</title>
    <script src="http://d3js.org/d3.v3.min.js"></script>

    <script>
    var Over = function(){
        d3.select(this)
        .style("stroke-opacity", 0.25);
    }
    var Out = function(){
        d3.select(this)
        .transition().duration(200)
        .style("stroke-opacity", 0);
    }

    function curve(n,x1,y1,x2,y2){

        var xr = (x1+x2)/2,
            yr = (y1+y2)/2,
            euDist = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)),
            x3 = -y1+xr+yr, x4 = -y2+xr+yr,
            y3 =  x1+yr-xr, y4 =  x2+yr-xr,
            ctrl , curveDescription;

        svg.append('path')
            .attr("stroke", 'blue')
            .attr('fill','none')
            .style("stroke-opacity",0.25)
            .attr('d', 'M'+x3+','+y3+'L'+x4+','+y4)
            .attr('stroke-width',strokeWidth);

        for(var j=0;j<=n;j++){
            ctrl = [(x4-x3)*j/n+x3 , (y4-y3)*j/n+y3] ,                  
            curveDescription=   
                    'M' +x1+','     +y1+ 
                    'Q' +ctrl[0]+','+ctrl[1]+','
                        +x2+','     +y2;

            svg.append('path')
                .attr("stroke", 'blue')
                .attr('fill','none')
                .style("stroke-opacity",0.25)
                .attr('d', curveDescription)
                .attr('stroke-width',strokeWidth);  

            svg.append('path')
                .attr("stroke", 'blue')
                .attr('fill','none')
                .style("stroke-opacity",0)
                .on("mouseover", Over)
                .on("mouseout", Out)
                .attr('d', curveDescription)
                .attr('stroke-width',strokeWidth*25);

        }

    }
    </script>

</head>

<body>
    <script>
    var w = 1268 , h = 680 , strokeWidth = 2;

    var svg = d3.select("body")
                .append("svg")
                .attr("width", w)
                .attr("height", h)

    curve(5, 100,100, 400,500);


    </script>
</body>
</html>

我无法直接包含它,所以我会上传它。 - Orio
3个回答

29
将贝塞尔曲线分成两个曲线非常简单。查找De Casteljau算法。 https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm 更新
De Casteljau比看起来简单。对于非数学家来说,该WP文章可能不够清晰。因此,我将更简单地解释。
想象一下,您有一个由点A、B、C和D定义的贝塞尔曲线。其中A和D是端点,B和C是控制点。
因此,假设您想要在曲线上找到点't'处的曲线值(其中t在范围0..1内)。您可以通过几何方式进行如下操作:
1. 找到点E,该点在直线AB上处于't'位置。 2. 找到点F,该点在直线BC上处于't'位置。 3. 找到点G,该点在直线CD上处于't'位置。 4. 找到点H,该点在直线EF上处于't'位置。 5. 找到点J,该点在直线FG上处于't'位置。 6. 最后,找到点K,该点在直线HJ上处于't'位置。
K也等于沿贝塞尔曲线't'位置的点。这就是De Casteljau算法。
但是,有用的是,它还为我们提供了两个贝塞尔曲线的控制点,这些曲线在点K处分割曲线时会产生。这两个贝塞尔曲线是:A、E、H、K和K、J、G、D。
在您的情况下,t = 0.5,因此找到两条曲线只需要进行一系列加法和除以2的操作。
  E = (A+B)/2
  F = (B+C)/2
  G = (C+D)/2
  H = (E+F)/2
  J = (F+G)/2
  K = (H+J)/2

很显然,这些计算都必须针对x和y进行。希望这能帮到你。

1
对我来说理解起来有些困难,但我正在努力 :D - Orio
1
你的解释让我理解了,非常感谢 ^_^ - Orio
2
非常有帮助的答案!谢谢。在另一个答案中,我添加了一张图表来说明您所描述的要点。 - Andrew Willems
大多数贝塞尔缓动曲线都有两个可配置的控制点(B和C),而A是(0,0),D是(1,1)。当我们将曲线分成两部分时,如何“规范化”新曲线,以便两者都具有(0,0),(1,1)的A和B? - Xys

21
Paul LeBeau所提供的答案对我非常有帮助。我认为如果有一个视觉效果与答案配合,其他人也会更受益,以下是我提供的内容。
下面的图示说明了Paul LeBeau答案中描述的要点,请参见该回答以获得相关解释。实际图示显示了t = 0.5的特殊情况,但对于0到1之间的任何t值,一般原理都相同。粗黑色线显示原始曲线的“控制线”,而红色线显示第一“半曲线”的控制线。 输入图像说明

感谢您,Andrew。 - Paul LeBeau
如果我们有一个不同形状的三次曲线会怎样呢? 我尝试了这种方法在一个形成类似于(x 立方)形状的不同三次曲线上,但是这种方法无法找到中心。 - hmali
@hmali,这不太可能是真的,因为De Casteljau公式直接遵循Bézier曲线的(递归)定义。在过去,它甚至是绘制Bézier曲线的首选算法。 - Wilco Brouwer

11
这里有一种将贝塞尔曲线分成两个曲线的公式。
var w = 800, h = 560;

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var pts = [{x:20, y:20},
           {x:20, y:100},
           {x:200, y:100},
           {x:200,  y:20}];
var t = 0.5;

function lerp(a, b, t)
{
    var s = 1 - t;
    return {x:a.x*s + b.x*t,
            y:a.y*s + b.y*t};
}


function splitcurve()
{
    var p0 = pts[0], p1 = pts[1], p2 = pts[2], p3 = pts[3];
    var p4 = lerp(p0, p1, t);
    var p5 = lerp(p1, p2, t);
    var p6 = lerp(p2, p3, t);
    var p7 = lerp(p4, p5, t);
    var p8 = lerp(p5, p6, t);
    var p9 = lerp(p7, p8, t);

    var firsthalf = [p0, p4, p7, p9];
    var secondhalf =  [p9, p8, p6, p3];

    console.log(firsthalf);
    console.log(secondhalf);

    ctx.beginPath();
    ctx.moveTo(20,20);
    ctx.lineWidth=5;
    ctx.bezierCurveTo(20,100,200,100,200,20);
    ctx.strokeStyle="black";
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(p0.x,p0.y);
    ctx.lineWidth=5;
    ctx.bezierCurveTo(p4.x,p4.y,p7.x,p7.y,p9.x,p9.y);
    ctx.strokeStyle="blue";
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(p9.x,p9.y);
    ctx.lineWidth=5;
    ctx.bezierCurveTo(p8.x,p8.y,p6.x,p6.y,p3.x,p3.y);
    ctx.strokeStyle="red";
    ctx.stroke(); 
}

splitcurve();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接