@horizon36596/zenith-import 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +13 -0
  3. package/README.md +90 -2
  4. package/dist/bezier.d.ts +36 -0
  5. package/dist/bezier.d.ts.map +1 -0
  6. package/dist/bezier.js +133 -0
  7. package/dist/bezier.js.map +1 -0
  8. package/dist/errors.d.ts +17 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +24 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/frame.d.ts +89 -0
  13. package/dist/frame.d.ts.map +1 -0
  14. package/dist/frame.js +145 -0
  15. package/dist/frame.js.map +1 -0
  16. package/dist/index.d.ts +35 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +35 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interchange.d.ts +29 -0
  21. package/dist/interchange.d.ts.map +1 -0
  22. package/dist/interchange.js +41 -0
  23. package/dist/interchange.js.map +1 -0
  24. package/dist/java/lex.d.ts +35 -0
  25. package/dist/java/lex.d.ts.map +1 -0
  26. package/dist/java/lex.js +281 -0
  27. package/dist/java/lex.js.map +1 -0
  28. package/dist/java/mergeCommands.d.ts +54 -0
  29. package/dist/java/mergeCommands.d.ts.map +1 -0
  30. package/dist/java/mergeCommands.js +269 -0
  31. package/dist/java/mergeCommands.js.map +1 -0
  32. package/dist/java/projectScan.d.ts +34 -0
  33. package/dist/java/projectScan.d.ts.map +1 -0
  34. package/dist/java/projectScan.js +79 -0
  35. package/dist/java/projectScan.js.map +1 -0
  36. package/dist/java/scanCommands.d.ts +94 -0
  37. package/dist/java/scanCommands.d.ts.map +1 -0
  38. package/dist/java/scanCommands.js +1216 -0
  39. package/dist/java/scanCommands.js.map +1 -0
  40. package/dist/jsonschema.d.ts +11 -0
  41. package/dist/jsonschema.d.ts.map +1 -0
  42. package/dist/jsonschema.js +39 -0
  43. package/dist/jsonschema.js.map +1 -0
  44. package/dist/path/importPath.d.ts +32 -0
  45. package/dist/path/importPath.d.ts.map +1 -0
  46. package/dist/path/importPath.js +248 -0
  47. package/dist/path/importPath.js.map +1 -0
  48. package/dist/path/schema.d.ts +2383 -0
  49. package/dist/path/schema.d.ts.map +1 -0
  50. package/dist/path/schema.js +118 -0
  51. package/dist/path/schema.js.map +1 -0
  52. package/dist/pedro/format.d.ts +82 -0
  53. package/dist/pedro/format.d.ts.map +1 -0
  54. package/dist/pedro/format.js +207 -0
  55. package/dist/pedro/format.js.map +1 -0
  56. package/dist/pedro/importPedro.d.ts +70 -0
  57. package/dist/pedro/importPedro.d.ts.map +1 -0
  58. package/dist/pedro/importPedro.js +882 -0
  59. package/dist/pedro/importPedro.js.map +1 -0
  60. package/dist/textDiff.d.ts +23 -0
  61. package/dist/textDiff.d.ts.map +1 -0
  62. package/dist/textDiff.js +102 -0
  63. package/dist/textDiff.js.map +1 -0
  64. package/dist/trajectory/importTrajectory.d.ts +92 -0
  65. package/dist/trajectory/importTrajectory.d.ts.map +1 -0
  66. package/dist/trajectory/importTrajectory.js +161 -0
  67. package/dist/trajectory/importTrajectory.js.map +1 -0
  68. package/dist/trajectory/schema.d.ts +343 -0
  69. package/dist/trajectory/schema.d.ts.map +1 -0
  70. package/dist/trajectory/schema.js +83 -0
  71. package/dist/trajectory/schema.js.map +1 -0
  72. package/dist/trajectory/trajectoryAuto.d.ts +66 -0
  73. package/dist/trajectory/trajectoryAuto.d.ts.map +1 -0
  74. package/dist/trajectory/trajectoryAuto.js +273 -0
  75. package/dist/trajectory/trajectoryAuto.js.map +1 -0
  76. package/fixtures/java/biobuzz/BiobuzzRobot.java +62 -0
  77. package/fixtures/java/biobuzz/Intake.java +32 -0
  78. package/fixtures/java/kotlin/KtRobot.kt +24 -0
  79. package/fixtures/java/readme/MyRobot.java +22 -0
  80. package/fixtures/java/tricky/Names.java +12 -0
  81. package/fixtures/java/tricky/TrickyRobot.java +50 -0
  82. package/fixtures/path/broken/bezier-too-many-controls.path.json +171 -0
  83. package/fixtures/path/broken/command-step.path.json +159 -0
  84. package/fixtures/path/broken/duplicate-step-id.path.json +159 -0
  85. package/fixtures/path/broken/frame-without-preset-or-keys.path.json +159 -0
  86. package/fixtures/path/broken/linear-heading-missing-to.path.json +158 -0
  87. package/fixtures/path/broken/missing-start-pose.path.json +154 -0
  88. package/fixtures/path/broken/piecewise-gap.path.json +174 -0
  89. package/fixtures/path/broken/segments-do-not-join.path.json +170 -0
  90. package/fixtures/path/broken/unknown-field.path.json +169 -0
  91. package/fixtures/path/broken/wrong-season.path.json +159 -0
  92. package/fixtures/path/explicit-millimetres.path.json +72 -0
  93. package/fixtures/path/pedro-score-and-park.path.json +159 -0
  94. package/fixtures/path/roadrunner-piecewise.path.json +112 -0
  95. package/fixtures/pedro/broken/end-point-not-a-number.pp +232 -0
  96. package/fixtures/pedro/broken/missing-lines.pp +115 -0
  97. package/fixtures/pedro/broken/start-point-not-an-object.pp +227 -0
  98. package/fixtures/pedro/broken/unknown-heading-type.pp +231 -0
  99. package/fixtures/pedro/groups-and-piecewise.pp +259 -0
  100. package/fixtures/pedro/hand-1.2.1-path-chains.pp +62 -0
  101. package/fixtures/pedro/newer-version-with-event.pp +64 -0
  102. package/fixtures/pedro/score-and-park.pp +232 -0
  103. package/fixtures/pedro/visualizer-1.3.0-heading-on-end-point.pp +240 -0
  104. package/fixtures/pedro/visualizer-1.5.0-groups-piecewise.pp +342 -0
  105. package/fixtures/pedro/visualizer-1.5.0-lines-curves-headings.pp +285 -0
  106. package/fixtures/pedro/visualizer-drawn.json +691 -0
  107. package/fixtures/trajectory/broken/axes-not-perpendicular.trajectory.json +28 -0
  108. package/fixtures/trajectory/broken/frame-without-preset-or-keys.trajectory.json +27 -0
  109. package/fixtures/trajectory/broken/missing-bounding-box.trajectory.json +23 -0
  110. package/fixtures/trajectory/broken/phase-ends-before-it-starts.trajectory.json +34 -0
  111. package/fixtures/trajectory/broken/pose-row-too-short.trajectory.json +26 -0
  112. package/fixtures/trajectory/broken/rate-not-positive.trajectory.json +27 -0
  113. package/fixtures/trajectory/broken/unknown-field.trajectory.json +28 -0
  114. package/fixtures/trajectory/broken/wrong-season.trajectory.json +27 -0
  115. package/fixtures/trajectory/explicit-millimetres.trajectory.json +41 -0
  116. package/fixtures/trajectory/meepmeep-line-and-turn.trajectory.json +65 -0
  117. package/fixtures/trajectory/meepmeep-line-wait-spline-turn.trajectory.json +66 -0
  118. package/fixtures/trajectory/pedro-20hz.trajectory.json +274 -0
  119. package/json/path.json +460 -0
  120. package/json/trajectory.json +307 -0
  121. package/package.json +67 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Horizon (FTC 36596)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ Zenith
