Last active
July 21, 2016 03:32
-
-
Save gregtatum/2c5bb9cf83cfe7e2f58c29403540df31 to your computer and use it in GitHub Desktop.
Round a corner
This file contains 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 characters
const { | |
dot, | |
length, | |
add, | |
scale, | |
normalize, | |
divide, | |
subtract, | |
cross, | |
distance, | |
squaredDistance | |
} = require('gl-vec2') | |
function roundAngle(segment, inset, ctx) { | |
// Convex angles fail, so have something before the real | |
// function to re-order the segment and result, if convex. | |
const [a, b, c] = segment | |
/** | |
* vectors | |
* | |
* • •------> | |
* v ^ w | |
* | | |
* | | |
* • | |
*/ | |
const vX = b[0] - a[0] | |
const vY = b[1] - a[1] | |
const wX = c[0] - b[0] | |
const wY = c[1] - b[1] | |
const theta = Math.atan2(vX, vY) | |
const rotatedWX = wX * Math.cos(theta) - wY * Math.sin(theta) | |
// See which way vector W rotates, left or right | |
if (rotatedWX > 0) { | |
const [ra, rb, rc] = roundAngle_strict(c, b, a, inset, ctx) | |
return [rc, rb, ra] | |
} else { | |
return roundAngle_strict(a, b, c, inset, ctx) | |
} | |
} | |
function roundAngle_strict(a, b, c, inset, ctx) { | |
/** | |
* segment rounded vectors midway | |
* | |
* b --- c > ,-- c > • <------• • <--•---• | |
* | / ^ w ^ mw w | |
* | | | • mv | |
* a a | | | |
* v v v • v • | |
*/ | |
// Figure out some things about this vector | |
const v = [b[0] - a[0], b[1] - a[1]] | |
const w = [b[0] - c[0], b[1] - c[1]] | |
const unitV = normalize([], v) | |
const unitW = normalize([], w) | |
const lengthV = length(v) | |
const lengthW = length(w) | |
// Make sure the target distance doesn't bust out of the segments | |
const targetInset = Math.min(inset, length(v), length(w)) | |
const mv = add([], scale([], unitV, lengthV - targetInset), a) | |
const mw = add([], scale([], unitW, lengthW - targetInset), c) | |
const normalV = [-unitV[1], unitV[0]] | |
const normalW = [unitW[1], -unitW[0]] | |
// Find intersection of the normals | |
// Given: mw + t × normalW = mv + u × normalV | |
// t = (mv − mw) × normalW / (normalW × normalV) | |
const distanceOnNormalV = ( | |
magCross(subtract([], mv, mw), normalW) / | |
magCross(normalW, normalV) | |
) | |
const arcCenter = add([], mw, scale([], normalW, distanceOnNormalV)) | |
const arcRadius = distance(mv, arcCenter) | |
const arcStart = Math.PI + Math.atan2(normalV[1], normalV[0]) | |
const arcEnd = Math.PI + Math.atan2(normalW[1], normalW[0]) | |
const arc = { | |
center: arcCenter, | |
radius: arcRadius, | |
start: arcStart, | |
end: arcEnd | |
} | |
// Debug draw if a 2d context is passed in. | |
if (ctx) { | |
// Draw sides of triangle | |
ctx.beginPath() | |
ctx.strokeStyle = 'rgba(255,255,255,0.8)' | |
ctx.lineWidth = 2 | |
ctx.moveTo(a[0],a[1]) | |
ctx.lineTo(b[0],b[1]) | |
ctx.lineTo(c[0],c[1]) | |
ctx.stroke() | |
// Draw midway dots | |
const dotSize = 10 | |
ctx.fillStyle = 'rgba(255,0,0,0.8)' | |
ctx.fillRect(mv[0] - dotSize / 2, mv[1] - dotSize / 2, dotSize, dotSize) | |
ctx.fillStyle = 'rgba(0,255,0,0.8)' | |
ctx.fillRect(mw[0] - dotSize / 2, mw[1] - dotSize / 2, dotSize, dotSize) | |
// Draw normals | |
const normalLength = 40 | |
ctx.beginPath() | |
ctx.strokeStyle = 'rgba(0,255,255,0.8)' | |
ctx.lineWidth = 2 | |
ctx.moveTo(mv[0],mv[1]) | |
ctx.lineTo(mv[0] + normalLength * normalV[0], mv[1] + normalLength * normalV[1]) | |
ctx.stroke() | |
ctx.beginPath() | |
ctx.strokeStyle = 'rgba(0,255,255,0.8)' | |
ctx.lineWidth = 2 | |
ctx.moveTo(mw[0],mw[1]) | |
ctx.lineTo(mw[0] + normalLength * normalW[0], mw[1] + normalLength * normalW[1]) | |
ctx.stroke() | |
// Draw arcCenter dot | |
ctx.fillStyle = 'rgba(255,255,0,0.8)' | |
ctx.fillRect(arcCenter[0] - dotSize / 2, arcCenter[1] - dotSize / 2, dotSize, dotSize) | |
ctx.strokeStyle = 'rgba(255,255,0,0.1)' | |
ctx.beginPath() | |
ctx.arc(arc.center[0], arc.center[1], arc.radius, 0, 2 * Math.PI) | |
ctx.stroke() | |
// Draw full arc | |
ctx.beginPath() | |
ctx.moveTo(a[0], a[1]) | |
ctx.lineTo(mv[0], mv[1]) | |
ctx.arc(arc.center[0], arc.center[1], arc.radius, arc.start, arc.end) | |
ctx.moveTo(mw[0], mw[1]) | |
ctx.lineTo(c[0], c[1]) | |
ctx.lineWidth = 4 | |
ctx.strokeStyle = 'rgba(255, 0, 255, 0.5)' | |
ctx.stroke() | |
} | |
return [ | |
[a, mv], | |
arc, | |
[mw, c] | |
] | |
} | |
function magCross (v, w) { | |
return v[0] * w[1] - v[1] * w[0] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment