local glue = require'glue' local path_state = require'path2d' local reflect_point = require'path2d_point'.reflect_point local reflect_point_distance = require'path2d_point'.reflect_point_distance local distance = require'path2d_point'.distance local point_angle = require'path2d_point'.point_angle local point_around = require'path2d_point'.point_around local point_line_intersection = require'path2d_line'.point_line_intersection local arc3p_to_arc = require'path2d_arc_3p'.to_arc local svgarc_to_arc = require'path2d_svgarc'.to_arc local arc_endpoints = require'path2d_arc'.endpoints local arc_tangent_vector = require'path2d_arc'.tangent_vector local point_at = require'path2d_arc'.point_at local point_at = require'path2d_arc'.point_at local circle_3p_to_circle = require'path2d_circle_3p'.to_circle local star_to_star_2p = require'path2d_shapes'.star_to_star_2p local function recursion_safe(f) local setting = false return function(...) if setting then return end setting = true f(...) setting = false end end local function new_mutex() local setting = false return function(f) return function(...) if setting then return end setting = true f(...) setting = false end end end local function control_points(path) local points = {} --{x1, y1, x2, y2, ...} local setters = {} --{set_x1, set_y1, ...} --override the value setter at index px so that it chain-calls another setter local function chain(px, setter) local old_setter = assert(setters[px]) setters[px] = function(x) old_setter(x) setter(x) end end --create a point with a null update handler local function null_pt(x, y) local px, py = #points+1, #points+2 points[px] = x points[py] = y setters[px] = glue.pass setters[py] = glue.pass return px, py end --create a point that updates itself local function self_pt(x, y) local px, py = null_pt(x, y) --point updates its own coordinates setters[px] = function(x) points[px] = x end setters[py] = function(y) points[py] = y end return px, py end --create a point that directly represents (and thus updates) an abs. point in path at index cx, cy local function path_abs_pt(cx, cy) local px, py = null_pt(path[cx], path[cy]) --point updates itself and its representation in path setters[px] = function(x) points[px] = x; path[cx] = x end setters[py] = function(y) points[py] = y; path[cy] = y end return px, py end --create a point that directly represents (and thus updates) a rel. point in path at index cx, cy local function path_rel_pt(cx, cy, cpx, cpy) local px, py = null_pt(points[cpx] + path[cx], points[cpy] + path[cy]) --point updates itself and its representation in path setters[px] = function(x) points[px] = x; path[cx] = x - points[cpx] end setters[py] = function(y) points[py] = y; path[cy] = y - points[cpy] end --current point updates the point in path that is relative to it, so as to preserve that point's absolute position chain(cpx, function(x) path[cx] = points[px] - x end) chain(cpy, function(y) path[cy] = points[py] - y end) return px, py end --create a point that directly represents (and thus updates) a rel. or an abs. point in path at index cx, cy local function path_pt(cx, cy, rel, cpx, cpy) return (rel and path_rel_pt or path_abs_pt)(cx, cy, cpx, cpy) end --move px,py with delta (difference of movement) when cx,cy is updated (i.e. cx,cy carries or drags px,py with it) local dx, dy = 0, 0 local function move_delta(cx, cy, px, py, mutex) local move_px = function() setters[px](points[px] + dx) end local move_py = function() setters[py](points[py] + dy) end if mutex then move_px = mutex(move_px) move_py = mutex(move_py) end chain(cx, move_px) chain(cy, move_py) end local cpx, cpy, spx, spy, tkind, tx, ty, tclen for i,s in path_state.commands(path) do local s, rel = path_state.abs_name(s), path_state.is_rel(s) local tkind1 = tkind; tkind = nil if s == 'move' then local c2x, c2y = i+1, i+2 local p2x, p2y = path_pt(c2x, c2y, rel, cpx, cpy) cpx, cpy = p2x, p2y spx, spy = cpx, cpy elseif s == 'line' then local c2x, c2y = i+1, i+2 local p2x, p2y = path_pt(c2x, c2y, rel, cpx, cpy) cpx, cpy = p2x, p2y elseif s == 'close' then cpx, cpy = spx, spy elseif s == 'hline' then local p1x, p1y = cpx, cpy local c2x = i+1 local p2x, p2y = self_pt((rel and points[p1x] or 0) + path[c2x], points[p1y]) if not rel then --endpoint updates its representation in path chain(p2x, function(v) path[c2x] = points[p2x] end) else local function set_c2x(v) path[c2x] = points[p2x] - points[p1x] end --endpoint updates its representation in path chain(p2x, set_c2x) --current point updates relative points in path so as to preserve their absolute position chain(p1x, set_c2x) end --endpoint updates current point on the constrained axis to preserve horizontality chain(p2y, function(v) setters[p1y](v) end) --current point updates endpoint on the constrained axis to preserve horizontality chain(p1y, recursion_safe(function(v) setters[p2y](v) end)) cpx, cpy = p2x, p2y elseif s == 'vline' then local p1x, p1y = cpx, cpy local c2y = i+1 local p2x, p2y = self_pt(points[p1x], (rel and points[p1y] or 0) + path[c2y]) if not rel then --endpoint updates its representation in path chain(p2y, function(v) path[c2y] = points[p2y] end) else local function set_c2y(v) path[c2y] = points[p2y] - points[p1y] end --endpoint updates its representation in path chain(p2y, set_c2y) --current point updates relative points in path so as to preserve their absolute position chain(p1y, set_c2y) end --endpoint updates current point on the constrained axis to preserve verticality chain(p2x, function(v) setters[p1x](v) end) --current point updates endpoint on the constrained axis to preserve verticality chain(p1x, recursion_safe(function(v) setters[p2x](v) end)) cpx, cpy = p2x, p2y elseif s == 'quad_curve' then local p1x, p1y = cpx, cpy local c2x, c2y, c3x, c3y = i+1, i+2, i+3, i+4 local p2x, p2y = path_pt(c2x, c2y, rel, cpx, cpy) local p3x, p3y = path_pt(c3x, c3y, rel, cpx, cpy) --end points carry control point move_delta(p1x, p1y, p2x, p2y) move_delta(p3x, p3y, p2x, p2y) cpx, cpy = p3x, p3y tkind, tx, ty, tclen = 'quad', p2x, p2y, nil elseif s == 'quad_curve_3p' then local p1x, p1y = cpx, cpy local c2x, c2y, c3x, c3y = i+1, i+2, i+3, i+4 local p2x, p2y = path_pt(c2x, c2y, rel, cpx, cpy) local p3x, p3y = path_pt(c3x, c3y, rel, cpx, cpy) cpx, cpy = p3x, p3y tkind, tx, ty, tclen = 'tangent', p2x, p2y, nil elseif s == 'curve' then local p1x, p1y = cpx, cpy local c2x, c2y, c3x, c3y, c4x, c4y = i+1, i+2, i+3, i+4, i+5, i+6 local p2x, p2y = path_pt(c2x, c2y, rel, cpx, cpy) local p3x, p3y = path_pt(c3x, c3y, rel, cpx, cpy) local p4x, p4y = path_pt(c4x, c4y, rel, cpx, cpy) --endpoints carry control points move_delta(p1x, p1y, p2x, p2y) move_delta(p4x, p4y, p3x, p3y) cpx, cpy = p4x, p4y tkind, tx, ty, tclen = 'cubic', p3x, p3y, nil elseif s == 'symm_quad_curve' or s == 'symm_curve' then local kind = s:find'quad' and 'quad' or 'cubic' local p1x, p1y = cpx, cpy local c3x, c3y = i+1, i+2 local p3x, p3y = path_pt(c3x, c3y, rel, cpx, cpy) local p4x, p4y if kind == 'cubic' then local c4x, c4y = i+3, i+4 p4x, p4y = path_pt(c4x, c4y, rel, cpx, cpy) end local psx, psy if tkind1 == kind then psx, psy = self_pt(reflect_point(points[tx], points[ty], points[p1x], points[p1y])) local tx_, ty_ = tx, ty --virtual control point moves the tangent tip chain(psx, recursion_safe(function(v) setters[tx_](2 * points[p1x] - v) end)) chain(psy, recursion_safe(function(v) setters[ty_](2 * points[p1y] - v) end)) --tangent tip moves the virtual control point chain(tx, function(v) setters[psx](2 * points[p1x] - v) end) chain(ty, function(v) setters[psy](2 * points[p1y] - v) end) --endpoints carry second control points if kind == 'quad' then move_delta(p3x, p3y, psx, psy) else move_delta(p1x, p1y, psx, psy) end else --if the tangent tip is missing or not of the right type, the first endpoint serves as tangent tip psx, psy = p1x, p1y end --second endpoint carries second control point if kind == 'cubic' then --TODO: move_delta(p4x, p4y, p3x, p3y) end --advance the state if kind == 'quad' then cpx, cpy = p3x, p3y tkind, tx, ty, tclen = kind, psx, psy, nil else cpx, cpy = p4x, p4y tkind, tx, ty, tclen = kind, p3x, p3y, nil end elseif s == 'smooth_quad_curve' or s == 'smooth_curve' then local kind = s:find'quad' and 'quad' or 'cubic' local p1x, p1y = cpx, cpy local clen, c3x, c3y = i+1, i+2, i+3 local p3x, p3y = path_pt(c3x, c3y, rel, cpx, cpy) local p4x, p4y if kind == 'cubic' then local c4x, c4y = i+4, i+5 p4x, p4y = path_pt(c4x, c4y, rel, cpx, cpy) end local psx, psy if tkind1 == 'tangent' then --there's a tangent tip and it's immovable local ptx, pty = tx, ty psx, psy = null_pt(0, 0) local mutex = new_mutex() setters[psx] = mutex(function(x) local x, y = point_line_intersection(x, points[psy], points[p1x], points[p1y], points[ptx], points[pty]) points[psx] = x points[psy] = y path[clen] = distance(x, y, points[p1x], points[p1y]) end) setters[psy] = mutex(function(y) local x, y = point_line_intersection(points[psx], y, points[p1x], points[p1y], points[ptx], points[pty]) points[psx] = x points[psy] = y path[clen] = distance(x, y, points[p1x], points[p1y]) end) --tangent tip rotates smooth control point local set_ps = mutex(function() local x, y = reflect_point_distance(points[ptx], points[pty], points[p1x], points[p1y], path[clen]) points[psx] = x points[psy] = y setters[psx](x) setters[psy](y) end) chain(tx, set_ps) chain(ty, set_ps) set_ps() elseif tkind1 then --there's a tip and it's movable psx, psy = self_pt(reflect_point_distance(points[tx], points[ty], points[p1x], points[p1y], path[clen])) local tx_, ty_, tclen_ = tx, ty, tclen --moving the virtual control point updates clen local function set_clen() path[clen] = distance(points[psx], points[psy], points[p1x], points[p1y]) end chain(psx, set_clen) chain(psy, set_clen) local mutex = new_mutex() --virtual control point moves tangent tip local move_tip = mutex(function() if tclen_ then local tlen = path[tclen_] local x, y = reflect_point_distance(points[psx], points[psy], points[p1x], points[p1y], tlen) setters[tx_](x) setters[ty_](y) else local tlen = distance(points[tx_], points[ty_], points[p1x], points[p1y]) local x, y = reflect_point_distance(points[psx], points[psy], points[p1x], points[p1y], tlen) setters[tx_](x) setters[ty_](y) end end) chain(psx, move_tip) chain(psy, move_tip) --tangent tip moves virtual control point local move_vpoint = mutex(function() local x, y = reflect_point_distance(points[tx_], points[ty_], points[p1x], points[p1y], path[clen]) setters[psx](x) setters[psy](y) end) chain(tx, move_vpoint) chain(ty, move_vpoint) --second endpoint carries control point if kind == 'quad' then move_delta(p3x, p3y, psx, psy) end else --there's no tangent tip, so the first endpoint will serves as smooth point psx, psy = p1x, p1y end --second endpoint carries second control point if kind == 'cubic' then move_delta(p4x, p4y, p3x, p3y) end --advance the state if kind == 'quad' then cpx, cpy = p3x, p3y tkind, tx, ty, tclen = kind, psx, psy--, clen else cpx, cpy = p4x, p4y tkind, tx, ty, tclen = kind, p3x, p3y--, clen end elseif s == 'arc' or s == 'line_arc' or s == 'elliptic_arc' or s == 'line_elliptic_arc' then local p1x, p1y = cpx, cpy local ccx, ccy, crx, cry, cstart_angle, csweep_angle, crotation if s:find'elliptic' then ccx, ccy, crx, cry, cstart_angle, csweep_angle, crotation = i+1, i+2, i+3, i+4, i+5, i+6, i+7 else ccx, ccy, crx, cry, cstart_angle, csweep_angle = i+1, i+2, i+3, i+3, i+4, i+5 end local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) --arc endpoints local px1, py1 = self_pt(0, 0) local px2, py2 = self_pt(0, 0) local function set_endpoints() local cx, cy = points[pcx], points[pcy] local rx, ry, start_angle, sweep_angle = path[crx], path[cry], path[cstart_angle], path[csweep_angle] local rotation = crotation and path[crotation] local x1, y1, x2, y2 = arc_endpoints(cx, cy, rx, ry, start_angle, sweep_angle, rotation) points[px1] = x1 points[py1] = y1 --TODO --setters[px2](x2) --setters[py2](y2) points[px2] = x2 points[py2] = y2 end set_endpoints() --center carries endpoints move_delta(pcx, pcy, px1, py1) move_delta(pcx, pcy, px2, py2) --endpoints change angles local function move_angles() local a1 = point_angle(points[px1], points[py1], points[pcx], points[pcy]) local a2 = point_angle(points[px2], points[py2], points[pcx], points[pcy]) path[cstart_angle] = a1 --path[csweep_angle] = a2 - a1 set_endpoints() end chain(px1, move_angles) chain(py1, move_angles) chain(px2, move_angles) chain(py2, move_angles) if s:find'^line_' then cpx, cpy = px2, py2 else spx, spy, cpx, cpy = px1, py1, px2, py2 end elseif s == 'svgarc' then local px1, py1 = cpx, cpy local crx, cry, crotation, clarge_arc_flag, csweep_flag, cx2, cy2 = i+1, i+2, i+3, i+4, i+5, i+6, i+7 local px2, py2 = path_pt(cx2, cy2, rel, cpx, cpy) --arc's center, radii and rotation control points local pcx, pcy = self_pt(0, 0) local prxx, prxy = self_pt(0, 0) local pryx, pryy = self_pt(0, 0) local prxx1, prxy1 = self_pt(0, 0) local pryx1, pryy1 = self_pt(0, 0) local ptx, pty = null_pt(0, 0) --endpoints update control points local function set_pts() local cx, cy, rx, ry, start_angle, sweep_angle, rotation, x2, y2 = svgarc_to_arc(points[px1], points[py1], path[crx], path[cry], path[crotation], path[clarge_arc_flag], path[csweep_flag], points[px2], points[py2]) if cx then points[pcx], points[pcy] = cx, cy points[prxx], points[prxy] = point_at(0, cx, cy, rx, ry, rotation) points[pryx], points[pryy] = point_at(90, cx, cy, rx, ry, rotation) points[prxx1], points[prxy1] = point_at(180, cx, cy, path[crx], path[cry], rotation) points[pryx1], points[pryy1] = point_at(270, cx, cy, path[crx], path[cry], rotation) points[ptx], points[pty] = select(3, arc_tangent_vector(1, cx, cy, rx, ry, start_angle, sweep_angle, rotation, x2, y2)) else --TODO: find better spots to place these points[pcx], points[pcy] = points[px1], points[py1] points[prxx], points[prxy] = points[px1], points[py1] points[pryx], points[pryy] = points[px1], points[py1] points[prxx1], points[prxy1] = points[px1], points[py1] points[pryx1], points[pryy1] = points[px1], points[py1] points[ptx], points[pty] = points[px1], points[py1] end setters[ptx](x) setters[pty](y) end set_pts() chain(px2, set_pts) chain(py2, set_pts) chain(px1, set_pts) chain(py1, set_pts) --center carries endpoints move_delta(pcx, pcy, px1, py1) move_delta(pcx, pcy, px2, py2) --rotation points update rotation and other control points local function rotate_around_rx() path[crotation] = point_angle(points[prxx], points[prxy], points[pcx], points[pcy]) set_pts() end local function rotate_around_ry() path[crotation] = point_angle(points[pryx], points[pryy], points[pcx], points[pcy]) - 90 set_pts() end chain(prxx, rotate_around_rx) chain(prxy, rotate_around_rx) chain(pryx, rotate_around_ry) chain(pryy, rotate_around_ry) --radii points update radii and other control points local function set_radii() path[crx] = distance(points[prxx1], points[prxy1], points[pcx], points[pcy]) path[cry] = distance(points[pryx1], points[pryy1], points[pcx], points[pcy]) set_pts() end chain(prxx1, set_radii) chain(prxy1, set_radii) chain(pryx1, set_radii) chain(pryy1, set_radii) cpx, cpy = px2, py2 tkind, tx, ty = 'tangent', ptx, pty elseif s == 'arc_3p' then local px1, py1 = cpx, cpy local cxp, cyp, cx2, cy2 = i+1, i+2, i+3, i+4 local pxp, pyp = path_pt(cxp, cyp, rel, cpx, cpy) local px2, py2 = path_pt(cx2, cy2, rel, cpx, cpy) --tangent point: not directly updatable, but updated by any of the arc's endpoints local ptx, pty = null_pt(0, 0) local function setpt() local x1, y1, xp, yp, x2, y2 = points[px1], points[py1], points[pxp], points[pyp], points[px2], points[py2] local cx, cy, r, start_angle, sweep_angle, x2, y2 = arc3p_to_arc(x1, y1, xp, yp, x2, y2) if cx then x1, y1 = select(3, arc_tangent_vector(1, cx, cy, r, r, start_angle, sweep_angle, 0, x2, y2)) end points[ptx] = x1 points[pty] = y1 setters[ptx](x) setters[pty](y) end setpt() chain(px2, setpt) chain(px2, setpt) chain(px1, setpt) chain(py1, setpt) chain(pxp, setpt) chain(pyp, setpt) cpx, cpy = px2, py2 tkind, tx, ty = 'tangent', ptx, pty elseif s == 'circle_3p' then local cx1, cy1, cx2, cy2, cx3, cy3 = i+1, i+2, i+3, i+4, i+5, i+6 local px1, py1 = path_pt(cx1, cy1, rel, cpx, cpy) local px2, py2 = path_pt(cx2, cy2, rel, cpx, cpy) local px3, py3 = path_pt(cx3, cy3, rel, cpx, cpy) --center point local pcx, pcy = self_pt(0, 0) local function set_center() local cx, cy, r = circle_3p_to_circle(points[px1], points[py1], points[px2], points[py2], points[px3], points[py3]) if not cx then return end points[pcx], points[pcy] = cx, cy end set_center() --tangent points move center point chain(px1, set_center) chain(py1, set_center) chain(px2, set_center) chain(py2, set_center) chain(px3, set_center) chain(py3, set_center) --center point carries tangent points move_delta(pcx, pcy, px1, py1) move_delta(pcx, pcy, px2, py2) move_delta(pcx, pcy, px3, py3) elseif s == 'circle' then local ccx, ccy, cr = i+1, i+2, i+3 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) --arbitrary point on circle representing its radius local px, py = self_pt(points[pcx] - path[cr], points[pcy]) local function set_r() path[cr] = distance(points[px], points[py], points[pcx], points[pcy]) end --radius point changes radius chain(px, set_r) chain(py, set_r) --center point carries radius point move_delta(pcx, pcy, px, py) elseif s == 'ellipse' then local ccx, ccy, crx, cry, crotation = i+1, i+2, i+3, i+4, i+5 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) local mutex = new_mutex() --radii points change radii local prxx, prxy = self_pt(0, 0) local pryx, pryy = self_pt(0, 0) local function set_pts() local cx, cy, rx, ry, rotation = points[pcx], points[pcy], path[crx], path[cry], path[crotation] points[prxx], points[prxy] = point_at(0, cx, cy, rx, ry, rotation) points[pryx], points[pryy] = point_at(90, cx, cy, rx, ry, rotation) end set_pts() local set_rx = mutex(function() path[crotation] = point_angle(points[prxx], points[prxy], points[pcx], points[pcy]) path[crx] = distance(points[prxx], points[prxy], points[pcx], points[pcy]) set_pts() end) chain(prxx, set_rx) chain(prxy, set_rx) local set_ry = mutex(function() path[crotation] = point_angle(points[pryx], points[pryy], points[pcx], points[pcy]) - 90 path[cry] = distance(points[pryx], points[pryy], points[pcx], points[pcy]) set_pts() end) chain(pryx, set_ry) chain(pryy, set_ry) --center point carries radii points exclusive of their own updating move_delta(pcx, pcy, prxx, prxy, mutex) move_delta(pcx, pcy, pryx, pryy, mutex) elseif s == 'rect' or s == 'round_rect' or s == 'elliptic_rect' then local cx, cy, cw, ch = i+1, i+2, i+3, i+4 local px1, py1 = path_pt(cx, cy, rel, cpx, cpy) local px2, py2 = self_pt(points[px1] + path[cw], points[py1] + path[ch]) local function set_size() path[cw] = points[px2] - points[px1] path[ch] = points[py2] - points[py1] end chain(px1, set_size) chain(py1, set_size) chain(px2, set_size) chain(py2, set_size) if s == 'round_rect' or s == 'elliptic_rect' then local crx, cry = i+5, s == 'round_rect' and i+5 or i+6 local w, h, rx, ry = path[cw], path[ch], path[crx], path[cry] local min, max, abs = math.min, math.max, math.abs if crx == cry then rx = min(abs(rx), abs(w/2), abs(h/2)) ry = rx else rx = min(abs(rx), abs(w/2)) ry = min(abs(ry), abs(h/2)) end local prxx, prxy = self_pt(points[px1] - rx, points[py1]) local pryx, pryy = self_pt(points[px1], points[py1] - ry) local function set_rx() points[prxx] = math.max((points[px2] + points[px1]) / 2, math.min(points[px1], points[prxx])) path[crx] = points[px1] - points[prxx] end chain(prxx, set_rx) setters[prxy] = function(v) points[prxy] = points[py1] end local function set_ry() points[pryy] = math.max((points[py2] + points[py1]) / 2, math.min(points[py1], points[pryy])) path[cry] = points[py1] - points[pryy] end chain(pryy, set_ry) setters[pryx] = function(v) points[pryx] = points[px1] end move_delta(px1, py1, prxx, prxy) move_delta(px1, py1, pryx, pryy) end elseif s == 'star' then local ccx, ccy, cx1, cy1, cr2, cn = i+1, i+2, i+3, i+4, i+5, i+6 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) local px1, py1 = path_pt(cx1, cy1, rel, cpx, cpy) local px2, py2 = self_pt(0, 0) local function set_p2() local cx, cy, x1, y1, x2, y2, n = star_to_star_2p(points[pcx], points[pcy], points[px1], points[py1], path[cr2], path[cn]) points[px2], points[py2] = x2, y2 end set_p2() chain(px1, set_p2) chain(py1, set_p2) local function set_r2() path[cr2] = distance(points[px2], points[py2], points[pcx], points[pcy]) set_p2() end chain(px2, set_r2) chain(py2, set_r2) move_delta(pcx, pcy, px1, py1) elseif s == 'star_2p' then local ccx, ccy, cx1, cy1, cx2, cy2, cn = i+1, i+2, i+3, i+4, i+5, i+6, i+7 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) local px1, py1 = path_pt(cx1, cy1, rel, cpx, cpy) local px2, py2 = path_pt(cx2, cy2, rel, cpx, cpy) move_delta(pcx, pcy, px1, py1) move_delta(pcx, pcy, px2, py2) elseif s == 'rpoly' then local ccx, ccy, cx1, cy1, cn = i+1, i+2, i+3, i+4, i+5 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) local px1, py1 = path_pt(cx1, cy1, rel, cpx, cpy) move_delta(pcx, pcy, px1, py1) elseif s == 'superformula' then local ccx, ccy, csize, csteps, crotation = i+1, i+2, i+3, i+4, i+5 local pcx, pcy = path_pt(ccx, ccy, rel, cpx, cpy) local mutex = new_mutex() local prx, pry = self_pt(point_around(points[pcx], points[pcy], path[csize], path[crotation])) local set_size = mutex(function() path[crotation] = point_angle(points[prx], points[pry], points[pcx], points[pcy]) path[csize] = distance(points[prx], points[pry], points[pcx], points[pcy]) end) chain(prx, set_size) chain(pry, set_size) move_delta(pcx, pcy, prx, pry, mutex) end end local function update(i, px, py, co) dx, dy = px - points[i], py - points[i+1] setters[i](px) setters[i+1](py) end return points, update end local function tangent_tips(p) --for i,s in end if not ... then require'path2d_editor_demo' end return { control_points = control_points, }