【问题标题】:Split one quadratic bezier curve into two将一条二次贝塞尔曲线一分为二
【发布时间】:2016-05-06 23:21:15
【问题描述】:

所以我有一个假想的圆分成多个部分(为了简单起见,我使用 8,但最后我想将它分成 16 或 32 个部分)。

然后我有 N 条二次贝塞尔曲线,即在 2 个最近的线段之间。它可能位于圆上或离中心较远,但不能比圆更近。

我知道如何找到,我应该在女巫线上寻找交点,但我不知道如何将其分成两部分......我知道,如果我寻找直线和曲线的交点,我应该得到前一条曲线应该结束和下一条曲线应该开始的点,并且通过推导我可以得到向量,但我不知道该怎么做。

为了更容易解决问题,我只有 8 个部分的示例图像。

重点是,使用贝塞尔曲线制作“进度”条。旁注:曲线每帧都会改变,因为它们是音乐可视化的一部分。

如果有更好的方法给曲线吐颜色,我完全赞成!

【问题讨论】:

  • 这远非完美,但它可能会给你一个想法:jsfiddle.net/4ekgwLwo 样条计算见stackoverflow.com/questions/17083580/…
  • @Kaiido 这是很棒的方法!喜欢它;)如果你回答我会接受它。
  • @Kaiido 确实非常真实......它仅来自 IE11......无论如何,因为我需要一个 AudioContext,我从一开始就放弃了 IE:D
  • 请注意,如果您想使用线性距离(例如,它每秒填充 100 像素,或者您需要一个真正的进度条),那么常规曲线分割将是不够的,因为您将 @ 987654324@。在进度的 25%、50% 或 75% 时,平曲线分割将几乎永远不会产生一条占整个曲线 25%、50% 或 75% 的子曲线。当然,您的用户不会知道真正的进度,所以这可能没问题,但要记住贝塞尔曲线的一个重要事实。
  • @Mike'Pomax'Kamermans 是的,我不久前就想到了这一点......:/但globalCompositeOperation 似乎很棒......

标签: javascript math canvas bezier


【解决方案1】:

分割三次和二次贝塞尔曲线

分割贝塞尔曲线相对容易。由于已经有了答案,我将只复制在其路径范围从 0 到 1 的位置处拆分单个贝塞尔曲线、三次曲线或二次曲线所需的函数。函数 Bezier.splitAt 采用 position(0 到 1)和取决于 start = true 返回从 0 到位置或如果 start = false 返回从位置到 1 的贝塞尔曲线。它将处理二阶(二次)和三阶(三次)贝塞尔曲线

使用示例

var bezier = createBezierCubic( 146, 146, 134, 118, 184, 103, 217, 91 );
// split in two
var startingHalf = bezier.splitAt(0.5, true);
var endingHalf = bezier.splitAt(0.5, false);
// split into four. 
var quart1 = startingHalf.splitAt(0.5, true)
var quart2 = startingHalf.splitAt(0.5, false)
var quart3 = endingHalf.splitAt(0.5, true)
var quart4 = endingHalf.splitAt(0.5, false)

// getting a segment
var startFrom = 0.3;
var endAt = 0.8;
var section = bezier.splitAt(startFrom, false).splitAt((endAt - startFrom) / (1 - startFrom), true);

贝塞尔曲线由起点和终点 p1、p2 和一个或两个控制点 cp1、cp2 组成。如果贝塞尔是二阶,则 cp2 未定义。点是 Vec,取自 Vec.x, Vec.y

渲染第二个订单

ctx.moveTo(bezier.p1.x, bezier.p1.y);
ctx.quadraticCurveTo(bezier.cp1.x, bezier.cp1.y, bezier.p2.x, bezier.p2.y);

渲染第三个订单

ctx.moveTo(bezier.p1.x, bezier.p1.y);
ctx.bezierCurveTo(bezier.cp1.x, bezier.cp1.y, bezier.cp2.x, bezier.cp2.y, bezier.p2.x, bezier.p2.y);

