二次曲线的KineticJS边界框

时间:2013-07-13 20:30:30

标签: kineticjs hittest quadratic-curve

我已经实现了一个使用Modify Curves With Anchor Points教程中显示的四线的类。

this.shape = new Kinetic.Shape({
    drawFunc: function(canvas) {
      var context = canvas.getContext();
      context.beginPath();
      context.moveTo(self.anchors[0].getX(), self.anchors[0].getY());
      for(var i = 1; i < self.anchors.length; i+=2){
        context.quadraticCurveTo(self.anchors[i].getX(), self.anchors[i].getY(), self.anchors[i+1].getX(), self.anchors[i+1].getY()); 
      }
      context.strokeStyle = 'red';
      context.lineWidth = 4;
      context.stroke();
    },
    drawHitFunc: function(canvas) {
      /** Some Hit Test Code **/
    }
  });
this.shape.on('dblclick', click);

我原本以为这会是微不足道的,因为我可以试着测试一条胖线,但显然是does not work

我如何制作一条符合此行的形状以进行命中测试?

更新

我认为我正在接近使用以下drawhitFunc

drawHitFunc: function(canvas) {
      var context = canvas.getContext();
      context.beginPath();
      context.moveTo(self.anchors[0].getX(), self.anchors[0].getY()-10);
      for(var i = 1; i < self.anchors.length; i+=2){
        context.quadraticCurveTo(self.anchors[i].getX(), self.anchors[i].getY()-10, self.anchors[i+1].getX(), self.anchors[i+1].getY()-10);
      }

      context.lineTo(self.anchors[self.anchors.length-1].getX(), self.anchors[self.anchors.length-1].getY() + 10);
      for(var i = self.anchors.length - 2; i >= 0; i-=2){
        context.quadraticCurveTo(self.anchors[i].getX(), self.anchors[i].getY()+10, self.anchors[i-1].getX(), self.anchors[i-1].getY()+10);
      }
      canvas.fillStroke(this);
    }

上述代码的问题在于,由于曲线具有更大的斜率,因为计算偏移的方式,命中区域变小。我想我需要做一些计算,以根据垂直于锚点及其下一个控制点的直线获得偏移量。

1 个答案:

答案 0 :(得分:1)

以下是如何定义“胖”贝塞尔曲线以用作命中测试区域

enter image description here

此图显示红色的原始贝塞尔曲线。

曲线周围的黑色填充区域是它的“胖”命中测试区域。

胖区实际上是一条封闭的折线路径。

以下是如何建立脂肪曲线:

  • 从大约15-25步开始沿着曲线行进
  • 在每个步骤中,从曲线上的该点开始计算垂直线
  • 将垂直线从曲线延伸一段距离(t)
  • 保存每条扩展垂直线的x / y端点
  • (这些保存的点将定义“加肥的”折线路径)

注意:

如果您移动任何锚点,则需要重新计算胖路径。

如果您希望曲线是二次曲线而不是三次曲线,只需使两个控制点相同即可。

对于KineticJS命中测试:使用折线点来使用drawHitFunc定义命中区域。

在曲线上进行25步通常可以很好地处理“弯曲”的曲线。如果您知道曲线相对平滑,则可以减少步数。较少的步骤导致遵循曲线的确切路径的精度较低。

这是代码和小提琴:http://jsfiddle.net/m1erickson/bKTew/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; padding:20px; }
    #canvas{border:1px solid red;}
</style>

<script>
$(function(){

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

    // endpoints,controlpoints
    var s={x:50,y:150};
    var c1={x:100,y:50};
    var c2={x:200,y:200};
    var e={x:250,y:50};
    var t=12;

    // polypoints is a polyline path defining the "fat" bezier
    var polypoints=[];
    var back=[];
    var p0=s;

    // manually calc the first startpoint
    var p=getCubicBezierXYatPercent(s,c1,c2,e,.02);
    var dx=p.x-s.x;
    var dy=p.y-s.y;
    var radians=Math.atan2(dy,dx)+Math.PI/2;
    polypoints.push(extendedPoint(s,radians,-t));

    // travel along the bezier curve gathering "fatter" points off the curve
    for(var i=.005;i<=1.01;i+=.04){

        // calc another further point
        var p1=getCubicBezierXYatPercent(s,c1,c2,e,i);

        // calc radian angle between p0 and new p1
        var dx=p1.x-p0.x;
        var dy=p1.y-p0.y;
        var radians=Math.atan2(dy,dx)+Math.PI/2;

        // calc a "fatter" version of p1 -- fatter by tolerance (t)
        // find a perpendicular line off p1 in both directions
        // then find both x/y's on that perp line at tolerance (t) off p1
        polypoints.push(extendedPoint(p1,radians,-t));
        back.push(extendedPoint(p1,radians,t));
        p0=p1;

    }


    // return data was collected in reverse order so reverse the return data
    back=back.reverse();

    // add the return data to the forward data to complete the path
    polypoints.push.apply(polypoints, back)

    // draw the "fat" bezier made by a polyline path
    ctx.beginPath();
    ctx.moveTo(polypoints[0].x,polypoints[0].y);
    for(var i=1;i<polypoints.length;i++){
        ctx.lineTo(polypoints[i].x,polypoints[i].y);
    }
    // be sure to close the path!
    ctx.closePath();
    ctx.fill();


    // just for illustration, draw original bezier
    ctx.beginPath();
    ctx.moveTo(s.x,s.y);
    ctx.bezierCurveTo(c1.x,c1.y,c2.x,c2.y,e.x,e.y);
    ctx.lineWidth=3;
    ctx.strokeStyle="red";
    ctx.stroke();


    // calc x/y at distance==radius from centerpoint==center at angle==radians
    function extendedPoint(center,radians,radius){
        var x = center.x + Math.cos(radians) * radius;
        var y = center.y + Math.sin(radians) * radius;
        return({x:x,y:y});
    }


    // cubic bezier XY from 0.00-1.00 
    // BTW, not really a percent ;)
    function getCubicBezierXYatPercent(startPt,controlPt1,controlPt2,endPt,percent){
        var x=CubicN(percent,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
        var y=CubicN(percent,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
        return({x:x,y:y});
    }

    // cubic helper formula at 0.00-1.00 distance
    function CubicN(pct, a,b,c,d) {
        var t2 = pct * pct;
        var t3 = t2 * pct;
        return a + (-a * 3 + pct * (3 * a - a * pct)) * pct
        + (3 * b + pct * (-6 * b + b * 3 * pct)) * pct
        + (c * 3 - c * 3 * pct) * t2
        + d * t3;
    }

}); // end $(function(){});

</script>

</head>

<body>
     <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>