以下是如何定义“胖”贝塞尔曲线以用作命中测试区域
此图以红色显示原始贝塞尔曲线。
曲线周围的黑色填充区域是它的“胖”命中测试区域。
脂肪区域实际上是一个封闭的折线路径。
以下是如何构建脂肪曲线:
- 沿着曲线从开始到结束大约 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>