Skip to content

packages/core/src/lib/geometry/regular-polygon.ts

Read as Markdown

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

Back to reference

1 import type { LayoutBox } from '../layout/types.js';
2 import { createPolygonPath } from './polygon.js';
3 import {
4   resolveNumericCornerRadii,
5   type RoundedCornerPathOptions,
6 } from './rounded-corners.js';
7 import {
8   finiteNumber,
9   integerAtLeast,
10   nonnegativeNumber,
11   pathRangeError,
12   type GeometryErrorFactory,
13 } from './validation.js';
14 
15 /**
16  * Center, side count, and rotation for a regular polygon path.
17  *
18  * @see {@link RegularPolygonPathOptions}
19  */
20 export interface RegularPolygonPathPosition {
21   /** Number of sides of the regular polygon. See {@link RegularPolygonPathPosition}. */
22   sides: number;
23   /** Horizontal coordinate of the center. See {@link RegularPolygonPathPosition}. */
24   cx?: number;
25   /** Vertical coordinate of the center. See {@link RegularPolygonPathPosition}. */
26   cy?: number;
27   /** Rotation applied to the containing geometry. See {@link RegularPolygonPathPosition}. */
28   rotation?: number;
29 }
30 
31 /**
32  * Exactly one size measure for a regular polygon: circumradius, inradius, or side length.
33  *
34  * @see {@link RegularPolygonPathOptions}
35  */
36 export type RegularPolygonNumericSize =
37   | {
38       /** Distance from the polygon center to a vertex. See {@link RegularPolygonNumericSize}. */
39       circumradius: number;
40       /**
41        * Not accepted in this variant; use the alternative fields instead. See
42        * {@link RegularPolygonNumericSize}.
43        */
44       inradius?: never;
45       /**
46        * Not accepted in this variant; use the alternative fields instead. See
47        * {@link RegularPolygonNumericSize}.
48        */
49       sideLength?: never;
50     }
51   | {
52       /**
53        * Not accepted in this variant; use the alternative fields instead. See
54        * {@link RegularPolygonNumericSize}.
55        */
56       circumradius?: never;
57       /** Distance from the polygon center to an edge. See {@link RegularPolygonNumericSize}. */
58       inradius: number;
59       /**
60        * Not accepted in this variant; use the alternative fields instead. See
61        * {@link RegularPolygonNumericSize}.
62        */
63       sideLength?: never;
64     }
65   | {
66       /**
67        * Not accepted in this variant; use the alternative fields instead. See
68        * {@link RegularPolygonNumericSize}.
69        */
70       circumradius?: never;
71       /**
72        * Not accepted in this variant; use the alternative fields instead. See
73        * {@link RegularPolygonNumericSize}.
74        */
75       inradius?: never;
76       /** Length of one polygon edge. See {@link RegularPolygonNumericSize}. */
77       sideLength: number;
78     };
79 
80 /**
81  * Geometry and construction options for a regular polygon path.
82  *
83  * @see {@link RegularPolygonPathPosition}
84  * @see {@link RegularPolygonNumericSize}
85  * @see {@link RoundedCornerPathOptions}
86  * @see {@link createRegularPolygonPath}
87  */
88 export type RegularPolygonPathOptions = RegularPolygonPathPosition &
89   RegularPolygonNumericSize &
90   RoundedCornerPathOptions;
91 
92 export interface ResolvedRegularPolygonGeometry {
93   path: Path2D;
94   vertices: readonly (readonly [number, number])[];
95   bounds?: LayoutBox;
96 }
97 
98 const SIZE_KEYS = ['circumradius', 'inradius', 'sideLength'] as const;
99 
100 export function resolveRegularPolygonGeometry(
101   options: Readonly<RegularPolygonPathOptions>,
102   errorFactory: GeometryErrorFactory = pathRangeError,
103 ): ResolvedRegularPolygonGeometry {
104   const sides = integerAtLeast(options.sides, 3, 'sides', errorFactory);
105   const sizeKeys = SIZE_KEYS.filter(key => options[key] !== undefined);
106   if (sizeKeys.length !== 1) {
107     throw errorFactory(
108       'circumradius|inradius|sideLength',
109       options,
110       'must provide exactly one size option',
111     );
112   }
113 
114   const sizeKind = sizeKeys[0];
115   const size = nonnegativeNumber(options[sizeKind], sizeKind, errorFactory);
116   const cx = finiteNumber(options.cx ?? 0, 'cx', errorFactory);
117   const cy = finiteNumber(options.cy ?? 0, 'cy', errorFactory);
118   const rotation = finiteNumber(
119     options.rotation ?? -Math.PI / 2,
120     'rotation',
121     errorFactory,
122   );
123   const radii = resolveNumericCornerRadii(sides, options, errorFactory);
124   const circumradius = sizeKind === 'circumradius' ? size :
125     sizeKind === 'inradius' ? size / Math.cos(Math.PI / sides) :
126       size / (2 * Math.sin(Math.PI / sides));
127 
128   let left = Number.POSITIVE_INFINITY;
129   let right = Number.NEGATIVE_INFINITY;
130   let top = Number.POSITIVE_INFINITY;
131   let bottom = Number.NEGATIVE_INFINITY;
132   const vertices = Array.from({ length: sides }, (_, index) => {
133     const angle = rotation + (index * Math.PI * 2) / sides;
134     const vertex = [
135       cx + circumradius * Math.cos(angle),
136       cy + circumradius * Math.sin(angle),
137     ] as const;
138     left = Math.min(left, vertex[0]);
139     right = Math.max(right, vertex[0]);
140     top = Math.min(top, vertex[1]);
141     bottom = Math.max(bottom, vertex[1]);
142     return vertex;
143   });
144 
145   return {
146     path: circumradius === 0 ? new Path2D() :
147       createPolygonPath({ coords: vertices, cornerRadii: radii }),
148     vertices,
149     bounds: {
150       x: left,
151       y: top,
152       width: right - left,
153       height: bottom - top,
154     },
155   };
156 }
157 
158 /**
159  * Creates a Canvas Path2D for regular polygon geometry from validated options.
160  *
161  * @param options - Polygon center, radius, side count, and rotation. See
162  * {@link RegularPolygonPathOptions} .
163  * @returns A native Canvas path containing the regular polygon.
164  *
165  * @see {@link RegularPolygonPathOptions}
166  */
167 export function createRegularPolygonPath(
168   options: Readonly<RegularPolygonPathOptions>,
169 ): Path2D {
170   return resolveRegularPolygonGeometry(options).path;
171 }
172 

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