Created
June 5, 2024 09:02
-
-
Save mattdesl/72c98f48525930f9f61eb45f7a41096f to your computer and use it in GitHub Desktop.
Revisions
-
mattdesl created this gist
Jun 5, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,120 @@ /** * Outlines a line segment (a, b) with rounded caps on either side, and the ability * to adjust the ellipsoid-ness of the rounded caps to create a more 'stubby' edge. * * @license MIT * @author Matt DesLauriers (@mattdesl) */ export default function drawRoundedSegment(a, b, lineWidth, capSegments = 32, ellipsoid = 1) { let normX = b[0] - a[0]; let normY = b[1] - a[1]; const normLenSqr = normX * normX + normY * normY; const normLen = normLenSqr !== 0 ? Math.sqrt(normLenSqr) : normLenSqr; normX /= normLen; normY /= normLen; const perpX = -normY; const perpY = normX; const lineHalf = lineWidth / 2; const points = []; // first segment edge drawEdge(points, a, b, perpX, perpY, lineHalf); // prepare second segment edge const nextPoints = []; drawEdge(nextPoints, b, a, perpX, perpY, -lineHalf); // first cap after first segment drawCap( points, points[points.length - 1], nextPoints[0], normX, normY, capSegments, ellipsoid ); // now add in the already prepared second segment edge nextPoints.forEach((p) => points.push(p)); // finalise with second cap drawCap( points, nextPoints[nextPoints.length - 1], points[0], -normX, -normY, capSegments, ellipsoid ); return points; } function drawEdge(out = [], a, b, dx, dy, rad) { // segment edge [0, 1].forEach((t) => { const px = lerp(a[0], b[0], t); const py = lerp(a[1], b[1], t); out.push([px + dx * rad, py + dy * rad]); }); } function drawCap( out = [], prevPoint, nextPoint, nx, ny, capSegments = 32, ellipsoid = 1 ) { const midPoint = lerpArray(prevPoint, nextPoint, 0.5); // Calculate midpoint const radius = vec2Dist(prevPoint, nextPoint) / 2; // Calculate radius for (let i = 0; i < capSegments; i++) { const t = i / capSegments; const stepAngle = Math.PI * t; const angle = -stepAngle + Math.PI / 2; const r = radius; const dx = Math.cos(angle) * r * ellipsoid; const dy = Math.sin(angle) * r; // Calculate new point position using the pushDir to orient the cap correctly const c = [ midPoint[0] + dx * nx - dy * ny, midPoint[1] + dx * ny + dy * nx, ]; out.push(c); } return out; } function vec2Dist(a, b) { const dx = b[0] - a[0]; const dy = b[1] - a[1]; return Math.sqrt(dx * dx + dy * dy); } function lerp(min, max, t) { return min * (1 - t) + max * t; } function lerpArray(min, max, t, out) { out = out || []; if (min.length !== max.length) { throw new TypeError( "min and max array are expected to have the same length" ); } for (var i = 0; i < min.length; i++) { out[i] = lerp(min[i], max[i], t); } return out; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,49 @@ import canvasSketch from "canvas-sketch"; import drawRoundedSegment from './drawRoundedSegment.js'; const settings = { dimensions: [2048, 2048], animate: true, }; const sketch = () => { return ({ context, width, height, time }) => { context.fillStyle = "white"; context.fillRect(0, 0, width, height); // create a line segment const p = [ Math.sin(time) * 0.25 * width + width / 2, Math.cos(Math.sin(time * 2) + time) * 0.25 * height + height / 2, ]; const r = 0.15 * width; const angle = 0.2 + time * 1; const [a, b] = [-1, 1].map((d) => [ p[0] + Math.cos(angle) * d * r, p[1] + Math.sin(angle) * d * r, ]); const lineWidth = width * 0.1 * (0.05 + Math.sin(time * 2) * 0.5 + 0.5); // test native canvas approach context.beginPath(); context.lineTo(...a); context.lineTo(...b); context.strokeStyle = "lightGray"; context.lineWidth = lineWidth; context.lineCap = "round"; context.stroke(); const points = drawRoundedSegment(a, b, lineWidth, 32, 1); context.beginPath(); points.forEach((p) => context.lineTo(...p)); context.lineWidth = width * 0.002; context.strokeStyle = "blue"; context.closePath(); context.lineJoin = "round"; context.fillStyle = "red"; context.stroke(); }; }; canvasSketch(sketch, settings);