【问题标题】:KineticJS Bounding Box for Quadratic Curve二次曲线的 KineticJS 边界框
【发布时间】:2013-07-13 20:30:29
【问题描述】:

我已经实现了一个使用 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);
    }

上述代码的问题在于,随着曲线的斜率越大,由于偏移量的计算方式,命中区域变得越小。我想我需要根据垂直于锚点的线及其下一个控制点进行一些计算来获得偏移量。

【问题讨论】:

    标签: kineticjs hittest quadratic-curve


    【解决方案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>
    

    【讨论】:

    • 我想我需要更困难的方法,因为使用曲线下空间的命中区域会太大。
    • 我已经更新了原始问题,以展示我如何接近解决方案。
    • 我正在努力研究如何更好地完成增肥曲线。 context.isPointInPath 会很棒,但 IE 绝对是我必须支持的东西。跨浏览器的痛苦永远不会结束。
    • 我重写了我的答案,以展示如何创建可用于对贝塞尔曲线进行命中测试的“胖”路径。
    • 效果非常好!我利用了数学,并进行了修改,然后进行了一些进一步的修改,以提出一个不同的版本,该版本使用 quadraticCurveTo 函数而不是切片。 jsfiddle.net/M2rmw/1非常感谢您在这里的所有支持!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 2013-03-31
    • 2017-06-25
    相关资源
    最近更新 更多