0

我已经实现了一个类,该类使用“使用锚点修改曲线”教程中显示的四边形。

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

我最初认为这将是微不足道的,因为我可以测试一条粗线,但显然这不起作用

为了命中测试,我将如何制作一个遵循这条线的形状?

更新

我认为我正在使用以下 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);
    }

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

4

1 回答 1

1

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

在此处输入图像描述

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

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

脂肪区域实际上是一个封闭的折线路径。

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

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

笔记:

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

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

对于 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>
于 2013-07-13T22:13:33.907 回答