Skip to content

packages/core/src/lib/geometry/path-arc.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 /** @internal Analytic arc representation shared by conversion and geometry math. */
2 export interface ArcSegment {
3   type: 'arc';
4   cx: number; cy: number;
5   ux: number; uy: number;
6   vx: number; vy: number;
7   startAngle: number; sweep: number;
8 }
9 export const TAU = Math.PI * 2;
10 export function arcPoint(arc: ArcSegment, angle: number): [number, number] {
11   const cos = Math.cos(angle), sin = Math.sin(angle);
12   return [arc.cx + arc.ux * cos + arc.vx * sin, arc.cy + arc.uy * cos + arc.vy * sin];
13 }
14 export function arcSweep(start: number, end: number, ccw: boolean): number {
15   const delta = end - start;
16   if (!ccw && delta >= TAU) return TAU;
17   if (ccw && -delta >= TAU) return -TAU;
18   // Reducing separately also avoids overflow in end - start.
19   let sweep = ((end % TAU) - (start % TAU)) % TAU;
20   if (!ccw && sweep < 0) sweep += TAU;
21   if (ccw && sweep > 0) sweep -= TAU;
22   return sweep;
23 }
24 
25 /** Singular-value decomposition of a 2x2 ellipse basis. */
26 export function ellipseParameters(arc: ArcSegment) {
27   const scale = Math.max(Math.abs(arc.ux), Math.abs(arc.uy), Math.abs(arc.vx), Math.abs(arc.vy));
28   if (scale === 0) return null;
29   const ux = arc.ux / scale, uy = arc.uy / scale, vx = arc.vx / scale, vy = arc.vy / scale;
30   const determinant = ux * vy - uy * vx;
31   if (determinant === 0) return null;
32   const xx = ux * ux + vx * vx, yy = uy * uy + vy * vy, xy = ux * uy + vx * vy;
33   const rotation = Math.atan2(2 * xy, xx - yy) / 2;
34   const major = Math.sqrt((xx + yy + Math.hypot(xx - yy, 2 * xy)) / 2);
35   const minor = Math.abs(determinant) / major;
36   const cos = Math.cos(rotation), sin = Math.sin(rotation);
37   const phase = Math.atan2((-sin * ux + cos * uy) / minor, (cos * ux + sin * uy) / major);
38   const direction = determinant < 0 ? -1 : 1;
39   return {
40     radiusX: major * scale, radiusY: minor * scale, rotation,
41     start: phase + direction * arc.startAngle, sweep: direction * arc.sweep,
42   };
43 }
44 
45 /** Preserve all turns in the traversal of an ellipse collapsed onto a line. */
46 export function singularArcPoints(arc: ArcSegment): [number, number][] {
47   const useX = Math.hypot(arc.ux, arc.vx) >= Math.hypot(arc.uy, arc.vy);
48   const turning = Math.atan2(useX ? arc.vx : arc.vy, useX ? arc.ux : arc.uy);
49   const end = arc.startAngle + arc.sweep;
50   const low = Math.min(arc.startAngle, end), high = Math.max(arc.startAngle, end);
51   const angles: number[] = [];
52   for (let k = Math.ceil((low - turning) / Math.PI); k <= Math.floor((high - turning) / Math.PI); k++) {
53     const angle = turning + k * Math.PI;
54     if (angle > low && angle < high) angles.push(angle);
55   }
56   if (arc.sweep < 0) angles.reverse();
57   angles.push(end);
58   return angles.map(angle => arcPoint(arc, angle));
59 }
60 

Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.