将一个二次贝塞尔曲线分成两个

时间:2016-05-06 23:21:15

标签: javascript math canvas bezier

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

然后我有N个二次贝塞尔曲线,即在2个最近的段之间。它可以放在圆圈上或远离中心,但不要靠近圆圈。

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

示例图片,其中我只有8个部分,以便于解决问题。

smaller example of problem

关键是要做出进步"使用贝塞尔曲线的条形图。旁注:曲线将改变每一帧,因为它们是音乐可视化的一部分。

如果有更好的方法来为曲线吐色,我就是为了它!

3 个答案:

答案 0 :(得分:4)

分裂立方和二次贝塞尔

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

使用示例

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);

渲染第3个订单

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;
}

答案 1 :(得分:2)

[编辑]

获得长度的算法仍然不起作用,似乎我忘了计算最后一条路径,如果有人想指出那个非常好的解决方案,因为我现在没有时间。 (否则,我会试着在周末找到它......)

由于您不需要支持较旧的IE(&lt; = 11),因此一种简单的方法是使用setLineDash()方法。

这将允许您只绘制一次路径,并且只需要获得路径的全长。

为此,我使用this algo生成的tunght13488的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();
<canvas width="500" height="500" id="c"></canvas>

答案 2 :(得分:0)

对于仍停留在此页面上的任何人,请查看Bezier.jshttps://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();