@gg-web-engine/core 0.0.49 → 0.0.57

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 (53) hide show
  1. package/dist/2d/entities/entity-2d.d.ts +6 -3
  2. package/dist/2d/entities/entity-2d.js +20 -17
  3. package/dist/2d/gg-2d-world.d.ts +3 -0
  4. package/dist/2d/gg-2d-world.js +21 -13
  5. package/dist/2d/models/body-options.d.ts +4 -4
  6. package/dist/2d/models/body-options.js +6 -1
  7. package/dist/3d/entities/controllers/input/free-camera.controller.d.ts +9 -1
  8. package/dist/3d/entities/controllers/input/free-camera.controller.js +45 -12
  9. package/dist/3d/entities/controllers/input/orbit-camera.controller.d.ts +9 -8
  10. package/dist/3d/entities/controllers/input/orbit-camera.controller.js +46 -32
  11. package/dist/3d/entities/entity-3d.d.ts +6 -3
  12. package/dist/3d/entities/entity-3d.js +20 -17
  13. package/dist/3d/entities/raycast-vehicle-3d.entity.js +5 -5
  14. package/dist/3d/entities/surface-following.entity.d.ts +96 -0
  15. package/dist/3d/entities/surface-following.entity.js +296 -0
  16. package/dist/3d/gg-3d-world.d.ts +3 -0
  17. package/dist/3d/gg-3d-world.js +21 -13
  18. package/dist/3d/index.d.ts +1 -0
  19. package/dist/3d/index.js +1 -0
  20. package/dist/3d/loader.js +1 -1
  21. package/dist/3d/models/body-options.d.ts +4 -4
  22. package/dist/3d/models/body-options.js +6 -1
  23. package/dist/base/clock/global-clock.d.ts +2 -6
  24. package/dist/base/clock/global-clock.js +16 -16
  25. package/dist/base/clock/i-clock.d.ts +14 -5
  26. package/dist/base/clock/i-clock.js +43 -1
  27. package/dist/base/clock/pausable-clock.d.ts +3 -13
  28. package/dist/base/clock/pausable-clock.js +8 -16
  29. package/dist/base/components/physics/i-body.component.d.ts +5 -5
  30. package/dist/base/components/physics/i-physics-world.component.d.ts +43 -5
  31. package/dist/base/data-structures/bitmask.d.ts +5 -4
  32. package/dist/base/data-structures/bitmask.js +4 -3
  33. package/dist/base/gg-world.d.ts +8 -1
  34. package/dist/base/gg-world.js +102 -65
  35. package/dist/base/inputs/keyboard.input.d.ts +1 -1
  36. package/dist/base/inputs/keyboard.input.js +10 -10
  37. package/dist/base/inputs/mouse.input.js +3 -2
  38. package/dist/base/math/point2.d.ts +2 -0
  39. package/dist/base/math/point2.js +6 -3
  40. package/dist/base/math/point3.d.ts +2 -0
  41. package/dist/base/math/point3.js +6 -3
  42. package/dist/base/models/body-options.d.ts +27 -9
  43. package/dist/base/models/body-options.js +65 -1
  44. package/dist/dev/gg-console.ui.js +14 -4
  45. package/dist/dev/gg-debugger.ui.d.ts +2 -2
  46. package/dist/dev/gg-debugger.ui.js +15 -20
  47. package/dist/dev/gg-static.d.ts +6 -1
  48. package/dist/dev/gg-static.js +104 -25
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +3 -0
  51. package/dist/version.d.ts +1 -0
  52. package/dist/version.js +1 -0
  53. package/package.json +4 -3
@@ -1,16 +1,11 @@
1
1
  import { filter, Subject } from 'rxjs';
2
2
  import { map, tap } from 'rxjs/operators';
3
3
  import { GgGlobalClock } from './global-clock';
4
+ import { IClock } from './i-clock';
4
5
  /**
5
6
  * A class providing the ability to track time, fire ticks, provide time elapsed, and tick delta with the ability to suspend/resume it.
6
7
  */
