@johnmorrisdotca/kyogijo-stadiums 0.6.0 → 0.6.1

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.1] - 2026-10-14
11
+
12
+ ### Fixed
13
+
14
+ - **The 3D view zoomed in and out as it turned.** The camera fitted the scene to its box for each angle on its own, so every angle fitted but the scale changed with the yaw: a long floor was drawn up to 1.8 times as large (overhead; 1.5 times at a tip of 30 degrees and 1.15 to 1.65 times from 60 to 80) at the angle where it is seen end-on as side-on, and the field grew and shrank under a drag or the auto-turn. `fit` now fits the scene at every angle of the circle at the view's pitch (so it takes the pitch and not the yaw) and takes the one scale and centre that hold them all: turning never changes the zoom or sways the scene, and no angle is cut off. The floor is the size it had at its tightest angle (down to 0.55 of its old size where it had room). Tipping the view still rescales it, since the pitch changes the extent; zoom is laid on top of the fit as before. `src/turn.camera.test.ts` holds the scale constant over every yaw, pitch and container, for every sport's scene, and measures every degree of the turn against the box.
15
+
10
16
  ## [0.6.0] - 2026-10-14
11
17
 
12
18
  Six budō sports and a dojo builder (39 sports in all), the situation of a game, lineups, zones, a pointer and goal mouths, the arenas of the NHL, the NBA, the WNBA and the PWHL (330 places on the atlas), and Dougu Sports 0.2.0 for an icon on every sport. The 3D view's keys, buttons and drag now turn the surface the same way.
@@ -94,8 +94,10 @@ export declare function covered(b: Box3, view: TurnView, at: Placement, distance
94
94
  y1: number;
95
95
  };
96
96
  /**
97
- * The placement that fits the scene in a container of width `w` and height `h` pixels, centred, with `pad` of its width and height left round it. The
98
- * perspective bends the sum, so it is found by a few rounds of scaling and re-centring; six is plenty.
97
+ * The placement that fits the scene in a container of width `w` and height `h` pixels, centred, with `pad` of its width and height left round it. It fits the scene at every turn about the
98
+ * vertical at the view's pitch, not at the view's own turn (its `yaw` is not used): the scale and the place are one for the whole circle, so that turning never zooms in and out or sways, and
99
+ * nothing is cut off at any angle. (A turn used to fit each angle alone, so a long floor was drawn up to 1.8 times as large side-on as end-on.) Tipping the view still rescales it, since the
100
+ * extent of the scene on the screen changes with the pitch. The perspective bends the sum, so it is found by a few rounds of scaling and re-centring; six is plenty.
99
101
  */
100
102
  export declare function fit(b: Box3, view: TurnView, w: number, h: number, distance: number, pad?: number): Placement;
101
103
  /** A 4 by 4 matrix, as CSS writes it in `matrix3d`: sixteen numbers, a column after a column. */
@@ -121,24 +121,54 @@ export function covered(b, view, at, distance) {
121
121
  const shown = corners(b).map((c) => project(c, view, at, distance));
122
122
  return { x0: Math.min(...shown.map((p) => p[0])), x1: Math.max(...shown.map((p) => p[0])), y0: Math.min(...shown.map((p) => p[1])), y1: Math.max(...shown.map((p) => p[1])) };
123
123
  }
124
+ /** The turns the fit looks at: the whole circle in steps of five degrees. The pad round the scene is many times the little the extent can grow between two of them (a tenth of a percent). */
125
+ const FIT_YAWS = Array.from({ length: 72 }, (_, i) => i * 5);
126
+ /** The rectangle on the screen that a box covers over every turn of the circle, at this pitch. */
127
+ function coveredAround(b, pitch, at, distance) {
128
+ const all = corners(b);
129
+ let x0 = Infinity;
130
+ let y0 = Infinity;
131
+ let x1 = -Infinity;
132
+ let y1 = -Infinity;
133
+ for (const yaw of FIT_YAWS) {
134
+ for (const c of all) {
135
+ const [x, y] = project(c, { yaw, pitch, zoom: 1 }, at, distance);
136
+ if (x < x0)
137
+ x0 = x;
138
+ if (x > x1)
139
+ x1 = x;
140
+ if (y < y0)
141
+ y0 = y;
142
+ if (y > y1)
143
+ y1 = y;
144
+ }
145
+ }
146
+ return { x0, y0, x1, y1 };
147
+ }
148
+ let lastFit;
124
149
  /**
125
- * The placement that fits the scene in a container of width `w` and height `h` pixels, centred, with `pad` of its width and height left round it. The
126
- * perspective bends the sum, so it is found by a few rounds of scaling and re-centring; six is plenty.
150
+ * The placement that fits the scene in a container of width `w` and height `h` pixels, centred, with `pad` of its width and height left round it. It fits the scene at every turn about the
151
+ * vertical at the view's pitch, not at the view's own turn (its `yaw` is not used): the scale and the place are one for the whole circle, so that turning never zooms in and out or sways, and
152
+ * nothing is cut off at any angle. (A turn used to fit each angle alone, so a long floor was drawn up to 1.8 times as large side-on as end-on.) Tipping the view still rescales it, since the
153
+ * extent of the scene on the screen changes with the pitch. The perspective bends the sum, so it is found by a few rounds of scaling and re-centring; six is plenty.
127
154
  */
128
155
  export function fit(b, view, w, h, distance, pad = 0.03) {
156
+ const key = `${b.x0},${b.y0},${b.x1},${b.y1},${b.z0},${b.z1}|${view.pitch}|${w}|${h}|${distance}|${pad}`;
157
+ if (lastFit?.key === key)
158
+ return { ...lastFit.at };
129
159
  const at = { k: 0.5, tx: 0, ty: 0 };
130
- const flat = { ...view, zoom: 1 };
131
160
  for (let i = 0; i < 8; i += 1) {
132
- const r = covered(b, flat, at, distance);
161
+ const r = coveredAround(b, view.pitch, at, distance);
133
162
  const cw = r.x1 - r.x0;
134
163
  const ch = r.y1 - r.y0;
135
164
  if (cw <= 0 || ch <= 0)
136
165
  break;
137
166
  at.k *= Math.min((w * (1 - 2 * pad)) / cw, (h * (1 - 2 * pad)) / ch);
138
- const c = covered(b, flat, at, distance);
167
+ const c = coveredAround(b, view.pitch, at, distance);
139
168
  at.tx -= (c.x0 + c.x1) / 2;
140
169
  at.ty -= (c.y0 + c.y1) / 2;
141
170
  }
171
+ lastFit = { key, at: { ...at } };
142
172
  return at;
143
173
  }
144
174
  /** Two matrices as one: `b` first, then `a`. */
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export declare const VERSION = "0.6.0";
2
+ export declare const VERSION = "0.6.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.6.0";
2
+ export const VERSION = "0.6.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Kyogijo Stadiums 競技場, 39 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",