@woosh/meep-engine 2.159.0 → 2.161.0

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/engine/.fuse_hidden0000001500000001 +581 -0
  3. package/src/engine/Engine.d.ts.map +1 -1
  4. package/src/engine/Engine.js +4 -1
  5. package/src/engine/graphics/ecs/path/tube/build/compute_smooth_profile_normals.d.ts +8 -0
  6. package/src/engine/graphics/ecs/path/tube/build/compute_smooth_profile_normals.d.ts.map +1 -1
  7. package/src/engine/graphics/ecs/path/tube/build/compute_smooth_profile_normals.js +27 -20
  8. package/src/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.d.ts +11 -5
  9. package/src/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.d.ts.map +1 -1
  10. package/src/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.js +25 -31
  11. package/src/engine/graphics/ecs/path/tube/build/make_cap.d.ts.map +1 -1
  12. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +25 -0
  13. package/src/engine/input/devices/GamepadDevice.d.ts +26 -0
  14. package/src/engine/input/devices/GamepadDevice.d.ts.map +1 -0
  15. package/src/engine/input/devices/GamepadDevice.js +280 -0
  16. package/src/engine/input/devices/events/GamepadEvents.d.ts +10 -0
  17. package/src/engine/input/devices/events/GamepadEvents.d.ts.map +1 -0
  18. package/src/engine/input/devices/events/GamepadEvents.js +10 -0
  19. package/src/engine/input/devices/gamepad/GamepadAxes.d.ts +14 -0
  20. package/src/engine/input/devices/gamepad/GamepadAxes.d.ts.map +1 -0
  21. package/src/engine/input/devices/gamepad/GamepadAxes.js +15 -0
  22. package/src/engine/input/devices/gamepad/GamepadButtons.d.ts +28 -0
  23. package/src/engine/input/devices/gamepad/GamepadButtons.d.ts.map +1 -0
  24. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -0
  25. package/src/engine/input/devices/gamepad/GamepadHandle.d.ts +29 -0
  26. package/src/engine/input/devices/gamepad/GamepadHandle.d.ts.map +1 -0
  27. package/src/engine/input/devices/gamepad/GamepadHandle.js +232 -0
  28. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  29. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
@@ -1,6 +1,14 @@
1
1
  import { v2_length } from "../../../../../../core/geom/vec2/v2_length.js";
2
2
 
