【问题标题】:how to calculate control points on a bezier curve?如何计算贝塞尔曲线上的控制点?
【发布时间】:2025-12-27 15:10:07
【问题描述】:

我确实有一条贝塞尔曲线,并且在某个点上,我想要第二条贝塞尔曲线以平滑的方式“分支”第一条曲线。除了计算交点(按照贝塞尔曲线的百分比),我还需要控制点(切线和权重)。 交点是用下面这段 javascript 计算的:

getBezier = function getBez(percent,p1,cp1,cp2,p2) {
    function b1(t) { return t*t*t }
    function b2(t) { return 3*t*t*(1-t) }
    function b3(t) { return 3*t*(1-t)*(1-t) }
    function b4(t) { return (1-t)*(1-t)*(1-t) }
    var pos = {x:0,y:0};
    pos.x = p1.x*b1(percent) + cp1.x*b2(percent) + cp2.x*b3(percent) + p2.x*b4(percent);
    pos.y = p1.y*b1(percent) + cp1.y*b2(percent) + cp2.y*b3(percent) + p2.y*b4(percent);
    return pos;
}

(非 IE 浏览器可以在 http://www.iscriptdesign.com -> Tutorial -> Groups & Paths 看到它的实际效果)。 我现在需要的是分支点的控制点或(切线和权重)(我不知道从哪里开始,我希望有人可以指出一些代码或数学方程,如果可能的话,作为来自相同参数的函数如上面的 getBezier 函数)。

【问题讨论】:

  • 是否有特定原因需要使用单独的曲线,而不是另一个三次或二次线段?这很重要,因为 SVG 规范支持自动选择下一段的第一个控制点,这样曲线就会变得平滑。在此处查看 S/s 和 T/t:w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
  • 我正在尝试设计一个托盘架。支架和托盘是两条不同的曲线,支架将托盘支撑到曲线的某一点,但在某一点我希望支架和托盘之间有一个孔。在另一点,支架和托盘曲线将再次连接以完成孔,并为托盘制作稳定的对称支架。支架需要 2 个凸块来防止托盘沿其长度轴移动我尝试编译一个小示例:www.iscriptdesign.com -> 教程 -> 托盘支架。
  • 红线是托盘支架的上侧,黑线是托盘的下侧。关键是我希望黑线在边缘之前跟随红线中的“沟渠”而不是分支。

标签: javascript svg bezier curve-fitting


【解决方案1】:

找到并实现它:de-Casteljau 算法原来是最快的可实现解决方案。它目前存在于: iScriptDesign(教程->吐贝塞尔)。

【讨论】:

  • 非常感谢分享这个,差不多 4 年后我才开始使用它。但是我需要一些帮助。如果您还在,请回复。
【解决方案2】:

示例用法(我想,我需要 @drjerry 的帮助)

我有一个 css3 计时功能,这叫做ease-in-out:cubic-bezier(.42,0,.58,1)。从图形上看,这看起来像:http://cubic-bezier.com/#.42,0,.58,1

我想创建一个新的计时函数,它只是这张图的下半部分,然后是上半部分。

所以下半部分是ease-in: cubic-bezier(.42,0,1,1)。图形化:http://cubic-bezier.com/#.42,0,1,1

上半部分是ease-out:cubic-bezier(0,0,.58,1)。图形化:http://cubic-bezier.com/#0,0,.58,1

所以现在让我感到困惑的是,根据 iScriptDesign 的脚本,它告诉我它们是不同的。

iScriptDeisgn 说ease-in-out 的前半部分是(ease-in)是:cubic-bezier(.21, 0, .355, .25)。图形化:http://cubic-bezier.com/#.21,0,.35,.25

iScriptDeisgn 说ease-in-out 的结尾部分是(ease-out)是:cubic-bezier(.145, .25, .29, .5)。图形化:http://cubic-bezier.com/#.14,.25,.29,.5

为什么 iScriptDesign 拆分函数返回的 ease-inease-out 与真实的 ease-in 和真实的 ease-out 不同?没看懂。

此示例的代码。

//////////////////START FROM ISCRIPTDESIGN
var splitBezier = function(array, perc) {
    array.unshift({x:0, y:0});
    var coll = [];
    while (array.length > 0) {
    for (var i = 0;i < array.length-1; i++) {
        coll.unshift(array[i]);
        array[i] = interpolate(array[i], array[i+1], perc);
    }
    coll.unshift(array.pop());
    }
    return {b1: [{x:coll[5].x, y:coll[5].y}, {x:coll[2].x, y:coll[2].y},{x:coll[0].x, y:coll[0].y}]
        , b2: [{x:coll[1].x - coll[0].x,y:coll[1].y-coll[0].y}, {x:coll[3].x - coll[0].x,y:coll[3].y-coll[0].y}, {x:coll[6].x - coll[0].x,y:coll[6].y-coll[0].y}]};
}

var interpolate = function (p0, p1, percent) {
    if (typeof percent === 'undefined') percent = 0.5;  
    return  {x: p0.x + (p1.x - p0.x) * percent
         , y: p0.y + (p1.y - p0.y) * percent};
}
//////////////////END FROM ISCRIPTDESIGN
var coord = function (x,y) {
  if(!x) var x=0;
  if(!y) var y=0;
  return {x: x, y: y};
}
var easeInOut = [new coord(.42,0), new coord(.58,1), new coord(1,1)];
var split50percent = splitBezier(easeInOut.slice(), .5);

所以split50percent 设置为:

({
    b1: [{
        x: 0.21,
        y: 0
    }, {
        x: 0.355,
        y: 0.25
    }, {
        x: 0.5,
        y: 0.5
    }],
    b2: [{
        x: 0.14500000000000002,
        y: 0.25
    }, {
        x: 0.29000000000000004,
        y: 0.5
    }, {
        x: 0.5,
        y: 0.5
    }]
})

easeInOutSine 相同

  • easeInOutSine:.44,.05,.55,.95
  • 真实
    • easeInSine:0.47, 0, 0.745, 0.715
    • easeOutSine:0.39, 0.575, 0.565, 1
  • iScriptDesign
    • easeInSine:.22,.03,.36,.26
    • easeOutSine:.14,.24,.28,.48

【讨论】:

  • 另外我认为endpoint 数组中返回的endpoint 有问题。 endpoint 不应该是 {x:1,y:1} 吗?
  • 我认为b1与真正的iScriptDesign不同的原因是因为iScriptDesign推出endpoint{x:.5,y:.5}你有像interpolate相反的功能吗?所以像extrapolate?你认为 iScriptDesign 与 real 会匹配吗?
  • Notidart,很久以前.. 我​​在 iScriptdesign 中使用的算法采用相对值(增量,这反映在路径属性中的小写 c)所以 iscriptdesign 返回的示例看起来符合我的期望:连接第一个贝塞尔曲线 b1,第二个贝塞尔曲线得到完整的贝塞尔曲线。因此,将第一个端点添加到第二个端点应该会产生总端点。在这种情况下是正确的 (0.5,0.5) + (0.5,0.5) = (1,1)。我不是每天都在这个网站上,但经常出现,以防您需要更多说明:-)
  • 将分割的贝塞尔曲线的两个控制点相加(向量)不会得到原始未分割贝塞尔曲线的控制点。这仅适用于分割贝塞尔曲线的端点(即 b1[2] 和 b2[2]。在 iscriptdesing 的示例中,分割贝塞尔曲线的控制点的位置是绿线和绿线与原始线的交点控制点(向量)。iscriptdesign 使用的算法是用 javascript 实现的通用 de-casteljau 算法,在我看来是“真实”的东西。
  • 此外,如果您正在考虑使用贝塞尔曲线作为时间线,我还鼓励您查看 d3js,这是一个更加坚固的框架,可以做到这一点。 iScriptdesign 主要用于为 cnc 设备(如激光切割机)创建、显示和修改在线构建模型,尽管它也用于在线可编写脚本的图形。