@gg-web-engine/core 0.0.58 → 0.0.60

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 (59) hide show
  1. package/dist/2d/components/rendering/i-camera-2d.component.d.ts +5 -0
  2. package/dist/2d/components/rendering/i-visual-scene-2d.component.d.ts +1 -2
  3. package/dist/2d/entities/renderer-2d.entity.d.ts +2 -1
  4. package/dist/2d/gg-2d-world.d.ts +5 -1
  5. package/dist/2d/gg-2d-world.js +57 -2
  6. package/dist/2d/index.d.ts +3 -0
  7. package/dist/2d/index.js +3 -0
  8. package/dist/2d/level-loader.d.ts +101 -0
  9. package/dist/2d/level-loader.js +88 -0
  10. package/dist/2d/loader.d.ts +11 -0
  11. package/dist/2d/loader.js +10 -0
  12. package/dist/3d/components/rendering/{i-camera.component.d.ts → i-camera-3d.component.d.ts} +1 -1
  13. package/dist/3d/components/rendering/i-camera-3d.component.js +1 -0
  14. package/dist/3d/components/rendering/i-renderer-3d.component.d.ts +0 -1
  15. package/dist/3d/components/rendering/i-visual-scene-3d.component.d.ts +1 -2
  16. package/dist/3d/entities/camera-3d.entity.d.ts +24 -0
  17. package/dist/3d/entities/camera-3d.entity.js +35 -0
  18. package/dist/3d/entities/renderer-3d.entity.d.ts +0 -5
  19. package/dist/3d/entities/renderer-3d.entity.js +0 -15
  20. package/dist/3d/gg-3d-world.d.ts +2 -2
  21. package/dist/3d/gg-3d-world.js +73 -0
  22. package/dist/3d/index.d.ts +3 -1
  23. package/dist/3d/index.js +3 -1
  24. package/dist/3d/level-loader.d.ts +300 -0
  25. package/dist/3d/level-loader.js +267 -0
  26. package/dist/3d/loader.d.ts +44 -3
  27. package/dist/3d/loader.js +41 -4
  28. package/dist/3d/models/gg-meta.d.ts +10 -0
  29. package/dist/3d/models/gg-meta.js +2 -1
  30. package/dist/base/blueprint/blueprint-node.d.ts +75 -0
  31. package/dist/base/blueprint/blueprint-node.js +51 -0
  32. package/dist/base/blueprint/blueprint.d.ts +123 -0
  33. package/dist/base/blueprint/blueprint.js +86 -0
  34. package/dist/base/blueprint/nodes/remove-entity.node.d.ts +26 -0
  35. package/dist/base/blueprint/nodes/remove-entity.node.js +29 -0
  36. package/dist/base/clock/pausable-clock.d.ts +12 -0
  37. package/dist/base/clock/pausable-clock.js +24 -0
  38. package/dist/base/components/rendering/i-renderer.component.d.ts +1 -0
  39. package/dist/base/components/rendering/i-visual-scene.component.d.ts +2 -0
  40. package/dist/base/entities/group.entity.d.ts +17 -0
  41. package/dist/base/entities/group.entity.js +19 -0
  42. package/dist/base/entities/i-entity.d.ts +11 -0
  43. package/dist/base/entities/i-entity.js +28 -0
  44. package/dist/base/entities/i-renderer.entity.d.ts +7 -1
  45. package/dist/base/entities/i-renderer.entity.js +15 -0
  46. package/dist/base/gg-world.d.ts +13 -0
  47. package/dist/base/gg-world.js +83 -1
  48. package/dist/base/index.d.ts +5 -0
  49. package/dist/base/index.js +5 -0
  50. package/dist/base/inputs/mouse.input.js +1 -0
  51. package/dist/base/level-loader.d.ts +217 -0
  52. package/dist/base/level-loader.js +258 -0
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/package.json +10 -9
  56. package/tsconfig.json +4 -1
  57. package/blender_exporter/build_blender_scene.py +0 -179
  58. package/blender_exporter/spherical_to_cube_texture.py +0 -116
  59. /package/dist/{3d/components/rendering/i-camera.component.js → 2d/components/rendering/i-camera-2d.component.js} +0 -0