3
3
  /**
4
+ * Per-vertex OUTWARD normal of a closed 2D profile.
5
+ *
6
+ * Each vertex normal is the angle bisector of its two adjacent edge normals,
7
+ * oriented to point away from the profile centroid (assumed at the origin —
8
+ * profiles are authored centred). The result is stored in the convention
9
+ * {@link make_ring_vertices} consumes for its surface-normal mapping: the X
10
+ * component negated, i.e. `(-outX, outY)`. This keeps the lit surface normal
11
+ * radial (pointing out of the tube) regardless of which way the shape winds.
4
12
  *
5
13
  * @param {number[]|Float32Array} shape
6
14
  * @param {number[]|Float32Array} normals
@@ -13,31 +21,30 @@ export function compute_smooth_profile_normals(shape, normals) {
13
21
  const i1 = i;
14
22
  const i2 = (i + 1) % n;
15
23
 
16
- const i0_x = shape[i0 * 2];
17
- const i0_y = shape[i0 * 2 + 1];
18
-
19
- const i1_x = shape[i1 * 2];
20
- const i1_y = shape[i1 * 2 + 1];
21
-
22
- const i2_x = shape[i2 * 2];
23
- const i2_y = shape[i2 * 2 + 1];
24
+ const i0_x = shape[i0 * 2], i0_y = shape[i0 * 2 + 1];
25
+ const i1_x = shape[i1 * 2], i1_y = shape[i1 * 2 + 1];
26
+ const i2_x = shape[i2 * 2], i2_y = shape[i2 * 2 + 1];
24
27
 
25
- const d0_x = i0_x - i1_x;
26
- const d0_y = i0_y - i1_y;
28
+ // edge directions into and out of the vertex
29
+ const e0_x = i1_x - i0_x, e0_y = i1_y - i0_y;
30
+ const e1_x = i2_x - i1_x, e1_y = i2_y - i1_y;
27
31
 
28
- const d1_x = i1_x - i2_x;
29
- const d1_y = i1_y - i2_y;
32
+ const e0_l = 1 / v2_length(e0_x, e0_y);
33
+ const e1_l = 1 / v2_length(e1_x, e1_y);
30
34
 
31
- const d0_l_inv = 1 / v2_length(d0_x, d0_y);
32
- const d1_l_inv = 1 / v2_length(d1_x, d1_y);
35
+ // edge normals = edge rotated 90deg: (dx,dy) -> (dy,-dx). Summing the two
36
+ // adjacent edge normals gives the (un-oriented) vertex normal bisector.
37
+ let nx = e0_y * e0_l + e1_y * e1_l;
38
+ let ny = -e0_x * e0_l - e1_x * e1_l;
33
39
 
34
- const nx = d0_x * d0_l_inv + d1_x * d1_l_inv;
35
- const ny = d0_y * d0_l_inv + d1_y * d1_l_inv;
40
+ const nl = v2_length(nx, ny);
41
+ if (nl !== 0) { nx /= nl; ny /= nl; }
36
42
 
37
- const dn_l_inv = 1 / v2_length(nx, ny);
43
+ // orient OUTWARD (away from the centred profile's origin)
44
+ if (nx * i1_x + ny * i1_y < 0) { nx = -nx; ny = -ny; }
38
45
 
39
- // write out normal
40
- normals[i * 2] = nx * dn_l_inv;
41
- normals[i * 2 + 1] = ny * dn_l_inv;
46
+ // store in make_ring_vertices' convention: X negated
47
+ normals[i * 2] = -nx;
48
+ normals[i * 2 + 1] = ny;
42
49
  }
43
50
  }
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Depending on the direction in which shape winds, normals of the generated geometry faces might end up flipped,
3
- * so we pre-process the shape to avoid that here
4
- * @param {number[]|Float32Array} shape
5
- * @returns {Float32Array}
2
+ * Canonicalise a profile's winding so the generated tube faces point OUTWARD
3
+ * regardless of the order the caller authored the shape in. The tube builder's
4
+ * face index pattern is fixed, so the outward/inward orientation is decided
5
+ * purely by the shape's winding direction; we force a single signed-area sign
6
+ * here. (Empirically, clockwise — negative signed area — yields outward faces
7
+ * for the builder's index order.)
8
+ *
9
+ * @param {number[]|Float32Array} shape flat [x0,y0, x1,y1, ...]
10
+ * @returns {number[]|Float32Array} the shape wound for outward faces (input if
11
+ * already correct, otherwise a reversed copy)
6
12
  */
7
- export function fix_shape_normal_order(shape: number[] | Float32Array): Float32Array;
13
+ export function fix_shape_normal_order(shape: number[] | Float32Array): number[] | Float32Array;
8
14
  //# sourceMappingURL=fix_shape_normal_order.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fix_shape_normal_order.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,8CAHW,MAAM,EAAE,GAAC,YAAY,GACnB,YAAY,CAsCxB"}
1
+ {"version":3,"file":"fix_shape_normal_order.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,8CAJW,MAAM,EAAE,GAAC,YAAY,GACnB,MAAM,EAAE,GAAC,YAAY,CA6BjC"}
@@ -1,45 +1,39 @@
1
- import { v2_bearing_angle_towards } from "../../../../../../core/geom/vec2/v2_bearing_angle_towards.js";
2
-
3
1
  /**
4
- * Depending on the direction in which shape winds, normals of the generated geometry faces might end up flipped,
5
- * so we pre-process the shape to avoid that here
6
- * @param {number[]|Float32Array} shape
7
- * @returns {Float32Array}
2
+ * Canonicalise a profile's winding so the generated tube faces point OUTWARD
3
+ * regardless of the order the caller authored the shape in. The tube builder's
4
+ * face index pattern is fixed, so the outward/inward orientation is decided
5
+ * purely by the shape's winding direction; we force a single signed-area sign
6
+ * here. (Empirically, clockwise — negative signed area — yields outward faces
7
+ * for the builder's index order.)
8
+ *
9
+ * @param {number[]|Float32Array} shape flat [x0,y0, x1,y1, ...]
10
+ * @returns {number[]|Float32Array} the shape wound for outward faces (input if
11
+ * already correct, otherwise a reversed copy)
8
12
  */
