function point_right_of_line(a, b, p) { // a bit of cross-product tomfoolery (we check sign of z of the crossproduct) return ((b.x - a.x) * (a.y - p.y) - (a.y - b.y) * (p.x - a.x)) <= 0; } function rdp_find_max(points, start, end) { const EPS = 0.5; let result = -1; let max_dist = 0; const a = points[start]; const b = points[end]; const dx = b.x - a.x; const dy = b.y - a.y; const dist_ab = Math.sqrt(dx * dx + dy * dy); const sin_theta = dy / dist_ab; const cos_theta = dx / dist_ab; for (let i = start; i < end; ++i) { const p = points[i]; const ox = p.x - a.x; const oy = p.y - a.y; const rx = cos_theta * ox + sin_theta * oy; const ry = -sin_theta * ox + cos_theta * oy; const x = rx + a.x; const y = ry + a.y; const dist = Math.abs(y - a.y); if (dist > EPS && dist > max_dist) { result = i; max_dist = dist; } } return result; } function process_rdp_r(points, start, end) { let result = []; const max = rdp_find_max(points, start, end); if (max !== -1) { const before = process_rdp_r(points, start, max); const after = process_rdp_r(points, max, end); result = [...before, points[max], ...after]; } return result; } function process_rdp(points) { const result = process_rdp_r(points, 0, points.length - 1); result.unshift(points[0]); result.push(points[points.length - 1]); return result; } function process_ewmv(points, round = false) { const result = []; const alpha = 0.4; result.push(points[0]); for (let i = 1; i < points.length; ++i) { const p = points[i]; const x = Math.round(alpha * p.x + (1 - alpha) * result[result.length - 1].x); const y = Math.round(alpha * p.y + (1 - alpha) * result[result.length - 1].y); result.push({'x': x, 'y': y}); } return result; } function process_stroke(points) { // const result0 = process_ewmv(points); const result1 = process_rdp(points, true); return result1; } function stroke_stats(points, width) { if (points.length === 0) { const bbox = { 'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0 }; return { 'bbox': bbox, 'length': 0, }; } let length = 0; let xmin = points[0].x, ymin = points[0].y; let xmax = xmin, ymax = ymin; for (let i = 0; i < points.length; ++i) { const point = points[i]; if (point.x < xmin) xmin = point.x; if (point.y < ymin) ymin = point.y; if (point.x > xmax) xmax = point.x; if (point.y > ymax) ymax = point.y; if (i > 0) { const last = points[i - 1]; const dx = point.x - last.x; const dy = point.y - last.y; length += Math.sqrt(dx * dx + dy * dy); } } xmin -= width; ymin -= width; xmax += width * 2; ymax += width * 2; const bbox = { 'xmin': Math.floor(xmin), 'ymin': Math.floor(ymin), 'xmax': Math.ceil(xmax), 'ymax': Math.ceil(ymax) }; return { 'bbox': bbox, 'length': length, }; } function rectangles_intersect(a, b) { const result = ( a.xmin <= b.xmax && a.xmax >= b.xmin && a.ymin <= b.ymax && a.ymax >= b.ymin ); return result; } function stroke_intersects_region(points, bbox) { if (points.length === 0) { return false; } const stats = stroke_stats(points, storage.cursor.width); return rectangles_intersect(stats.bbox, bbox); } function color_to_u32(color_str) { const r = parseInt(color_str.substring(1, 3), 16); const g = parseInt(color_str.substring(3, 5), 16); const b = parseInt(color_str.substring(5, 7), 16); return (r << 16) | (g << 8) | b; } function color_from_u32(color_u32) { const r = (color_u32 >> 16) & 0xFF; const g = (color_u32 >> 8) & 0xFF; const b = color_u32 & 0xFF; let r_str = r.toString(16); let g_str = g.toString(16); let b_str = b.toString(16); if (r <= 0xF) r_str = '0' + r_str; if (g <= 0xF) g_str = '0' + g_str; if (b <= 0xF) b_str = '0' + b_str; return '#' + r_str + g_str + b_str; } function ccw(A, B, C) { return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x); } // https://stackoverflow.com/a/9997374/11420590 function segments_intersect(A, B, C, D) { return ccw(A, C, D) != ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D); } function strokes_intersect_line(x1, y1, x2, y2) { const result = []; for (const event of storage.events) { if (event.type === EVENT.STROKE && !event.deleted) { if (event.points.length < 2) { continue; } for (let i = 0; i < event.points.length - 1; ++i) { const sx1 = event.points[i].x; const sy1 = event.points[i].y; const sx2 = event.points[i + 1].x; const sy2 = event.points[i + 1].y; const A = {'x': x1, 'y': y1}; const B = {'x': x2, 'y': y2}; const C = {'x': sx1, 'y': sy1}; const D = {'x': sx2, 'y': sy2}; if (segments_intersect(A, B, C, D)) { result.push(event.stroke_id); break; } } } } return result; } function dist_v2(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); } function perpendicular(ax, ay, bx, by, width) { // Place points at (stroke_width / 2) distance from the line const dirx = bx - ax; const diry = by - ay; let pdirx = diry; let pdiry = -dirx; const pdir_norm = Math.sqrt(pdirx * pdirx + pdiry * pdiry); pdirx /= pdir_norm; pdiry /= pdir_norm; return { 'p1': { 'x': ax + pdirx * width / 2, 'y': ay + pdiry * width / 2, }, 'p2': { 'x': ax - pdirx * width / 2, 'y': ay - pdiry * width / 2, } }; }