@@ -3,11 +3,12 @@ import { GgWorld, GgWorldTypeDocVPatch, VisualTypeDocRepo } from '../gg-world';
3
3
  import { BehaviorSubject, Observable } from 'rxjs';
4
4
  import { Point2 } from '../models/points';
5
5
  import { RendererOptions } from '../components/rendering/i-renderer.component';
6
+ import { IPositionable } from '../interfaces/i-positionable';
6
7
  /**
7
8
  * Represents a base class for a renderer entity.
8
9
  * @class
9
10
  */
10
- export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>> extends IEntity<D, R, GgWorldTypeDocVPatch<D, R, VTypeDoc>> {
11
+ export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>> extends IEntity<D, R, GgWorldTypeDocVPatch<D, R, VTypeDoc>> implements IPositionable<D, R> {
11
12
  readonly renderer: VTypeDoc['renderer'];
12
13
  readonly tickOrder = TickOrder.RENDERING;
13
14
  /** Represents the current size of the renderer. */
@@ -23,6 +24,11 @@ export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeD
23
24
  */
24
25
  get rendererSize(): Point2 | null;
25
26
  get rendererOptions(): RendererOptions;
27
+ get camera(): VTypeDoc['camera'];
28
+ get position(): D;
29
+ set position(value: D);
30
+ get rotation(): R;
31
+ set rotation(value: R);
26
32
  /** get flag whether renderer shows physics debugger view */
27
33
  get physicsDebugViewActive(): boolean;
28
34
  /** turns on/off physics debugger view for this renderer */
@@ -23,6 +23,21 @@ export class IRendererEntity extends IEntity {
23
23
  get rendererOptions() {
24
24
  return this.renderer.rendererOptions;
25
25
  }
26
+ get camera() {
27
+ return this.renderer.camera;
28
+ }
29
+ get position() {
30
+ return this.camera.position;
31
+ }
32
+ set position(value) {
33
+ this.renderer.camera.position = value;
34
+ }
35
+ get rotation() {
36
+ return this.renderer.camera.rotation;
37
+ }
38
+ set rotation(value) {
39
+ this.renderer.camera.rotation = value;
40
+ }
26
41
  /** get flag whether renderer shows physics debugger view */
27
42
  get physicsDebugViewActive() {
28
43
  return this.renderer.physicsDebugViewActive;
@@ -6,6 +6,7 @@ export type VisualTypeDocRepo<D, R> = {
6
6
  displayObject: IDisplayObjectComponent<D, R>;
7
7
  renderer: IRendererComponent<D, R>;
8
8
  rendererExtraOpts: {};
9
+ camera: IPositionable<D, R>;
9
10
  };
10
11
  export type PhysicsTypeDocRepo<D, R> = {
11
12
  factory: unknown;
@@ -32,6 +33,8 @@ export type GgWorldSceneTypeDocVPatch<D, R, VTypeDoc extends VisualTypeDocRepo2D
32
33
  export type GgWorldSceneTypeDocPPatch<D, R, PTypeDoc extends PhysicsTypeDocRepo<D, R>, PW extends IPhysicsWorldComponent<D, R, PTypeDoc> | null> = Omit<GgWorldSceneTypeRepo<D, R>, 'physicsWorld'> & {
33
34
  physicsWorld: PW;
34
35
  };
36
+ export type TypeDocOf<W extends GgWorld<any, any>> = W extends GgWorld<infer D, infer R, infer TypeDoc> ? TypeDoc : never;
37
+ export type SceneTypeDocOf<W extends GgWorld<any, any>> = W extends GgWorld<infer D, infer R, infer TypeDoc, infer SceneTypeDoc> ? SceneTypeDoc : never;
35
38
  export declare abstract class GgWorld<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>, SceneTypeDoc extends GgWorldSceneTypeRepo<D, R, TypeDoc> = GgWorldSceneTypeRepo<D, R, TypeDoc>> {
36
39
  private static default_name_counter;
37
40
  private static _documentWorlds;
@@ -67,6 +70,16 @@ export declare abstract class GgWorld<D, R, TypeDoc extends GgWorldTypeDocRepo<D
67
70
  position?: D, rotation?: R, material?: unknown): IPositionable<D, R> & IRenderableEntity<D, R, TypeDoc>;
68
71
  addEntity(entity: IEntity): void;
69
72
  removeEntity(entity: IEntity, dispose?: boolean): void;
73
+ /**
74
+ * Find an entity anywhere in the world by name. `children` is a flat list of every entity ever
75
+ * added via `addEntity` (nested entities included - `addChildren`/`onSpawned` cascade into it
76
+ * too), so this is a plain linear scan, not a tree walk; to search inside one particular
77
+ * entity's own subtree instead, use `IEntity.getChildEntityByName`.
78
+ * @param name - The entity's `name`
79
+ * @returns The first entity found with that name (insertion order), if more than one shares it
80
+ * @throws if no entity in the world has that name
81
+ */
82
+ getEntityByName<T extends IEntity = IEntity>(name: string): T;
70
83
  private onGgStaticInitialized;
71
84
  protected registerConsoleCommands(ggstatic: {
72
85
  registerConsoleCommand: (world: GgWorld<any, any> | null, command: string, handler: (...args: string[]) => Promise<string>, doc?: string) => void;
@@ -134,7 +134,13 @@ export class GgWorld {
134
134
  this.disposed$.complete();
135
135
  }
136
136
  addEntity(entity) {
137
- if (!!entity.world) {
137
+ if (entity.world === this) {
138
+ // Already a member of this world - e.g. reparented (via addChildren) after having been
139
+ // added directly, as level-loaded entities are. Not an error: just a no-op, since
140
+ // addChildren already updated the parent/children bookkeeping before calling back in here.
141
+ return;
142
+ }
143
+ if (entity.world) {
138
144
  console.warn('Trying to spawn entity, which is already spawned');
139
145
  return;
140
146
  }
@@ -156,6 +162,22 @@ export class GgWorld {
156
162
  entity.dispose();
157
163
  }
158
164
  }
165
+ /**
166
+ * Find an entity anywhere in the world by name. `children` is a flat list of every entity ever
167
+ * added via `addEntity` (nested entities included - `addChildren`/`onSpawned` cascade into it
168
+ * too), so this is a plain linear scan, not a tree walk; to search inside one particular
169
+ * entity's own subtree instead, use `IEntity.getChildEntityByName`.
170
+ * @param name - The entity's `name`
171
+ * @returns The first entity found with that name (insertion order), if more than one shares it
172
+ * @throws if no entity in the world has that name
173
+ */
174
+ getEntityByName(name) {
175
+ const found = this.children.find(e => e.name === name);
176
+ if (!found) {
177
+ throw new Error(`No entity named "${name}" found in the world`);
178
+ }
179
+ return found;
180
+ }
159
181
  onGgStaticInitialized() {
160
182
  window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
161
183
  this.registerConsoleCommands(window.ggstatic);
@@ -174,6 +196,19 @@ export class GgWorld {
174
196
  }
175
197
  return this.worldClock.tickRateLimit.toString();
176
198
  }), 'args: [ int? ]; Get current tick rate limit of selected world clock or set it. 0 means no limit applied');
199
+ ggstatic.registerConsoleCommand(this, 'step', (...args) => __awaiter(this, void 0, void 0, function* () {
200
+ if (!this.worldClock.isPaused) {
201
+ throw new Error('World must be paused first (run "timescale 0") before it can be stepped');
202
+ }
203
+ const ms = args[0] === undefined ? 1000 / 120 : +args[0];
204
+ if (isNaN(ms) || ms <= 0) {
205
+ throw new Error('usage: step [ms]; ms must be a positive number');
206
+ }
207
+ this.worldClock.step(ms);
208
+ return `stepped ${ms} ms`;
209
+ }), 'args: [ float? ]; Advance a paused world clock by exactly one tick of the given duration ' +
210
+ 'in milliseconds (default 8, i.e. 1000/120). Only works while the world is paused via ' +
211
+ '"timescale 0"; rejects otherwise');
177
212
  ggstatic.registerConsoleCommand(this, 'renderers', () => __awaiter(this, void 0, void 0, function* () {
178
213
  return this.renderers.map(r => r.name).join('\n');
179
214
  }), 'no args; Print all renderers in selected world');
@@ -236,6 +271,53 @@ export class GgWorld {
236
271
  }), 'args: [ int?, avg|peak? ]; Measure how much time was spent per ' +
237
272
  'entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are ' +
238
273
  'optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity');
274
+ ggstatic.registerConsoleCommand(this, 'entities', (...args) => __awaiter(this, void 0, void 0, function* () {
275
+ var _a;
276
+ const filter = (_a = args[0]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
277
+ const list = this.children.filter(e => !filter || e.name.toLowerCase().includes(filter));
278
+ if (list.length === 0) {
279
+ return '<span style="color:#aaa">(no entities)</span>';
280
+ }
281
+ return list
282
+ .map(e => `<span style='color:yellow'>${e.name}</span>\t<span style='color:#aaa'>${e.constructor.name}</span>`)
283
+ .join('\n');
284
+ }), 'args: [ string? ]; List all entities in this world (name and class), optionally filtered by ' +
285
+ 'a case-insensitive substring of the name. Use "entity <name>" to inspect one of them');
286
+ ggstatic.registerConsoleCommand(this, 'entity', (...args) => __awaiter(this, void 0, void 0, function* () {
287
+ const name = args[0];
288
+ if (!name) {
289
+ throw new Error('usage: entity <name>; use "entities" to list available names');
290
+ }
291
+ const entity = this.getEntityByName(name);
292
+ const lines = [
293
+ `class: ${entity.constructor.name}`,
294
+ `active: ${entity.active}`,
295
+ `parent: ${entity.parent ? entity.parent.name : '(none)'}`,
296
+ ];
297
+ if ('visible' in entity) {
298
+ lines.push(`visible: ${entity.visible}`);
299
+ }
300
+ if ('position' in entity) {
301
+ lines.push(`position: ${JSON.stringify(entity.position)}`);
302
+ }
303
+ if ('rotation' in entity) {
304
+ lines.push(`rotation: ${JSON.stringify(entity.rotation)}`);
305
+ }
306
+ lines.push(`children: ${entity.children.length === 0 ? '(none)' : entity.children.map(c => c.name).join(', ')}`);
307
+ return lines.join('\n');
308
+ }), 'args: [ string ]; Print class, position/rotation (if any) and children of one entity. Use ' +
309
+ '"entities" to list available names, "set_position"/"set_rotation" to move it');
310
+ ggstatic.registerConsoleCommand(this, 'remove', (...args) => __awaiter(this, void 0, void 0, function* () {
311
+ const name = args[0];
312
+ if (!name) {
313
+ throw new Error('usage: remove <name> [dispose=0|1]');
314
+ }
315
+ const entity = this.getEntityByName(name);
316
+ const dispose = args[1] === undefined ? true : args[1] === '1';
317
+ this.removeEntity(entity, dispose);
318
+ return `removed "${name}"`;
319
+ }), 'args: [ string, 0|1? ]; Remove the named entity from this world, disposing it by default. ' +
320
+ 'Pass 0 as second arg to detach without disposing (e.g. before re-adding it elsewhere)');
239
321
  }
240
322
  }
241
323
  GgWorld.default_name_counter = 0;
@@ -1,3 +1,6 @@
1
+ export * from './blueprint/blueprint';
2
+ export * from './blueprint/blueprint-node';
3
+ export * from './blueprint/nodes/remove-entity.node';
1
4
  export * from './clock/global-clock';
2
5
  export * from './clock/i-clock';
3
6
  export * from './clock/pausable-clock';
@@ -14,6 +17,7 @@ export * from './data-structures/bitmask';
14
17
  export * from './data-structures/graph';
15
18
  export * from './entities/controllers/animation-mixer';
16
19
  export * from './entities/controllers/inline-controller';
20
+ export * from './entities/group.entity';
17
21
  export * from './entities/i-entity';
18
22
  export * from './entities/i-renderer.entity';
19
23
  export * from './entities/i-renderable.entity';
@@ -36,3 +40,4 @@ export * from './math/matrix4';
36
40
  export * from './math/splines';
37
41
  export * from './pipes/gg-elastic.pipe';
38
42
  export * from './gg-world';
43
+ export * from './level-loader';
@@ -1,3 +1,6 @@
1
+ export * from './blueprint/blueprint';
2
+ export * from './blueprint/blueprint-node';
3
+ export * from './blueprint/nodes/remove-entity.node';
1
4
  export * from './clock/global-clock';
2
5
  export * from './clock/i-clock';
3
6
  export * from './clock/pausable-clock';
@@ -14,6 +17,7 @@ export * from './data-structures/bitmask';
14
17
  export * from './data-structures/graph';
15
18
  export * from './entities/controllers/animation-mixer';
16
19
  export * from './entities/controllers/inline-controller';
20
+ export * from './entities/group.entity';
17
21
  export * from './entities/i-entity';
18
22
  export * from './entities/i-renderer.entity';
19
23
  export * from './entities/i-renderable.entity';
@@ -36,3 +40,4 @@ export * from './math/matrix4';
36
40
  export * from './math/splines';
37
41
  export * from './pipes/gg-elastic.pipe';
38
42
  export * from './gg-world';
43
+ export * from './level-loader';
@@ -10,6 +10,7 @@ const DEFAULT_MOUSE_INPUT_OPTIONS = {
10
10
  */
11
11
  export var MouseInputState;
12
12
  (function (MouseInputState) {
13
+ //MouseInputState {
13
14
  /**
14
15
  No mouse or touch input is detected. Mouse move can still be emitted
15
16
  */
@@ -0,0 +1,217 @@
1
+ import { GgWorld, GgWorldTypeDocRepo } from './gg-world';
2
+ import { GroupEntity } from './entities/group.entity';
3
+ import { BlueprintJson, BlueprintNodeFactory } from './blueprint/blueprint';
4
+ /**
5
+ * A function that turns per-entity JSON settings into a spawned `IEntity` (e.g. a primitive body,
6
+ * a trigger, a camera). Registered against a class alias via {@link LevelLoader.registerClass}.
7
+ * May be `async`/return a `Promise` (e.g. the built-in `"Glb"` 3D class, which fetches a model) -
8
+ * {@link LevelLoader.loadLevel} awaits every generator before moving to the next entity. A
9
+ * generator that returns anything other than an `IEntity` (including `null`/`undefined`) has its
10
+ * result discarded - see {@link LevelLoader.loadLevel}.
11
+ * @template D - The position type
12
+ * @template R - The rotation type
13
+ * @template TypeDoc - The type document repository
14
+ * @template Settings - The settings object type
15
+ * @template W - The world type
16
+ */
17
+ export type EntityGenerator<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>, Settings = any, W = GgWorld<D, R, TypeDoc>> = (world: W, settings: Settings) => any;
18
+ /**
19
+ * A level/scene, serializable as a single JSON document (e.g. to be hosted as a static file and
20
+ * loaded via {@link LevelLoader.loadLevelFromUrl}).
21
+ */
22
+ export interface LevelJson {
23
+ /**
24
+ * Entities in the level
25
+ */
26
+ entities: EntityJson[];
27
+ /**
28
+ * Blueprint graphs available to this level's entities, keyed by name - referenced from an
29
+ * `EntityJson.events` entry to run a blueprint whenever the named observable on that entity
30
+ * fires. See {@link BlueprintJson} and the `gg-engine-level-json` skill's "Blueprints" section.
31
+ */
32
+ blueprints?: Record<string, BlueprintJson>;
33
+ }
34
+ /**
35
+ * JSON description of a single entity in a level. `position`/`rotation` are left untyped here
36
+ * since their shape depends on the dimensionality (`Point2`/`number` for 2D, `Point3`/`Point4`
37
+ * for 3D) of whichever `LevelLoader` subclass parses this JSON.
38
+ */
39
+ export interface EntityJson {
40
+ /**
41
+ * Class alias for the entity, matching a class registered via `registerClass`. Built-in
42
+ * primitive shapes (box/sphere/square/circle/...) all share the single `"Primitive"` alias and
43
+ * are distinguished by `shape` instead of by a per-shape class - e.g. `{ class: "Primitive",
44
+ * shape: "BOX" }` rather than `{ class: "BOX" }`. Apps register their own aliases (e.g.
45
+ * `"ShapeSpawner"`) the same way the dimensionality-specific `LevelLoader` subclasses register
46
+ * their built-ins, via `registerClass`.
47
+ */
48
+ class: string;
49
+ /**
50
+ * Shape identifier for the built-in `"Primitive"` entity class (e.g. `"Box"`, `"Circle"`) - see
51
+ * the dimensionality-specific `LevelLoader` subclass (`Gg2dLevelLoader`/`Gg3dLevelLoader`) for
52
+ * the supported values. Ignored for any other `class`.
53
+ */
54
+ shape?: string;
55
+ /**
56
+ * Position of the entity
57
+ */
58
+ position?: any;
59
+ /**
60
+ * Rotation of the entity
61
+ */
62
+ rotation?: any;
63
+ /**
64
+ * Name of the entity. `loadLevel` sets the generator's returned `IEntity`'s `.name` to this
65
+ * (overriding whatever default the generator gave it), so it can be found afterwards with
66
+ * `GgWorld.getEntityByName`/`IEntity.getChildEntityByName`. Moot if the generator doesn't return
67
+ * an `IEntity` - that result is discarded (with a console warning) before naming is applied.
68
+ */
69
+ name?: string;
70
+ /**
71
+ * Configuration for the entity, passed to its generator alongside position/rotation/name
72
+ */
73
+ config?: any;
74
+ /**
75
+ * Maps an observable property name on this entity's generated `IEntity` (e.g. `Trigger3dEntity`'s
76
+ * `"onEntityEntered"`) to what should run whenever that observable fires - see
77
+ * {@link EntityEventBinding}. `loadLevel` subscribes to the observable and triggers a fresh
78
+ * `Blueprint` instance (via its `"in"` entry point) with whatever value it emits, each time it
79
+ * fires - see `LevelLoader.loadLevel` and the `gg-engine-level-json` skill's "Blueprints"
80
+ * section. Silently ignored (with a console warning) if the binding can't be resolved to a
81
+ * blueprint, or the named property isn't an `Observable`.
82
+ */
83
+ events?: Record<string, EntityEventBinding>;
84
+ }
85
+ /**
86
+ * What an `EntityJson.events` entry runs. Either:
87
+ * - a plain `string` - first tried as a key into the level's top-level `blueprints` map (a named,
88
+ * possibly multi-node graph); if not found there, tried as a blueprint node type alias
89
+ * registered via `registerBlueprintNode` (e.g. the built-in `"RemoveEntity"`) instead, with no
90
+ * settings - shorthand for the single-node form below with `settings` omitted.
91
+ * - `{ type, settings? }` - a single built-in/registered blueprint node used directly as the
92
+ * handler, with inline `settings`, no `blueprints` entry needed at all - e.g.
93
+ * `{ "type": "RemoveEntity", "settings": { "dispose": true } }`. Only node types registered with
94
+ * a default input pin (every built-in one is - see `registerBlueprintNode`) support this form;
95
+ * others require a full graph declared in `blueprints` instead, addressing the desired input pin
96
+ * explicitly via `inputs`.
97
+ */
98
+ export type EntityEventBinding = string | {
99
+ type: string;
100
+ settings?: Record<string, any>;
101
+ };
102
+ /**
103
+ * Base class for level loaders: parses a {@link LevelJson} document into world entities by
104
+ * dispatching each `EntityJson.class` to a generator function registered with {@link registerClass}.
105
+ *
106
+ * A generator is required to return an `IEntity`. Every `IEntity` a generator produces is parented
107
+ * under one {@link GroupEntity} per `loadLevel`/`loadLevelFromUrl` call (added to the world
108
+ * immediately, and handed back once loading completes) - so a whole level can be torn down in one
109
+ * shot with `world.removeEntity(level, true)`, which cascades removal/disposal to every child, and
110
+ * any named entity can be found afterwards with `level.getChildEntityByName(name)`. If a generator
111
+ * returns anything other than an `IEntity` (including `null`/`undefined`), `loadLevel` logs a
112
+ * `console.warn` and skips that entity - it's never parented, named, or tracked.
113
+ * @template D - The position type
114
+ * @template R - The rotation type
115
+ * @template TypeDoc - The type document repository
116
+ */
117
+ export declare abstract class LevelLoader<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>> {
118
+ protected readonly world: GgWorld<D, R, TypeDoc>;
119
+ /**
120
+ * Map of class aliases to generator functions
121
+ */
122
+ protected generators: Map<string, EntityGenerator<D, R, TypeDoc, any, any>>;
123
+ /**
124
+ * Map of blueprint node type aliases to node factory functions - see {@link registerBlueprintNode}.
125
+ */
126
+ protected blueprintNodes: Map<string, BlueprintNodeFactory<D, R, TypeDoc>>;
127
+ /**
128
+ * Map of blueprint node type aliases to their default input pin name, for node types registered
129
+ * with one - see {@link registerBlueprintNode}.
130
+ */
131
+ protected blueprintNodeDefaultInputs: Map<string, string>;
132
+ /**
133
+ * Constructor
134
+ * @param world - The world instance
135
+ */
136
+ constructor(world: GgWorld<D, R, TypeDoc>);
137
+ /**
138
+ * Register a generator function for a class alias
139
+ * @param classAlias - The class alias
140
+ * @param generator - The generator function
141
+ */
142
+ registerClass<Settings, W = any>(classAlias: string, generator: EntityGenerator<D, R, TypeDoc, Settings, W>): void;
143
+ /**
144
+ * Register a {@link BlueprintNode} factory for a node type alias, so a `BlueprintJson`'s
145
+ * `nodes` can reference it by `type` (e.g. the built-in `"RemoveEntity"`, registered by every
146
+ * `LevelLoader` out of the box). Same pattern as {@link registerClass}, one level down (node
147
+ * types within a blueprint graph, rather than entity classes within a level).
148
+ * @param typeAlias - The node type alias
149
+ * @param factory - Builds a node instance from its baked-in settings
150
+ * @param defaultInputPin - This node type's sole "trigger me" input pin name, if it has one
151
+ * canonical one (e.g. `"RemoveEntity"`'s `"entity"`). Enables the node type to be used directly
152
+ * as an `EntityJson.events` binding (`{ "eventName": "TypeAlias" }` or
153
+ * `{ "eventName": { "type": "TypeAlias", "settings": {...} } }`) without declaring a full
154
+ * `BlueprintJson` graph in `blueprints` - see {@link EntityEventBinding}. Omit for a node type
155
+ * with zero or multiple input pins, or one with no single obviously-correct default; it remains
156
+ * usable from a full graph either way.
157
+ */
158
+ registerBlueprintNode(typeAlias: string, factory: BlueprintNodeFactory<D, R, TypeDoc>, defaultInputPin?: string): void;
159
+ /**
160
+ * Load a level from an already-parsed JSON document. Every `IEntity` the level's entities
161
+ * produce is parented under - and, on failure, torn down along with - the returned
162
+ * {@link GroupEntity}, already added to the world.
163
+ * @param levelJson - The level JSON
164
+ * @param levelName - Optional name for the returned group entity (e.g. so a debugger/console
165
+ * listing entities by name shows something more meaningful than the default auto-generated one)
166
+ * @returns The level's root group entity
167
+ */
168
+ loadLevel(levelJson: LevelJson, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
169
+ /**
170
+ * Resolve `eventBinding` (see {@link EntityEventBinding}) to a `BlueprintJson`, instantiate a
171
+ * fresh `Blueprint` from it, and subscribe it to `entity[eventName]` so every value that
172
+ * observable emits triggers the blueprint's `"in"` entry point. Wrapped in a
173
+ * `BlueprintBindingEntity` so the subscription (and the blueprint's own node state) is torn down
174
+ * automatically once that entity is disposed - the caller parents the returned entity under the
175
+ * level's group for that reason.
176
+ * @param entity - The entity carrying the observable property
177
+ * @param eventName - Name of the observable property on `entity`
178
+ * @param eventBinding - What to run - a `blueprints` name, a bare node type alias, or `{ type,
179
+ * settings? }`
180
+ * @param blueprints - The level's top-level blueprint map, if any
181
+ * @returns The binding entity to parent under the level, or `undefined` if `eventBinding`
182
+ * couldn't be resolved or the named property isn't an `Observable` (both logged via
183
+ * `console.warn`)
184
+ */
185
+ private bindEvent;
186
+ /**
187
+ * Turn an `EntityEventBinding` into a `BlueprintJson` to run. An object form (`{ type,
188
+ * settings? }`) always builds a single-node inline graph via {@link inlineNodeBlueprint}. A
189
+ * string form is tried first as a key into `blueprints` (a named, possibly multi-node graph),
190
+ * then - if not found there - as a bare node type alias, same as the object form with no
191
+ * settings.
192
+ * @param eventName - Name of the observable property being bound, for warning messages
193
+ * @param eventBinding - The binding to resolve
194
+ * @param blueprints - The level's top-level blueprint map, if any
195
+ * @returns The resolved graph, or `undefined` (logged via `console.warn`) if it couldn't be
196
+ */
197
+ private resolveEventBlueprint;
198
+ /**
199
+ * Build a single-node `BlueprintJson` wrapping one blueprint node type, wired so the node's
200
+ * registered default input pin (see {@link registerBlueprintNode}) is reachable as `"in"` - what
201
+ * powers the `EntityEventBinding` shorthand that skips declaring a `blueprints` entry entirely.
202
+ * @param eventName - Name of the observable property being bound, for warning messages
203
+ * @param nodeType - The blueprint node type alias
204
+ * @param settings - Settings to bake into the node, if any
205
+ * @returns The single-node graph, or `undefined` (logged via `console.warn`) if `nodeType` isn't
206
+ * registered, or was registered without a default input pin
207
+ */
208
+ private inlineNodeBlueprint;
209
+ /**
210
+ * Fetch a level JSON document hosted at `url` and load it, so a whole level/scene can be
211
+ * shipped and consumed as a single static JSON file.
212
+ * @param url - URL (or path) of the level JSON document
213
+ * @param levelName - Optional name for the returned group entity, see {@link loadLevel}
214
+ * @returns The level's root group entity
215
+ */
216
+ loadLevelFromUrl(url: string, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
217
+ }