9
13
  export function fix_shape_normal_order(shape) {
10
- const shape_array_length = shape.length;
11
-
12
- if (shape_array_length <= 2) {
13
- // less than 2 points in the shape
14
+ const n = shape.length / 2;
15
+ if (n < 3) {
14
16
  return shape;
15
17
  }
16
18
 
17
- // compute initial direction
18
- const x0 = shape[0];
19
- const y0 = shape[1];
20
- const x1 = shape[2];
21
- const y1 = shape[3];
22
-
23
- const angle = v2_bearing_angle_towards(x0, y0, x1, y1);
19
+ // signed area (shoelace); > 0 is counter-clockwise
20
+ let area2 = 0;
21
+ for (let i = 0; i < n; i++) {
22
+ const j = (i + 1) % n;
23
+ area2 += shape[i * 2] * shape[j * 2 + 1] - shape[j * 2] * shape[i * 2 + 1];
24
+ }
24
25
 
25
- if (angle > Math.PI) {
26
- // order is fine
26
+ if (area2 <= 0) {
27
+ // already clockwise — the orientation the builder wants
27
28
  return shape;
28
29
  }
29
30
 
30
- // reverse order
31
- const reversed = new Float32Array(shape_array_length);
32
-
33
- const n = shape_array_length / 2;
34
-
31
+ // reverse to clockwise
32
+ const reversed = new Float32Array(shape.length);
35
33
  for (let i = 0; i < n; i++) {
36
- const i2 = i * 2;
37
-
38
- const j2 = (n - (i + 1)) * 2;
39
-
40
- reversed[i2] = shape[j2];
41
- reversed[i2 + 1] = shape[j2 + 1];
34
+ const j = n - 1 - i;
35
+ reversed[i * 2] = shape[j * 2];
36
+ reversed[i * 2 + 1] = shape[j * 2 + 1];
42
37
  }
43
-
44
38
  return reversed;
45
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"make_cap.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/make_cap.js"],"names":[],"mappings":"AAgNA;;;;;;;;;;;;;;GAcG;AACH,4DAbW,MAAM,gBACN,YAAY,GAAC,MAAM,EAAE,cACrB,OAAO,EAAE,gBACT,OAAO,EAAE,eAET,OAAO,EAAE,SACT,MAAM,EAAE,gBACR,MAAM,EAAE,GAAC,YAAY,gBACrB,MAAM,mBACN,MAAM,EAAE,GAAC,YAAY,aACrB,MAAM,QACN,OAAO,QA6BjB;AAED;;;;;;GAMG;AACH,wDALW,MAAM,OACN;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,YAAY,EAAC,MAAM,CAAA;CAAC,mBAC3C,MAAM,QACN,OAAO,QAiBjB;wBAlRuB,OAAO;wBAMP,eAAe"}
1
+ {"version":3,"file":"make_cap.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/make_cap.js"],"names":[],"mappings":"AAyOA;;;;;;;;;;;;;;GAcG;AACH,4DAbW,MAAM,gBACN,YAAY,GAAC,MAAM,EAAE,cACrB,OAAO,EAAE,gBACT,OAAO,EAAE,eAET,OAAO,EAAE,SACT,MAAM,EAAE,gBACR,MAAM,EAAE,GAAC,YAAY,gBACrB,MAAM,mBACN,MAAM,EAAE,GAAC,YAAY,aACrB,MAAM,QACN,OAAO,QA6BjB;AAED;;;;;;GAMG;AACH,wDALW,MAAM,OACN;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,YAAY,EAAC,MAAM,CAAA;CAAC,mBAC3C,MAAM,QACN,OAAO,QAiBjB;wBA3SuB,OAAO;wBAMP,eAAe"}
@@ -138,6 +138,31 @@ function make_cap_round(
138
138
  }
139
139
 
140
140
  make_ring_faces(out, index_offset, cap_segment_count, shape_length);
