From 5593536485fcf4db4f6515576f926dd7cd110366 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Tue, 11 Apr 2023 20:43:47 +0300 Subject: [PATCH] Working rounded lines! --- client/webgl.js | 323 +++++++----------------------------------------- 1 file changed, 48 insertions(+), 275 deletions(-) diff --git a/client/webgl.js b/client/webgl.js index 4bdeaf5..30ee759 100644 --- a/client/webgl.js +++ b/client/webgl.js @@ -102,60 +102,20 @@ const bgcolor = { 'r': 0, 'g': 0, 'b': 0 }; const stroke_color = { 'r': 0.2, 'g': 0.2, 'b': 0.2 }; let debug_draw = true; -function push_cap_triangle(positions, cap_points, i1, i2, i3) { - positions.push(cap_points[i1].x, cap_points[i1].y); - positions.push(cap_points[i2].x, cap_points[i2].y); - positions.push(cap_points[i3].x, cap_points[i3].y); -} - -function push_joint_triangle(positions, joint_points, i1, i2, i3) { - positions.push(joint_points[i1].x, joint_points[i1].y); - positions.push(joint_points[i2].x, joint_points[i2].y); - positions.push(joint_points[i3].x, joint_points[i3].y); -} - -function push_cap(positions, pps, p, pnext, stroke_width) { - // Rounded cap! - const cap_points = []; - - cap_points.push(p); /* not used in positions, added for convenience */ - cap_points.push(pps.p1); - cap_points.push(pps.p2); - - const dy = cap_points[1].y - cap_points[0].y; - const dx = cap_points[1].x - cap_points[0].x; - - const down = (pnext.y >= p.y); // = accounts correctly for horizontal lines somehow - - const phi_step = Math.PI / 8; - const r = stroke_width / 2; - const starting_phi = Math.atan(dy / dx); - const sign = down ? -1 : 1; - - for (let i = 1; i <= 7; ++i) { - const phi = starting_phi + i * phi_step; - const ox = r * Math.cos(phi); - const oy = r * Math.sin(phi); - const x = cap_points[0].x + sign * ox; - const y = cap_points[0].y + sign * oy; - cap_points.push({'x': x, 'y': y}); - } - - push_cap_triangle(positions, cap_points, 2, 1, 6); - push_cap_triangle(positions, cap_points, 2, 6, 4); - push_cap_triangle(positions, cap_points, 6, 1, 8); - push_cap_triangle(positions, cap_points, 2, 4, 3); - push_cap_triangle(positions, cap_points, 4, 6, 5); - push_cap_triangle(positions, cap_points, 6, 8, 7); - push_cap_triangle(positions, cap_points, 8, 1, 9); +function push_circle_at(positions, c, o) { + positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[4].x, c.y + o[4].y, c.x + o[8].x, c.y + o[8].y); + positions.push(c.x + o[4].x, c.y + o[4].y, c.x + o[0].x, c.y + o[0].y, c.x + o[2].x, c.y + o[2].y); + positions.push(c.x + o[8].x, c.y + o[8].y, c.x + o[4].x, c.y + o[4].y, c.x + o[6].x, c.y + o[6].y); + positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[8].x, c.y + o[8].y, c.x + o[10].x, c.y + o[10].y); + positions.push(c.x + o[2].x, c.y + o[2].y, c.x + o[0].x, c.y + o[0].y, c.x + o[1].x, c.y + o[1].y); + positions.push(c.x + o[4].x, c.y + o[4].y, c.x + o[2].x, c.y + o[2].y, c.x + o[3].x, c.y + o[3].y); + positions.push(c.x + o[6].x, c.y + o[6].y, c.x + o[4].x, c.y + o[4].y, c.x + o[5].x, c.y + o[5].y); + positions.push(c.x + o[8].x, c.y + o[8].y, c.x + o[6].x, c.y + o[6].y, c.x + o[7].x, c.y + o[7].y); + positions.push(c.x + o[10].x, c.y + o[10].y, c.x + o[8].x, c.y + o[8].y, c.x + o[9].x, c.y + o[9].y); + positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[10].x, c.y + o[10].y, c.x + o[11].x, c.y + o[11].y); } function push_stroke_positions(stroke, stroke_width, positions) { - let last_x1; - let last_y1; - let last_x2; - let last_y2; - const points = stroke.points; if (points.length < 2) { @@ -163,243 +123,55 @@ function push_stroke_positions(stroke, stroke_width, positions) { return; } - for (let i = 0; i < points.length; ++i) { - const px = points[i].x; - const py = points[i].y; - - // These might be undefined - let nextpx; - let nextpy; - - if (i < points.length - 1) { - nextpx = points[i + 1].x; - nextpy = points[i + 1].y; - } - - if (i === 0) { - const pps = perpendicular(px, py, nextpx, nextpy, stroke_width); - - last_x1 = pps.p1.x; - last_y1 = pps.p1.y; - last_x2 = pps.p2.x; - last_y2 = pps.p2.y; - - push_cap(positions, pps, points[0], points[1], stroke_width); - - continue; - } - - // Place points at (stroke_width / 2) distance from the line - const prevpx = points[i - 1].x; - const prevpy = points[i - 1].y; - - let x1; - let y1; - let x2; - let y2; - - if (i < points.length - 1) { - let d1x = px - prevpx; - let d1y = py - prevpy; - - let d2x = px - nextpx; - let d2y = py - nextpy; + // Simple 12 point circle (store offsets and reuse) + const POINTS = 12; + const phi_step = 2 * Math.PI / POINTS; - // Construct "inner" sides and find their intersection point - let perp1x = d1y; - let perp1y = -d1x; + const circle_offsets = []; - const perpnorm1 = Math.sqrt(perp1x * perp1x + perp1y * perp1y); - - perp1x /= perpnorm1; - perp1y /= perpnorm1; - - // Starting point for first "inner" line - const inner1x = prevpx + perp1x * stroke_width / 2; - const inner1y = prevpy + perp1y * stroke_width / 2; - - let perp2x = -d2y; - let perp2y = d2x; - - const perpnorm2 = Math.sqrt(perp2x * perp2x + perp2y * perp2y); - - perp2x /= perpnorm2; - perp2y /= perpnorm2; - - // Starting point for second "inner" line - const inner2x = nextpx + perp2x * stroke_width / 2; - const inner2y = nextpy + perp2y * stroke_width / 2; - - const innerintt2 = (d1x * (inner1y - inner2y) + d1y * (inner2x - inner1x)) / (d1x * d2y - d1y * d2x); - const innerint1x = inner2x + innerintt2 * d2x; - const innerint1y = inner2y + innerintt2 * d2y; - - const sanityt1 = (inner2x + innerintt2 * d2x - inner1x) / d1x; - const sanity1x = inner1x + sanityt1 * d1x; - const sanity1y = inner1y + sanityt1 * d1y; - - // Starting point for first "outer" line - const outer1x = prevpx - perp1x * stroke_width / 2; - const outer1y = prevpy - perp1y * stroke_width / 2; - - // Starting point for second "outer" line - const outer2x = nextpx - perp2x * stroke_width / 2; - const outer2y = nextpy - perp2y * stroke_width / 2; - - const outerintt2 = (d1x * (outer1y - outer2y) + d1y * (outer2x - outer1x)) / (d1x * d2y - d1y * d2x); - const outerint1x = outer2x + outerintt2 * d2x; - const outerint1y = outer2y + outerintt2 * d2y; - - x1 = innerint1x; - y1 = innerint1y; - x2 = outerint1x; - y2 = outerint1y; - - // If we are turning left, then we should place the circle on the right, and vice versa - const turn_left = point_right_of_line(points[i - 1], points[i + 1], points[i]); - // if (turn_left) { - const s1x = px - perp2x * stroke_width / 2; - const s1y = py - perp2y * stroke_width / 2; - - const s2x = px - perp1x * stroke_width / 2; - const s2y = py - perp1y * stroke_width / 2; - - let s12prod = perp1x * perp2x + perp1y * perp2y; - let angle = Math.acos(s12prod); - - // Generate circular segment - const npoints = Math.ceil(angle / Math.PI * 7); - - - if (npoints > 1) { - const joint_points = []; + for (let i = 0; i < POINTS; ++i) { + const phi = phi_step * i; + const ox = stroke_width / 2 * Math.cos(phi); + const oy = stroke_width / 2 * Math.sin(phi); + circle_offsets.push({'x': ox, 'y': oy}); + } - joint_points.push(points[i]); - joint_points.push({'x': s2x, 'y': s2y}); - joint_points.push({'x': s1x, 'y': s1y}); + for (let i = 0; i < points.length - 1; ++i) { + const px = points[i].x; + const py = points[i].y; - const pnext = points[i + 1]; - const p = points[i]; - const down = (pnext.y > p.y); - const phi_step = angle / (npoints + 1); - const r = stroke_width / 2; - const starting_phi = Math.atan(perp2y / perp2x); - const sign = down ? -1 : 1; + const nextpx = points[i + 1].x; + const nextpy = points[i + 1].y; - for (let i = 1; i <= npoints; ++i) { - const phi = starting_phi + i * phi_step; - const ox = r * Math.cos(phi); - const oy = r * Math.sin(phi); - const x = p.x + sign * ox; - const y = p.y + sign * oy; - joint_points.push({'x': x, 'y': y}); - } + const d1x = nextpx - px; + const d1y = nextpy - py; - // Rectangular segment up to here - if (innerintt2 > 0) { - positions.push(last_x1, last_y1); - positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width); - positions.push(last_x2, last_y2); + // Perpendicular to (d1x, d1y), points to the LEFT + let perp1x = -d1y; + let perp1y = d1x; - positions.push(last_x1, last_y1); - positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width); - positions.push(innerint1x, innerint1y); + const perpnorm1 = Math.sqrt(perp1x * perp1x + perp1y * perp1y); - // Four triangles to cover the non-circle part of the join - positions.push(innerint1x, innerint1y); - positions.push(px, py); - positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width); - - positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width); - positions.push(px, py); - positions.push(s2x, s2y); - - positions.push(innerint1x - perp2x * stroke_width, innerint1y - perp2y * stroke_width); - positions.push(px, py); - positions.push(innerint1x, innerint1y); - - positions.push(innerint1x - perp2x * stroke_width, innerint1y - perp2y * stroke_width); - positions.push(px, py); - positions.push(s1x, s1y); - - - last_x1 = innerint1x; - last_y1 = innerint1y; - - last_x2 = innerint1x - perp2x * stroke_width; - last_y2 = innerint1y - perp2y * stroke_width; - } else { - last_x1 = nextpx + perp2x * stroke_width / 2; - last_y1 = nextpy + perp2y * stroke_width / 2; - - last_x2 = nextpx - perp2x * stroke_width / 2; - last_y2 = nextpy - perp2y * stroke_width / 2; - } - - - // push_joint_triangle(positions, joint_points, 0, 2, npoints + 2); - for (let i = 0; i < npoints; ++i) { - push_joint_triangle(positions, joint_points, 0, i + 3, i + 2); - } - push_joint_triangle(positions, joint_points, 0, 2 + npoints, 1); - - } else { - if (innerintt2 > 0) { - // Rectangular segment up to interpolated perpendicular - let perp3x = (perp1x + perp2x) / 2.0; - let perp3y = (perp1y + perp2y) / 2.0; - - const perp3norm = Math.sqrt(perp3x * perp3x + perp3y * perp3y); - - perp3x /= perp3norm; - perp3y /= perp3norm; - - positions.push(last_x1, last_y1); - positions.push(px - perp3x * stroke_width / 2, py - perp3y * stroke_width / 2); - positions.push(last_x2, last_y2); - - positions.push(px - perp3x * stroke_width / 2, py - perp3y * stroke_width / 2); - positions.push(px + perp3x * stroke_width / 2, py + perp3y * stroke_width / 2); - positions.push(last_x1, last_y1); - - last_x1 = px + perp3x * stroke_width / 2; - last_y1 = py + perp3y * stroke_width / 2; - - last_x2 = px - perp3x * stroke_width / 2; - last_y2 = py - perp3y * stroke_width / 2; - } else { - last_x1 = nextpx + perp2x * stroke_width / 2; - last_y1 = nextpy + perp2y * stroke_width / 2; - - last_x2 = nextpx - perp2x * stroke_width / 2; - last_y2 = nextpy - perp2y * stroke_width / 2; - // last_x1 = px + perp3x * stroke_width / 2; - // last_y1 = py + perp3y * stroke_width / 2; - - // last_x2 = px - perp3x * stroke_width / 2; - // last_y2 = py - perp3y * stroke_width / 2; - } - } - // } - } else { - const pps = perpendicular(px, py, prevpx, prevpy, stroke_width); + perp1x /= perpnorm1; + perp1y /= perpnorm1; - x1 = pps.p2.x; - y1 = pps.p2.y; - x2 = pps.p1.x; - y2 = pps.p1.y; + const s1x = px + perp1x * stroke_width / 2; + const s1y = py + perp1y * stroke_width / 2; + const s2x = px - perp1x * stroke_width / 2; + const s2y = py - perp1y * stroke_width / 2; - push_cap(positions, pps, points[points.length - 1], points[points.length - 2], stroke_width); + const s3x = nextpx + perp1x * stroke_width / 2; + const s3y = nextpy + perp1y * stroke_width / 2; + const s4x = nextpx - perp1x * stroke_width / 2; + const s4y = nextpy - perp1y * stroke_width / 2; - positions.push(last_x1, last_y1); - positions.push(x2, y2); - positions.push(last_x2, last_y2); + positions.push(s1x, s1y, s2x, s2y, s4x, s4y); + positions.push(s1x, s1y, s4x, s4y, s3x, s3y); - positions.push(last_x1, last_y1); - positions.push(x1, y1); - positions.push(x2, y2); - } + push_circle_at(positions, points[i], circle_offsets); } + + push_circle_at(positions, points[points.length - 1], circle_offsets); } function draw(gl, program, locations, buffers, strokes) { @@ -527,6 +299,7 @@ function main() { {'x': 100, 'y': 100}, {'x': 105, 'y': 500}, {'x': 108, 'y': 140}, + {'x': 508, 'y': 240}, ] } ];