2
+ Copyright (c) 2026 Horizon (FTC 36596)
3
+
4
+ Zenith is licensed under the MIT License; see LICENSE.
5
+
6
+ Bundled fonts
7
+
8
+ The following fonts are bundled in apps/web/public/fonts/. Each is licensed under the SIL Open Font
9
+ License, Version 1.1. The full licence text ships next to each font file.
10
+
11
+ - Jost, licensed under the SIL Open Font License 1.1 (jost-OFL.txt).
12
+ - IBM Plex Sans, licensed under the SIL Open Font License 1.1 (ibm-plex-sans-OFL.txt).
13
+ - JetBrains Mono, licensed under the SIL Open Font License 1.1 (jetbrains-mono-OFL.txt).
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ # @horizon36596/zenith-import
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Bring paths from other planners into [Zenith](https://libraries.horizon36596.org/zenith/), the FTC
4
+ autonomous planner by Horizon (FTC 36596). Licence: MIT.
5
+
6
+ ```
7
+ npm install @horizon36596/zenith-import
8
+ ```
9
+
10
+ ## Package reference
11
+
12
+ Pure, like `@horizon36596/zenith-core`: no DOM, no clock, no randomness, no Node APIs. Reading the
13
+ file is the caller's job; `zenith import` in `@horizon36596/zenith-cli` does it for you.
14
+
15
+ - `importPedroProject(json, field, options?)` turns a Pedro Pathing Visualizer project (`.pp`) into an
16
+ auto in the season's frame, and returns `{ auto, robotFootprint?, warnings }`. Paths become `path`
17
+ steps, headings keep Pedro's names, sequence waits become `wait` steps, and any sequence item of a
18
+ kind the importer does not know becomes a placeholder command `imported1`, `imported2` and so on, to
19
+ map to one of your robot's commands. Every pose carries `CARRIED OVER (Pedro Visualizer import)`.
20
+ - `importTrajectory(json, field)` turns an interchange `trajectory.json` into a view projection:
21
+ poses at 10 Hz in the season's frame, rounded to 0.5 in and one degree. `readTrajectory` returns the
22
+ same with its warnings.
23
+ - `importPath(json, field, options?)` turns an interchange `path.json` into an auto that can be
24
+ edited, and returns `{ auto, warnings }`. Every pose carries `CARRIED OVER (path.json import)`.
25
+ - `interchangeKindOf(doc)` says which interchange file a document is, by its `kind` key
26
+ (`"trajectory"` or `"path"`), and `checkInterchange(kind, doc, field)` checks it the way importing
27
+ it would, returning `{ kind, errors, warnings }`. This is what `zenith validate` reports.
28
+ - `scanCommands(sources)` reads a robot's `NamedCommands.register` and `registerCondition` calls
29
+ out of its Java or Kotlin source text (`{ path, text }[]`), with each command's arguments from its
30
+ `argInt`, `argDouble`, `argString` and `argEnum` calls, and lists what it could not read as
31
+ `issues` with the file and line. `checkCommandRegistry(robot, scan)` compares the result with
32
+ `robot.json`, and `mergeCommandScan(robot, scan)` adds the commands, conditions and params
33
+ `robot.json` lacks without changing anything already there. `zenith import-commands` does it for
34
+ you. `diffLines` and `formatDiff` preview the change as a line diff.
35
+ - All three importers throw `ImportError`, whose `issues` name every field that is wrong by its JSON
36
+ path.
37
+
38
+ The frames, file formats and APIs these rest on were read from source and are recorded in
39
+ Zenith's ADR 0008, in the repository's decision records.
40
+
41
+ ## `trajectory.json`
42
+
43
+ The JSON Schema is `json/trajectory.json`, served at
44
+ `https://libraries.horizon36596.org/zenith/schema/v1/trajectory.json`. A planner writes its poses in
45
+ its own coordinates and declares the frame, either by preset or key by key:
46
+
47
+ | preset | origin | axes | units |
48
+ |---|---|---|---|
49
+ | `zenith` | field centre | the season's field frame | inches, radians counter-clockwise from +x |
50
+ | `pedro` | the Pedro Visualizer's corner, centre at (70.75, 70.75) | the field frame's, for BIOBUZZ | inches, degrees counter-clockwise from +x |
51
+ | `roadrunner` | field centre | MeepMeep's: +x toward the audience, +y away from the Red Wall, for BIOBUZZ | inches, radians counter-clockwise from +x |
52
+
53
+ Keys given beside a preset override it. Without a preset every key is required: `originIn` (where
54
+ the planner's origin is, in field-frame inches), `xAxis` and `yAxis` (the field-frame direction,
55
+ `+x`, `-x`, `+y` or `-y`, each planner axis points along), `units` (`in`, `mm`, `cm` or `m`),
56
+ `headingUnit` (`rad` or `deg`), `headingZero` (`+x` or `+y`, of the planner's axes) and
57
+ `headingPositive` (`ccw` or `cw`).
58
+
59
+ The rest of the file: `formatVersion: 1`, `kind: "trajectory"`, `season`, `alliance`, optional
60
+ `title` and `source`, `rateHz` (samples per second; 10 or better), `poses` (`[x, y, heading]`, the
61
+ i-th at i / rateHz seconds), `boundingBox` (`lengthIn` along the heading, `widthIn`, optional
62
+ `centreOffsetIn` forward and left, optional `provenance`), and the optional `phases` (`startS`,
63
+ `endS`, `label` from `drive`, `score`, `collect`, `wait`, `park`, `other`), `claims` (`elementId`,
64
+ `action` `take` or `score`, `atS`) and `envelopes` (`startS`, `endS`, `polygon` of `{ x, y }` in the
65
+ declared frame). Unknown keys are refused, with their path.
66
+
67
+ `robot/meepmeep-export/` has a copy-in class that writes this file from MeepMeep.
68
+
69
+ ## `path.json`
70
+
71
+ The JSON Schema is `json/path.json`, served at
72
+ `https://libraries.horizon36596.org/zenith/schema/v1/path.json`. It is the structure of a
73
+ propose-level projection with the commands taken out, and the same `frame` block as
74
+ `trajectory.json`: `formatVersion: 1`, `kind: "path"`, `season`, `alliance`, optional `title` and
75
+ `source`, `frame`, `startPose` (`{ x, y, heading }`) and `steps`. A step is a path step (`kind:
76
+ "path"`, optional `id`, `segments` of `line` or `bezier` with one to three `control` points,
77
+ optional `heading`, `speedFraction` and `timeoutS`) or a wait step (`kind: "wait"`, optional `id`,
78
+ `seconds`). Heading modes are Zenith's: `tangent`, `tangentReversed`, `constant` (`heading`),
79
+ `linear` (`from`, `to`, turning from one to the other as written), `facePoint` (`x`, `y`, optional
80
+ `offset`) and `piecewise` (`ranges` of `{ startT, endT, heading }` running from 0 to 1). Positions
81
+ and headings are in the declared frame's units, which is why their keys carry no unit suffix.
82
+
83
+ ## Fixtures
84
+
85
+ `fixtures/pedro/` holds `.pp` projects in the visualizer's 1.5.0 format (and one saved by a newer,
86
+ imagined 1.7.0, with an `event` item), `fixtures/trajectory/` interchange files, and each `broken/`
87
+ folder files that must fail with a named field. `fixtures/path/` holds `path.json` files in the
88
+ Pedro, RoadRunner and an explicit millimetre frame, with its own `broken/` folder. The tests hold every good `.pp` fixture to the
89
+ milestone's contract: the imported auto loads, and its path lies within 0.5 in of the visualizer's.
90
+ The `path.json` fixtures are held to the same: each imports to an auto with no error finding whose
91
+ path lies within 0.5 in of the file's own.
@@ -0,0 +1,36 @@
1
+ /** A point in field-frame inches. */
2
+ export interface Point {
3
+ xIn: number;
4
+ yIn: number;
5
+ }
6
+ /**
7
+ * How far a replacement curve may stray from the curve it replaces, in inches. It is a tenth of the
8
+ * half inch allowed between an import and the planner's own path, so rounding on save
9
+ * and this approximation together stay well inside it.
10
+ */
11
+ export declare const APPROXIMATION_TOLERANCE_IN = 0.05;
12
+ /** The point at parameter u of the Bézier with control polygon `points` (de Casteljau). */
13
+ export declare function bezierPoint(points: readonly Point[], u: number): Point;
14
+ /** The derivative with respect to u, in inches per unit of u. */
15
+ export declare function bezierDerivative(points: readonly Point[], u: number): Point;
16
+ /**
17
+ * The same curve as control polygons of at most `maxControls` interior points each.
18
+ *
19
+ * A polygon that already fits is returned as it is, so the result is exact. A higher degree cannot be
20
+ * written exactly with fewer control points, so it is cut into equal stretches of u and each is
21
+ * replaced by the cubic with the same end points and the same end derivatives (a cubic Hermite
22
+ * piece), doubling the number of pieces until every one is within `toleranceIn` of the original at
23
+ * every sample. The distance is measured between points at the same parameter, which bounds the
24
+ * distance between the two curves from above.
25
+ */
26
+ export declare function reduceDegree(points: readonly Point[], maxControls?: number, toleranceIn?: number): {
27
+ polygons: Point[][];
28
+ maxErrorIn: number;
29
+ };
30
+ /**
31
+ * The control polygon of the single Bézier that passes through every one of `through`, reached at
32
+ * evenly spaced parameters, which is what Pedro's `BezierCurve.through` builds (ADR 0008 section 1):
33
+ * solve the Bernstein matrix at u = i / (n - 1) for the control points.
34
+ */
35
+ export declare function interpolateThrough(through: readonly Point[]): Point[];
36
+ //# sourceMappingURL=bezier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bezier.d.ts","sourceRoot":"","sources":["../src/bezier.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,CAYtE;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,CAU3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,WAAW,GAAE,MAAkC,EAC/C,WAAW,GAAE,MAAmC,GAC/C;IAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAgC7C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAoBrE"}
package/dist/bezier.js ADDED
@@ -0,0 +1,133 @@
1
+ import { MAX_BEZIER_CONTROL_POINTS } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * How far a replacement curve may stray from the curve it replaces, in inches. It is a tenth of the
4
+ * half inch allowed between an import and the planner's own path, so rounding on save
5
+ * and this approximation together stay well inside it.
6
+ */
7
+ export const APPROXIMATION_TOLERANCE_IN = 0.05;
8
+ /** The point at parameter u of the Bézier with control polygon `points` (de Casteljau). */
9
+ export function bezierPoint(points, u) {
10
+ let current = points.slice();
11
+ while (current.length > 1) {
12
+ const next = [];
13
+ for (let i = 0; i + 1 < current.length; i += 1) {
14
+ const a = current[i];
15
+ const b = current[i + 1];
16
+ next.push({ xIn: a.xIn + (b.xIn - a.xIn) * u, yIn: a.yIn + (b.yIn - a.yIn) * u });
17
+ }
18
+ current = next;
19
+ }
20
+ return current[0];
21
+ }
22
+ /** The derivative with respect to u, in inches per unit of u. */
23
+ export function bezierDerivative(points, u) {
24
+ const degree = points.length - 1;
25
+ if (degree < 1)
26
+ return { xIn: 0, yIn: 0 };
27
+ const differences = [];
28
+ for (let i = 0; i < degree; i += 1) {
29
+ const a = points[i];
30
+ const b = points[i + 1];
31
+ differences.push({ xIn: (b.xIn - a.xIn) * degree, yIn: (b.yIn - a.yIn) * degree });
32
+ }
33
+ return bezierPoint(differences, u);
34
+ }
35
+ /**
36
+ * The same curve as control polygons of at most `maxControls` interior points each.
37
+ *
38
+ * A polygon that already fits is returned as it is, so the result is exact. A higher degree cannot be
39
+ * written exactly with fewer control points, so it is cut into equal stretches of u and each is
40
+ * replaced by the cubic with the same end points and the same end derivatives (a cubic Hermite
41
+ * piece), doubling the number of pieces until every one is within `toleranceIn` of the original at
42
+ * every sample. The distance is measured between points at the same parameter, which bounds the
43
+ * distance between the two curves from above.
44
+ */
45
+ export function reduceDegree(points, maxControls = MAX_BEZIER_CONTROL_POINTS, toleranceIn = APPROXIMATION_TOLERANCE_IN) {
46
+ if (points.length - 2 <= maxControls)
47
+ return { polygons: [points.slice()], maxErrorIn: 0 };
48
+ let best = null;
49
+ for (let pieces = 1; pieces <= 256; pieces *= 2) {
50
+ const polygons = [];
51
+ let maxErrorIn = 0;
52
+ for (let k = 0; k < pieces; k += 1) {
53
+ const u0 = k / pieces;
54
+ const u1 = (k + 1) / pieces;
55
+ const span = u1 - u0;
56
+ const p0 = bezierPoint(points, u0);
57
+ const p3 = bezierPoint(points, u1);
58
+ const d0 = bezierDerivative(points, u0);
59
+ const d3 = bezierDerivative(points, u1);
60
+ const cubic = [
61
+ p0,
62
+ { xIn: p0.xIn + (d0.xIn * span) / 3, yIn: p0.yIn + (d0.yIn * span) / 3 },
63
+ { xIn: p3.xIn - (d3.xIn * span) / 3, yIn: p3.yIn - (d3.yIn * span) / 3 },
64
+ p3,
65
+ ];
66
+ for (let i = 1; i < 16; i += 1) {
67
+ const s = i / 16;
68
+ const a = bezierPoint(cubic, s);
69
+ const b = bezierPoint(points, u0 + s * span);
70
+ maxErrorIn = Math.max(maxErrorIn, Math.hypot(a.xIn - b.xIn, a.yIn - b.yIn));
71
+ }
72
+ polygons.push(cubic);
73
+ }
74
+ best = { polygons, maxErrorIn };
75
+ if (maxErrorIn <= toleranceIn)
76
+ break;
77
+ }
78
+ return best;
79
+ }
80
+ /**
81
+ * The control polygon of the single Bézier that passes through every one of `through`, reached at
82
+ * evenly spaced parameters, which is what Pedro's `BezierCurve.through` builds (ADR 0008 section 1):
83
+ * solve the Bernstein matrix at u = i / (n - 1) for the control points.
84
+ */
85
+ export function interpolateThrough(through) {
86
+ const n = through.length;
87
+ if (n <= 2)
88
+ return through.slice();
89
+ const degree = n - 1;
90
+ const binomial = (k) => {
91
+ let value = 1;
92
+ for (let i = 1; i <= k; i += 1)
93
+ value = (value * (degree - k + i)) / i;
94
+ return value;
95
+ };
96
+ // Rows are the n parameters, columns the n Bernstein basis polynomials.
97
+ const matrix = [];
98
+ for (let i = 0; i < n; i += 1) {
99
+ const u = i / degree;
100
+ const row = [];
101
+ for (let j = 0; j <= degree; j += 1)
102
+ row.push(binomial(j) * u ** j * (1 - u) ** (degree - j));
103
+ matrix.push(row);
104
+ }
105
+ const xs = solve(matrix, through.map((point) => point.xIn));
106
+ const ys = solve(matrix, through.map((point) => point.yIn));
107
+ return xs.map((xIn, i) => ({ xIn, yIn: ys[i] }));
108
+ }
109
+ /** Gaussian elimination with partial pivoting; the matrix is small (a handful of points). */
110
+ function solve(matrix, rhs) {
111
+ const n = rhs.length;
112
+ const a = matrix.map((row, i) => [...row, rhs[i]]);
113
+ for (let col = 0; col < n; col += 1) {
114
+ let pivot = col;
115
+ for (let row = col + 1; row < n; row += 1) {
116
+ if (Math.abs(a[row][col]) > Math.abs(a[pivot][col]))
117
+ pivot = row;
118
+ }
119
+ [a[col], a[pivot]] = [a[pivot], a[col]];
120
+ const top = a[col];
121
+ const lead = top[col];
122
+ for (let row = 0; row < n; row += 1) {
123
+ if (row === col)
124
+ continue;
125
+ const current = a[row];
126
+ const factor = current[col] / lead;
127
+ for (let k = col; k <= n; k += 1)
128
+ current[k] = current[k] - factor * top[k];
129
+ }
130
+ }
131
+ return a.map((row, i) => row[n] / row[i]);
132
+ }
133
+ //# sourceMappingURL=bezier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bezier.js","sourceRoot":"","sources":["../src/bezier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /** One thing wrong with an imported file: where, as a JSON path, and what, as a sentence. */
2
+ export interface ImportIssue {
3
+ /** The JSON path of the offending value, such as `lines[2].endPoint.x`; empty for the whole file. */
4
+ path: string;
5
+ message: string;
6
+ }
7
+ /**
8
+ * Thrown when a file cannot be imported at all. Every issue names the field it is about, so a
9
+ * planner author can fix the file from the message alone.
10
+ */
11
+ export declare class ImportError extends Error {
12
+ readonly issues: readonly ImportIssue[];
13
+ constructor(what: string, issues: readonly ImportIssue[]);
14
+ }
15
+ /** Formats a JSON path from its keys: `["lines", 2, "endPoint"]` is `lines[2].endPoint`. */
16
+ export declare function jsonPath(keys: readonly (string | number)[]): string;
17
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,MAAM,WAAW,WAAW;IAC1B,qGAAqG;IACrG,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;gBAE5B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,WAAW,EAAE;CAKzD;AAED,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,CAOnE"}
package/dist/errors.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Thrown when a file cannot be imported at all. Every issue names the field it is about, so a
3
+ * planner author can fix the file from the message alone.
4
+ */
5
+ export class ImportError extends Error {
6
+ issues;
7
+ constructor(what, issues) {
8
+ super(`Cannot import ${what}:\n${issues.map((issue) => ` ${issue.path === "" ? "(file)" : issue.path}: ${issue.message}`).join("\n")}`);
9
+ this.name = "ImportError";
10
+ this.issues = issues;
11
+ }
12
+ }
13
+ /** Formats a JSON path from its keys: `["lines", 2, "endPoint"]` is `lines[2].endPoint`. */
14
+ export function jsonPath(keys) {
15
+ let out = "";
16
+ for (const key of keys) {
17
+ if (typeof key === "number")
18
+ out += `[${String(key)}]`;
19
+ else
20
+ out += out === "" ? key : `.${key}`;
21
+ }
22
+ return out;
23
+ }
24
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAyB;IAExC,YAAY,IAAY,EAAE,MAA8B;QACtD,KAAK,CAAC,iBAAiB,IAAI,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzI,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,UAAU,QAAQ,CAAC,IAAkC;IACzD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,GAAG,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;;YAClD,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { Field } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * A planner's coordinate frame, described by where it sits on the season's field (the frame of the
4
+ * season's `field.json`, which Zenith calls the field frame: for BIOBUZZ, centre origin, +x to the
5
+ * audience's right, +y away from the audience, inches, heading counter-clockwise from +x).
6
+ *
7
+ * Every direction is named as a direction of the field frame, so a frame that is turned, mirrored or
8
+ * offset against Zenith's can be written down exactly, and a frame written in any planner's own
9
+ * words has one reading.
10
+ */
11
+ export interface FrameSpec {
12
+ /** Where the planner's (0, 0) sits, in field-frame inches. */
13
+ originIn: {
14
+ xIn: number;
15
+ yIn: number;
16
+ };
17
+ /** The field-frame direction the planner's +x points along. */
18
+ xAxis: AxisDirection;
19
+ /** The field-frame direction the planner's +y points along. */
20
+ yAxis: AxisDirection;
21
+ /** The length unit of the planner's positions. */
22
+ units: LengthUnit;
23
+ /** The unit of the planner's headings. */
24
+ headingUnit: "rad" | "deg";
25
+ /** The planner axis a heading of zero points along. */
26
+ headingZero: "+x" | "+y";
27
+ /** Which way a positive heading turns, seen from above in the planner's own axes. */
28
+ headingPositive: "ccw" | "cw";
29
+ }
30
+ export declare const AXIS_DIRECTIONS: readonly ["+x", "-x", "+y", "-y"];
31
+ export type AxisDirection = (typeof AXIS_DIRECTIONS)[number];
32
+ export declare const LENGTH_UNITS: readonly ["in", "mm", "cm", "m"];
33
+ export type LengthUnit = (typeof LENGTH_UNITS)[number];
34
+ /** Inches per unit. An inch is exactly 25.4 mm by definition. */
35
+ export declare const INCHES_PER_UNIT: Record<LengthUnit, number>;
36
+ export declare const FRAME_PRESETS: readonly ["zenith", "pedro", "roadrunner"];
37
+ export type FramePreset = (typeof FRAME_PRESETS)[number];
38
+ /**
39
+ * The Pedro Pathing Visualizer's field centre in its own coordinates, in inches: half its
40
+ * `FIELD_SIZE` of 141.5 in, the axis its own "Mirror Horizontally" export mirrors about
41
+ * (ADR 0008 section 2).
42
+ */
43
+ export declare const PEDRO_VISUALIZER_CENTRE_IN: number;
44
+ /**
45
+ * The seasons each preset's placement was checked against. A preset used with another season still
46
+ * converts, with a warning, because the check rests on where that season's field image and Red Wall
47
+ * sit (ADR 0008 sections 2 and 4).
48
+ */
49
+ export declare const PRESET_VERIFIED_SEASONS: Record<FramePreset, readonly string[] | "any">;
50
+ /**
51
+ * The three named frames (ADR 0008).
52
+ *
53
+ * - `zenith`: the field frame itself, inches and radians.
54
+ * - `pedro`: the Pedro Visualizer's frame: corner origin, the same axes as the field frame for
55
+ * BIOBUZZ, inches, degrees counter-clockwise from +x.
56
+ * - `roadrunner`: MeepMeep's frame, which for BIOBUZZ is the FTC Field Coordinate System: centre
57
+ * origin, +x toward the audience (field -y), +y away from the Red Wall (field +x), inches, radians.
58
+ */
59
+ export declare function presetFrame(preset: FramePreset, pedroCentreIn?: number): FrameSpec;
60
+ /** A warning when `preset` was not checked against this season's field, else null. */
61
+ export declare function presetSeasonWarning(preset: FramePreset, field: Field): string | null;
62
+ /** Null when the two axes are perpendicular, else a sentence saying why the frame is unusable. */
63
+ export declare function frameProblem(frame: FrameSpec): string | null;
64
+ /** A planner point in field-frame inches. */
65
+ export declare function pointToField(frame: FrameSpec, x: number, y: number): {
66
+ xIn: number;
67
+ yIn: number;
68
+ };
69
+ /** A planner heading as a field-frame heading in radians, wrapped to (-pi, pi]. */
70
+ export declare function headingToField(frame: FrameSpec, heading: number): number;
71
+ /**
72
+ * A turn written in the planner's heading unit and direction (the difference between two headings,
73
+ * or an offset added to one), as a field-frame turn in radians. It is not wrapped: a turn of 270
74
+ * degrees stays 3 pi / 2, so a linear heading that turns the long way keeps doing so.
75
+ *
76
+ * A turn flips sign when the planner counts headings clockwise, and flips again when the planner's
77
+ * axes are mirrored against the field frame (a left-handed frame), since a counter-clockwise turn
78
+ * in such a frame is a clockwise one on the field.
79
+ */
80
+ export declare function turnToField(frame: FrameSpec, turn: number): number;
81
+ /** A planner pose in the field frame. */
82
+ export declare function poseToField(frame: FrameSpec, x: number, y: number, heading: number): {
83
+ xIn: number;
84
+ yIn: number;
85
+ headingRad: number;
86
+ };
87
+ /** Wraps an angle to (-pi, pi]. */
88
+ export declare function wrap(rad: number): number;
89
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../src/frame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,+DAA+D;IAC/D,KAAK,EAAE,aAAa,CAAC;IACrB,+DAA+D;IAC/D,KAAK,EAAE,aAAa,CAAC;IACrB,kDAAkD;IAClD,KAAK,EAAE,UAAU,CAAC;IAClB,0CAA0C;IAC1C,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC;IAC3B,uDAAuD;IACvD,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,qFAAqF;IACrF,eAAe,EAAE,KAAK,GAAG,IAAI,CAAC;CAC/B;AAED,eAAO,MAAM,eAAe,mCAAoC,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAC7D,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,iEAAiE;AACjE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAKtD,CAAC;AAEF,eAAO,MAAM,aAAa,4CAA6C,CAAC;AACxE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAY,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAIlF,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,aAAa,GAAE,MAAmC,GAAG,SAAS,CAiC9G;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAIpF;AASD,kGAAkG;AAClG,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED,6CAA6C;AAC7C,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAQjG;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOlE;AAED,yCAAyC;AACzC,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,EAAE,MAAM,GACd;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAElD;AAED,mCAAmC;AACnC,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKxC"}
package/dist/frame.js ADDED
@@ -0,0 +1,145 @@
1
+ export const AXIS_DIRECTIONS = ["+x", "-x", "+y", "-y"];
2
+ export const LENGTH_UNITS = ["in", "mm", "cm", "m"];
3
+ /** Inches per unit. An inch is exactly 25.4 mm by definition. */
4
+ export const INCHES_PER_UNIT = {
5
+ in: 1,
6
+ mm: 1 / 25.4,
7
+ cm: 1 / 2.54,
8
+ m: 1 / 0.0254,
9
+ };
10
+ export const FRAME_PRESETS = ["zenith", "pedro", "roadrunner"];
11
+ /**
12
+ * The Pedro Pathing Visualizer's field centre in its own coordinates, in inches: half its
13
+ * `FIELD_SIZE` of 141.5 in, the axis its own "Mirror Horizontally" export mirrors about
14
+ * (ADR 0008 section 2).
15
+ */
16
+ export const PEDRO_VISUALIZER_CENTRE_IN = 141.5 / 2;
17
+ /**
18
+ * The seasons each preset's placement was checked against. A preset used with another season still
19
+ * converts, with a warning, because the check rests on where that season's field image and Red Wall
20
+ * sit (ADR 0008 sections 2 and 4).
21
+ */
22
+ export const PRESET_VERIFIED_SEASONS = {
23
+ zenith: "any",
24
+ pedro: ["biobuzz"],
25
+ roadrunner: ["biobuzz"],
26
+ };
27
+ /**
28
+ * The three named frames (ADR 0008).
29
+ *
30
+ * - `zenith`: the field frame itself, inches and radians.
31
+ * - `pedro`: the Pedro Visualizer's frame: corner origin, the same axes as the field frame for
32
+ * BIOBUZZ, inches, degrees counter-clockwise from +x.
33
+ * - `roadrunner`: MeepMeep's frame, which for BIOBUZZ is the FTC Field Coordinate System: centre
34
+ * origin, +x toward the audience (field -y), +y away from the Red Wall (field +x), inches, radians.
35
+ */
36
+ export function presetFrame(preset, pedroCentreIn = PEDRO_VISUALIZER_CENTRE_IN) {
37
+ switch (preset) {
38
+ case "zenith":
39
+ return {
40
+ originIn: { xIn: 0, yIn: 0 },
41
+ xAxis: "+x",
42
+ yAxis: "+y",
43
+ units: "in",
44
+ headingUnit: "rad",
45
+ headingZero: "+x",
46
+ headingPositive: "ccw",
47
+ };
48
+ case "pedro":
49
+ return {
50
+ originIn: { xIn: -pedroCentreIn, yIn: -pedroCentreIn },
51
+ xAxis: "+x",
52
+ yAxis: "+y",
53
+ units: "in",
54
+ headingUnit: "deg",
55
+ headingZero: "+x",
56
+ headingPositive: "ccw",
57
+ };
58
+ case "roadrunner":
59
+ return {
60
+ originIn: { xIn: 0, yIn: 0 },
61
+ xAxis: "-y",
62
+ yAxis: "+x",
63
+ units: "in",
64
+ headingUnit: "rad",
65
+ headingZero: "+x",
66
+ headingPositive: "ccw",
67
+ };
68
+ }
69
+ }
70
+ /** A warning when `preset` was not checked against this season's field, else null. */
71
+ export function presetSeasonWarning(preset, field) {
72
+ const verified = PRESET_VERIFIED_SEASONS[preset];
73
+ if (verified === "any" || verified.includes(field.season))
74
+ return null;
75
+ return `The ${preset} frame preset was checked against ${verified.join(", ")} only; on ${field.season} its placement may be turned. Declare the frame explicitly if the imported path looks rotated.`;
76
+ }
77
+ const UNIT_VECTOR = {
78
+ "+x": { x: 1, y: 0 },
79
+ "-x": { x: -1, y: 0 },
80
+ "+y": { x: 0, y: 1 },
81
+ "-y": { x: 0, y: -1 },
82
+ };
83
+ /** Null when the two axes are perpendicular, else a sentence saying why the frame is unusable. */
84
+ export function frameProblem(frame) {
85
+ const ex = UNIT_VECTOR[frame.xAxis];
86
+ const ey = UNIT_VECTOR[frame.yAxis];
87
+ if (ex.x * ey.x + ex.y * ey.y !== 0) {
88
+ return `frame.xAxis (${frame.xAxis}) and frame.yAxis (${frame.yAxis}) must point along different field axes.`;
89
+ }
90
+ return null;
91
+ }
92
+ /** A planner point in field-frame inches. */
93
+ export function pointToField(frame, x, y) {
94
+ const k = INCHES_PER_UNIT[frame.units];
95
+ const ex = UNIT_VECTOR[frame.xAxis];
96
+ const ey = UNIT_VECTOR[frame.yAxis];
97
+ return {
98
+ xIn: frame.originIn.xIn + k * (x * ex.x + y * ey.x),
99
+ yIn: frame.originIn.yIn + k * (x * ex.y + y * ey.y),
100
+ };
101
+ }
102
+ /** A planner heading as a field-frame heading in radians, wrapped to (-pi, pi]. */
103
+ export function headingToField(frame, heading) {
104
+ const rad = frame.headingUnit === "deg" ? (heading * Math.PI) / 180 : heading;
105
+ const zero = frame.headingZero === "+x" ? 0 : Math.PI / 2;
106
+ const planner = zero + (frame.headingPositive === "ccw" ? rad : -rad);
107
+ // The direction in the planner's axes, carried into the field frame by the same axis map as a
108
+ // point, so a mirrored (left-handed) frame turns the right way too.
109
+ const ex = UNIT_VECTOR[frame.xAxis];
110
+ const ey = UNIT_VECTOR[frame.yAxis];
111
+ const c = Math.cos(planner);
112
+ const s = Math.sin(planner);
113
+ return wrap(Math.atan2(c * ex.y + s * ey.y, c * ex.x + s * ey.x));
114
+ }
115
+ /**
116
+ * A turn written in the planner's heading unit and direction (the difference between two headings,
117
+ * or an offset added to one), as a field-frame turn in radians. It is not wrapped: a turn of 270
118
+ * degrees stays 3 pi / 2, so a linear heading that turns the long way keeps doing so.
119
+ *
120
+ * A turn flips sign when the planner counts headings clockwise, and flips again when the planner's
121
+ * axes are mirrored against the field frame (a left-handed frame), since a counter-clockwise turn
122
+ * in such a frame is a clockwise one on the field.
123
+ */
124
+ export function turnToField(frame, turn) {
125
+ const rad = frame.headingUnit === "deg" ? (turn * Math.PI) / 180 : turn;
126
+ const ex = UNIT_VECTOR[frame.xAxis];
127
+ const ey = UNIT_VECTOR[frame.yAxis];
128
+ const handedness = ex.x * ey.y - ex.y * ey.x;
129
+ const direction = frame.headingPositive === "ccw" ? 1 : -1;
130
+ return rad * direction * handedness;
131
+ }
132
+ /** A planner pose in the field frame. */
133
+ export function poseToField(frame, x, y, heading) {
134
+ return { ...pointToField(frame, x, y), headingRad: headingToField(frame, heading) };
135
+ }
136
+ /** Wraps an angle to (-pi, pi]. */
137
+ export function wrap(rad) {
138
+ let a = rad % (2 * Math.PI);
139
+ if (a <= -Math.PI)
140
+ a += 2 * Math.PI;
141
+ if (a > Math.PI)
142
+ a -= 2 * Math.PI;
143
+ return a;
144
+ }
145
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../src/frame.ts"],"names":[],"mappings":"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"}