141
+
142
+ // Correct the cap vertex normals. make_ring_vertices wrote the tube WALL
143
+ // normal (purely radial) for each cap ring, which shades the dome like a
144
+ // flat cylinder end. Every cap vertex lies on the sphere of radius `radius`
145
+ // centred at the cap base (Px, Py, Pz), so the true outward normal is just
146
+ // the direction from that centre to the vertex. This bends the normals from
147
+ // radial (at the base ring) to axial (at the pole), independent of the
148
+ // path frame's roll.
149
+ const cap_normals = out.normals;
150
+ const cap_positions = out.positions;
151
+ for (let v = index_offset; v < out.cursor_vertices; v++) {
152
+ const v3 = v * 3;
153
+
154
+ const nx = cap_positions[v3] - Px;
155
+ const ny = cap_positions[v3 + 1] - Py;
156
+ const nz = cap_positions[v3 + 2] - Pz;
157
+
158
+ const length = Math.sqrt(nx * nx + ny * ny + nz * nz);
159
+ if (length > 1e-8) {
160
+ const inv = 1 / length;
161
+ cap_normals[v3] = nx * inv;
162
+ cap_normals[v3 + 1] = ny * inv;
163
+ cap_normals[v3 + 2] = nz * inv;
164
+ }
165
+ }
141
166
  }
142
167
 
143
168
  const m3_zero = new Float32Array(9);
