@kvyverse/world-runtime 0.1.0 → 0.4.2
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 +84 -59
- package/dist/{BallCollider-8ZrxReEd.js → BallCollider-3L5vOrcL.js} +2 -2
- package/dist/{CapsuleCollider-bynyf6Mc.js → CapsuleCollider-CBTsR-Ys.js} +2 -2
- package/dist/ChatModule-2tiWS_Yr.js +411 -0
- package/dist/{Collider-4_rCTHVA.js → Collider-CPUCG3RA.js} +2 -2
- package/dist/{ConeCollider-DtRZmgK8.js → ConeCollider-C3XU3Y9X.js} +2 -2
- package/dist/{ConvexMeshCollider-BSmDM4o3.js → ConvexMeshCollider-DzNV4HTO.js} +1 -1
- package/dist/{CuboidCollider-CJvq7--s.js → CuboidCollider-Dp1DZoFM.js} +1 -1
- package/dist/{CylinderCollider-CQPSh4tu.js → CylinderCollider-C0h_nhB5.js} +2 -2
- package/dist/{FpsCameraController-BE2EirRj.js → FpsCameraController-2GNWJM5D.js} +1 -1
- package/dist/RapierDebugRenderer-BQy_m-3C.js +73 -0
- package/dist/{RapierFpsPlayerController-VsNfQl6v.js → RapierFpsPlayerController-DthD8dw9.js} +5 -22
- package/dist/{RapierPhysics-C_3b7QOn.js → RapierPhysics-CU6X--c7.js} +34 -15
- package/dist/{Rigidbody-b7QNF4ev.js → Rigidbody-DLhN8ddv.js} +12 -1
- package/dist/World.d.ts +4 -2
- package/dist/WorldRuntime.d.ts +15 -5
- package/dist/addons/AddonsRuntime.d.ts +37 -0
- package/dist/addons/builtin-libs.d.ts +20 -0
- package/dist/addons/lib-registry.d.ts +60 -0
- package/dist/addons/required-libs.d.ts +9 -0
- package/dist/addons/types.d.ts +28 -0
- package/dist/behaviours/physics/BallCollider.d.ts +1 -1
- package/dist/behaviours/physics/CapsuleCollider.d.ts +1 -1
- package/dist/behaviours/physics/ConeCollider.d.ts +1 -1
- package/dist/behaviours/physics/ConvexMeshCollider.d.ts +1 -1
- package/dist/behaviours/physics/CuboidCollider.d.ts +1 -1
- package/dist/behaviours/physics/CylinderCollider.d.ts +1 -1
- package/dist/behaviours/physics/HalfspaceCollider.d.ts +1 -1
- package/dist/behaviours/physics/_required-patch.d.ts +10 -8
- package/dist/behaviours/registry.d.ts +9 -2
- package/dist/chat.module-descriptor-BTsep-Kq.js +107 -0
- package/dist/{get-scale-from-obj-QKv3Fig5.js → get-scale-from-obj-B7JlVPni.js} +1 -1
- package/dist/index.d.ts +26 -14
- package/dist/index.js +594 -202
- package/dist/lib-registry-D1VyVPBg.js +142 -0
- package/dist/modules/HtmlHudModule.d.ts +5 -1
- package/dist/modules/InputSystemModule.d.ts +14 -32
- package/dist/modules/chat/ChatDom.d.ts +100 -0
- package/dist/modules/chat/ChatModule.d.ts +36 -0
- package/dist/modules/chat/chat.module-descriptor.d.ts +43 -0
- package/dist/modules/contracts/addons.module-api.d.ts +15 -0
- package/dist/modules/contracts/chat.module-api.d.ts +52 -0
- package/dist/modules/contracts/controls.module-api.d.ts +58 -0
- package/dist/modules/contracts/html.module-api.d.ts +22 -2
- package/dist/modules/contracts/input.module-api.d.ts +11 -11
- package/dist/modules/contracts/module-events.d.ts +17 -0
- package/dist/modules/contracts/rapier.module-api.d.ts +13 -1
- package/dist/modules/controls/ControlsModule.d.ts +45 -0
- package/dist/modules/controls/KeyboardControlsDriver.d.ts +12 -0
- package/dist/modules/index.d.ts +30 -3
- package/dist/modules/optional-module-descriptor.d.ts +39 -0
- package/dist/modules/optional-modules.d.ts +11 -0
- package/dist/modules/physics/RapierDebugRenderer.d.ts +19 -0
- package/dist/{behaviours/physics/rapier-addon-core → modules/physics}/RapierPhysics.d.ts +13 -3
- package/dist/modules/physics/physics.module-descriptor.d.ts +16 -0
- package/dist/modules/registry.d.ts +7 -3
- package/dist/rendering/SceneBloom.d.ts +1 -1
- package/dist/rendering/renderer-apply.d.ts +1 -1
- package/dist/{settings → rendering}/types.d.ts +1 -12
- package/dist/types.d.ts +10 -3
- package/dist/utils/lazy.d.ts +7 -0
- package/dist/version.gen.d.ts +1 -1
- package/dist/world-stores.d.ts +39 -0
- package/package.json +10 -1
- package/dist/RapierDebugRenderer-sURZDLP4.js +0 -55
- package/dist/behaviours/physics/rapier-addon-core/RapierDebugRenderer.d.ts +0 -17
- package/dist/modules/contracts/rapierDebug.module-api.d.ts +0 -5
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/settings/AddonsRuntime.d.ts +0 -19
- package/dist/settings/lib-loaders.d.ts +0 -7
- package/dist/settings/lib-registry.d.ts +0 -24
- package/dist/settings/module-registry.d.ts +0 -25
- /package/dist/{settings → addons}/externals.d.ts +0 -0
- /package/dist/behaviours/physics/{rapier-addon-core/Collider.d.ts → Collider.d.ts} +0 -0
- /package/dist/behaviours/physics/{rapier-addon-core/get-scale-from-obj.d.ts → get-scale-from-obj.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
1
|
+
import { a as OPTIONAL_MODULE_IDS, i as normalizeChat, n as CHAT_INPUT_MODES, o as OptionalModuleId, r as chatModuleDescriptor } from "./chat.module-descriptor-BTsep-Kq.js";
|
|
2
|
+
import { a as LibId, c as libByRequireKey, i as LIB_IDS, n as LIBS, o as OPTIONAL_LIB_DESCRIPTORS, r as LIB_DESCRIPTORS, s as OPTIONAL_LIB_IDS, t as BUILTIN_LIB_DESCRIPTORS } from "./lib-registry-D1VyVPBg.js";
|
|
3
|
+
import { n as getRadiusScale, t as getHalfheightRadiusScale } from "./get-scale-from-obj-B7JlVPni.js";
|
|
3
4
|
import * as THREE from "three/webgpu";
|
|
4
5
|
import { BufferGeometryLoader, LoadingManager, TextureLoader } from "three/webgpu";
|
|
5
6
|
import * as ThreeStartNS from "three-start";
|
|
@@ -7,10 +8,13 @@ import { ContextModule, ThreeContextEvents, ThreeStart } from "three-start";
|
|
|
7
8
|
import * as TSL from "three/tsl";
|
|
8
9
|
import { emissive, mrt, output } from "three/tsl";
|
|
9
10
|
import van from "vanjs-core";
|
|
10
|
-
import
|
|
11
|
+
import * as howler from "howler";
|
|
11
12
|
import { Howl, Howler } from "howler";
|
|
12
13
|
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
13
14
|
import { clone } from "three/examples/jsm/utils/SkeletonUtils.js";
|
|
15
|
+
import EventEmitter from "eventemitter3";
|
|
16
|
+
import * as nanostores from "nanostores";
|
|
17
|
+
import { atom } from "nanostores";
|
|
14
18
|
//#region packages/world-runtime/src/world-events.ts
|
|
15
19
|
/**
|
|
16
20
|
* World lifecycle events, emitted on `game.emitter` (`world.events` in scripts)
|
|
@@ -40,12 +44,16 @@ var World = class extends ThreeStart {
|
|
|
40
44
|
super(options);
|
|
41
45
|
this._runtime = _runtime;
|
|
42
46
|
}
|
|
43
|
-
/** Asset URL by project path — same resolving as `getUrl` in scripts. */
|
|
44
|
-
getUrl = (
|
|
47
|
+
/** Asset URL by project path or resource — same resolving as `getUrl` in scripts. */
|
|
48
|
+
getUrl = (source) => this._runtime.getUrl(source);
|
|
45
49
|
/** World lifecycle event bus — the same one as `world.events` in scripts. */
|
|
46
50
|
get events() {
|
|
47
51
|
return this._runtime.emitter;
|
|
48
52
|
}
|
|
53
|
+
/** Shared reactive state — the same one as `world.stores` in scripts. */
|
|
54
|
+
get stores() {
|
|
55
|
+
return this._runtime.stores;
|
|
56
|
+
}
|
|
49
57
|
start() {
|
|
50
58
|
const wasStarted = this.isStarted;
|
|
51
59
|
super.start();
|
|
@@ -59,6 +67,26 @@ var World = class extends ThreeStart {
|
|
|
59
67
|
};
|
|
60
68
|
//#endregion
|
|
61
69
|
//#region packages/world-runtime/src/utils/lazy.ts
|
|
70
|
+
/**
|
|
71
|
+
* Object that forwards every access to `resolve()`. For script globals that are
|
|
72
|
+
* shortcuts into something created later (`modules` → `ctx.modules`): the script
|
|
73
|
+
* bag is destructured eagerly, so a plain reference or a getter would be read
|
|
74
|
+
* before the target exists.
|
|
75
|
+
*/
|
|
76
|
+
function lazyProxy(resolve) {
|
|
77
|
+
return new Proxy({}, {
|
|
78
|
+
get: (_target, key) => Reflect.get(resolve(), key),
|
|
79
|
+
has: (_target, key) => Reflect.has(resolve(), key),
|
|
80
|
+
ownKeys: () => Reflect.ownKeys(resolve()),
|
|
81
|
+
getOwnPropertyDescriptor: (_target, key) => {
|
|
82
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(resolve(), key);
|
|
83
|
+
return descriptor && {
|
|
84
|
+
...descriptor,
|
|
85
|
+
configurable: true
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
62
90
|
/** Lazy cached value: the factory is called once on the first `get()`. */
|
|
63
91
|
function lazy(factory) {
|
|
64
92
|
let has = false;
|
|
@@ -268,6 +296,248 @@ function finiteOr(value, fallback) {
|
|
|
268
296
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
269
297
|
}
|
|
270
298
|
//#endregion
|
|
299
|
+
//#region packages/world-runtime/src/modules/controls/KeyboardControlsDriver.ts
|
|
300
|
+
/** A diagonal must not be faster than a straight line. */
|
|
301
|
+
var DIAGONAL = Math.SQRT1_2;
|
|
302
|
+
/**
|
|
303
|
+
* Keyboard driver for `controls`: WASD/arrows, shift to run, Space to jump.
|
|
304
|
+
* Reads key state from the `input` module and binds no listeners of its own.
|
|
305
|
+
*/
|
|
306
|
+
var KeyboardControlsDriver = class {
|
|
307
|
+
_input;
|
|
308
|
+
kind = "keyboard";
|
|
309
|
+
constructor(_input) {
|
|
310
|
+
this._input = _input;
|
|
311
|
+
}
|
|
312
|
+
read(state) {
|
|
313
|
+
const key = this._input.isKeyDown;
|
|
314
|
+
let x = 0;
|
|
315
|
+
let y = 0;
|
|
316
|
+
if (key("KeyW") || key("ArrowUp")) y += 1;
|
|
317
|
+
if (key("KeyS") || key("ArrowDown")) y -= 1;
|
|
318
|
+
if (key("KeyD") || key("ArrowRight")) x += 1;
|
|
319
|
+
if (key("KeyA") || key("ArrowLeft")) x -= 1;
|
|
320
|
+
if (x !== 0 && y !== 0) {
|
|
321
|
+
x *= DIAGONAL;
|
|
322
|
+
y *= DIAGONAL;
|
|
323
|
+
}
|
|
324
|
+
state.move.set(x, y);
|
|
325
|
+
state.force = x !== 0 || y !== 0 ? 1 : 0;
|
|
326
|
+
state.run = this._input.shiftKey;
|
|
327
|
+
state.jump = key("Space");
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region packages/world-runtime/src/modules/controls/ControlsModule.ts
|
|
332
|
+
/**
|
|
333
|
+
* The player's movement intent — what they want to do, whatever the hardware is.
|
|
334
|
+
* Input sources register as drivers (`addDriver`); the module polls them once a
|
|
335
|
+
* frame and decides which one is driving. Details — `./README.md`.
|
|
336
|
+
*/
|
|
337
|
+
var ControlsModule = class extends ContextModule {
|
|
338
|
+
/** x — strafe, y — forward. Reused vector: copy it before storing. */
|
|
339
|
+
move = new THREE.Vector2();
|
|
340
|
+
_drivers = [];
|
|
341
|
+
_state = {
|
|
342
|
+
move: new THREE.Vector2(),
|
|
343
|
+
force: 0,
|
|
344
|
+
run: false,
|
|
345
|
+
jump: false
|
|
346
|
+
};
|
|
347
|
+
_keyboard = null;
|
|
348
|
+
_active = null;
|
|
349
|
+
_source = "keyboard";
|
|
350
|
+
_force = 0;
|
|
351
|
+
_run = false;
|
|
352
|
+
_jump = false;
|
|
353
|
+
onAwake() {
|
|
354
|
+
this._keyboard = new KeyboardControlsDriver(this.modules.input);
|
|
355
|
+
this.addDriver(this._keyboard);
|
|
356
|
+
}
|
|
357
|
+
/** Speed multiplier: 1 is a normal walk, higher means a stronger stick tilt. */
|
|
358
|
+
get force() {
|
|
359
|
+
return this._force;
|
|
360
|
+
}
|
|
361
|
+
/** Run modifier: shift, a stick pushed past its ring, a UI button. */
|
|
362
|
+
get run() {
|
|
363
|
+
return this._run;
|
|
364
|
+
}
|
|
365
|
+
/** Kind of the device driving right now. */
|
|
366
|
+
get source() {
|
|
367
|
+
return this._source;
|
|
368
|
+
}
|
|
369
|
+
get drivers() {
|
|
370
|
+
return this._drivers;
|
|
371
|
+
}
|
|
372
|
+
/** The built-in keyboard driver — detach it with `removeDriver`. */
|
|
373
|
+
get keyboard() {
|
|
374
|
+
if (!this._keyboard) throw new Error("controls: keyboard driver is not ready yet");
|
|
375
|
+
return this._keyboard;
|
|
376
|
+
}
|
|
377
|
+
addDriver(driver) {
|
|
378
|
+
if (!this._drivers.includes(driver)) this._drivers.push(driver);
|
|
379
|
+
return () => this.removeDriver(driver);
|
|
380
|
+
}
|
|
381
|
+
removeDriver(driver) {
|
|
382
|
+
const index = this._drivers.indexOf(driver);
|
|
383
|
+
if (index < 0) return;
|
|
384
|
+
this._drivers.splice(index, 1);
|
|
385
|
+
if (this._active === driver) this._active = null;
|
|
386
|
+
}
|
|
387
|
+
jump() {
|
|
388
|
+
this.emit("jump");
|
|
389
|
+
}
|
|
390
|
+
onUpdate() {
|
|
391
|
+
const driver = this._readActiveDriver();
|
|
392
|
+
this._apply(driver);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The active driver keeps the channel while it produces input — otherwise the
|
|
396
|
+
* keyboard and a stick would steal control from each other every frame. Once it
|
|
397
|
+
* goes quiet, the first driver with input takes over. The state is left filled
|
|
398
|
+
* by the winner, so nobody is read twice.
|
|
399
|
+
*/
|
|
400
|
+
_readActiveDriver() {
|
|
401
|
+
if (this._active && this._readDriver(this._active)) return this._active;
|
|
402
|
+
for (const driver of this._drivers) {
|
|
403
|
+
if (driver === this._active) continue;
|
|
404
|
+
if (this._readDriver(driver)) return driver;
|
|
405
|
+
}
|
|
406
|
+
this._resetState();
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
_readDriver(driver) {
|
|
410
|
+
this._resetState();
|
|
411
|
+
driver.read(this._state);
|
|
412
|
+
const { move, force, jump } = this._state;
|
|
413
|
+
return move.x !== 0 || move.y !== 0 || force > 0 || jump;
|
|
414
|
+
}
|
|
415
|
+
_resetState() {
|
|
416
|
+
const state = this._state;
|
|
417
|
+
state.move.set(0, 0);
|
|
418
|
+
state.force = 0;
|
|
419
|
+
state.run = false;
|
|
420
|
+
state.jump = false;
|
|
421
|
+
}
|
|
422
|
+
_apply(driver) {
|
|
423
|
+
const state = this._state;
|
|
424
|
+
this._active = driver ?? this._active;
|
|
425
|
+
const source = this._active?.kind ?? this._source;
|
|
426
|
+
if (source !== this._source) {
|
|
427
|
+
this._source = source;
|
|
428
|
+
this.emit("sourcechanged", source);
|
|
429
|
+
}
|
|
430
|
+
if (!this.move.equals(state.move)) {
|
|
431
|
+
this.move.copy(state.move);
|
|
432
|
+
this.emit("movechanged", this.move);
|
|
433
|
+
}
|
|
434
|
+
if (this._force !== state.force) {
|
|
435
|
+
this._force = state.force;
|
|
436
|
+
this.emit("forcechanged", state.force);
|
|
437
|
+
}
|
|
438
|
+
this._run = state.run;
|
|
439
|
+
if (state.jump && !this._jump) this.jump();
|
|
440
|
+
this._jump = state.jump;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region packages/world-runtime/src/behaviours/physics/_required-patch.ts
|
|
445
|
+
var lazyRapierPhysicsModule = lazy(async () => (await import("./RapierPhysics-CU6X--c7.js")).default);
|
|
446
|
+
/** The only place the physics module is created — called by the registry
|
|
447
|
+
* descriptor. The module itself takes rapier from `ctx.modules.addons` on awake. */
|
|
448
|
+
async function createRapierPhysicsModule(config) {
|
|
449
|
+
return new (await (lazyRapierPhysicsModule.get()))({ debug: config.debug });
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* What a physics behaviour needs on top of its class: the rapier lib (loaded
|
|
453
|
+
* with the other addons, before the world starts) and the physics ctx module.
|
|
454
|
+
* A collider on the scene is enough — no line in `settings.addons.json`.
|
|
455
|
+
*/
|
|
456
|
+
var rapierPhysicsPatch = {
|
|
457
|
+
requiredLibs: [LibId.Rapier],
|
|
458
|
+
apply: (starter) => applyRapierPhysicsPatch(starter)
|
|
459
|
+
};
|
|
460
|
+
/**
|
|
461
|
+
* Registers the physics module. Idempotent, and strictly before `start()` —
|
|
462
|
+
* three-start forbids `addModules` afterwards.
|
|
463
|
+
*/
|
|
464
|
+
async function applyRapierPhysicsPatch(starter) {
|
|
465
|
+
if (starter.ctx.modules.rapier) return;
|
|
466
|
+
const descriptor = physicsModuleDescriptor;
|
|
467
|
+
const module = await descriptor.create(descriptor.defaultConfig());
|
|
468
|
+
starter.addModules({ rapier: module });
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region packages/world-runtime/src/modules/physics/physics.module-descriptor.ts
|
|
472
|
+
var PHYSICS_ENGINES = ["rapier"];
|
|
473
|
+
/** Physics defaults: the rapier engine, debug off. */
|
|
474
|
+
function normalizePhysics(value) {
|
|
475
|
+
if (!value) return null;
|
|
476
|
+
if (typeof value === "string") return {
|
|
477
|
+
engine: value,
|
|
478
|
+
debug: false
|
|
479
|
+
};
|
|
480
|
+
return {
|
|
481
|
+
engine: value.engine,
|
|
482
|
+
debug: value.debug ?? false
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
var physicsModuleDescriptor = {
|
|
486
|
+
id: OptionalModuleId.Physics,
|
|
487
|
+
normalize: (raw) => normalizePhysics(raw),
|
|
488
|
+
defaultConfig: () => ({
|
|
489
|
+
engine: "rapier",
|
|
490
|
+
debug: false
|
|
491
|
+
}),
|
|
492
|
+
create: (config) => createRapierPhysicsModule(config),
|
|
493
|
+
requiredLibs: [LibId.Rapier],
|
|
494
|
+
schema: {
|
|
495
|
+
description: "Physics engine module. false | \"rapier\" | { engine, debug }.",
|
|
496
|
+
oneOf: [
|
|
497
|
+
{
|
|
498
|
+
type: "boolean",
|
|
499
|
+
enum: [false]
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
type: "string",
|
|
503
|
+
enum: [...PHYSICS_ENGINES]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
type: "object",
|
|
507
|
+
additionalProperties: false,
|
|
508
|
+
required: ["engine"],
|
|
509
|
+
properties: {
|
|
510
|
+
engine: {
|
|
511
|
+
type: "string",
|
|
512
|
+
enum: [...PHYSICS_ENGINES]
|
|
513
|
+
},
|
|
514
|
+
debug: {
|
|
515
|
+
type: "boolean",
|
|
516
|
+
description: "Draw collider debug lines from the start (the renderer is loaded on demand)."
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region packages/world-runtime/src/modules/optional-modules.ts
|
|
525
|
+
/**
|
|
526
|
+
* Table of contents for the world's pluggable modules: what can be enabled from
|
|
527
|
+
* `settings.addons.json → modules`. The descriptors themselves live next to
|
|
528
|
+
* their module code; this is only the list.
|
|
529
|
+
*
|
|
530
|
+
* Add a descriptor here and the module shows up both in the runtime and in the
|
|
531
|
+
* config's JSON schema (the platform builds it from `descriptor.schema`).
|
|
532
|
+
*/
|
|
533
|
+
var OPTIONAL_MODULES = [physicsModuleDescriptor, chatModuleDescriptor];
|
|
534
|
+
var DESCRIPTOR_BY_ID = new Map(OPTIONAL_MODULES.map((m) => [m.id, m]));
|
|
535
|
+
function getOptionalModuleDescriptor(id) {
|
|
536
|
+
const descriptor = DESCRIPTOR_BY_ID.get(id);
|
|
537
|
+
if (!descriptor) throw new Error(`Unknown world module "${id}"`);
|
|
538
|
+
return descriptor;
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
271
541
|
//#region packages/world-runtime/src/modules/is-mobile-device.ts
|
|
272
542
|
function isMobileDevice() {
|
|
273
543
|
if (typeof navigator === "undefined" || typeof window === "undefined") return false;
|
|
@@ -296,19 +566,25 @@ var EnvModule = class extends ContextModule {
|
|
|
296
566
|
//#endregion
|
|
297
567
|
//#region packages/world-runtime/src/modules/HtmlHudModule.ts
|
|
298
568
|
var { div } = van.tags;
|
|
569
|
+
var LAYER_STYLE = "position:fixed;inset:0;pointer-events:none;";
|
|
299
570
|
var HtmlHudModule = class extends ContextModule {
|
|
300
|
-
|
|
571
|
+
overlay;
|
|
572
|
+
underlay;
|
|
301
573
|
crosshair;
|
|
302
574
|
constructor() {
|
|
303
575
|
super();
|
|
304
576
|
this.crosshair = div({
|
|
305
|
-
id: "
|
|
577
|
+
id: "kvy-crosshair",
|
|
306
578
|
style: "position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);"
|
|
307
579
|
});
|
|
308
|
-
this.
|
|
309
|
-
id: "
|
|
310
|
-
style:
|
|
580
|
+
this.overlay = div({
|
|
581
|
+
id: "kvy-overlay",
|
|
582
|
+
style: LAYER_STYLE
|
|
311
583
|
}, this.crosshair);
|
|
584
|
+
this.underlay = div({
|
|
585
|
+
id: "kvy-underlay",
|
|
586
|
+
style: LAYER_STYLE
|
|
587
|
+
});
|
|
312
588
|
}
|
|
313
589
|
onAwake() {
|
|
314
590
|
const ctx = this.ctx;
|
|
@@ -316,8 +592,13 @@ var HtmlHudModule = class extends ContextModule {
|
|
|
316
592
|
ctx.on(ThreeContextEvents.Mount, this.onMount);
|
|
317
593
|
ctx.on(ThreeContextEvents.Unmount, this.onUnmount);
|
|
318
594
|
}
|
|
595
|
+
/** Appends nodes over the canvas (the HUD). */
|
|
319
596
|
append(...nodes) {
|
|
320
|
-
this.
|
|
597
|
+
this.overlay.append(...nodes);
|
|
598
|
+
}
|
|
599
|
+
/** Appends nodes under the canvas — visible only through a transparent canvas. */
|
|
600
|
+
appendUnderlay(...nodes) {
|
|
601
|
+
this.underlay.append(...nodes);
|
|
321
602
|
}
|
|
322
603
|
setCrosshair(innerHtml) {
|
|
323
604
|
this.crosshair.innerHTML = innerHtml;
|
|
@@ -328,54 +609,18 @@ var HtmlHudModule = class extends ContextModule {
|
|
|
328
609
|
this.ctx.off(ThreeContextEvents.Unmount, this.onUnmount);
|
|
329
610
|
}
|
|
330
611
|
onMount = (container) => {
|
|
331
|
-
container.
|
|
612
|
+
container.prepend(this.underlay);
|
|
613
|
+
container.append(this.overlay);
|
|
614
|
+
const canvas = this.ctx.renderer.domElement;
|
|
615
|
+
if (!canvas.style.position) canvas.style.position = "relative";
|
|
332
616
|
};
|
|
333
617
|
onUnmount = () => {
|
|
334
|
-
this.
|
|
618
|
+
this.overlay.remove();
|
|
619
|
+
this.underlay.remove();
|
|
335
620
|
};
|
|
336
621
|
};
|
|
337
622
|
//#endregion
|
|
338
623
|
//#region packages/world-runtime/src/modules/InputSystemModule.ts
|
|
339
|
-
var PlayerInput = class extends EventEmitter {
|
|
340
|
-
_forward = 0;
|
|
341
|
-
_lateral = 0;
|
|
342
|
-
_force = 1;
|
|
343
|
-
/**
|
|
344
|
-
* @default 0
|
|
345
|
-
*/
|
|
346
|
-
get forward() {
|
|
347
|
-
return this._forward;
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* @default 0
|
|
351
|
-
*/
|
|
352
|
-
get lateral() {
|
|
353
|
-
return this._lateral;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* @default 1
|
|
357
|
-
*/
|
|
358
|
-
get force() {
|
|
359
|
-
return this._force;
|
|
360
|
-
}
|
|
361
|
-
setDir(forward, lateral) {
|
|
362
|
-
this._forward = forward;
|
|
363
|
-
this._lateral = lateral;
|
|
364
|
-
this.emit("dirchanged", forward, lateral);
|
|
365
|
-
}
|
|
366
|
-
setForce(force) {
|
|
367
|
-
this._force = force;
|
|
368
|
-
this.emit("forcechanged", force);
|
|
369
|
-
}
|
|
370
|
-
jump() {
|
|
371
|
-
this.emit("jump");
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* 0 is keyboard, 1 is joystick
|
|
375
|
-
* @default 0
|
|
376
|
-
*/
|
|
377
|
-
mode = 0;
|
|
378
|
-
};
|
|
379
624
|
var InputSystemModule = class extends ContextModule {
|
|
380
625
|
_dom;
|
|
381
626
|
_keys = /* @__PURE__ */ new Set();
|
|
@@ -421,12 +666,30 @@ var InputSystemModule = class extends ContextModule {
|
|
|
421
666
|
isKeyDown = (key) => {
|
|
422
667
|
return this._keys.has(key);
|
|
423
668
|
};
|
|
424
|
-
|
|
669
|
+
_captureKeyboard = true;
|
|
670
|
+
/**
|
|
671
|
+
* The game listens to the keyboard on the whole document, not just the canvas
|
|
672
|
+
* (otherwise any HTML over the world would swallow input). `false` hands the
|
|
673
|
+
* keyboard entirely to the UI: the game sees no keys and held ones are reset.
|
|
674
|
+
* @default true
|
|
675
|
+
*/
|
|
676
|
+
get captureKeyboard() {
|
|
677
|
+
return this._captureKeyboard;
|
|
678
|
+
}
|
|
679
|
+
set captureKeyboard(value) {
|
|
680
|
+
if (this._captureKeyboard === value) return;
|
|
681
|
+
this._captureKeyboard = value;
|
|
682
|
+
if (!value) {
|
|
683
|
+
this.resetKeys();
|
|
684
|
+
this.emit("keyschanged");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
425
687
|
onAwake() {
|
|
426
688
|
const ctx = this.ctx;
|
|
427
689
|
this._dom = ctx.renderer.domElement;
|
|
428
690
|
window.addEventListener("resize", this.onWindowResize);
|
|
429
691
|
window.addEventListener("scroll", this.onWindowScroll);
|
|
692
|
+
window.addEventListener("blur", this.onBlur);
|
|
430
693
|
ctx.on(ThreeContextEvents.Resized, this.onDomResize);
|
|
431
694
|
const on = this._dom.addEventListener.bind(this._dom);
|
|
432
695
|
on("focus", this.onFocus);
|
|
@@ -436,13 +699,15 @@ var InputSystemModule = class extends ContextModule {
|
|
|
436
699
|
on("pointermove", this.onPointerMove);
|
|
437
700
|
on("pointerup", this.onPointerUp);
|
|
438
701
|
on("pointerleave", this.onPointerLeave);
|
|
439
|
-
|
|
440
|
-
|
|
702
|
+
document.addEventListener("keydown", this.onKeyDown);
|
|
703
|
+
document.addEventListener("keyup", this.onKeyUp);
|
|
704
|
+
document.addEventListener("focusin", this.onDocumentFocusIn);
|
|
441
705
|
}
|
|
442
706
|
/** three-start `ContextModule` has no `onDestroy`; teardown is explicit. */
|
|
443
707
|
dispose() {
|
|
444
708
|
window.removeEventListener("resize", this.onWindowResize);
|
|
445
709
|
window.removeEventListener("scroll", this.onWindowScroll);
|
|
710
|
+
window.removeEventListener("blur", this.onBlur);
|
|
446
711
|
this.ctx.off(ThreeContextEvents.Resized, this.onDomResize);
|
|
447
712
|
const off = this._dom.removeEventListener.bind(this._dom);
|
|
448
713
|
off("focus", this.onFocus);
|
|
@@ -452,8 +717,9 @@ var InputSystemModule = class extends ContextModule {
|
|
|
452
717
|
off("pointermove", this.onPointerMove);
|
|
453
718
|
off("pointerup", this.onPointerUp);
|
|
454
719
|
off("pointerleave", this.onPointerLeave);
|
|
455
|
-
|
|
456
|
-
|
|
720
|
+
document.removeEventListener("keydown", this.onKeyDown);
|
|
721
|
+
document.removeEventListener("keyup", this.onKeyUp);
|
|
722
|
+
document.removeEventListener("focusin", this.onDocumentFocusIn);
|
|
457
723
|
}
|
|
458
724
|
onWindowResize = () => {
|
|
459
725
|
this.updateDomBoundingClientRect();
|
|
@@ -472,15 +738,28 @@ var InputSystemModule = class extends ContextModule {
|
|
|
472
738
|
this.resetKeys();
|
|
473
739
|
this.resetPointers();
|
|
474
740
|
};
|
|
741
|
+
onDocumentFocusIn = (event) => {
|
|
742
|
+
if (!isUiKeyboardTarget(event.target)) return;
|
|
743
|
+
this.resetKeys();
|
|
744
|
+
this.emit("keyschanged");
|
|
745
|
+
};
|
|
746
|
+
/** The key belongs to the game, not to the UI. */
|
|
747
|
+
acceptsKey(event) {
|
|
748
|
+
return this._captureKeyboard && !isUiKeyboardTarget(event.target);
|
|
749
|
+
}
|
|
475
750
|
onKeyDown = (event) => {
|
|
751
|
+
if (!this.acceptsKey(event)) return;
|
|
476
752
|
this.updFnKeys(event);
|
|
477
753
|
this._keys.add(event.code);
|
|
478
754
|
this.emit("keyschanged");
|
|
755
|
+
this.emit("keydown", event);
|
|
479
756
|
};
|
|
480
757
|
onKeyUp = (event) => {
|
|
758
|
+
if (!this.acceptsKey(event)) return;
|
|
481
759
|
this.updFnKeys(event);
|
|
482
760
|
this._keys.delete(event.code);
|
|
483
761
|
this.emit("keyschanged");
|
|
762
|
+
this.emit("keyup", event);
|
|
484
763
|
};
|
|
485
764
|
onPointerEnter = (_) => {};
|
|
486
765
|
onPointerDown = (event) => {
|
|
@@ -577,6 +856,21 @@ var InputSystemModule = class extends ContextModule {
|
|
|
577
856
|
return this._domBoundingClientRect;
|
|
578
857
|
}
|
|
579
858
|
};
|
|
859
|
+
/**
|
|
860
|
+
* Focus is in a text field (or contenteditable) — the keyboard belongs to the UI
|
|
861
|
+
* and the game does not see it. Engine plumbing: nobody has to think about it,
|
|
862
|
+
* `isKeyDown` simply stays quiet while the player types.
|
|
863
|
+
*/
|
|
864
|
+
function isUiKeyboardTarget(target) {
|
|
865
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
866
|
+
if (target.isContentEditable) return true;
|
|
867
|
+
switch (target.tagName) {
|
|
868
|
+
case "INPUT":
|
|
869
|
+
case "TEXTAREA":
|
|
870
|
+
case "SELECT": return true;
|
|
871
|
+
default: return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
580
874
|
//#endregion
|
|
581
875
|
//#region packages/world-runtime/src/modules/SoundsModule.ts
|
|
582
876
|
var SoundsModule = class extends ContextModule {
|
|
@@ -599,39 +893,36 @@ var UtilsModule = class extends ContextModule {
|
|
|
599
893
|
};
|
|
600
894
|
//#endregion
|
|
601
895
|
//#region packages/world-runtime/src/modules/index.ts
|
|
602
|
-
/**
|
|
603
|
-
|
|
604
|
-
|
|
896
|
+
/**
|
|
897
|
+
* Assembles the world's ctx modules: core ones always, pluggable ones per config.
|
|
898
|
+
* Pluggable classes are pulled dynamically, so a disabled module never reaches
|
|
899
|
+
* the bundle. Call strictly before `start()` and with addons already loaded: the
|
|
900
|
+
* physics module takes rapier from `ctx.modules.addons`.
|
|
901
|
+
*
|
|
902
|
+
* A module the scene needs without a line in the config (physics when a physics
|
|
903
|
+
* behaviour is present) is added separately — through the behaviour's
|
|
904
|
+
* `meta.requiredPatch`, with the same descriptor and idempotently.
|
|
905
|
+
*/
|
|
906
|
+
async function createWorldModules(mode = "prod", options) {
|
|
907
|
+
const modules = {
|
|
908
|
+
addons: options.addons,
|
|
909
|
+
controls: new ControlsModule(),
|
|
605
910
|
env: new EnvModule(mode),
|
|
606
911
|
html: new HtmlHudModule(),
|
|
607
912
|
input: new InputSystemModule(),
|
|
608
913
|
sounds: new SoundsModule(),
|
|
609
914
|
utils: new UtilsModule()
|
|
610
915
|
};
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
var lazyRapierDebugModule = lazy(async () => (await import("./RapierDebugRenderer-sURZDLP4.js")).default);
|
|
622
|
-
/**
|
|
623
|
-
* Registers rapier modules before bootstrap: three-start forbids `addModules`
|
|
624
|
-
* after `start()`. Idempotent — a repeated call sees the module in ctx and exits.
|
|
625
|
-
*/
|
|
626
|
-
async function applyRapierPhysicsPatch(starter) {
|
|
627
|
-
const RAPIER = await lazyRapier.get();
|
|
628
|
-
const RapierPhysicsModule = await lazyRapierPhysicsModule.get();
|
|
629
|
-
const RapierDebugRenderer = await lazyRapierDebugModule.get();
|
|
630
|
-
if (RapierPhysics.findInCtx(starter.ctx)) return;
|
|
631
|
-
starter.addModules({
|
|
632
|
-
rapier: new RapierPhysicsModule(RAPIER),
|
|
633
|
-
rapierDebug: new RapierDebugRenderer()
|
|
634
|
-
});
|
|
916
|
+
for (const descriptor of OPTIONAL_MODULES) {
|
|
917
|
+
const config = descriptor.normalize(options.modules?.[descriptor.id]);
|
|
918
|
+
if (!config) continue;
|
|
919
|
+
try {
|
|
920
|
+
modules[descriptor.id] = await descriptor.create(config);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error(`[modules] failed to create "${descriptor.id}"`, error);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return modules;
|
|
635
926
|
}
|
|
636
927
|
//#endregion
|
|
637
928
|
//#region packages/world-runtime/src/behaviours/registry.ts
|
|
@@ -664,7 +955,7 @@ var entries = [
|
|
|
664
955
|
{
|
|
665
956
|
uuid: "fpscameracontroller",
|
|
666
957
|
name: "FpsCameraController",
|
|
667
|
-
load: () => loadDefault(import("./FpsCameraController-
|
|
958
|
+
load: () => loadDefault(import("./FpsCameraController-2GNWJM5D.js"))
|
|
668
959
|
},
|
|
669
960
|
{
|
|
670
961
|
uuid: "sightinteractablearea",
|
|
@@ -679,56 +970,56 @@ var entries = [
|
|
|
679
970
|
{
|
|
680
971
|
uuid: "rrb",
|
|
681
972
|
name: "Rigidbody",
|
|
682
|
-
load: () => loadDefault(import("./Rigidbody-
|
|
683
|
-
requiredPatch:
|
|
973
|
+
load: () => loadDefault(import("./Rigidbody-DLhN8ddv.js").then((n) => n.n)),
|
|
974
|
+
requiredPatch: rapierPhysicsPatch
|
|
684
975
|
},
|
|
685
976
|
{
|
|
686
977
|
uuid: "rapierballcol",
|
|
687
978
|
name: "BallCollider",
|
|
688
|
-
load: () => loadDefault(import("./BallCollider-
|
|
689
|
-
requiredPatch:
|
|
979
|
+
load: () => loadDefault(import("./BallCollider-3L5vOrcL.js")),
|
|
980
|
+
requiredPatch: rapierPhysicsPatch
|
|
690
981
|
},
|
|
691
982
|
{
|
|
692
983
|
uuid: "rapiercapscol",
|
|
693
984
|
name: "CapsuleCollider",
|
|
694
|
-
load: () => loadDefault(import("./CapsuleCollider-
|
|
695
|
-
requiredPatch:
|
|
985
|
+
load: () => loadDefault(import("./CapsuleCollider-CBTsR-Ys.js")),
|
|
986
|
+
requiredPatch: rapierPhysicsPatch
|
|
696
987
|
},
|
|
697
988
|
{
|
|
698
989
|
uuid: "rapierconecol",
|
|
699
990
|
name: "ConeCollider",
|
|
700
|
-
load: () => loadDefault(import("./ConeCollider-
|
|
701
|
-
requiredPatch:
|
|
991
|
+
load: () => loadDefault(import("./ConeCollider-C3XU3Y9X.js")),
|
|
992
|
+
requiredPatch: rapierPhysicsPatch
|
|
702
993
|
},
|
|
703
994
|
{
|
|
704
995
|
uuid: "rapierconvxcol",
|
|
705
996
|
name: "ConvexMeshCollider",
|
|
706
|
-
load: () => loadDefault(import("./ConvexMeshCollider-
|
|
707
|
-
requiredPatch:
|
|
997
|
+
load: () => loadDefault(import("./ConvexMeshCollider-DzNV4HTO.js")),
|
|
998
|
+
requiredPatch: rapierPhysicsPatch
|
|
708
999
|
},
|
|
709
1000
|
{
|
|
710
1001
|
uuid: "rapiercubcol",
|
|
711
1002
|
name: "CuboidCollider",
|
|
712
|
-
load: () => loadDefault(import("./CuboidCollider-
|
|
713
|
-
requiredPatch:
|
|
1003
|
+
load: () => loadDefault(import("./CuboidCollider-Dp1DZoFM.js")),
|
|
1004
|
+
requiredPatch: rapierPhysicsPatch
|
|
714
1005
|
},
|
|
715
1006
|
{
|
|
716
1007
|
uuid: "rapiercylcol",
|
|
717
1008
|
name: "CylinderCollider",
|
|
718
|
-
load: () => loadDefault(import("./CylinderCollider-
|
|
719
|
-
requiredPatch:
|
|
1009
|
+
load: () => loadDefault(import("./CylinderCollider-C0h_nhB5.js")),
|
|
1010
|
+
requiredPatch: rapierPhysicsPatch
|
|
720
1011
|
},
|
|
721
1012
|
{
|
|
722
1013
|
uuid: "rapiertricol",
|
|
723
1014
|
name: "TrimeshCollider",
|
|
724
1015
|
load: () => loadDefault(import("./TrimeshCollider-7dIs_xtZ.js")),
|
|
725
|
-
requiredPatch:
|
|
1016
|
+
requiredPatch: rapierPhysicsPatch
|
|
726
1017
|
},
|
|
727
1018
|
{
|
|
728
1019
|
uuid: "rapierfpsplayercontroller",
|
|
729
1020
|
name: "RapierFpsPlayerController",
|
|
730
|
-
load: () => loadDefault(import("./RapierFpsPlayerController-
|
|
731
|
-
requiredPatch:
|
|
1021
|
+
load: () => loadDefault(import("./RapierFpsPlayerController-DthD8dw9.js")),
|
|
1022
|
+
requiredPatch: rapierPhysicsPatch
|
|
732
1023
|
}
|
|
733
1024
|
];
|
|
734
1025
|
/** Registry of built-in behaviours by uuid — source of truth for both game and export. */
|
|
@@ -803,81 +1094,124 @@ function createFont(url, family, mime, name) {
|
|
|
803
1094
|
return source;
|
|
804
1095
|
}
|
|
805
1096
|
//#endregion
|
|
806
|
-
//#region packages/world-runtime/src/
|
|
1097
|
+
//#region packages/world-runtime/src/addons/builtin-libs.ts
|
|
807
1098
|
/**
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
* - game: dynamic loading (`lib-loaders`),
|
|
813
|
-
* - editor: JSON schema (`libs` props) + require typings (`AddonsRequireTypings`).
|
|
814
|
-
*
|
|
815
|
-
* Adding a lib = add an entry here (+ loader in game, + d.ts loader in editor).
|
|
1099
|
+
* Libraries the runtime bundles itself: `require("<key>")` hands these back in
|
|
1100
|
+
* every world, no config and no importer involved. Metadata (keys, labels) is
|
|
1101
|
+
* in `lib-registry`; the modules live here so the registry stays importable
|
|
1102
|
+
* from the editor without dragging three & co along.
|
|
816
1103
|
*/
|
|
817
|
-
var
|
|
818
|
-
LibId
|
|
819
|
-
LibId
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
[
|
|
824
|
-
|
|
825
|
-
requireKey: "@tweenjs/tween.js",
|
|
826
|
-
label: "Tween.js"
|
|
827
|
-
},
|
|
828
|
-
["anime"]: {
|
|
829
|
-
id: "anime",
|
|
830
|
-
requireKey: "animejs/animation",
|
|
831
|
-
label: "Anime.js (animate)"
|
|
832
|
-
}
|
|
1104
|
+
var BUILTIN_LIB_MODULES = {
|
|
1105
|
+
[LibId.Three]: THREE,
|
|
1106
|
+
[LibId.Tsl]: TSL,
|
|
1107
|
+
[LibId.ThreeStart]: ThreeStartNS,
|
|
1108
|
+
[LibId.Van]: van,
|
|
1109
|
+
[LibId.Nanostores]: nanostores,
|
|
1110
|
+
[LibId.EventEmitter]: EventEmitter,
|
|
1111
|
+
[LibId.Howler]: howler
|
|
833
1112
|
};
|
|
834
|
-
var LIB_IDS = Object.values(LibId);
|
|
835
|
-
var LIB_DESCRIPTORS = LIB_IDS.map((id) => LIBS[id]);
|
|
836
|
-
function libByRequireKey(requireKey) {
|
|
837
|
-
return LIB_DESCRIPTORS.find((l) => l.requireKey === requireKey);
|
|
838
|
-
}
|
|
839
|
-
//#endregion
|
|
840
|
-
//#region packages/world-runtime/src/settings/lib-loaders.ts
|
|
841
1113
|
/**
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
1114
|
+
* The subset of built-ins that is also a bare global in script scope — same
|
|
1115
|
+
* objects as above, so `THREE === require("three")`. Typed literally: the
|
|
1116
|
+
* script context contract depends on these exact shapes.
|
|
845
1117
|
*/
|
|
846
|
-
var
|
|
847
|
-
|
|
848
|
-
|
|
1118
|
+
var BUILTIN_GLOBALS = {
|
|
1119
|
+
THREE,
|
|
1120
|
+
TSL,
|
|
1121
|
+
van
|
|
849
1122
|
};
|
|
850
1123
|
//#endregion
|
|
851
|
-
//#region packages/world-runtime/src/
|
|
1124
|
+
//#region packages/world-runtime/src/addons/required-libs.ts
|
|
852
1125
|
/**
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
1126
|
+
* Libs the world needs beyond what `libs` lists: an enabled module or a built-in
|
|
1127
|
+
* behaviour on the scene declares them itself (`requiredLibs`), so physics keeps
|
|
1128
|
+
* working without a line in `settings.addons.json`. Same answer for the game and
|
|
1129
|
+
* for the export — the build emits importers from it.
|
|
857
1130
|
*/
|
|
858
|
-
|
|
1131
|
+
function collectRequiredLibs(addons, usedBuiltinUuids = []) {
|
|
1132
|
+
const required = /* @__PURE__ */ new Set();
|
|
1133
|
+
const configured = addons?.libs ?? {};
|
|
1134
|
+
for (const id of Object.keys(configured)) if (configured[id] && LIB_IDS.includes(id) && !LIBS[id].builtin) required.add(id);
|
|
1135
|
+
for (const descriptor of OPTIONAL_MODULES) {
|
|
1136
|
+
if (!descriptor.requiredLibs?.length) continue;
|
|
1137
|
+
if (descriptor.normalize(addons?.modules?.[descriptor.id]) === null) continue;
|
|
1138
|
+
for (const id of descriptor.requiredLibs) required.add(id);
|
|
1139
|
+
}
|
|
1140
|
+
for (const uuid of usedBuiltinUuids) {
|
|
1141
|
+
const libs = getBuiltinBehaviour(uuid)?.requiredPatch?.requiredLibs;
|
|
1142
|
+
if (libs) for (const id of libs) required.add(id);
|
|
1143
|
+
}
|
|
1144
|
+
return [...required];
|
|
1145
|
+
}
|
|
1146
|
+
//#endregion
|
|
1147
|
+
//#region packages/world-runtime/src/addons/AddonsRuntime.ts
|
|
1148
|
+
/**
|
|
1149
|
+
* External dependencies of the world: libs and external scripts. Built-in libs
|
|
1150
|
+
* are registered right away (the runtime bundles them), optional ones are loaded
|
|
1151
|
+
* before the world starts — enabled in `settings.addons.json → libs` or declared
|
|
1152
|
+
* by a module/behaviour through `requiredLibs`. Scripts reach all of them by
|
|
1153
|
+
* `require("<key>")`.
|
|
1154
|
+
*
|
|
1155
|
+
* This is the `addons` ctx module, but it also works without a ctx: launcher
|
|
1156
|
+
* worlds have no 3d context while `require` still has to work, so there the
|
|
1157
|
+
* instance is used directly. How to import optional packages is up to the
|
|
1158
|
+
* runtime owner (see `WorldImports`).
|
|
1159
|
+
*/
|
|
1160
|
+
var AddonsRuntime = class extends ContextModule {
|
|
1161
|
+
_imports;
|
|
859
1162
|
/** libs — by requireKey (`@tweenjs/tween.js`), externals — by accessor (`Tone`). */
|
|
860
1163
|
_byKey = /* @__PURE__ */ new Map();
|
|
1164
|
+
_byLibId = /* @__PURE__ */ new Map();
|
|
861
1165
|
_scriptTags = [];
|
|
862
|
-
|
|
863
|
-
|
|
1166
|
+
constructor(_imports = {}) {
|
|
1167
|
+
super();
|
|
1168
|
+
this._imports = _imports;
|
|
1169
|
+
this._registerBuiltins();
|
|
1170
|
+
}
|
|
1171
|
+
/** `usedBuiltinUuids` — built-in behaviours on the scene; they may require libs. */
|
|
1172
|
+
async load(addons, usedBuiltinUuids = []) {
|
|
1173
|
+
await this._loadLibs(collectRequiredLibs(addons, usedBuiltinUuids));
|
|
864
1174
|
await this._loadExternals(addons.externals);
|
|
865
1175
|
}
|
|
866
|
-
/** Resolves a require key:
|
|
1176
|
+
/** Resolves a require key: a lib or an external accessor; undefined otherwise. */
|
|
867
1177
|
resolve(key) {
|
|
868
1178
|
return this._byKey.get(key);
|
|
869
1179
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1180
|
+
/** The lib itself, for runtime code that knows what it needs (physics → rapier). */
|
|
1181
|
+
lib(id) {
|
|
1182
|
+
return this._byLibId.get(id);
|
|
1183
|
+
}
|
|
1184
|
+
/** Libs bundled with the runtime: no importer, no config, always available. */
|
|
1185
|
+
_registerBuiltins() {
|
|
1186
|
+
for (const descriptor of BUILTIN_LIB_DESCRIPTORS) {
|
|
1187
|
+
const module = BUILTIN_LIB_MODULES[descriptor.id];
|
|
1188
|
+
if (module === void 0) {
|
|
1189
|
+
console.error(`[addons] built-in lib "${descriptor.id}" has no module`);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
this._register(descriptor.id, module);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
async _loadLibs(ids) {
|
|
1196
|
+
for (const id of ids) {
|
|
1197
|
+
const descriptor = LIBS[id];
|
|
1198
|
+
const load = this._imports.libs?.[id];
|
|
1199
|
+
if (!load) {
|
|
1200
|
+
console.error(`[addons] lib "${id}" is needed but has no importer`);
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
874
1203
|
try {
|
|
875
|
-
|
|
1204
|
+
const module = await load();
|
|
1205
|
+
this._register(id, unwrapLib(id, await (descriptor.init?.(module) ?? module)));
|
|
876
1206
|
} catch (e) {
|
|
877
1207
|
console.error(`[addons] failed to load lib "${id}"`, e);
|
|
878
1208
|
}
|
|
879
1209
|
}
|
|
880
1210
|
}
|
|
1211
|
+
_register(id, value) {
|
|
1212
|
+
this._byLibId.set(id, value);
|
|
1213
|
+
this._byKey.set(LIBS[id].requireKey, value);
|
|
1214
|
+
}
|
|
881
1215
|
async _loadExternals(externals) {
|
|
882
1216
|
if (!externals) return;
|
|
883
1217
|
for (const ext of externals) try {
|
|
@@ -907,37 +1241,76 @@ var AddonsRuntime = class {
|
|
|
907
1241
|
for (const tag of this._scriptTags) tag.remove();
|
|
908
1242
|
this._scriptTags.length = 0;
|
|
909
1243
|
this._byKey.clear();
|
|
1244
|
+
this._byLibId.clear();
|
|
910
1245
|
}
|
|
911
1246
|
};
|
|
1247
|
+
/** A package like nipplejs exposes its API as the default export — that is what
|
|
1248
|
+
* `require` should hand back. */
|
|
1249
|
+
function unwrapLib(id, module) {
|
|
1250
|
+
if (!LIBS[id].defaultExport) return module;
|
|
1251
|
+
return module.default ?? module;
|
|
1252
|
+
}
|
|
912
1253
|
//#endregion
|
|
913
|
-
//#region packages/world-runtime/src/
|
|
1254
|
+
//#region packages/world-runtime/src/world-stores.ts
|
|
914
1255
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
* The only module so far is physics.
|
|
921
|
-
* Installation lives in game (`game/settings/module-installers`).
|
|
1256
|
+
* Shared reactive key-value state of a world (`world.stores` in scripts) — the
|
|
1257
|
+
* state counterpart of the `world.events` bus. Stores are created lazily on
|
|
1258
|
+
* first touch; `undefined` means "no value", so `delete`/`clear` only reset
|
|
1259
|
+
* values and never invalidate a reference taken from `of()`.
|
|
922
1260
|
*/
|
|
923
|
-
var
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}
|
|
940
|
-
|
|
1261
|
+
var WorldStores = class {
|
|
1262
|
+
_stores = /* @__PURE__ */ new Map();
|
|
1263
|
+
/** The store behind a key, created on first touch. */
|
|
1264
|
+
of(key) {
|
|
1265
|
+
let store = this._stores.get(key);
|
|
1266
|
+
if (!store) {
|
|
1267
|
+
store = atom(void 0);
|
|
1268
|
+
this._stores.set(key, store);
|
|
1269
|
+
}
|
|
1270
|
+
return store;
|
|
1271
|
+
}
|
|
1272
|
+
get(key) {
|
|
1273
|
+
return this.of(key).get();
|
|
1274
|
+
}
|
|
1275
|
+
set(key, value) {
|
|
1276
|
+
this.of(key).set(value);
|
|
1277
|
+
}
|
|
1278
|
+
/** Subscribe to a key; returns unsubscribe. Not called with the current value. */
|
|
1279
|
+
listen(key, listener) {
|
|
1280
|
+
return this.of(key).listen(listener);
|
|
1281
|
+
}
|
|
1282
|
+
/** Same as `listen`, but also fires immediately with the current value. */
|
|
1283
|
+
subscribe(key, listener) {
|
|
1284
|
+
return this.of(key).subscribe(listener);
|
|
1285
|
+
}
|
|
1286
|
+
/** A value has been written to the key and not deleted since. */
|
|
1287
|
+
has(key) {
|
|
1288
|
+
return this._stores.get(key)?.get() !== void 0;
|
|
1289
|
+
}
|
|
1290
|
+
/** Keys that currently hold a value. */
|
|
1291
|
+
keys() {
|
|
1292
|
+
const keys = [];
|
|
1293
|
+
for (const [key, store] of this._stores) if (store.get() !== void 0) keys.push(key);
|
|
1294
|
+
return keys;
|
|
1295
|
+
}
|
|
1296
|
+
/** Plain object with all current values — for debugging and saves. */
|
|
1297
|
+
snapshot() {
|
|
1298
|
+
const result = {};
|
|
1299
|
+
for (const [key, store] of this._stores) {
|
|
1300
|
+
const value = store.get();
|
|
1301
|
+
if (value !== void 0) result[key] = value;
|
|
1302
|
+
}
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
1305
|
+
/** Resets the key to `undefined`; listeners are notified. */
|
|
1306
|
+
delete(key) {
|
|
1307
|
+
this._stores.get(key)?.set(void 0);
|
|
1308
|
+
}
|
|
1309
|
+
/** Resets every key; listeners are notified. */
|
|
1310
|
+
clear() {
|
|
1311
|
+
for (const store of this._stores.values()) store.set(void 0);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
941
1314
|
//#endregion
|
|
942
1315
|
//#region packages/world-runtime/src/WorldRuntime.ts
|
|
943
1316
|
/**
|
|
@@ -949,17 +1322,26 @@ var WorldRuntime = class {
|
|
|
949
1322
|
options;
|
|
950
1323
|
/** World lifecycle event bus (`world.events` in scripts). */
|
|
951
1324
|
emitter = new EventEmitter();
|
|
952
|
-
|
|
1325
|
+
/** Shared reactive state of the world (`world.stores` in scripts). */
|
|
1326
|
+
stores = new WorldStores();
|
|
1327
|
+
_addons;
|
|
953
1328
|
_executedScripts = /* @__PURE__ */ new Map();
|
|
954
1329
|
_builtins = /* @__PURE__ */ new Map();
|
|
955
1330
|
_styleElements = [];
|
|
956
1331
|
_resources = {};
|
|
1332
|
+
/** Reverse index for `getUrl(resource)` — play mode resolves objects too. */
|
|
1333
|
+
_pathByResource = /* @__PURE__ */ new Map();
|
|
957
1334
|
_ctx = null;
|
|
958
1335
|
_bloom = null;
|
|
959
1336
|
_scriptContext = null;
|
|
960
1337
|
constructor(definition, options) {
|
|
961
1338
|
this.definition = definition;
|
|
962
1339
|
this.options = options;
|
|
1340
|
+
this._addons = new AddonsRuntime(definition.imports);
|
|
1341
|
+
}
|
|
1342
|
+
/** The world's external dependencies; also the `addons` ctx module. */
|
|
1343
|
+
get addons() {
|
|
1344
|
+
return this._addons;
|
|
963
1345
|
}
|
|
964
1346
|
/** ctx appears at the 3d-context creation step; unavailable to scripts before that. */
|
|
965
1347
|
get ctx() {
|
|
@@ -974,14 +1356,18 @@ var WorldRuntime = class {
|
|
|
974
1356
|
setBloom(bloom) {
|
|
975
1357
|
this._bloom = bloom;
|
|
976
1358
|
}
|
|
977
|
-
/** @internal
|
|
1359
|
+
/** @internal Libs (config + required by modules/behaviours) and externals. */
|
|
978
1360
|
async loadAddons() {
|
|
979
|
-
|
|
980
|
-
if (addons) await this._addons.load(addons);
|
|
1361
|
+
await this._addons.load(this.definition.addons ?? {}, this.definition.usedBuiltins ?? []);
|
|
981
1362
|
}
|
|
982
1363
|
/** @internal World resources by asset path — result of `loadAssets`. */
|
|
983
1364
|
setResources(resources) {
|
|
984
1365
|
this._resources = resources;
|
|
1366
|
+
this._pathByResource.clear();
|
|
1367
|
+
for (const path in resources) {
|
|
1368
|
+
const resource = resources[path];
|
|
1369
|
+
if (resource !== null && typeof resource === "object") this._pathByResource.set(resource, path);
|
|
1370
|
+
}
|
|
985
1371
|
}
|
|
986
1372
|
reportProgress(stage, details) {
|
|
987
1373
|
const progress = {
|
|
@@ -990,9 +1376,11 @@ var WorldRuntime = class {
|
|
|
990
1376
|
};
|
|
991
1377
|
this.options.onProgress?.(progress);
|
|
992
1378
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1379
|
+
/** Accepts an asset path or a resource returned by `require` — like play mode. */
|
|
1380
|
+
getUrl = (source) => {
|
|
1381
|
+
const path = typeof source === "string" ? source : this._pathByResource.get(source);
|
|
1382
|
+
const url = path === void 0 ? void 0 : this.definition.assets[path];
|
|
1383
|
+
if (url === void 0) throw new Error(`World runtime: unknown asset "${typeof source === "string" ? source : path ?? "<unknown resource>"}"`);
|
|
996
1384
|
if (isAbsoluteUrl(url)) return url;
|
|
997
1385
|
return `${this.options.assetsBaseUrl ?? ""}${url}`;
|
|
998
1386
|
};
|
|
@@ -1027,16 +1415,11 @@ var WorldRuntime = class {
|
|
|
1027
1415
|
const patch = getBuiltinBehaviour(uuid)?.requiredPatch;
|
|
1028
1416
|
if (!patch) continue;
|
|
1029
1417
|
try {
|
|
1030
|
-
await patch(starter);
|
|
1418
|
+
await patch.apply(starter);
|
|
1031
1419
|
} catch (error) {
|
|
1032
1420
|
console.error(`Required patch of "${uuid}" failed`, error);
|
|
1033
1421
|
}
|
|
1034
1422
|
}
|
|
1035
|
-
if (normalizePhysics(this.definition.addons?.modules?.physics)) try {
|
|
1036
|
-
await applyRapierPhysicsPatch(starter);
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
console.error("Physics module patch failed", error);
|
|
1039
|
-
}
|
|
1040
1423
|
}
|
|
1041
1424
|
/** Addon (built-in lib / external) → script by path → asset resource. Same
|
|
1042
1425
|
* resolve order as in play mode. */
|
|
@@ -1050,7 +1433,8 @@ var WorldRuntime = class {
|
|
|
1050
1433
|
};
|
|
1051
1434
|
/**
|
|
1052
1435
|
* Runtime spawn: a prefab (build-time factory) is assembled anew, a regular
|
|
1053
|
-
* object is cloned.
|
|
1436
|
+
* object is cloned. The instance is only created and returned — adding it to
|
|
1437
|
+
* the scene hierarchy is up to the caller (`ctx.scene.add(obj)`).
|
|
1054
1438
|
*/
|
|
1055
1439
|
instantiate = (source, options) => {
|
|
1056
1440
|
const object = typeof source === "function" ? source() : clone(source);
|
|
@@ -1105,6 +1489,7 @@ var WorldRuntime = class {
|
|
|
1105
1489
|
this._styleElements.length = 0;
|
|
1106
1490
|
this._executedScripts.clear();
|
|
1107
1491
|
this.emitter.removeAllListeners();
|
|
1492
|
+
this.stores.clear();
|
|
1108
1493
|
}
|
|
1109
1494
|
/** Narrow bloom facade for scripts: mode is read-only, params are live
|
|
1110
1495
|
* (uniforms, can be animated per frame). `null` — bloom is disabled. */
|
|
@@ -1144,18 +1529,22 @@ var WorldRuntime = class {
|
|
|
1144
1529
|
get ctx() {
|
|
1145
1530
|
return runtime.ctx;
|
|
1146
1531
|
},
|
|
1532
|
+
/** Short path to the chat module, same as in play mode. */
|
|
1533
|
+
get chat() {
|
|
1534
|
+
return runtime.ctx.modules.chat;
|
|
1535
|
+
},
|
|
1147
1536
|
events: this.emitter,
|
|
1148
1537
|
emitter: this.emitter,
|
|
1538
|
+
stores: this.stores,
|
|
1149
1539
|
rendering: { get bloom() {
|
|
1150
1540
|
return runtime.bloomApi;
|
|
1151
1541
|
} }
|
|
1152
1542
|
},
|
|
1543
|
+
modules: lazyProxy(() => runtime.ctx.modules),
|
|
1153
1544
|
require: this.require,
|
|
1154
1545
|
instantiate: this.instantiate,
|
|
1155
1546
|
getUrl: this.getUrl,
|
|
1156
|
-
|
|
1157
|
-
TSL,
|
|
1158
|
-
van
|
|
1547
|
+
...BUILTIN_GLOBALS
|
|
1159
1548
|
};
|
|
1160
1549
|
return this._scriptContext;
|
|
1161
1550
|
}
|
|
@@ -1177,7 +1566,7 @@ function isAbsoluteUrl(url) {
|
|
|
1177
1566
|
}
|
|
1178
1567
|
//#endregion
|
|
1179
1568
|
//#region packages/world-runtime/src/version.gen.ts
|
|
1180
|
-
var RUNTIME_VERSION = "0.
|
|
1569
|
+
var RUNTIME_VERSION = "0.4.2";
|
|
1181
1570
|
//#endregion
|
|
1182
1571
|
//#region packages/world-runtime/src/version.ts
|
|
1183
1572
|
/**
|
|
@@ -1243,7 +1632,10 @@ async function createWorld(definition, runtime) {
|
|
|
1243
1632
|
scene: new THREE.Scene(),
|
|
1244
1633
|
autoInitRenderer: false
|
|
1245
1634
|
}, runtime);
|
|
1246
|
-
world.addModules(createWorldModules("prod"
|
|
1635
|
+
world.addModules(await createWorldModules("prod", {
|
|
1636
|
+
addons: runtime.addons,
|
|
1637
|
+
modules: runtime.definition.addons?.modules
|
|
1638
|
+
}));
|
|
1247
1639
|
applyRendererConfig(renderer, config);
|
|
1248
1640
|
if (config.bloom?.enabled) {
|
|
1249
1641
|
if (config.bloom.mode === "emissive") enableSceneEmissiveMrt(world.ctx.scenePass);
|
|
@@ -1678,7 +2070,7 @@ var UNIFORM_DEFAULTS = {
|
|
|
1678
2070
|
texture: () => getDefaultResource(DefaultResourceId.Texture)
|
|
1679
2071
|
};
|
|
1680
2072
|
//#endregion
|
|
1681
|
-
//#region packages/world-runtime/src/
|
|
2073
|
+
//#region packages/world-runtime/src/addons/externals.ts
|
|
1682
2074
|
var EXTERNAL_TYPES = ["umd", "module"];
|
|
1683
2075
|
//#endregion
|
|
1684
2076
|
//#region packages/world-runtime/src/rendering/renderer-options.ts
|
|
@@ -1704,4 +2096,4 @@ var TONE_MAPPING_NAMES = [
|
|
|
1704
2096
|
"Neutral"
|
|
1705
2097
|
];
|
|
1706
2098
|
//#endregion
|
|
1707
|
-
export { AddonsRuntime, AssetLoaders, AudioSource, BLOOM_MODES, BUILTIN_BEHAVIOURS, BUILTIN_BEHAVIOUR_LIST, DEFAULT_RESOURCE_IDS, DefaultResourceId, EXTERNAL_TYPES, EnvModule, FontSource, HtmlHudModule, InputSystemModule, LIBS, LIB_DESCRIPTORS, LIB_IDS, LOADING_MANAGER, LibId,
|
|
2099
|
+
export { AddonsRuntime, AssetLoaders, AudioSource, BLOOM_MODES, BUILTIN_BEHAVIOURS, BUILTIN_BEHAVIOUR_LIST, BUILTIN_LIB_DESCRIPTORS, CHAT_INPUT_MODES, DEFAULT_RESOURCE_IDS, DefaultResourceId, EXTERNAL_TYPES, EnvModule, FontSource, HtmlHudModule, InputSystemModule, LIBS, LIB_DESCRIPTORS, LIB_IDS, LOADING_MANAGER, LibId, OPTIONAL_LIB_DESCRIPTORS, OPTIONAL_LIB_IDS, OPTIONAL_MODULES, OPTIONAL_MODULE_IDS, OptionalModuleId, PHYSICS_ENGINES, RUNTIME_VERSION, SHADOW_MAP_TYPES, SceneBloom, SoundsModule, TONE_MAPPING_NAMES, TslNode, UNIFORM_DEFAULTS, UNIFORM_TYPES, UtilsModule, VideoSource, WORLD_FORMAT_VERSION, World, WorldEvent, WorldStores, applyRendererConfig, applyTextureDefaults, collectModelSubResources, collectRequiredLibs, configureAssetLoaders, createAudio, createFont, createTslNode, createUniformNode, createVideo, createWorldModules, defineWorld, enableSceneEmissiveMrt, fontFormatFromMime, getBuiltinBehaviour, getDefaultResource, getHalfheightRadiusScale, getOptionalModuleDescriptor, getRadiusScale, isDefaultResourceId, isMobileDevice, isUniformType, lazyProxy, libByRequireKey, loadGeometry, loadHdr, loadModel, loadTexture, normalizeChat, normalizePhysics, parseGeometryJson, parseHdr, parseModel, rapierPhysicsPatch, setColor, uniformType };
|