具有依赖关系的代码。

由于您都是程序员,请查看代码以获取更多使用信息。警告可能存在拼写错误,因为这是从更广泛的几何界面中提取的。

var geom = (function(){
    function Vec(x,y){ // creates a vector
        if(x === undefined){
            x = 1;
            y = 0;
        }
        this.x = x;
        this.y = y;
    }
    Vec.prototype.set = function(x,y){
        this.x = x;
        this.y = y;
        return this;
    };
    // closure vars to stop constant GC
    var v1 = Vec();
    var v2 = Vec();
    var v3 = Vec();
    var v4 = Vec();
    var v5 = Vec();
    const BEZIER_TYPES  = {
        cubic : "cubic",
        quadratic : "quadratic",
    };

    // creates a bezier  p1 and p2 are the end points as vectors.
    // if p1 is a string then returns a empty bezier object.
    //          with the type as quadratic (default) or cubic
    //  cp1, [cp2] are the control points. cp2 is optional and if omitted will create a quadratic 
    function Bezier(p1,p2,cp1,cp2){
        if(typeof p1 === 'string'){
            this.p1 = new Vec();
            this.p2 = new Vec();
            this.cp1 = new Vec();
            if(p1 === BEZIER_TYPES.cubic){
                this.cp2 = new Vec();
            }
        }else{
            this.p1 = p1 === undefined ? new Vec() : p1;
            this.p2 = p2 === undefined ? new Vec() : p2;
            this.cp1 = cp1 === undefined ? new Vec() : cp1;
            this.cp2 = cp2;
        }
    }    
    Bezier.prototype.type = function(){
        if(this.cp2 === undefined){
            return BEZIER_TYPES.quadratic;
        }
        return BEZIER_TYPES.cubic;
    }
    Bezier.prototype.splitAt = function(position,start){ // 0 <= position <= 1 where to split. Start if true returns 0 to position and else from position to 1
        var retBezier,c;
        if(this.cp2 !== undefined){ retBezier = new Bezier(BEZIER_TYPES.cubic); }
        else{ retBezier = new Bezier(BEZIER_TYPES.quadratic); }
        v1.x = this.p1.x;
        v1.y = this.p1.y;
        c = Math.max(0, Math.min(1, position));  // clamp for safe use in Stack Overflow answer
        if(start === true){
            retBezier.p1.x = this.p1.x;
            retBezier.p1.y = this.p1.y;            
        }else{
            retBezier.p2.x = this.p2.x;
            retBezier.p2.y = this.p2.y;            
        }
        if(this.cp2 === undefined){ // returns a quadratic
            v2.x = this.cp1.x;
            v2.y = this.cp1.y;
            if(start){
                retBezier.cp1.x = (v1.x += (v2.x - v1.x) * c);
                retBezier.cp1.y = (v1.y += (v2.y - v1.y) * c);
                v2.x += (this.p2.x - v2.x) * c;
                v2.y += (this.p2.y - v2.y) * c;
                retBezier.p2.x = v1.x + (v2.x - v1.x) * c;
                retBezier.p2.y = v1.y + (v2.y - v1.y) * c;
                retBezier.cp2 = undefined;
            }else{
                v1.x += (v2.x - v1.x) * c;
                v1.y += (v2.y - v1.y) * c;
                retBezier.cp1.x = (v2.x += (this.p2.x - v2.x) * c);
                retBezier.cp1.y = (v2.y += (this.p2.y - v2.y) * c);
                retBezier.p1.x = v1.x + (v2.x - v1.x) * c;
                retBezier.p1.y = v1.y + (v2.y - v1.y) * c;
                retBezier.cp2 = undefined;
            }
            return retBezier;
        }
        v2.x = this.cp1.x;
        v3.x = this.cp2.x;
        v2.y = this.cp1.y;
        v3.y = this.cp2.y;
        if(start){
            retBezier.cp1.x = (v1.x += (v2.x - v1.x) * c);
            retBezier.cp1.y = (v1.y += (v2.y - v1.y) * c);
            v2.x += (v3.x - v2.x) * c;
            v2.x += (v3.x - v2.x) * c;
            v2.y += (v3.y - v2.y) * c;
            v3.x += (this.p2.x - v3.x) * c;
            v3.y += (this.p2.y - v3.y) * c;
            retBezier.cp2.x = (v1.x += (v2.x - v1.x) * c);
            retBezier.cp2.y = (v1.y += (v2.y - v1.y) * c);
            retBezier.p2.y = v1.y + (v2.y - v1.y) * c;
            retBezier.p2.x = v1.x + (v2.x - v1.x) * c;
        }else{
            v1.x += (v2.x - v1.x) * c;                
            v1.y += (v2.y - v1.y) * c;
            v2.x += (v3.x - v2.x) * c;
            v2.y += (v3.y - v2.y) * c;
            retBezier.cp2.x = (v3.x += (this.p2.x - v3.x) * c);
            retBezier.cp2.y = (v3.y += (this.p2.y - v3.y) * c);
            v1.x += (v2.x - v1.x) * c;
            v1.y += (v2.y - v1.y) * c;
            retBezier.cp1.x = (v2.x += (v3.x - v2.x) * c);
            retBezier.cp1.y = (v2.y += (v3.y - v2.y) * c);
            retBezier.p1.x = v1.x + (v2.x - v1.x) * c;
            retBezier.p1.y = v1.y + (v2.y - v1.y) * c;
        }
        return retBezier;              
    }

    return {
        Vec : Vec,
        Bezier : Bezier,
        bezierTypes : BEZIER_TYPES,
    };
})();

