Skip to content

Instantly share code, notes, and snippets.

@steveruizok
Last active September 7, 2022 10:23
Show Gist options
  • Save steveruizok/4b3474cfd9a784167691a91cf220cbae to your computer and use it in GitHub Desktop.
Save steveruizok/4b3474cfd9a784167691a91cf220cbae to your computer and use it in GitHub Desktop.
TypeScript methods for calculating arcs.
const TAU = Math.PI / 2
const PI2 = Math.PI * 2
interface VecLike {x: number, y: number }
/**
* Get info about an arc formed by three points.
*
* @param a The start of the arc
* @param b A point on the arc
* @param c The end of the arc
*/
export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): ArcInfo {
// find a circle from the three points
const u = -2 * (a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y)
const center = {
x:
((a.x * a.x + a.y * a.y) * (c.y - b.y) +
(b.x * b.x + b.y * b.y) * (a.y - c.y) +
(c.x * c.x + c.y * c.y) * (b.y - a.y)) /
u,
y:
((a.x * a.x + a.y * a.y) * (b.x - c.x) +
(b.x * b.x + b.y * b.y) * (c.x - a.x) +
(c.x * c.x + c.y * c.y) * (a.x - b.x)) /
u,
}
const radius = Math.hypot(center.y - a.y, center.x - a.x)
const ab = Math.hypot(a.y - b.y, a.x - b.x)
const bc = Math.hypot(b.y - c.y, b.x - c.x)
const ca = Math.hypot(c.y - a.y, c.x - a.x)
const angle = Math.acos((bc * bc + ca * ca - ab * ab) / (2 * bc * ca))
// Whether to draw the long arc or short arc
const largeArcFlag = +(TAU > angle)
// Whether to draw the arc clockwise or counter-clockwise
const sweepFlag = +((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) < 0)
// The size of the arc in radians
const theta = (PI2 - angle * 2) * (sweepFlag ? 1 : -1)
// The length of the arc in distance units
const arcLength = theta * radius
return {
center,
radius,
theta,
arcLength,
chordLength: ab,
largeArcFlag,
sweepFlag,
}
}
/**
* Get an SVG path for an arc.
*
* @param start - The start point of the arc.
* @param end - The end point of the arc.
* @param largeArcFlag - Whether the arc is a large arc.
* @param smallArcFlag - Whether the arc is a clockwise arc.
*/
function getArcPath(
start: VecLike,
end: VecLike,
radius: number,
largeArcFlag: number,
smallArcFlag: number
) {
return `M${start.x},${start.y}A${radius} ${radius} 0 ${largeArcFlag} ${smallArcFlag} ${end.x},${end.y}`
}
/**
* Get a bounding box for an arc.
*
* @param center - The arc's center.
* @param radius - The arc's radius.
* @param start - The start point of the arc.
* @param theta - The angle of the arc.
*/
export function getCurvedArrowBoundingBox(
center: VecLike,
radius: number,
start: VecLike,
theta: number
) {
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
const startAngle = Vec2d.Angle(center, start)
for (let i = 0; i < 20; i++) {
const angle = startAngle + theta * (i / 19)
const x = center.x + radius * Math.cos(angle)
const y = center.y + radius * Math.sin(angle)
minX = Math.min(x, minX)
minY = Math.min(y, minY)
maxX = Math.max(x, maxX)
maxY = Math.max(y, maxY)
}
return new Box2d(minX, minY, maxX - minX, maxY - minY)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment