Here’s how to define a “fat” bezier curve for use as a hit test area
This illustration shows the original bezier curve in red.
The black filled area surrounding the curve is its “fat” hit test area.
The fat area is actually a closed polyline-path.
Here’s how to build the fat curve:
- Travel along the curve from start to end in about 15-25 steps
- At each step, calc a perpendicular line off that point on the curve
- Extend the perpendicular line out from the curve by a distance (t)
- Save the x/y endpoint of each extended perpendicular line
- (These saved points will define the “fattened” polyline-path)
Notes:
If you move any anchor, you need to recalculate the fat path.
If you want your curve to be quadratic instead of cubic, just make the 2 control points identical.
For KineticJS hit-testing: use the polyline points to define the hit region using drawHitFunc.
Making 25 steps on the curve will usually do a good job on even "kinked" curves. If you know you will have relatively smooth curves, you could take fewer steps. Fewer steps results in less precision in following the exact path of the curve.
Here’s code and a Fiddle: 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>