// helper function 
// Returns second order quadratic from points in the same order as most rendering api take then
// The second two coordinates x1,y1 are the control points
function createBezierQuadratic(x, y, x1, y1, x2, y2){
    var b = new geom.Bezier(geom.bezierTypes.quadratic);
    b.p1.set(x, y);
    b.p2.set(x2, y2);
    b.cp1.set(x1, y1);
    return b;
}
// Returns third order cubic from points in the same order as most rendering api take then
// The coordinates x1, y1 and x2, y2 are the control points
function createBezierCubic(x, y, x1, y1, x2, y2, x3, y3){
    var b = new geom.Bezier(geom.bezierTypes.cubic);
    b.p1.set(x, y);
    b.p2.set(x3, y3);
    b.cp1.set(x1, y1);
    b.cp2.set(x2, y2);
    return b;
}

【讨论】:

  • 既然是从某个地方拉出来的,是不是也有相交函数?
  • @Akxe 它是从我自己的 geom 界面中提取的(正在进行中的工作),我还没有为直线、其他贝塞尔曲线、圆和弧线做拦截功能。
  • @Akxe 哦,我在获取片段时犯了一个错误。现在更新答案。是endAt / (1 - startFrom) 应该是(endAt-startFrom) / (1 - startFrom)
  • 好吧,我只需要一行(正如我在问题中显示的那样),这样应该会更容易一些,但我知道这可能需要一段时间......无论如何祝你好运
  • @Akxe 此页面为您提供操作方法,但没有功能。如果您不介意数学,它可能会有所帮助。 pomax.github.io/bezierinfo/#intersections
【解决方案2】:

[编辑]

获取长度的算法仍然不起作用,我似乎忘记计算最后一条路径,如果有人想指出我现在没有时间的解决方案,那将非常好。 (要不我周末再找找……)