@@ -0,0 +1,26 @@
1
+ import Signal from "../../../core/events/signal/Signal";
2
+ import {GamepadHandle} from "./gamepad/GamepadHandle";
3
+
4
+ export class GamepadDevice {
5
+ constructor()
6
+
7
+ start(): void
8
+
9
+ stop(): void
10
+
11
+ poll(): void
12
+
13
+ deadZone: number
14
+
15
+ readonly pads: GamepadHandle[]
16
+
17
+ readonly main: GamepadHandle | undefined
18
+
19
+ readonly on: {
20
+ connected: Signal<GamepadHandle>,
21
+ disconnected: Signal<GamepadHandle>,
22
+ down: Signal<number /*buttonIndex*/, GamepadHandle>,
23
+ up: Signal<number /*buttonIndex*/, GamepadHandle>,
24
+ axis: Signal<number /*axisIndex*/, number /*value*/, GamepadHandle>
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GamepadDevice.d.ts","sourceRoot":"","sources":["../../../../../src/engine/input/devices/GamepadDevice.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH;IAEI;;OAEG;IACH;QACI;;;WAGG;mBADO,OAAO,aAAa,CAAC;QAG/B;;;WAGG;sBADO,OAAO,aAAa,CAAC;QAG/B;;;WAGG;cADO,OAAO,MAAM,EAAE,aAAa,CAAC;QAGvC;;;WAGG;YADO,OAAO,MAAM,EAAE,aAAa,CAAC;QAGvC;;;WAGG;cADO,OAAO,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC;MAGjD;IAEF;;;;;;OAMG;IACH,eAFU,aAAa,EAAE,CAEf;IAEV;;;;OAIG;IACH,UAFU,MAAM,CAED;IAEf;;;OAGG;IACH,kBAAkB;IAQlB;;;;;OAKG;IACH,0BAaC;IAmFD;;;OAGG;IACH,aA+BC;IAQD;;;OAGG;IACH,SAFa,IAAI,CAmBhB;IAED;;;OAGG;IACH,QAFa,IAAI,CA6BhB;;CACJ;mBAvRkB,uCAAuC;8BAE5B,4BAA4B"}
@@ -0,0 +1,280 @@
1
+ import Signal from "../../../core/events/signal/Signal.js";
2
+ import { GamepadEvents } from "./events/GamepadEvents.js";
3
+ import { GamepadHandle } from "./gamepad/GamepadHandle.js";
4
+
5
+ /**
6
+ * Provides gamepad input on top of the Gamepad API.
7
+ * see https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API
8
+ *
9
+ * Unlike mouse/keyboard, the Gamepad API offers no events for button/axis state changes, state must be polled instead.
10
+ * While running, the device polls automatically once per animation frame. {@link GamepadDevice#poll} can also be
11
+ * invoked manually for finer control (e.g. from a fixed-rate simulation loop).
12
+ *
13
+ * NOTE: when a gamepad disconnects, any "pressed" buttons will be automatically released with appropriate signals,
14
+ * preventing inputs from getting "stuck".
15
+ *
16
+ * @author Alex Goldring
17
+ * @copyright Company Named Limited (c) 2026
18
+ */
19
+ export class GamepadDevice {
20
+
21
+ /**
22
+ * @readonly
23
+ */
24
+ on = {
25
+ /**
26
+ * Fires when a gamepad is connected
27
+ * @type {Signal<GamepadHandle>}
28
+ */
29
+ connected: new Signal(),
30
+ /**
31
+ * Fires when a gamepad is disconnected
32
+ * @type {Signal<GamepadHandle>}
33
+ */
34
+ disconnected: new Signal(),
35
+ /**
36
+ * Fires when any button on any gamepad is pressed, dispatches (buttonIndex, pad)
37
+ * @type {Signal<number, GamepadHandle>}
38
+ */
39
+ down: new Signal(),
40
+ /**
41
+ * Fires when any button on any gamepad is released, dispatches (buttonIndex, pad)
42
+ * @type {Signal<number, GamepadHandle>}
43
+ */
44
+ up: new Signal(),
45
+ /**
46
+ * Fires when any axis on any gamepad changes, dispatches (axisIndex, value, pad)
47
+ * @type {Signal<number, number, GamepadHandle>}
48
+ */
49
+ axis: new Signal()
50
+ };
51
+
52
+ /**
53
+ * Known gamepads, indexed by {@link Gamepad.index}. May be sparse.
54
+ * Handles are retained after disconnection and re-used on re-connection, so they are safe to hold references to.
55
+ *
56
+ * @readonly
57
+ * @type {GamepadHandle[]}
58
+ */
59
+ pads = [];
60
+
61
+ /**
62
+ * Axis values with magnitude below this threshold are treated as 0.
63
+ * Compensates for analog stick noise/drift around the neutral position.
64
+ * @type {number}
65
+ */
66
+ deadZone = 0.1;
67
+
68
+ /**
69
+ * @private
70
+ * @type {boolean}
71
+ */
72
+ isRunning = false;
73
+
74
+ /**
75
+ *
76
+ * @type {number}
77
+ */
78
+ #animationFrameId = -1;
79
+
80
+ /**
81
+ * First connected gamepad, or undefined if none are connected.
82
+ * Convenience accessor for the common single-player case.
83
+ *
84
+ * @returns {GamepadHandle|undefined}
85
+ */
86
+ get main() {
87
+ const pads = this.pads;
88
+ const n = pads.length;
89
+
90
+ for (let i = 0; i < n; i++) {
91
+ const pad = pads[i];
92
+
93
+ if (pad !== undefined && pad.connected) {
94
+ return pad;
95
+ }
96
+ }
97
+
98
+ return undefined;
99
+ }
100
+
101
+ /**
102
+ * Returns handle for a given pad index, creating it if it doesn't exist yet
103
+ *
104
+ * @param {number} index
105
+ * @returns {GamepadHandle}
106
+ */
107
+ #obtainHandle(index) {
108
+ let pad = this.pads[index];
109
+
110
+ if (pad === undefined) {
111
+ pad = new GamepadHandle();
112
+ pad.index = index;
113
+
114
+ // forward pad signals to device-level signals
115
+ pad.on.down.add(buttonIndex => this.on.down.send2(buttonIndex, pad));
116
+ pad.on.up.add(buttonIndex => this.on.up.send2(buttonIndex, pad));
117
+ pad.on.axis.add((axisIndex, value) => this.on.axis.send3(axisIndex, value, pad));
118
+
119
+ this.pads[index] = pad;
120
+ }
121
+
122
+ return pad;
123
+ }
124
+
125
+ /**
126
+ *
127
+ * @param {Gamepad} gamepad native gamepad object
128
+ * @returns {GamepadHandle}
129
+ */
130
+ #connect(gamepad) {
131
+ const pad = this.#obtainHandle(gamepad.index);
132
+
133
+ pad.update(gamepad, this.deadZone);
134
+
135
+ if (!pad.connected) {
136
+ pad.connected = true;
137
+
138
+ this.on.connected.send1(pad);
139
+ }
140
+
141
+ return pad;
142
+ }
143
+
144
+ /**
145
+ *
146
+ * @param {GamepadHandle} pad
147
+ */
148
+ #disconnect(pad) {
149
+ if (!pad.connected) {
150
+ // already disconnected
151
+ return;
152
+ }
153
+
154
+ pad.connected = false;
155
+
156
+ // release everything to avoid stuck inputs
157
+ pad.reset();
158
+
159
+ this.on.disconnected.send1(pad);
160
+ }
161
+
162
+ /**
163
+ *
164
+ * @param {GamepadEvent} event
165
+ */
166
+ #handleConnectedEvent = (event) => {
167
+ this.#connect(event.gamepad);
168
+ }
169
+
170
+ /**
171
+ *
172
+ * @param {GamepadEvent} event
173
+ */
174
+ #handleDisconnectedEvent = (event) => {
175
+ const pad = this.pads[event.gamepad.index];
176
+
177
+ if (pad !== undefined) {
178
+ this.#disconnect(pad);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Read current state of all gamepads and dispatch signals for any changes.
184
+ * Invoked automatically once per animation frame while the device is running.
185
+ */
186
+ poll() {
187
+ if (typeof navigator === "undefined" || typeof navigator.getGamepads !== "function") {
188
+ // Gamepad API is not supported in this environment
189
+ return;
190
+ }
191
+
192
+ const gamepads = navigator.getGamepads();
193
+ const n = gamepads.length;
194
+
195
+ for (let i = 0; i < n; i++) {
196
+ const gamepad = gamepads[i];
197
+
198
+ if (gamepad === null || gamepad === undefined) {
199
+ // empty slot, disconnect matching handle if there is one
200
+ const pad = this.pads[i];
201
+
202
+ if (pad !== undefined) {
203
+ this.#disconnect(pad);
204
+ }
205
+
206
+ continue;
207
+ }
208
+
209
+ /*
210
+ Some browsers only dispatch "gamepadconnected" after a button is pressed,
211
+ so connection is detected during polling as well
212
+ */
213
+ const pad = this.#connect(gamepad);
214
+
215
+ pad.update(gamepad, this.deadZone);
216
+ }
217
+ }
218
+
219
+ #update = () => {
220
+ this.poll();
221
+
222
+ this.#animationFrameId = requestAnimationFrame(this.#update);
223
+ }
224
+
225
+ /**
226
+ * Starts listening for gamepad connections and polling gamepad state.
227
+ * @returns {void}
228
+ */
229
+ start() {
230
+ if (this.isRunning) {
231
+ //already running
232
+ return;
233
+ }
234
+
235
+ this.isRunning = true;
236
+
237
+ window.addEventListener(GamepadEvents.Connected, this.#handleConnectedEvent);
238
+ window.addEventListener(GamepadEvents.Disconnected, this.#handleDisconnectedEvent);
239
+
240
+ // pick up gamepads that were connected before start
241
+ this.poll();
242
+
243
+ if (typeof requestAnimationFrame === "function") {
244
+ this.#animationFrameId = requestAnimationFrame(this.#update);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Stops listening for gamepad connections and polling gamepad state.
250
+ * @returns {void}
251
+ */
252
+ stop() {
253
+ if (!this.isRunning) {
254
+ //not running
255
+ return;
256
+ }
257
+
258
+ this.isRunning = false;
259
+
260
+ window.removeEventListener(GamepadEvents.Connected, this.#handleConnectedEvent);
261
+ window.removeEventListener(GamepadEvents.Disconnected, this.#handleDisconnectedEvent);
262
+
263
+ if (this.#animationFrameId !== -1) {
264
+ cancelAnimationFrame(this.#animationFrameId);
265
+ this.#animationFrameId = -1;
266
+ }
267
+
268
+ // release all buttons, as we will not be able to observe release while stopped
269
+ const pads = this.pads;
270
+ const n = pads.length;
271
+
272
+ for (let i = 0; i < n; i++) {
273
+ const pad = pads[i];
274
+
275
+ if (pad !== undefined) {
276
+ pad.reset();
277
+ }
278
+ }
279
+ }
280
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * DOM event names for the Gamepad API
3
+ * see https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API
4
+ */
5
+ export type GamepadEvents = string;
6
+ export namespace GamepadEvents {
7
+ let Connected: string;
8
+ let Disconnected: string;
9
+ }
10
+ //# sourceMappingURL=GamepadEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GamepadEvents.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/input/devices/events/GamepadEvents.js"],"names":[],"mappings":";;;;4BAIU,MAAM"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * DOM event names for the Gamepad API
3
+ * see https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API
4
+ *
5
+ * @enum {string}
6
+ */
7
+ export const GamepadEvents = {
8
+ Connected: "gamepadconnected",
9
+ Disconnected: "gamepaddisconnected"
10
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Axis indices for the "standard" gamepad mapping
3
+ * see https://w3c.github.io/gamepad/#remapping
4
+ *
5
+ * Negative values are left/up, positive values are right/down
6
+ */
7
+ export type GamepadAxes = number;
8
+ export namespace GamepadAxes {
9
+ let left_stick_x: number;
10
+ let left_stick_y: number;
11
+ let right_stick_x: number;
12
+ let right_stick_y: number;
13
+ }
14
+ //# sourceMappingURL=GamepadAxes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GamepadAxes.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/input/devices/gamepad/GamepadAxes.js"],"names":[],"mappings":";;;;;;0BAOU,MAAM"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Axis indices for the "standard" gamepad mapping
3
+ * see https://w3c.github.io/gamepad/#remapping
4
+ *
5
+ * Negative values are left/up, positive values are right/down
6
+ *
7
+ * @readonly
8
+ * @enum {number}
9
+ */
10
+ export const GamepadAxes = {
11
+ 'left_stick_x': 0,
12
+ 'left_stick_y': 1,
13
+ 'right_stick_x': 2,
14
+ 'right_stick_y': 3
15
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Button indices for the "standard" gamepad mapping
3
+ * see https://w3c.github.io/gamepad/#remapping
4
+ *
5
+ * Names of face buttons (a/b/x/y) follow the Xbox controller layout:
6
+ * a = bottom, b = right, x = left, y = top
7
+ */
8
+ export type GamepadButtons = number;
9
+ export namespace GamepadButtons {
10
+ let a: number;
11
+ let b: number;
12
+ let x: number;
13
+ let y: number;
14
+ let left_bumper: number;
15
+ let right_bumper: number;
16
+ let left_trigger: number;
17
+ let right_trigger: number;
18
+ let back: number;
19
+ let start: number;
20
+ let left_stick: number;
21
+ let right_stick: number;
22
+ let dpad_up: number;
23
+ let dpad_down: number;
24
+ let dpad_left: number;
25
+ let dpad_right: number;
26
+ let home: number;
27
+ }
28
+ //# sourceMappingURL=GamepadButtons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GamepadButtons.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/input/devices/gamepad/GamepadButtons.js"],"names":[],"mappings":";;;;;;;6BAQU,MAAM"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Button indices for the "standard" gamepad mapping
3
+ * see https://w3c.github.io/gamepad/#remapping
4
+ *
5
+ * Names of face buttons (a/b/x/y) follow the Xbox controller layout:
6
+ * a = bottom, b = right, x = left, y = top
7
+ *
8
+ * @readonly
9
+ * @enum {number}
10
+ */
11
+ export const GamepadButtons = {
12
+ 'a': 0,
13
+ 'b': 1,
14
+ 'x': 2,
15
+ 'y': 3,
16
+ 'left_bumper': 4,
17
+ 'right_bumper': 5,
18
+ 'left_trigger': 6,
19
+ 'right_trigger': 7,
20
+ 'back': 8,
21
+ 'start': 9,
22
+ 'left_stick': 10,
23
+ 'right_stick': 11,
24
+ 'dpad_up': 12,
25
+ 'dpad_down': 13,
26
+ 'dpad_left': 14,
27
+ 'dpad_right': 15,
28
+ 'home': 16
29
+ };
@@ -0,0 +1,29 @@
1
+ import Signal from "../../../../core/events/signal/Signal";
2
+ import Vector2 from "../../../../core/geom/Vector2";
3
+ import {InputDeviceSwitch} from "../InputDeviceSwitch";
4
+
5
+ export class GamepadHandle {
6
+ readonly index: number
7
+ readonly id: string
8
+ readonly mapping: string
9
+ readonly connected: boolean
10
+
11
+ readonly buttons: InputDeviceSwitch[]
12
+ readonly buttonValues: number[]
13
+ readonly axes: number[]
14
+
15
+ readonly stickLeft: Vector2
16
+ readonly stickRight: Vector2
17
+
18
+ getButton(index: number): InputDeviceSwitch
19
+
20
+ update(source: Gamepad, deadZone?: number): void
21
+
22
+ reset(): void
23
+
24
+ readonly on: {
25
+ down: Signal<number /*buttonIndex*/>,
26
+ up: Signal<number /*buttonIndex*/>,
27
+ axis: Signal<number /*axisIndex*/, number /*value*/, number /*previousValue*/>
28
+ }
29
+ }