UNPKG

adaptive-bezier-curve

Version:
198 lines (170 loc) 7.43 kB
function clone(point) { //TODO: use gl-vec2 for this return [point[0], point[1]] } function vec2(x, y) { return [x, y] } module.exports = function createBezierBuilder(opt) { opt = opt||{} var RECURSION_LIMIT = typeof opt.recursion === 'number' ? opt.recursion : 8 var FLT_EPSILON = typeof opt.epsilon === 'number' ? opt.epsilon : 1.19209290e-7 var PATH_DISTANCE_EPSILON = typeof opt.pathEpsilon === 'number' ? opt.pathEpsilon : 1.0 var curve_angle_tolerance_epsilon = typeof opt.angleEpsilon === 'number' ? opt.angleEpsilon : 0.01 var m_angle_tolerance = opt.angleTolerance || 0 var m_cusp_limit = opt.cuspLimit || 0 return function bezierCurve(start, c1, c2, end, scale, points) { if (!points) points = [] scale = typeof scale === 'number' ? scale : 1.0 var distanceTolerance = PATH_DISTANCE_EPSILON / scale distanceTolerance *= distanceTolerance begin(start, c1, c2, end, points, distanceTolerance) return points } ////// Based on: ////// https://github.com/pelson/antigrain/blob/master/agg-2.4/src/agg_curves.cpp function begin(start, c1, c2, end, points, distanceTolerance) { points.push(clone(start)) var x1 = start[0], y1 = start[1], x2 = c1[0], y2 = c1[1], x3 = c2[0], y3 = c2[1], x4 = end[0], y4 = end[1] recursive(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, 0) points.push(clone(end)) } function recursive(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) { if(level > RECURSION_LIMIT) return var pi = Math.PI // Calculate all the mid-points of the line segments //---------------------- var x12 = (x1 + x2) / 2 var y12 = (y1 + y2) / 2 var x23 = (x2 + x3) / 2 var y23 = (y2 + y3) / 2 var x34 = (x3 + x4) / 2 var y34 = (y3 + y4) / 2 var x123 = (x12 + x23) / 2 var y123 = (y12 + y23) / 2 var x234 = (x23 + x34) / 2 var y234 = (y23 + y34) / 2 var x1234 = (x123 + x234) / 2 var y1234 = (y123 + y234) / 2 if(level > 0) { // Enforce subdivision first time // Try to approximate the full cubic curve by a single straight line //------------------ var dx = x4-x1 var dy = y4-y1 var d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx) var d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx) var da1, da2 if(d2 > FLT_EPSILON && d3 > FLT_EPSILON) { // Regular care //----------------- if((d2 + d3)*(d2 + d3) <= distanceTolerance * (dx*dx + dy*dy)) { // If the curvature doesn't exceed the distanceTolerance value // we tend to finish subdivisions. //---------------------- if(m_angle_tolerance < curve_angle_tolerance_epsilon) { points.push(vec2(x1234, y1234)) return } // Angle & Cusp Condition //---------------------- var a23 = Math.atan2(y3 - y2, x3 - x2) da1 = Math.abs(a23 - Math.atan2(y2 - y1, x2 - x1)) da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - a23) if(da1 >= pi) da1 = 2*pi - da1 if(da2 >= pi) da2 = 2*pi - da2 if(da1 + da2 < m_angle_tolerance) { // Finally we can stop the recursion //---------------------- points.push(vec2(x1234, y1234)) return } if(m_cusp_limit !== 0.0) { if(da1 > m_cusp_limit) { points.push(vec2(x2, y2)) return } if(da2 > m_cusp_limit) { points.push(vec2(x3, y3)) return } } } } else { if(d2 > FLT_EPSILON) { // p1,p3,p4 are collinear, p2 is considerable //---------------------- if(d2 * d2 <= distanceTolerance * (dx*dx + dy*dy)) { if(m_angle_tolerance < curve_angle_tolerance_epsilon) { points.push(vec2(x1234, y1234)) return } // Angle Condition //---------------------- da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)) if(da1 >= pi) da1 = 2*pi - da1 if(da1 < m_angle_tolerance) { points.push(vec2(x2, y2)) points.push(vec2(x3, y3)) return } if(m_cusp_limit !== 0.0) { if(da1 > m_cusp_limit) { points.push(vec2(x2, y2)) return } } } } else if(d3 > FLT_EPSILON) { // p1,p2,p4 are collinear, p3 is considerable //---------------------- if(d3 * d3 <= distanceTolerance * (dx*dx + dy*dy)) { if(m_angle_tolerance < curve_angle_tolerance_epsilon) { points.push(vec2(x1234, y1234)) return } // Angle Condition //---------------------- da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2)) if(da1 >= pi) da1 = 2*pi - da1 if(da1 < m_angle_tolerance) { points.push(vec2(x2, y2)) points.push(vec2(x3, y3)) return } if(m_cusp_limit !== 0.0) { if(da1 > m_cusp_limit) { points.push(vec2(x3, y3)) return } } } } else { // Collinear case //----------------- dx = x1234 - (x1 + x4) / 2 dy = y1234 - (y1 + y4) / 2 if(dx*dx + dy*dy <= distanceTolerance) { points.push(vec2(x1234, y1234)) return } } } } // Continue subdivision //---------------------- recursive(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1) recursive(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1) } }