由于您不需要支持旧版 IE (setLineDash() 方法。

这将允许您只绘制一次路径,并且只需要获取路径的完整长度。

为此,我使用由tunght13488 制作的this algo 的js 实现。可能会有更好的实现。

var ctx = c.getContext('2d');
var percent = 90;
var length = 0;

// all our quadraticCurves points
var curves = [
  [146, 146, 134, 118, 184, 103],
  [217, 91, 269, 81, 271, 107],
  [263, 155, 381, 158, 323, 173],
  [279, 182, 314, 225, 281, 223],
  [246, 219, 247, 274, 207, 236],
  [177, 245, 133, 248, 137, 211],
  [123, 238, 10, 145, 130, 150]
];

// get the full length of our spline
curves.forEach(function(c) {
  length += quadraticBezierLength.apply(null, c);
});
// that's still not it...
length += quadraticBezierLength.apply(null,curves[curves.length-1]);

var anim = function() {

  var offset = (percent / 100) * length;

  ctx.clearRect(0, 0, c.width, c.height);
  ctx.beginPath();

  ctx.moveTo(133, 150);
  // draw our splines
  curves.forEach(function(c) {
    ctx.bezierCurveTo.apply(ctx, c);
  })
  ctx.closePath();

  // the non completed part
  ctx.strokeStyle = "gray";
  // this will make the part from 0 to offset non drawn
  ctx.setLineDash([0, offset, length]);
  ctx.stroke();

  // the completed part
  ctx.setLineDash([offset, length]);
  ctx.strokeStyle = "blue";
  ctx.stroke();

  percent = (percent + .25) % 100;
  requestAnimationFrame(anim);
}

// modified from https://gist.github.com/tunght13488/6744e77c242cc7a94859
function Point(x, y) {
  this.x = x;
  this.y = y;
}

function quadraticBezierLength(p0x, p0y, p1x, p1y, p2x, p2y) {
  var a = new Point(
    p0x - 2 * p1x + p2x,
    p0y - 2 * p1y + p2y
  );
  var b = new Point(
    2 * p1x - 2 * p0x,
    2 * p1y - 2 * p0y
  );
  var A = 4 * (a.x * a.x + a.y * a.y);
  var B = 4 * (a.x * b.x + a.y * b.y);
  var C = b.x * b.x + b.y * b.y;

  var Sabc = 2 * Math.sqrt(A + B + C);
  var A_2 = Math.sqrt(A);
  var A_32 = 2 * A * A_2;
  var C_2 = 2 * Math.sqrt(C);
  var BA = B / A_2;

  return (A_32 * Sabc + A_2 * B * (Sabc - C_2) + (4 * C * A - B * B) * Math.log((2 * A_2 + BA + Sabc) / (BA + C_2))) / (4 * A_32);
};

anim();
&lt;canvas width="500" height="500" id="c"&gt;&lt;/canvas&gt;

【讨论】:

  • 你太棒了!谢谢我已经开始自己处理所有的点点滴滴,但是你做得更好更快。
  • @Akxe,不幸的是,它仍然无法正常工作,getLength 的事情不会占用最后一个路径段,如果你能找到发生了什么,那就太好了 ;-)
  • 我也发现了...嗯,是的,我明天会想到这个...我会告诉你的
  • 我认为,最后你应该添加第一个路径而不是最后一个......不是吗?
  • @Akxe,那就简单多了,用globalCompositeOperation在路径上画弧,你会得到想要的结果
【解决方案3】:

对于仍然登陆此页面的任何人,请查看Bezier.js (https://github.com/Pomax/bezierjs),尤其是在 API:https://pomax.github.io/bezierjs/

您可以像这样提取t = 0.25t = 0.75 之间的二次贝塞尔曲线:

var curve = new Bezier(150,40 , 80,30 , 105,150);
var segment_curve = curve.split(0.25, 0.75);

context.moveTo(segment_curve.points[0].x, segment_curve.points[0].y);
context.quadraticCurveTo(segment_curve.points[1].x, segment_curve.points[1].y, segment_curve.points[2].x, segment_curve.points[2].y);
context.stroke();

【讨论】:

    猜你喜欢
    • 2011-03-10
    • 2016-10-05
    • 1970-01-01
    • 2011-08-03
    • 2012-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多