--2D cubic bezier adaptive interpolation from AGG. --adapted from http://www.antigrain.com/research/adaptive_bezier/index.html by Cosmin Apreutesei. local bezier3 = require'path2d_bezier3' local distance2 = require'path2d_point'.distance2 local pi, atan2, abs, radians = math.pi, math.atan2, math.abs, math.rad local curve_collinearity_epsilon = 1e-30 local curve_angle_tolerance_epsilon = 0.01 local curve_recursion_limit = 32 local recursive_bezier --forward decl. --tip: adjust approximation_scale to the scale of the world-to-screen transformation. --tip: enable angle_tolerance when stroke width * scale > 1. --tip: cusp_limit should not exceed 10-15 degrees. function bezier3.interpolate(write, x1, y1, x2, y2, x3, y3, x4, y4, approximation_scale, angle_tolerance, cusp_limit) approximation_scale = approximation_scale or 1 angle_tolerance = angle_tolerance and radians(angle_tolerance) or 0 cusp_limit = cusp_limit and cusp_limit ~= 0 and pi - radians(cusp_limit) or 0 local distance_tolerance2 = (1 / (2 * approximation_scale))^2 recursive_bezier(write, x1, y1, x2, y2, x3, y3, x4, y4, 0, distance_tolerance2, angle_tolerance, cusp_limit) write('line', x4, y4) end function recursive_bezier(write, x1, y1, x2, y2, x3, y3, x4, y4, level, distance_tolerance2, angle_tolerance, cusp_limit) if level > curve_recursion_limit then return end --Calculate all the mid-points of the line segments local x12 = (x1 + x2) / 2 local y12 = (y1 + y2) / 2 local x23 = (x2 + x3) / 2 local y23 = (y2 + y3) / 2 local x34 = (x3 + x4) / 2 local y34 = (y3 + y4) / 2 local x123 = (x12 + x23) / 2 local y123 = (y12 + y23) / 2 local x234 = (x23 + x34) / 2 local y234 = (y23 + y34) / 2 local x1234 = (x123 + x234) / 2 local y1234 = (y123 + y234) / 2 --Try to approximate the full cubic curve by a single straight line local dx = x4-x1 local dy = y4-y1 local d2 = abs((x2 - x4) * dy - (y2 - y4) * dx) local d3 = abs((x3 - x4) * dy - (y3 - y4) * dx) local da1, da2, k local case = (d2 > curve_collinearity_epsilon and 2 or 0) + (d3 > curve_collinearity_epsilon and 1 or 0) if case == 0 then --All collinear OR p1==p4 k = dx^2 + dy^2 if k == 0 then d2 = distance2(x1, y1, x2, y2) d3 = distance2(x4, y4, x3, y3) else k = 1 / k da1 = x2 - x1 da2 = y2 - y1 d2 = k * (da1*dx + da2*dy) da1 = x3 - x1 da2 = y3 - y1 d3 = k * (da1*dx + da2*dy) if d2 > 0 and d2 < 1 and d3 > 0 and d3 < 1 then --Simple collinear case, 1---2---3---4 --We can leave just two endpoints return end if d2 <= 0 then d2 = distance2(x2, y2, x1, y1) elseif d2 >= 1 then d2 = distance2(x2, y2, x4, y4) else d2 = distance2(x2, y2, x1 + d2*dx, y1 + d2*dy) end if d3 <= 0 then d3 = distance2(x3, y3, x1, y1) elseif d3 >= 1 then d3 = distance2(x3, y3, x4, y4) else d3 = distance2(x3, y3, x1 + d3*dx, y1 + d3*dy) end end if d2 > d3 then if d2 < distance_tolerance2 then write('line', x2, y2) return end elseif d3 < distance_tolerance2 then write('line', x3, y3) return end elseif case == 1 then -- p1,p2,p4 are collinear, p3 is significant if d3^2 <= distance_tolerance2 * (dx^2 + dy^2) then if angle_tolerance < curve_angle_tolerance_epsilon then write('line', x23, y23) return end -- Angle Condition da1 = abs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2)) if da1 >= pi then da1 = 2*pi - da1 end if da1 < angle_tolerance then write('line', x2, y2) write('line', x3, y3) return end if cusp_limit ~= 0 then if da1 > cusp_limit then write('line', x3, y3) return end end end elseif case == 2 then -- p1,p3,p4 are collinear, p2 is significant if d2^2 <= distance_tolerance2 * (dx^2 + dy^2) then if angle_tolerance < curve_angle_tolerance_epsilon then write('line', x23, y23) return end -- Angle Condition da1 = abs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1)) if da1 >= pi then da1 = 2*pi - da1 end if da1 < angle_tolerance then write('line', x2, y2) write('line', x3, y3) return end if cusp_limit ~= 0 then if da1 > cusp_limit then write('line', x2, y2) return end end end elseif case == 3 then -- Regular case if (d2 + d3)^2 <= distance_tolerance2 * (dx^2 + dy^2) then -- If the curvature doesn't exceed the distance_tolerance value -- we tend to finish subdivisions. if angle_tolerance < curve_angle_tolerance_epsilon then write('line', x23, y23) return end -- Angle & Cusp Condition k = atan2(y3 - y2, x3 - x2) da1 = abs(k - atan2(y2 - y1, x2 - x1)) da2 = abs(atan2(y4 - y3, x4 - x3) - k) if da1 >= pi then da1 = 2*pi - da1 end if da2 >= pi then da2 = 2*pi - da2 end if da1 + da2 < angle_tolerance then -- Finally we can stop the recursion write('line', x23, y23) return end if cusp_limit ~= 0 then if da1 > cusp_limit then write('line', x2, y2) return end if da2 > cusp_limit then write('line', x3, y3) return end end end end -- Continue subdivision recursive_bezier(write, x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1, distance_tolerance2, angle_tolerance, cusp_limit) recursive_bezier(write, x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1, distance_tolerance2, angle_tolerance, cusp_limit) end if not ... then require'path2d_bezier3_demo' end