7
- export class PausableClock {
8
- /**
9
- * Observable stream of ticks, emitting an array containing the current time and the tick delta.
10
- */
11
- get tick$() {
12
- return this._tick$.asObservable();
13
- }
8
+ export class PausableClock extends IClock {
14
9
  /**
15
10
  * Checks if the clock is currently running.
16
11
  */
@@ -76,10 +71,10 @@ export class PausableClock {
76
71
  * @param parentClock The parent clock to synchronize with. Defaults to GgGlobalClock instance.
77
72
  */
78
73
  constructor(autoStart = false, parentClock = GgGlobalClock.instance) {
74
+ super(parentClock);
79
75
  this.parentClock = parentClock;
80
76
  this.tickSub = null;
81
77
  this._internalTick$ = new Subject();
82
- this._tick$ = new Subject();
83
78
  /**
84
79
  * Tick rate limiter. If set to 0 - tick rate is unlimited, 15 means "allow at most 15 ticks per second"
85
80
  */
@@ -103,14 +98,6 @@ export class PausableClock {
103
98
  Math.floor((elapsed * this.tickRateLimit) / 1000)), tap(([elapsed]) => (this.lastFiredTickElapsed = elapsed)))
104
99
  .subscribe(this._tick$);
105
100
  }
106
- /**
107
- * Creates a child clock.
108
- * @param autoStart Indicates whether the child clock should start automatically.
109
- * @returns A new instance of PausableClock.
110
- */
111
- createChildClock(autoStart) {
112
- return new PausableClock(autoStart, this);
113
- }
114
101
  /**
115
102
  * Starts the clock.
116
103
  */
@@ -170,4 +157,9 @@ export class PausableClock {
170
157
  (_a = this.tickSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
171
158
  this.tickSub = null;
172
159
  }
160
+ dispose() {
161
+ this.stopListeningTicks();
162
+ this._internalTick$.complete();
163
+ super.dispose();
164
+ }
173
165
  }
@@ -7,12 +7,12 @@ export interface IBodyComponent<D, R, TypeDoc extends PhysicsTypeDocRepo<D, R> =
7
7
  position: D;
8
8
  rotation: R;
9
9
  name: string;
10
- get ownCollisionGroups(): CollisionGroup[];
11
- set ownCollisionGroups(value: CollisionGroup[] | 'all');
12
- get interactWithCollisionGroups(): CollisionGroup[];
13
- set interactWithCollisionGroups(value: CollisionGroup[] | 'all');
10
+ get ownCollisionGroups(): ReadonlyArray<CollisionGroup>;
11
+ set ownCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
12
+ get interactWithCollisionGroups(): ReadonlyArray<CollisionGroup>;
13
+ set interactWithCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
14
14
  /** body info for physics debugger view */
15
- readonly debugBodySettings: DebugBodySettings;
15
+ readonly debugBodySettings: DebugBodySettings<any>;
16
16
  clone(): IBodyComponent<D, R, TypeDoc>;
17
17
  addToWorld(world: GgWorld<D, R, VisualTypeDocRepo<D, R>, TypeDoc>): void;
18
18
  removeFromWorld(world: GgWorld<D, R, VisualTypeDocRepo<D, R>, TypeDoc>): void;
@@ -2,22 +2,60 @@ import { PhysicsTypeDocRepo } from '../../gg-world';
2
2
  import { IComponent } from '../i-component';
3
3
  import { CollisionGroup } from '../../models/body-options';
4
4
  import { Subject } from 'rxjs';
5
+ /**
6
+ * Interface representing a physics world component.
7
+ *
8
+ * @template D - Data type used for representing physics properties.
9
+ * @template R - Type representing the physics engine's rigid body.
10
+ * @template TypeDoc - Physics typings repository.
11
+ */
5
12
  export interface IPhysicsWorldComponent<D, R, TypeDoc extends PhysicsTypeDocRepo<D, R> = PhysicsTypeDocRepo<D, R>> extends IComponent {
13
+ /**
14
+ * Factory function for creating physics-related objects.
15
+ */
6
16
  readonly factory: TypeDoc['factory'];
17
+ /**
18
+ * The gravity vector affecting the physics world.
19
+ */
7
20
  gravity: D;
8
- /** event emitter, emits newly added physics components */
21
+ /**
22
+ * Event emitter that emits newly added physics components.
23
+ */
9
24
  readonly added$: Subject<TypeDoc['rigidBody'] | TypeDoc['trigger'] | any>;
10
- /** event emitter, emits just removed physics components */
25
+ /**
26
+ * Event emitter that emits just removed physics components.
27
+ */
11
28
  readonly removed$: Subject<TypeDoc['rigidBody'] | TypeDoc['trigger'] | any>;
12
- /** list of currently added to world physics components */
29
+ /**
30
+ * List of currently added physics components in the world.
31
+ */
13
32
  readonly children: (TypeDoc['rigidBody'] | TypeDoc['trigger'] | any)[];
33
+ /**
34
+ * The main collision group. All physics bodies have this collision group set by default.
35
+ */
36
+ readonly mainCollisionGroup: CollisionGroup;
37
+ /**
38
+ * Initializes the physics world component.
39
+ *
40
+ * @returns A promise that resolves when initialization is complete.
41
+ */
14
42
  init(): Promise<void>;
15
43
  /**
16
- * Runs simulation of the physics world.
44
+ * Runs the simulation of the physics world for the given time step.
17
45
  *
18
- * @param delta delta time from last tick in milliseconds.
46
+ * @param delta - The time step in milliseconds since the last update.
19
47
  */
20
48
  simulate(delta: number): void;
49
+ /**
50
+ * Registers and returns a new collision group.
51
+ *
52
+ * @returns A newly registered collision group.
53
+ */
21
54
  registerCollisionGroup(): CollisionGroup;
55
+ /**
56
+ * Deregisters a previously registered collision group.
57
+ *
58
+ * @param group - The collision group to be removed.
59
+ */
22
60
  deregisterCollisionGroup(group: CollisionGroup): void;
23
61
  }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * BitMask class provides static methods to manipulate bits in a number.
3
- */ /**
4
- * BitMask class provides static methods to manipulate bits in a number.
5
- */
3
+ */
4
+ /**
5
+ * BitMask class provides static methods to manipulate bits in a number.
6
+ */
6
7
  export declare class BitMask {
7
8
  /**
8
9
  * Generates a bitmask with all bits set to 1 upto the provided bit count
@@ -20,7 +21,7 @@ export declare class BitMask {
20
21
  * @param bits - The maximum bit index that can be used
21
22
  * @throws Will throw an Error if a bit index in `value` is larger than `bits`
22
23
  */
23
- static pack(value: number[], bits: number): number;
24
+ static pack(value: number[] | ReadonlyArray<number>, bits: number): number;
24
25
  /**
25
26
  * Unpacks a bitmask into an array of bit indices
26
27
  * @param mask - The bit mask to be unpacked
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * BitMask class provides static methods to manipulate bits in a number.
3
- */ /**
4
- * BitMask class provides static methods to manipulate bits in a number.
5
- */
3
+ */
4
+ /**
5
+ * BitMask class provides static methods to manipulate bits in a number.
6
+ */
6
7
  export class BitMask {
7
8
  /**
8
9
  * Generates a bitmask with all bits set to 1 upto the provided bit count
@@ -1,4 +1,4 @@
1
- import { IDisplayObjectComponent, IEntity, IPhysicsWorldComponent, IPositionable, IRenderableEntity, IRendererComponent, IRigidBodyComponent, ITriggerComponent, IVisualSceneComponent, KeyboardInput, PausableClock } from '../base';
1
+ import { IDisplayObjectComponent, IEntity, IPhysicsWorldComponent, IPositionable, IRenderableEntity, IRendererComponent, IRendererEntity, IRigidBodyComponent, ITriggerComponent, IVisualSceneComponent, KeyboardInput, PausableClock } from '../base';
2
2
  import { Subject } from 'rxjs';
3
3
  export type VisualTypeDocRepo<D, R> = {
4
4
  factory: unknown;
@@ -16,16 +16,19 @@ export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D
16
16
  readonly physicsWorld: PW;
17
17
  private static default_name_counter;
18
18
  private static _documentWorlds;
19
+ static readonly worldCreated$: Subject<GgWorld<any, any>>;
19
20
  static get documentWorlds(): GgWorld<any, any>[];
20
21
  readonly worldClock: PausableClock;
21
22
  readonly keyboardInput: KeyboardInput;
22
23
  name: string;
23
24
  readonly children: IEntity[];
24
25
  protected readonly tickListeners: IEntity[];
26
+ get renderers(): IRendererEntity<D, R>[];
25
27
  readonly tickStarted$: Subject<void>;
26
28
  readonly tickForwardTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
27
29
  readonly tickForwardedTo$: Subject<IEntity | 'PHYSICS_WORLD'>;
28
30
  readonly paused$: Subject<boolean>;
31
+ readonly disposed$: Subject<void>;
29
32
  protected constructor(visualScene: VS, physicsWorld: PW);
30
33
  init(): Promise<void>;
31
34
  start(): void;
@@ -40,4 +43,8 @@ export declare abstract class GgWorld<D, R, VTypeDoc extends VisualTypeDocRepo<D
40
43
  position?: D, rotation?: R, material?: unknown): IPositionable<D, R> & IRenderableEntity<D, R, VTypeDoc>;
41
44
  addEntity(entity: IEntity): void;
42
45
  removeEntity(entity: IEntity, dispose?: boolean): void;
46
+ private onGgStaticInitialized;
47
+ protected registerConsoleCommands(ggstatic: {
48
+ registerConsoleCommand: (world: GgWorld<any, any> | null, command: string, handler: (...args: string[]) => Promise<string>, doc?: string) => void;
49
+ }): void;
43
50
  }
@@ -7,17 +7,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { GgGlobalClock, IRendererEntity, KeyboardInput, TickOrder, } from '../base';
10
+ import { IRendererEntity, KeyboardInput, PausableClock, TickOrder, } from '../base';
11
11
  import { lastValueFrom, Subject, take } from 'rxjs';
12
12
  import { PerformanceMeterEntity } from '../dev';
13
13
  export class GgWorld {
14
14
  static get documentWorlds() {
15
15
  return [...GgWorld._documentWorlds];
16
16
  }
17
+ get renderers() {
18
+ return this.tickListeners.filter(e => e instanceof IRendererEntity);
19
+ }
17
20
  constructor(visualScene, physicsWorld) {
18
21
  this.visualScene = visualScene;
19
22
  this.physicsWorld = physicsWorld;
20
- this.worldClock = GgGlobalClock.instance.createChildClock(false);
23
+ this.worldClock = new PausableClock(false);
21
24
  this.keyboardInput = new KeyboardInput();
22
25
  this.name = 'w0x' + (GgWorld.default_name_counter++).toString(16);
23
26
  this.children = [];
@@ -28,72 +31,18 @@ export class GgWorld {
28
31
  this.tickForwardTo$ = new Subject();
29
32
  this.tickForwardedTo$ = new Subject();
30
33
  this.paused$ = new Subject();
31
- GgWorld._documentWorlds.push(this);
34
+ this.disposed$ = new Subject();
32
35
  this.keyboardInput.start();
33
36
  if (window.ggstatic) {
34
- window.ggstatic.registerConsoleCommand(this, 'ph_timescale', (...args) => __awaiter(this, void 0, void 0, function* () {
35
- this.worldClock.timeScale = +args[0];
36
- return JSON.stringify(this.worldClock.timeScale);
37
- }), 'args: [float]; change time scale of physics engine. Default value is 1.0');
38
- window.ggstatic.registerConsoleCommand(this, 'ls_renderers', () => __awaiter(this, void 0, void 0, function* () {
39
- return this.children
40
- .filter(e => e instanceof IRendererEntity)
41
- .map(r => r.name)
42
- .join('\n');
43
- }), 'no args; print all renderers in selected world');
44
- window.ggstatic.registerConsoleCommand(this, 'dr_drawphysics', (...args) => __awaiter(this, void 0, void 0, function* () {
45
- const value = ['1', 'true', '+'].includes(args[0]);
46
- const rendererName = args[1];
47
- let renderer;
48
- if (rendererName) {
49
- renderer = this.children.find(x => x instanceof IRendererEntity && x.name === rendererName);
50
- }
51
- else {
52
- renderer = this.children.find(x => x instanceof IRendererEntity);
53
- }
54
- if (renderer) {
55
- renderer.physicsDebugViewActive = value;
56
- return '' + value;
57
- }
58
- return 'false';
59
- }), 'args: [0 or 1] or [0 or 1, string]; turn on/off physics debug view. Second argument expects renderer ' +
60
- 'name, if not provided first renderer will be picked. Look up for renderer names using command "ls_renderers"');
61
- window.ggstatic.registerConsoleCommand(this, 'performance_report', (...args) => __awaiter(this, void 0, void 0, function* () {
62
- let mode = 'avg';
63
- let samples = 20;
64
- for (let i = 0; i < 2; i++) {
65
- if (['avg', 'peak'].includes(args[i])) {
66
- mode = args[i];
67
- }
68
- else if (!isNaN(+args[i])) {
69
- samples = +args[i];
70
- }
71
- }
72
- const meter = new PerformanceMeterEntity(samples, 250);
73
- this.addEntity(meter);
74
- yield lastValueFrom(this.worldClock.tick$.pipe(take(samples)));
75
- const report = mode === 'avg' ? meter.avgReport : meter.peakReport;
76
- this.removeEntity(meter);
77
- const renderItems = report.entries.map(([name, value]) => `<span style='color:lightgray;'>${name}:</span>` +
78
- new Array(Math.max(0, 26 - name.length)).join('&nbsp;') +
79
- `${value.toFixed(2)} ms` +
80
- (mode === 'avg' ? ` (${((value * 100) / report.totalTime).toFixed(2)}%)` : ''));
81
- let totalColor = 'lightgreen';
82
- if (report.totalTime > 12) {
83
- totalColor = report.totalTime < 16 ? 'yellow' : 'red';
84
- }
85
- const title = `${mode === 'avg' ? 'Average' : 'Peak'} Frame time`;
86
- renderItems.unshift(title +
87
- ':' +
88
- new Array(Math.max(0, 26 - title.length)).join('&nbsp;') +
89
- `<span style='color:${totalColor};'>${report.totalTime.toFixed(2)} ms</span>`);
90
- renderItems.unshift(`Performance report (${samples} samples)`);
91
- return renderItems.join('\n');
92
- }), 'args: [int, avg|peak]; [avg|peak, int]; [avg|peak]; [int] or []; measure how much time was spent per ' +
93
- 'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
94
- 'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
37
+ this.registerConsoleCommands(window.ggstatic);
38
+ }
39
+ else {
40
+ this.onGgStaticInitialized = this.onGgStaticInitialized.bind(this);
41
+ window.addEventListener('ggstatic_added', this.onGgStaticInitialized);
95
42
  }
96
43
  this.worldClock.paused$.subscribe(this.paused$);
44
+ GgWorld._documentWorlds.push(this);
45
+ GgWorld.worldCreated$.next(this);
97
46
  }
98
47
  init() {
99
48
  return __awaiter(this, void 0, void 0, function* () {
@@ -145,12 +94,15 @@ export class GgWorld {
145
94
  return this.worldClock.elapsedTime;
146
95
  }
147
96
  createClock(autoStart) {
148
- return this.worldClock.createChildClock(autoStart);
97
+ return new PausableClock(autoStart, this.worldClock);
149
98
  }
150
99
  dispose() {
151
100
  if (window.ggstatic) {
152
101
  window.ggstatic.deregisterWorldCommands(this);
153
102
  }
103
+ else {
104
+ window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
105
+ }
154
106
  this.worldClock.stop();
155
107
  this.keyboardInput.stop();
156
108
  this.tickStarted$.complete();
@@ -164,6 +116,9 @@ export class GgWorld {
164
116
  this.tickListeners.splice(0, this.tickListeners.length);
165
117
  this.physicsWorld.dispose();
166
118
  this.visualScene.dispose();
119
+ GgWorld._documentWorlds.splice(GgWorld._documentWorlds.indexOf(this), 1);
120
+ this.disposed$.next();
121
+ this.disposed$.complete();
167
122
  }
168
123
  addEntity(entity) {
169
124
  if (!!entity.world) {
@@ -188,6 +143,88 @@ export class GgWorld {
188
143
  entity.dispose();
189
144
  }
190
145
  }
146
+ onGgStaticInitialized() {
147
+ window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
148
+ this.registerConsoleCommands(window.ggstatic);
149
+ }
150
+ registerConsoleCommands(ggstatic) {
151
+ ggstatic.registerConsoleCommand(this, 'timescale', (...args) => __awaiter(this, void 0, void 0, function* () {
152
+ if (!isNaN(+args[0])) {
153
+ this.worldClock.timeScale = +args[0];
154
+ }
155
+ return this.worldClock.timeScale.toString();
156
+ }), 'args: [ float? ]; Get current time scale of selected world clock or set it.' +
157
+ ' Default value is 1.0 (no time scale applied)');
158
+ ggstatic.registerConsoleCommand(this, 'fps_limit', (...args) => __awaiter(this, void 0, void 0, function* () {
159
+ if (!isNaN(+args[0])) {
160
+ this.worldClock.tickRateLimit = +args[0];
161
+ }
162
+ return this.worldClock.tickRateLimit.toString();
163
+ }), 'args: [ int? ]; Get current tick rate limit of selected world clock or set it. 0 means no limit applied');
164
+ ggstatic.registerConsoleCommand(this, 'renderers', () => __awaiter(this, void 0, void 0, function* () {
165
+ return this.renderers.map(r => r.name).join('\n');
166
+ }), 'no args; Print all renderers in selected world');
167
+ ggstatic.registerConsoleCommand(this, 'debug_view', (...args) => __awaiter(this, void 0, void 0, function* () {
168
+ let value = 'toggle';
169
+ let rendererName = undefined;
170
+ for (let arg of args) {
171
+ if (['1', '0'].includes(arg)) {
172
+ value = arg === '1';
173
+ }
174
+ else {
175
+ rendererName = arg;
176
+ }
177
+ }
178
+ let renderer = rendererName ? this.renderers.find(x => x.name === rendererName) : this.renderers[0];
179
+ if (renderer) {
180
+ renderer.physicsDebugViewActive = value === 'toggle' ? !renderer.physicsDebugViewActive : value;
181
+ return renderer.physicsDebugViewActive ? '1' : '0';
182
+ }
183
+ else if (rendererName) {
184
+ throw new Error(`Renderer with name "${rendererName}" not found`);
185
+ }
186
+ else {
187
+ throw new Error(`No renderer found`);
188
+ }
189
+ }), 'args: [ 0|1?, string? ]; Turn on/off physics debug view, skip first argument to toggle value.' +
190
+ ' Second argument expects renderer name, if not provided first renderer will be picked.' +
191
+ ' Use "renderers" to get list of renderers in the world');
192
+ ggstatic.registerConsoleCommand(this, 'performance', (...args) => __awaiter(this, void 0, void 0, function* () {
193
+ let mode = 'avg';
194
+ let samples = 20;
195
+ for (let arg of args) {
196
+ if (['avg', 'peak'].includes(arg)) {
197
+ mode = arg;
198
+ }
199
+ else if (!isNaN(+arg)) {
200
+ samples = +arg;
201
+ }
202
+ }
203
+ const meter = new PerformanceMeterEntity(samples, 250);
204
+ this.addEntity(meter);
205
+ yield lastValueFrom(this.worldClock.tick$.pipe(take(samples)));
206
+ const report = mode === 'avg' ? meter.avgReport : meter.peakReport;
207
+ this.removeEntity(meter);
208
+ const renderItems = report.entries.map(([name, value]) => `<span style='color:lightgray;'>${name}:</span>` +
209
+ new Array(Math.max(0, 26 - name.length)).join('&nbsp;') +
210
+ `${value.toFixed(2)} ms` +
211
+ (mode === 'avg' ? ` (${((value * 100) / report.totalTime).toFixed(2)}%)` : ''));
212
+ let totalColor = 'lightgreen';
213
+ if (report.totalTime > 12) {
214
+ totalColor = report.totalTime < 16 ? 'yellow' : 'red';
215
+ }
216
+ const title = `${mode === 'avg' ? 'Average' : 'Peak'} Frame time`;
217
+ renderItems.unshift(title +
218
+ ':' +
219
+ new Array(Math.max(0, 26 - title.length)).join('&nbsp;') +
220
+ `<span style='color:${totalColor};'>${report.totalTime.toFixed(2)} ms</span>`);
221
+ renderItems.unshift(`Performance report (${samples} samples)`);
222
+ return renderItems.join('\n');
223
+ }), 'args: [ int?, avg|peak? ]; Measure how much time was spent per ' +
224
+ 'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
225
+ 'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
226
+ }
191
227
  }
192
228
  GgWorld.default_name_counter = 0;
193
229
  GgWorld._documentWorlds = [];
230
+ GgWorld.worldCreated$ = new Subject();
@@ -14,7 +14,7 @@ export declare class KeyboardInput extends IInput {
14
14
  /**
15
15
  * Which element types should filter key downs when focused
16
16
  */
17
- externalFocusBlacklist: {
17
+ static externalFocusBlacklist: {
18
18
  new (): HTMLElement;
19
19
  }[];
20
20
  /**
@@ -17,15 +17,6 @@ export class KeyboardInput extends IInput {
17
17
  * Flag which disables handling key downs, when document has some "typeable" element focused
18
18
  */
19
19
  this.skipKeyDownsOnExternalFocus = true;
20
- /**
21
- * Which element types should filter key downs when focused
22
- */
23
- this.externalFocusBlacklist = [
24
- HTMLInputElement,
25
- HTMLTextAreaElement,
26
- HTMLSelectElement,
27
- HTMLButtonElement,
28
- ];
29
20
  this.handleKeys = this.handleKeys.bind(this);
30
21
  this.resetAllKeys = this.resetAllKeys.bind(this);
31
22
  this.onPointerLockChange = this.onPointerLockChange.bind(this);
@@ -127,7 +118,7 @@ export class KeyboardInput extends IInput {
127
118
  }
128
119
  const pressed = e.type == 'keydown';
129
120
  if (pressed && this.skipKeyDownsOnExternalFocus && document.activeElement) {
130
- for (const k of this.externalFocusBlacklist) {
121
+ for (const k of KeyboardInput.externalFocusBlacklist) {
131
122
  if (document.activeElement instanceof k) {
132
123
  return;
133
124
  }
@@ -151,3 +142,12 @@ export class KeyboardInput extends IInput {
151
142
  }
152
143
  }
153
144
  }
145
+ /**
146
+ * Which element types should filter key downs when focused
147
+ */
148
+ KeyboardInput.externalFocusBlacklist = [
149
+ HTMLInputElement,
150
+ HTMLTextAreaElement,
151
+ HTMLSelectElement,
152
+ HTMLButtonElement,
153
+ ];
@@ -156,7 +156,7 @@ export class MouseInput extends IInput {
156
156
  if (this.options.canvas) {
157
157
  this.options.canvas.releasePointerCapture(event.pointerId);
158
158
  }
159
- this._element.removeEventListener('pointerup', onPointerUp);
159
+ window.removeEventListener('pointerup', onPointerUp);
160
160
  this._element.removeEventListener('pointercancel', onPointerUp);
161
161
  }
162
162
  this._state$.next(pointerLengthsStateMap[Math.min(pointers.length, 2)]);
@@ -169,7 +169,8 @@ export class MouseInput extends IInput {
169
169
  if (this.options.canvas) {
170
170
  this.options.canvas.setPointerCapture(event.pointerId);
171
171
  }
172
- this._element.addEventListener('pointerup', onPointerUp);
172
+ // use window instead of this._element to handle case when mouse was released over other element
173
+ window.addEventListener('pointerup', onPointerUp);
173
174
  this._element.addEventListener('pointercancel', onPointerUp);
174
175
  }
175
176
  catch (err) {
@@ -36,6 +36,8 @@ export declare class Pnt2 {
36
36
  static norm(p: Point2): Point2;
37
37
  /** scalar multiplication */
38
38
  static scalarMult(p: Point2, m: number): Point2;
39
+ /** dot multiplication */
40
+ static dot(a: Point2, b: Point2): number;
39
41
  /** linear interpolation */
40
42
  static lerp(a: Point2, b: Point2, t: number): Point2;
41
43
  /** angle between vectors in radians */
@@ -78,6 +78,10 @@ export class Pnt2 {
78
78
  y: p.y * m,
79
79
  };
80
80
  }
81
+ /** dot multiplication */
82
+ static dot(a, b) {
83
+ return a.x * b.x + a.y * b.y;
84
+ }
81
85
  /** linear interpolation */
82
86
  static lerp(a, b, t) {
83
87
  return {
@@ -87,9 +91,8 @@ export class Pnt2 {
87
91
  }
88
92
  /** angle between vectors in radians */
89
93
  static angle(a, b) {
90
- const dotProduct = a.x * b.x + a.y * b.y;
91
- const magnitudeProduct = Math.sqrt(a.x ** 2 + a.y ** 2) * Math.sqrt(b.x ** 2 + b.y ** 2);
92
- return Math.acos(dotProduct / magnitudeProduct);
94
+ const magnitudeProduct = Pnt2.len(a) * Pnt2.len(b);
95
+ return Math.acos(Pnt2.dot(a, b) / magnitudeProduct);
93
96
  }
94
97
  /** rotate point around zero by provided angle */
95
98
  static rot(p, angle) {
@@ -42,6 +42,8 @@ export declare class Pnt3 {
42
42
  static norm(p: Point3): Point3;
43
43
  /** scalar multiplication */
44
44
  static scalarMult(p: Point3, m: number): Point3;
45
+ /** dot multiplication */
46
+ static dot(a: Point3, b: Point3): number;
45
47
  /** linear interpolation */
46
48
  static lerp(a: Point3, b: Point3, t: number): Point3;
47
49
  /** angle between vectors in radians */
@@ -102,6 +102,10 @@ export class Pnt3 {
102
102
  z: p.z * m,
103
103
  };
104
104
  }
105
+ /** dot multiplication */
106
+ static dot(a, b) {
107
+ return a.x * b.x + a.y * b.y + a.z * b.z;
108
+ }
105
109
  /** linear interpolation */
106
110
  static lerp(a, b, t) {
107
111
  return {
@@ -112,9 +116,8 @@ export class Pnt3 {
112
116
  }
113
117
  /** angle between vectors in radians */
114
118
  static angle(a, b) {
115
- const dotProduct = a.x * b.x + a.y * b.y + a.z * b.z;
116
- const magnitudeProduct = Math.sqrt(a.x ** 2 + a.y ** 2 + a.z ** 2) * Math.sqrt(b.x ** 2 + b.y ** 2 + b.z ** 2);
117
- return Math.acos(dotProduct / magnitudeProduct);
119
+ const magnitudeProduct = Pnt3.len(a) * Pnt3.len(b);
120
+ return Math.acos(Pnt3.dot(a, b) / magnitudeProduct);
118
121
  }
119
122
  /** rotate point a with quaternion q */
120
123
  static rot(p, q) {
@@ -4,15 +4,33 @@ export interface BodyOptions {
4
4
  mass: number;
5
5
  restitution: number;
6
6
  friction: number;
7
- ownCollisionGroups: CollisionGroup[] | 'all';
8
- interactWithCollisionGroups: CollisionGroup[] | 'all';
7
+ ownCollisionGroups: ReadonlyArray<CollisionGroup> | 'all';
8
+ interactWithCollisionGroups: ReadonlyArray<CollisionGroup> | 'all';
9
9
  }
10
- export type DebugBodySettings = {
11
- shape: any;
12
- ignoreTransform?: boolean;
13
- } & ({
14
- type: 'RIGID_STATIC' | 'TRIGGER';
10
+ export type DebugBodyType = {
11
+ type: 'RIGID_STATIC';
12
+ } | {
13
+ type: 'TRIGGER';
14
+ activated: () => boolean;
15
15
  } | {
16
16
  type: 'RIGID_DYNAMIC';
17
- sleeping: boolean;
18
- });
17
+ sleeping: () => boolean;
18
+ };
19
+ export declare abstract class DebugBodySettings<S> {
20
+ private _type;
21
+ private _shape;
22
+ private _ignoreTransform;
23
+ private _color;
24
+ private _revision;
25
+ get revision(): number;
26
+ get type(): DebugBodyType;
27
+ set type(value: DebugBodyType);
28
+ get shape(): S;
29
+ set shape(value: S);
30
+ get ignoreTransform(): boolean;
31
+ set ignoreTransform(value: boolean);
32
+ private lastRetrievedColorCache;
33
+ get color(): number;
34
+ set color(value: number | undefined);
35
+ protected constructor(_type: DebugBodyType, _shape: S, _ignoreTransform?: boolean, _color?: number | undefined);
36
+ }