packages/core/src/lib/geometry/path-arc.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
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 version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.