emeraldengine 3.0.0 → 3.1.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.
- package/README.md +1498 -1659
- package/dist/types/index.d.ts +4 -1
- package/dist/types/src/Animator.d.ts +1 -1
- package/dist/types/src/CollisionLayers.d.ts +2 -2
- package/dist/types/src/Color.d.ts +1 -0
- package/dist/types/src/Drawable.d.ts +1 -1
- package/dist/types/src/EmeraldDB.d.ts +2 -2
- package/dist/types/src/InstancedTexture.d.ts +17 -2
- package/dist/types/src/Material.d.ts +2 -2
- package/dist/types/src/MathUtils.d.ts +2 -1
- package/dist/types/src/ParticleEmitter.d.ts +1 -1
- package/dist/types/src/Physics.d.ts +148 -18
- package/dist/types/src/Scene.d.ts +1 -1
- package/dist/types/src/Shaders.d.ts +2 -2
- package/dist/types/src/Tilemap.d.ts +1 -1
- package/dist/types/src/UI.d.ts +1 -1
- package/dist/types/src/components/Behaviour.d.ts +2 -2
- package/dist/types/src/components/Collider.d.ts +7 -1
- package/dist/types/src/components/GameObject.d.ts +2 -2
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +281 -8
- package/dist/types/src/importers/Aseprite.d.ts +2 -2
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +1 -1
- package/dist/types/src/managers/EventManager.d.ts +1 -1
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/InputManager.d.ts +93 -2
- package/dist/types/src/managers/NetworkManager.d.ts +2 -2
- package/dist/types/src/managers/RenderStats.d.ts +1 -1
- package/dist/types/src/managers/TextureManager.d.ts +1 -1
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +6 -0
- package/package.json +2 -3
- package/src/Animator.js +1 -1
- package/src/CollisionLayers.js +3 -3
- package/src/Color.js +8 -0
- package/src/Drawable.js +1 -1
- package/src/Emerald.js +1 -1
- package/src/EmeraldDB.js +2 -2
- package/src/InstancedTexture.js +57 -9
- package/src/Material.js +2 -2
- package/src/MathUtils.js +2 -1
- package/src/ParticleEmitter.js +1 -1
- package/src/Physics.js +270 -60
- package/src/Scene.js +1 -1
- package/src/Shaders.js +20 -20
- package/src/Tilemap.js +1 -1
- package/src/UI.js +1 -1
- package/src/components/Behaviour.js +2 -2
- package/src/components/BoxCollider.js +7 -9
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +7 -9
- package/src/components/CircleColliderDebug.js +3 -2
- package/src/components/Collider.js +13 -3
- package/src/components/GameObject.js +2 -2
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +441 -14
- package/src/importers/Aseprite.js +2 -2
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +1 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/InputManager.js +129 -3
- package/src/managers/NetworkManager.js +2 -2
- package/src/managers/RenderStats.js +1 -1
- package/src/managers/TextureManager.js +1 -1
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- package/src/physics/index.js +79 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class Gamepad
|
|
3
|
+
* @description An autocomplete-friendly way to build the raw gamepad token
|
|
4
|
+
* strings InputManager already understands ("pad:0:south", etc.). It does
|
|
5
|
+
* not read input itself, and it is unrelated to the browser's own native
|
|
6
|
+
* `Gamepad` interface (the objects `navigator.getGamepads()` returns). It
|
|
7
|
+
* exists purely so you don't have to remember button-name spelling: every
|
|
8
|
+
* button/stick-direction name InputManager's standard mapping recognizes is
|
|
9
|
+
* a static constant here, so typing `Gamepad.` in an editor lists them the
|
|
10
|
+
* way an enum's members would.
|
|
11
|
+
*
|
|
12
|
+
* `Gamepad.get(index).key(name)` produces exactly the same string as writing
|
|
13
|
+
* the token by hand, so the two are fully interchangeable anywhere
|
|
14
|
+
* InputManager takes one: `mapAction`, `isDown`, `justPressed`,
|
|
15
|
+
* `justReleased`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { Gamepad, InputManager } from "./index.js";
|
|
19
|
+
*
|
|
20
|
+
* const input = new InputManager();
|
|
21
|
+
* const pad = Gamepad.get(0); // the first controller
|
|
22
|
+
*
|
|
23
|
+
* input.mapAction("jump", [pad.key(Gamepad.SOUTH)]);
|
|
24
|
+
* input.mapAction("left", [pad.key(Gamepad.DPAD_LEFT), pad.key(Gamepad.LEFT_STICK_LEFT)]);
|
|
25
|
+
*
|
|
26
|
+
* // in the loop:
|
|
27
|
+
* if (input.justPressed("jump")) player.jump();
|
|
28
|
+
*/
|
|
29
|
+
class Gamepad {
|
|
30
|
+
/**
|
|
31
|
+
* @method get
|
|
32
|
+
* @description Returns the (cached) handle for a pad index: 0 for the
|
|
33
|
+
* first controller, 1 for the second, and so on.
|
|
34
|
+
* @param {number} [index=0]
|
|
35
|
+
* @returns {Gamepad}
|
|
36
|
+
*/
|
|
37
|
+
static get(index = 0) {
|
|
38
|
+
let pad = Gamepad._instances.get(index);
|
|
39
|
+
if (!pad) {
|
|
40
|
+
pad = new Gamepad(index);
|
|
41
|
+
Gamepad._instances.set(index, pad);
|
|
42
|
+
}
|
|
43
|
+
return pad;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @private Use `Gamepad.get(index)` rather than constructing directly, so
|
|
48
|
+
* every caller asking for the same pad index shares one instance.
|
|
49
|
+
*/
|
|
50
|
+
constructor(index) {
|
|
51
|
+
this.index = index;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @method key
|
|
56
|
+
* @description A face/shoulder/centre/d-pad/stick-as-button token for this
|
|
57
|
+
* pad. Pass one of the `Gamepad.*` constants (`Gamepad.SOUTH`,
|
|
58
|
+
* `Gamepad.DPAD_UP`, …). InputManager's standard mapping also accepts a
|
|
59
|
+
* few vendor aliases these don't cover (e.g. "a", "cross" for `SOUTH`) if
|
|
60
|
+
* you'd rather think in Xbox/PlayStation terms; those still work as plain
|
|
61
|
+
* strings, `key()` just doesn't need to name them since `SOUTH` already
|
|
62
|
+
* reads the same on every pad.
|
|
63
|
+
* @param {string} name - One of the `Gamepad.*` button constants
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
key(name) {
|
|
67
|
+
return `pad:${this.index}:${name}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @method button
|
|
72
|
+
* @description A raw button index, mapping-independent: an escape hatch
|
|
73
|
+
* for a pad whose layout the standard/custom mapping tables don't cover.
|
|
74
|
+
* @param {number} n
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
button(n) {
|
|
78
|
+
return `pad:${this.index}:${n}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @method axis
|
|
83
|
+
* @description A raw analog axis past the deadzone, in one direction.
|
|
84
|
+
* @param {number} n - Axis index (0/1 = left stick X/Y, 2/3 = right stick X/Y on a standard pad)
|
|
85
|
+
* @param {"+"|"-"} [sign="+"]
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
axis(n, sign = "+") {
|
|
89
|
+
return `pad:${this.index}:axis${n}${sign}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Gamepad._instances = new Map();
|
|
94
|
+
|
|
95
|
+
Gamepad.SOUTH = "south";
|
|
96
|
+
Gamepad.EAST = "east";
|
|
97
|
+
Gamepad.WEST = "west";
|
|
98
|
+
Gamepad.NORTH = "north";
|
|
99
|
+
|
|
100
|
+
Gamepad.L1 = "l1";
|
|
101
|
+
Gamepad.R1 = "r1";
|
|
102
|
+
Gamepad.L2 = "l2";
|
|
103
|
+
Gamepad.R2 = "r2";
|
|
104
|
+
|
|
105
|
+
Gamepad.SELECT = "select";
|
|
106
|
+
Gamepad.START = "start";
|
|
107
|
+
Gamepad.HOME = "home";
|
|
108
|
+
|
|
109
|
+
Gamepad.L3 = "l3";
|
|
110
|
+
Gamepad.R3 = "r3";
|
|
111
|
+
|
|
112
|
+
Gamepad.DPAD_UP = "dpadUp";
|
|
113
|
+
Gamepad.DPAD_DOWN = "dpadDown";
|
|
114
|
+
Gamepad.DPAD_LEFT = "dpadLeft";
|
|
115
|
+
Gamepad.DPAD_RIGHT = "dpadRight";
|
|
116
|
+
|
|
117
|
+
Gamepad.LEFT_STICK_UP = "leftStickUp";
|
|
118
|
+
Gamepad.LEFT_STICK_DOWN = "leftStickDown";
|
|
119
|
+
Gamepad.LEFT_STICK_LEFT = "leftStickLeft";
|
|
120
|
+
Gamepad.LEFT_STICK_RIGHT = "leftStickRight";
|
|
121
|
+
Gamepad.RIGHT_STICK_UP = "rightStickUp";
|
|
122
|
+
Gamepad.RIGHT_STICK_DOWN = "rightStickDown";
|
|
123
|
+
Gamepad.RIGHT_STICK_LEFT = "rightStickLeft";
|
|
124
|
+
Gamepad.RIGHT_STICK_RIGHT = "rightStickRight";
|
|
125
|
+
|
|
126
|
+
export default Gamepad;
|
|
@@ -173,10 +173,10 @@ for (const b of BUILTIN_MAPPINGS) {
|
|
|
173
173
|
* gamepad support (analog sticks/triggers, semantic button names, rumble,
|
|
174
174
|
* connect events, and per-controller mapping), with rebindable named actions.
|
|
175
175
|
* Call `update()` once per frame so `justPressed`/`justReleased` edge queries
|
|
176
|
-
* work for every device
|
|
176
|
+
* work for every device, including the gamepad.
|
|
177
177
|
*
|
|
178
178
|
* Gamepad tokens (usable anywhere a key token is, including in mapAction and
|
|
179
|
-
* justPressed)
|
|
179
|
+
* justPressed); `<i>` is the pad index:
|
|
180
180
|
* "pad:<i>:south" / "pad:<i>:a" - face buttons (also east/b, west/x, north/y)
|
|
181
181
|
* "pad:<i>:l1" / "pad:<i>:r2" ... - shoulders / triggers
|
|
182
182
|
* "pad:<i>:start" / "pad:<i>:select" - center buttons
|
|
@@ -189,6 +189,11 @@ for (const b of BUILTIN_MAPPINGS) {
|
|
|
189
189
|
* Names resolve through the active mapping, so "pad:0:south" is the bottom face
|
|
190
190
|
* button regardless of whether the pad reports Xbox or PlayStation ordering.
|
|
191
191
|
*
|
|
192
|
+
* Typing these by hand means remembering the exact spelling of every button
|
|
193
|
+
* name above; {@link Gamepad} builds the same strings from named constants
|
|
194
|
+
* instead (`Gamepad.get(0).key(Gamepad.SOUTH)` === "pad:0:south"), which most
|
|
195
|
+
* editors will autocomplete the way an enum's members would.
|
|
196
|
+
*
|
|
192
197
|
* @example
|
|
193
198
|
* const input = new InputManager();
|
|
194
199
|
* input.mapAction("jump", ["Space", " ", "pad:0:south"]);
|
|
@@ -245,15 +250,28 @@ class InputManager {
|
|
|
245
250
|
this._disconnectHandlers = [];
|
|
246
251
|
/** @private */
|
|
247
252
|
this._connected = new Map();
|
|
253
|
+
/**
|
|
254
|
+
* Which device last produced real input: "keyboard", "mouse",
|
|
255
|
+
* "gamepad", "touch", or null before anything has happened yet. See
|
|
256
|
+
* {@link getLastActiveDevice}.
|
|
257
|
+
* @private
|
|
258
|
+
*/
|
|
259
|
+
this._lastActiveDevice = null;
|
|
260
|
+
/** Pending {@link identifyButton} calls, resolved from `update()`. @private */
|
|
261
|
+
this._buttonWaiters = [];
|
|
248
262
|
|
|
249
263
|
/** @private */
|
|
250
|
-
this._onKeyDown = (e) =>
|
|
264
|
+
this._onKeyDown = (e) => {
|
|
265
|
+
this.down.add(this._normKey(e.key));
|
|
266
|
+
this._lastActiveDevice = "keyboard";
|
|
267
|
+
};
|
|
251
268
|
/** @private */
|
|
252
269
|
this._onKeyUp = (e) => this.down.delete(this._normKey(e.key));
|
|
253
270
|
/** @private */
|
|
254
271
|
this._onMouseDown = (e) => {
|
|
255
272
|
this.down.add(`mouse:${e.button}`);
|
|
256
273
|
this.mouse.buttons.add(e.button);
|
|
274
|
+
this._lastActiveDevice = "mouse";
|
|
257
275
|
};
|
|
258
276
|
/** @private */
|
|
259
277
|
this._onMouseUp = (e) => {
|
|
@@ -272,6 +290,7 @@ class InputManager {
|
|
|
272
290
|
y: t.clientY,
|
|
273
291
|
id: t.identifier,
|
|
274
292
|
}));
|
|
293
|
+
this._lastActiveDevice = "touch";
|
|
275
294
|
};
|
|
276
295
|
/** @private */
|
|
277
296
|
this._onGamepadConnected = (e) => {
|
|
@@ -636,6 +655,30 @@ class InputManager {
|
|
|
636
655
|
return !!this.gamepads[padIndex];
|
|
637
656
|
}
|
|
638
657
|
|
|
658
|
+
/**
|
|
659
|
+
* @method getLastActiveDevice
|
|
660
|
+
* @description Which device the player most recently *actually used*,
|
|
661
|
+
* not just "is a gamepad plugged in" (see `isGamepadConnected`, which stays
|
|
662
|
+
* true all game long once one is), but "did they just press a key, click,
|
|
663
|
+
* touch, or move a gamepad button/stick". A pad being connected doesn't
|
|
664
|
+
* mean it's what's driving the game right now; this is the signal for
|
|
665
|
+
* swapping on-screen prompts between keyboard and gamepad button icons as
|
|
666
|
+
* the player actually switches between them mid-session.
|
|
667
|
+
*
|
|
668
|
+
* Only counts deliberate input: keydown, mousedown, and touch, not
|
|
669
|
+
* incidental mouse movement, so idly nudging the mouse while playing on a
|
|
670
|
+
* pad won't flip prompts back to keyboard/mouse.
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* // once per frame, after input.update():
|
|
674
|
+
* hud.setPromptStyle(input.getLastActiveDevice() === "gamepad" ? "pad" : "keyboard");
|
|
675
|
+
*
|
|
676
|
+
* @returns {"keyboard"|"mouse"|"gamepad"|"touch"|null} - null before any input at all
|
|
677
|
+
*/
|
|
678
|
+
getLastActiveDevice() {
|
|
679
|
+
return this._lastActiveDevice;
|
|
680
|
+
}
|
|
681
|
+
|
|
639
682
|
/**
|
|
640
683
|
* @method getPressedButtons
|
|
641
684
|
* @description Diagnostic: raw indices of all currently pressed buttons on a
|
|
@@ -655,6 +698,71 @@ class InputManager {
|
|
|
655
698
|
return out;
|
|
656
699
|
}
|
|
657
700
|
|
|
701
|
+
/**
|
|
702
|
+
* @method identifyButton
|
|
703
|
+
* @description Resolves with the raw index of the next *new* button press
|
|
704
|
+
* on a pad: the reliable way to support a controller whose layout isn't
|
|
705
|
+
* already covered by the standard/registered mapping tables (a Steam
|
|
706
|
+
* Controller running outside Steam Input, an old flight stick, anything
|
|
707
|
+
* with a scrambled button order), instead of guessing indices: ask for one
|
|
708
|
+
* physical press and record wherever it actually lands on that specific
|
|
709
|
+
* device. Keep calling `update()` as normal while waiting; the promise
|
|
710
|
+
* resolves on the frame a button transitions from up to down. A button
|
|
711
|
+
* already held when this is called doesn't count; only a fresh press does.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* console.log("Press the button you want for Jump…");
|
|
715
|
+
* const index = await input.identifyButton(0);
|
|
716
|
+
* InputManager.registerGamepadMapping(input.getGamepadInfo(0).id, {
|
|
717
|
+
* buttons: { south: index },
|
|
718
|
+
* });
|
|
719
|
+
*
|
|
720
|
+
* @param {number} [padIndex=0]
|
|
721
|
+
* @returns {Promise<number>} - Never resolves if no new button is ever pressed
|
|
722
|
+
*/
|
|
723
|
+
identifyButton(padIndex = 0) {
|
|
724
|
+
return new Promise((resolve) => {
|
|
725
|
+
this._buttonWaiters.push({
|
|
726
|
+
padIndex,
|
|
727
|
+
before: new Set(this.getPressedButtons(padIndex)),
|
|
728
|
+
resolve,
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* @method calibrateGamepad
|
|
735
|
+
* @description Walks a list of semantic button names one at a time, asking
|
|
736
|
+
* for a physical press for each (see {@link identifyButton}), and resolves
|
|
737
|
+
* with a `{name: rawIndex}` map ready to hand straight to
|
|
738
|
+
* `InputManager.registerGamepadMapping`, a complete fix for an oddball
|
|
739
|
+
* controller in a few seconds, without knowing anything about its layout
|
|
740
|
+
* in advance.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* const mapping = await input.calibrateGamepad(
|
|
744
|
+
* 0,
|
|
745
|
+
* ["south", "east", "west", "north", "l1", "r1", "start"],
|
|
746
|
+
* (name, i, total) => showPrompt(`(${i + 1}/${total}) Press the button for "${name}"`)
|
|
747
|
+
* );
|
|
748
|
+
* InputManager.registerGamepadMapping(input.getGamepadInfo(0).id, { buttons: mapping });
|
|
749
|
+
*
|
|
750
|
+
* @param {number} padIndex
|
|
751
|
+
* @param {string[]} names - Semantic names to calibrate, e.g. ["south", "east", "start"]
|
|
752
|
+
* @param {(name: string, index: number, total: number) => void} [onPrompt] -
|
|
753
|
+
* Called right before waiting for each name, so you can show "press ___" UI
|
|
754
|
+
* @returns {Promise<Object.<string, number>>} - { [name]: rawButtonIndex }
|
|
755
|
+
*/
|
|
756
|
+
async calibrateGamepad(padIndex, names, onPrompt) {
|
|
757
|
+
const mapping = {};
|
|
758
|
+
for (let i = 0; i < names.length; i++) {
|
|
759
|
+
const name = names[i];
|
|
760
|
+
if (onPrompt) onPrompt(name, i, names.length);
|
|
761
|
+
mapping[name] = await this.identifyButton(padIndex);
|
|
762
|
+
}
|
|
763
|
+
return mapping;
|
|
764
|
+
}
|
|
765
|
+
|
|
658
766
|
/**
|
|
659
767
|
* @method _decodeHat
|
|
660
768
|
* @description Some non-standard pads report the d-pad as an 8-way "hat" on a
|
|
@@ -749,6 +857,24 @@ class InputManager {
|
|
|
749
857
|
for (const t of this._gamepadTokens) this.down.delete(t);
|
|
750
858
|
this._collectGamepadTokens();
|
|
751
859
|
for (const t of this._gamepadTokens) this.down.add(t);
|
|
860
|
+
if (this._gamepadTokens.size > 0) this._lastActiveDevice = "gamepad";
|
|
861
|
+
if (this._buttonWaiters.length) this._resolveButtonWaiters();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* @method _resolveButtonWaiters
|
|
866
|
+
* @description Resolves any {@link identifyButton} calls whose pad just
|
|
867
|
+
* saw a button go from up to down.
|
|
868
|
+
* @private
|
|
869
|
+
*/
|
|
870
|
+
_resolveButtonWaiters() {
|
|
871
|
+
this._buttonWaiters = this._buttonWaiters.filter((waiter) => {
|
|
872
|
+
const now = this.getPressedButtons(waiter.padIndex);
|
|
873
|
+
const fresh = now.find((b) => !waiter.before.has(b));
|
|
874
|
+
if (fresh == null) return true;
|
|
875
|
+
waiter.resolve(fresh);
|
|
876
|
+
return false;
|
|
877
|
+
});
|
|
752
878
|
}
|
|
753
879
|
|
|
754
880
|
/**
|
|
@@ -4,7 +4,7 @@ import Interpolator from "../Interpolator.js";
|
|
|
4
4
|
* @class NetworkManager
|
|
5
5
|
* @description A thin, optional multiplayer layer over Colyseus. `colyseus.js`
|
|
6
6
|
* is an optional peer dependency and is imported dynamically, so the engine has
|
|
7
|
-
* no hard dependency on it
|
|
7
|
+
* no hard dependency on it, so games that don't use networking never load it.
|
|
8
8
|
*
|
|
9
9
|
* It wraps connection/room lifecycle, exposes a small event API
|
|
10
10
|
* (onStateChange / onMessage / onAdd / onRemove / onLeave), and bundles an
|
|
@@ -140,7 +140,7 @@ class NetworkManager {
|
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* @method now
|
|
143
|
-
* @description Seconds since this manager was created
|
|
143
|
+
* @description Seconds since this manager was created, a convenient clock for
|
|
144
144
|
* feeding the interpolator.
|
|
145
145
|
* @returns {number}
|
|
146
146
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Frame-level render counters, incremented by every draw site in
|
|
4
4
|
* the engine (Drawable, InstancedTexture, SpriteBatch, post-processing) and
|
|
5
5
|
* reset at the start of each drawScene. Read the previous completed frame via
|
|
6
|
-
* `RenderStats.frame` or `emerald.getRenderStats()
|
|
6
|
+
* `RenderStats.frame` or `emerald.getRenderStats()`. DebugOverlay shows it
|
|
7
7
|
* automatically.
|
|
8
8
|
*
|
|
9
9
|
* - drawCalls: GPU draw commands issued (the batching win shows up here)
|
|
@@ -102,7 +102,7 @@ class TextureManager {
|
|
|
102
102
|
* @method restoreAll
|
|
103
103
|
* @description Re-uploads every cached texture after a WebGL context loss.
|
|
104
104
|
* The image cache survives the loss, so this is upload-only (no network).
|
|
105
|
-
* Reference counts are untouched
|
|
105
|
+
* Reference counts are untouched: they track logical ownership by
|
|
106
106
|
* drawables, which still exist.
|
|
107
107
|
*/
|
|
108
108
|
static restoreAll() {
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Vec2 } from "./Math2D.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class AABB
|
|
5
|
+
* @description An axis-aligned bounding box in physics space. Used by the
|
|
6
|
+
* broadphase to reject pairs long before any real collision maths runs.
|
|
7
|
+
*/
|
|
8
|
+
class AABB {
|
|
9
|
+
constructor(lowerX = 0, lowerY = 0, upperX = 0, upperY = 0) {
|
|
10
|
+
this.lowerBound = new Vec2(lowerX, lowerY);
|
|
11
|
+
this.upperBound = new Vec2(upperX, upperY);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @method set
|
|
16
|
+
* @description Sets all four bounds in place.
|
|
17
|
+
* @param {number} lx
|
|
18
|
+
* @param {number} ly
|
|
19
|
+
* @param {number} ux
|
|
20
|
+
* @param {number} uy
|
|
21
|
+
* @returns {AABB} - this
|
|
22
|
+
*/
|
|
23
|
+
set(lx, ly, ux, uy) {
|
|
24
|
+
this.lowerBound.set(lx, ly);
|
|
25
|
+
this.upperBound.set(ux, uy);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @method copy
|
|
31
|
+
* @description Copies another AABB.
|
|
32
|
+
* @param {AABB} other
|
|
33
|
+
* @returns {AABB} - this
|
|
34
|
+
*/
|
|
35
|
+
copy(other) {
|
|
36
|
+
this.lowerBound.copy(other.lowerBound);
|
|
37
|
+
this.upperBound.copy(other.upperBound);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @method getCenter
|
|
43
|
+
* @description Returns the box center.
|
|
44
|
+
* @returns {Vec2}
|
|
45
|
+
*/
|
|
46
|
+
getCenter() {
|
|
47
|
+
return new Vec2(
|
|
48
|
+
0.5 * (this.lowerBound.x + this.upperBound.x),
|
|
49
|
+
0.5 * (this.lowerBound.y + this.upperBound.y)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @method getExtents
|
|
55
|
+
* @description Returns the box half-extents.
|
|
56
|
+
* @returns {Vec2}
|
|
57
|
+
*/
|
|
58
|
+
getExtents() {
|
|
59
|
+
return new Vec2(
|
|
60
|
+
0.5 * (this.upperBound.x - this.lowerBound.x),
|
|
61
|
+
0.5 * (this.upperBound.y - this.lowerBound.y)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @method getPerimeter
|
|
67
|
+
* @description Perimeter of the box, the cost function the AABB tree
|
|
68
|
+
* minimizes when choosing where to insert a leaf.
|
|
69
|
+
* @returns {number}
|
|
70
|
+
*/
|
|
71
|
+
getPerimeter() {
|
|
72
|
+
return (
|
|
73
|
+
2 *
|
|
74
|
+
(this.upperBound.x -
|
|
75
|
+
this.lowerBound.x +
|
|
76
|
+
(this.upperBound.y - this.lowerBound.y))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @method combine
|
|
82
|
+
* @description Sets this box to the union of two others.
|
|
83
|
+
* @param {AABB} a
|
|
84
|
+
* @param {AABB} b
|
|
85
|
+
* @returns {AABB} - this
|
|
86
|
+
*/
|
|
87
|
+
combine(a, b) {
|
|
88
|
+
this.lowerBound.x = Math.min(a.lowerBound.x, b.lowerBound.x);
|
|
89
|
+
this.lowerBound.y = Math.min(a.lowerBound.y, b.lowerBound.y);
|
|
90
|
+
this.upperBound.x = Math.max(a.upperBound.x, b.upperBound.x);
|
|
91
|
+
this.upperBound.y = Math.max(a.upperBound.y, b.upperBound.y);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @method extend
|
|
97
|
+
* @description Grows the box by `amount` on every side.
|
|
98
|
+
* @param {number} amount
|
|
99
|
+
* @returns {AABB} - this
|
|
100
|
+
*/
|
|
101
|
+
extend(amount) {
|
|
102
|
+
this.lowerBound.x -= amount;
|
|
103
|
+
this.lowerBound.y -= amount;
|
|
104
|
+
this.upperBound.x += amount;
|
|
105
|
+
this.upperBound.y += amount;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @method contains
|
|
111
|
+
* @description True when `other` lies entirely inside this box.
|
|
112
|
+
* @param {AABB} other
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
contains(other) {
|
|
116
|
+
return (
|
|
117
|
+
this.lowerBound.x <= other.lowerBound.x &&
|
|
118
|
+
this.lowerBound.y <= other.lowerBound.y &&
|
|
119
|
+
other.upperBound.x <= this.upperBound.x &&
|
|
120
|
+
other.upperBound.y <= this.upperBound.y
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @method containsPoint
|
|
126
|
+
* @description True when a point lies inside this box.
|
|
127
|
+
* @param {Object} p - `{ x, y }`
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
containsPoint(p) {
|
|
131
|
+
return (
|
|
132
|
+
this.lowerBound.x <= p.x &&
|
|
133
|
+
p.x <= this.upperBound.x &&
|
|
134
|
+
this.lowerBound.y <= p.y &&
|
|
135
|
+
p.y <= this.upperBound.y
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @method testOverlap
|
|
141
|
+
* @description True when two boxes overlap.
|
|
142
|
+
* @param {AABB} a
|
|
143
|
+
* @param {AABB} b
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
static testOverlap(a, b) {
|
|
147
|
+
return !(
|
|
148
|
+
b.lowerBound.x - a.upperBound.x > 0 ||
|
|
149
|
+
b.lowerBound.y - a.upperBound.y > 0 ||
|
|
150
|
+
a.lowerBound.x - b.upperBound.x > 0 ||
|
|
151
|
+
a.lowerBound.y - b.upperBound.y > 0
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @method rayCast
|
|
157
|
+
* @description Slab test of a ray against this box.
|
|
158
|
+
* @param {Object} input - `{ p1, p2, maxFraction }`
|
|
159
|
+
* @returns {boolean} - True if the ray hits the box within maxFraction
|
|
160
|
+
*/
|
|
161
|
+
rayCast(input) {
|
|
162
|
+
let tmin = -Number.MAX_VALUE;
|
|
163
|
+
let tmax = Number.MAX_VALUE;
|
|
164
|
+
|
|
165
|
+
const p = input.p1;
|
|
166
|
+
const dx = input.p2.x - input.p1.x;
|
|
167
|
+
const dy = input.p2.y - input.p1.y;
|
|
168
|
+
const absDx = Math.abs(dx);
|
|
169
|
+
const absDy = Math.abs(dy);
|
|
170
|
+
|
|
171
|
+
if (absDx < 1e-12) {
|
|
172
|
+
if (p.x < this.lowerBound.x || this.upperBound.x < p.x) return false;
|
|
173
|
+
} else {
|
|
174
|
+
const invD = 1 / dx;
|
|
175
|
+
let t1 = (this.lowerBound.x - p.x) * invD;
|
|
176
|
+
let t2 = (this.upperBound.x - p.x) * invD;
|
|
177
|
+
if (t1 > t2) {
|
|
178
|
+
const t = t1;
|
|
179
|
+
t1 = t2;
|
|
180
|
+
t2 = t;
|
|
181
|
+
}
|
|
182
|
+
tmin = Math.max(tmin, t1);
|
|
183
|
+
tmax = Math.min(tmax, t2);
|
|
184
|
+
if (tmin > tmax) return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (absDy < 1e-12) {
|
|
188
|
+
if (p.y < this.lowerBound.y || this.upperBound.y < p.y) return false;
|
|
189
|
+
} else {
|
|
190
|
+
const invD = 1 / dy;
|
|
191
|
+
let t1 = (this.lowerBound.y - p.y) * invD;
|
|
192
|
+
let t2 = (this.upperBound.y - p.y) * invD;
|
|
193
|
+
if (t1 > t2) {
|
|
194
|
+
const t = t1;
|
|
195
|
+
t1 = t2;
|
|
196
|
+
t2 = t;
|
|
197
|
+
}
|
|
198
|
+
tmin = Math.max(tmin, t1);
|
|
199
|
+
tmax = Math.min(tmax, t2);
|
|
200
|
+
if (tmin > tmax) return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return tmax >= 0 && tmin <= (input.maxFraction ?? 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default AABB;
|