@needle-tools/engine 5.1.0-alpha.4 → 5.1.0-alpha.6
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/CHANGELOG.md +39 -0
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DQCuBTVp.umd.cjs → needle-engine.bundle-5avtTUMM.umd.cjs} +149 -148
- package/dist/{needle-engine.bundle-AjVIot3d.min.js → needle-engine.bundle-BHcw4C8f.min.js} +187 -186
- package/dist/{needle-engine.bundle-B7cqsI4c.js → needle-engine.bundle-C0gPOq4m.js} +7522 -7092
- package/dist/needle-engine.d.ts +715 -176
- package/dist/needle-engine.js +595 -593
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +3 -1
- package/lib/engine/api.js +3 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -10
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_context.d.ts +20 -7
- package/lib/engine/engine_context.js +36 -14
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +23 -4
- package/lib/engine/engine_input.js +2 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +185 -57
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +10 -0
- package/lib/engine/engine_physics_rapier.js +6 -0
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_types.d.ts +10 -0
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.js +6 -1
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +101 -23
- package/lib/engine-components/AnimatorController.builder.js +88 -20
- package/lib/engine-components/AnimatorController.builder.js.map +1 -1
- package/lib/engine-components/AnimatorController.js +2 -0
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DropListener.js +3 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +0 -2
- package/lib/engine-components/OrbitControls.js +14 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +6 -6
- package/lib/engine-components/codegen/components.js +6 -6
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -7
- package/lib/engine-components/timeline/PlayableDirector.js +6 -6
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +175 -9
- package/lib/engine-components/timeline/TimelineBuilder.js +108 -2
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +15 -7
- package/lib/engine-components/timeline/TimelineTracks.js +22 -14
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +0 -1
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +62 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +55 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +1 -1
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +26 -8
- package/plugins/vite/license.js +42 -7
- package/src/engine/api.ts +4 -1
- package/src/engine/codegen/register_types.ts +10 -10
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_context.ts +41 -16
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_input.ts +27 -6
- package/src/engine/engine_license.ts +201 -55
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_physics_rapier.ts +20 -6
- package/src/engine/engine_types.ts +22 -12
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +6 -1
- package/src/engine-components/AnimatorController.builder.ts +163 -37
- package/src/engine-components/AnimatorController.ts +1 -0
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DropListener.ts +3 -0
- package/src/engine-components/OrbitControls.ts +16 -5
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +6 -6
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/timeline/PlayableDirector.ts +20 -20
- package/src/engine-components/timeline/TimelineBuilder.ts +277 -17
- package/src/engine-components/timeline/TimelineTracks.ts +24 -16
- package/src/engine-components/web/CursorFollow.ts +0 -1
- package/src/engine-components/webxr/WebXRImageTracking.ts +77 -7
- package/src/vite-env.d.ts +0 -16
|
@@ -86,24 +86,38 @@ async function requestNeedleCloud(path, options) {
|
|
|
86
86
|
|
|
87
87
|
/** @typedef {{ ok: boolean, status: number, statusText: string, text: string }} NeedleCloudHttpResponse */
|
|
88
88
|
|
|
89
|
+
/** @typedef {{ type: string, jwt: string | null }} LicenseResult */
|
|
90
|
+
|
|
89
91
|
/**
|
|
90
92
|
* Replace license string - used for webpack
|
|
91
93
|
* @param {string} code
|
|
92
94
|
* @param {DefaultOptions & {accessToken?:string, team:string|undefined}} opts
|
|
93
95
|
*/
|
|
94
96
|
export async function replaceLicense(code, opts) {
|
|
95
|
-
const index = code?.indexOf("
|
|
97
|
+
const index = code?.indexOf("EzdGPQg");
|
|
96
98
|
if (index >= 0) {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
99
|
+
const licenseResult = await resolveLicense(opts);
|
|
100
|
+
if (!licenseResult) {
|
|
99
101
|
return code;
|
|
100
102
|
}
|
|
101
103
|
const end = code.indexOf(";", index);
|
|
102
104
|
if (end >= 0) {
|
|
103
105
|
const line = code.substring(index, end);
|
|
104
|
-
const replaced = "
|
|
106
|
+
const replaced = "EzdGPQg = \"" + licenseResult.type + "\"";
|
|
105
107
|
code = code.replace(line, replaced);
|
|
106
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Also inject JWT if available
|
|
111
|
+
if (licenseResult.jwt) {
|
|
112
|
+
const jwtIndex = code.indexOf("_$InwX");
|
|
113
|
+
if (jwtIndex >= 0) {
|
|
114
|
+
const jwtEnd = code.indexOf(";", jwtIndex);
|
|
115
|
+
if (jwtEnd >= 0) {
|
|
116
|
+
const jwtLine = code.substring(jwtIndex, jwtEnd);
|
|
117
|
+
const jwtReplaced = "_$InwX = \"" + licenseResult.jwt + "\"";
|
|
118
|
+
code = code.replace(jwtLine, jwtReplaced);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
123
|
return code;
|
|
@@ -112,7 +126,7 @@ export async function replaceLicense(code, opts) {
|
|
|
112
126
|
/**
|
|
113
127
|
* Resolve the license using the needle engine licensing server
|
|
114
128
|
* @param {DefaultOptions & {accessToken?:string, team?:string} | null} args
|
|
115
|
-
* @returns {Promise<
|
|
129
|
+
* @returns {Promise<LicenseResult | null>}
|
|
116
130
|
*/
|
|
117
131
|
export async function resolveLicense(args = null) {
|
|
118
132
|
let accessToken = args?.accessToken;
|
|
@@ -226,10 +240,11 @@ export async function resolveLicense(args = null) {
|
|
|
226
240
|
/**
|
|
227
241
|
* @param {string} str License string
|
|
228
242
|
* @param {{includeFetchLine?: boolean}} [options]
|
|
243
|
+
* @returns {LicenseResult | null}
|
|
229
244
|
*/
|
|
230
245
|
function tryParseLicense(str, options = undefined) {
|
|
231
246
|
try {
|
|
232
|
-
/** @type {{needle_engine_license:string}} */
|
|
247
|
+
/** @type {{needle_engine_license:string, needle_license_jwt?:string}} */
|
|
233
248
|
const licenseJson = JSON.parse(str);
|
|
234
249
|
if (licenseJson.needle_engine_license) {
|
|
235
250
|
const success = `INFO: Successfully received \"${licenseJson.needle_engine_license?.toUpperCase()}\" license`;
|
|
@@ -239,7 +254,10 @@ function tryParseLicense(str, options = undefined) {
|
|
|
239
254
|
else {
|
|
240
255
|
logLicense(success);
|
|
241
256
|
}
|
|
242
|
-
return
|
|
257
|
+
return {
|
|
258
|
+
type: licenseJson.needle_engine_license,
|
|
259
|
+
jwt: licenseJson.needle_license_jwt || null,
|
|
260
|
+
};
|
|
243
261
|
}
|
|
244
262
|
if ("error" in licenseJson) {
|
|
245
263
|
logLicense(`ERROR in license check: \"${licenseJson.error}\"`, "error");
|
package/plugins/vite/license.js
CHANGED
|
@@ -8,7 +8,9 @@ import { loadConfig } from './config.js';
|
|
|
8
8
|
* @returns {import('vite').Plugin}
|
|
9
9
|
*/
|
|
10
10
|
export function needleLicense(command, config, userSettings) {
|
|
11
|
-
|
|
11
|
+
/** @type {import('../common/license.js').LicenseResult | null | undefined} */
|
|
12
|
+
let licenseResult = undefined;
|
|
13
|
+
let appliedLicense = false;
|
|
12
14
|
|
|
13
15
|
return {
|
|
14
16
|
name: "needle:license",
|
|
@@ -23,7 +25,7 @@ export function needleLicense(command, config, userSettings) {
|
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
licenseResult = await resolveLicense({
|
|
27
29
|
team: team,
|
|
28
30
|
accessToken: userSettings?.license?.accessToken,
|
|
29
31
|
loglevel: userSettings?.debugLicense === true ? "verbose" : undefined
|
|
@@ -31,25 +33,58 @@ export function needleLicense(command, config, userSettings) {
|
|
|
31
33
|
|
|
32
34
|
},
|
|
33
35
|
async transform(src, id) {
|
|
34
|
-
|
|
36
|
+
// Vite 4 and 8 handling:
|
|
37
|
+
const isNeedleEngineFile = id.includes("engine/engine_license")
|
|
38
|
+
|| id.includes("needle-tools_engine")
|
|
39
|
+
|| id.includes("@needle-tools")
|
|
40
|
+
|| id.includes("needle-engine");
|
|
35
41
|
// sometimes the actual license parameter is in a unnamed chunk file
|
|
36
42
|
const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
|
|
37
43
|
if (isNeedleEngineFile || isViteChunkFile) {
|
|
38
44
|
|
|
39
|
-
if (!
|
|
45
|
+
if (!licenseResult) {
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
let modified = false;
|
|
50
|
+
|
|
51
|
+
// Replace license type
|
|
52
|
+
const index = src.indexOf("EzdGPQg");
|
|
44
53
|
if (index >= 0) {
|
|
45
54
|
const end = src.indexOf(";", index);
|
|
46
55
|
if (end >= 0) {
|
|
47
56
|
const line = src.substring(index, end);
|
|
48
|
-
const replaced = "
|
|
57
|
+
const replaced = "EzdGPQg = \"" + licenseResult.type + "\"";
|
|
49
58
|
src = src.replace(line, replaced);
|
|
50
|
-
|
|
59
|
+
modified = true;
|
|
51
60
|
}
|
|
52
61
|
}
|
|
62
|
+
|
|
63
|
+
// Replace license JWT (same pattern)
|
|
64
|
+
if (licenseResult.jwt) {
|
|
65
|
+
const jwtIndex = src.indexOf("_$InwX");
|
|
66
|
+
if (jwtIndex >= 0) {
|
|
67
|
+
const jwtEnd = src.indexOf(";", jwtIndex);
|
|
68
|
+
if (jwtEnd >= 0) {
|
|
69
|
+
const jwtLine = src.substring(jwtIndex, jwtEnd);
|
|
70
|
+
const jwtReplaced = "_$InwX = \"" + licenseResult.jwt + "\"";
|
|
71
|
+
src = src.replace(jwtLine, jwtReplaced);
|
|
72
|
+
modified = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (modified) {
|
|
78
|
+
appliedLicense = true;
|
|
79
|
+
return { code: src, map: null }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
buildEnd() {
|
|
84
|
+
if (!appliedLicense) {
|
|
85
|
+
if (process.env.NEEDLE_TEST_ENV) {
|
|
86
|
+
console.error("ERR: License was not applied!");
|
|
87
|
+
}
|
|
53
88
|
}
|
|
54
89
|
}
|
|
55
90
|
}
|
package/src/engine/api.ts
CHANGED
|
@@ -167,6 +167,9 @@ export * from "./engine_constants.js";
|
|
|
167
167
|
*/
|
|
168
168
|
export * from "./engine_context.js";
|
|
169
169
|
|
|
170
|
+
/** Typed event bus for decoupled component communication via {@link Context.events} */
|
|
171
|
+
export * from "./engine_context_eventbus.js";
|
|
172
|
+
|
|
170
173
|
/** Registry for managing multiple engine contexts */
|
|
171
174
|
export * from "./engine_context_registry.js";
|
|
172
175
|
|
|
@@ -227,7 +230,7 @@ export * from "./engine_input.js";
|
|
|
227
230
|
export { InstancingUtil } from "./engine_instancing.js";
|
|
228
231
|
|
|
229
232
|
/** License checking utilities */
|
|
230
|
-
export {
|
|
233
|
+
export { LynjGsV, __otwqOR, _cxKhKwDL } from "./engine_license.js";
|
|
231
234
|
|
|
232
235
|
|
|
233
236
|
// ============================================================================
|
|
@@ -101,12 +101,12 @@ import { TestRunner } from "../../engine-components/TestRunner.js";
|
|
|
101
101
|
import { TestSimulateUserData } from "../../engine-components/TestRunner.js";
|
|
102
102
|
import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector.js";
|
|
103
103
|
import { SignalReceiver } from "../../engine-components/timeline/SignalAsset.js";
|
|
104
|
-
import {
|
|
105
|
-
import {
|
|
106
|
-
import {
|
|
104
|
+
import { TimelineAnimationTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
105
|
+
import { TimelineAudioTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
106
|
+
import { TimelineMarkerTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
107
107
|
import { SignalTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
108
|
-
import {
|
|
109
|
-
import {
|
|
108
|
+
import { TimelineActivationTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
109
|
+
import { TimelineControlTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
110
110
|
import { TransformGizmo } from "../../engine-components/TransformGizmo.js";
|
|
111
111
|
import { BaseUIComponent } from "../../engine-components/ui/BaseUIComponent.js";
|
|
112
112
|
import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent.js";
|
|
@@ -257,12 +257,12 @@ export function initBuiltinTypes() {
|
|
|
257
257
|
TypeStore.add("TestSimulateUserData", TestSimulateUserData);
|
|
258
258
|
TypeStore.add("PlayableDirector", PlayableDirector);
|
|
259
259
|
TypeStore.add("SignalReceiver", SignalReceiver);
|
|
260
|
-
TypeStore.add("
|
|
261
|
-
TypeStore.add("
|
|
262
|
-
TypeStore.add("
|
|
260
|
+
TypeStore.add("TimelineAnimationTrack", TimelineAnimationTrack);
|
|
261
|
+
TypeStore.add("TimelineAudioTrack", TimelineAudioTrack);
|
|
262
|
+
TypeStore.add("TimelineMarkerTrack", TimelineMarkerTrack);
|
|
263
263
|
TypeStore.add("SignalTrackHandler", SignalTrackHandler);
|
|
264
|
-
TypeStore.add("
|
|
265
|
-
TypeStore.add("
|
|
264
|
+
TypeStore.add("TimelineActivationTrack", TimelineActivationTrack);
|
|
265
|
+
TypeStore.add("TimelineControlTrack", TimelineControlTrack);
|
|
266
266
|
TypeStore.add("TransformGizmo", TransformGizmo);
|
|
267
267
|
TypeStore.add("BaseUIComponent", BaseUIComponent);
|
|
268
268
|
TypeStore.add("UIRootComponent", UIRootComponent);
|
|
@@ -257,12 +257,23 @@ export function fitCamera(options?: FitCameraOptions): null | FitCameraReturnTyp
|
|
|
257
257
|
else {
|
|
258
258
|
direction.sub(camera.worldPosition);
|
|
259
259
|
}
|
|
260
|
-
if (centerCamera === "y")
|
|
261
|
-
|
|
260
|
+
if (centerCamera === "y") {
|
|
261
|
+
// Preserve the camera's current elevation angle when it's already above the center,
|
|
262
|
+
// but clamp to a minimum elevation to prevent the camera from ending up at or below
|
|
263
|
+
// the scene center (which causes a "looking up from below" effect).
|
|
264
|
+
// direction points FROM camera TO center, so negative Y = camera is above center.
|
|
265
|
+
const horizontalLen = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
|
|
266
|
+
if (horizontalLen > 0.0001) {
|
|
267
|
+
const minY = -horizontalLen * verticalOffset * 4;
|
|
268
|
+
if (direction.y > minY) direction.y = minY;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Camera is directly above/below center — pick a default slight angle from +Z
|
|
272
|
+
direction.set(0, -verticalOffset * 4, 1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
262
275
|
direction.normalize();
|
|
263
276
|
direction.multiplyScalar(distance);
|
|
264
|
-
if (centerCamera === "y")
|
|
265
|
-
direction.y += -verticalOffset * 4 * distance;
|
|
266
277
|
|
|
267
278
|
let cameraLocalPosition = center.clone().sub(direction);
|
|
268
279
|
if (options.cameraOffset) {
|
|
@@ -23,6 +23,7 @@ import { Application } from './engine_application.js';
|
|
|
23
23
|
import { AssetDatabase } from './engine_assetdatabase.js';
|
|
24
24
|
import { FocusRect, FocusRectSettings, updateCameraFocusRect } from './engine_camera.js';
|
|
25
25
|
import { VERSION } from './engine_constants.js';
|
|
26
|
+
import { EventBus } from './engine_context_eventbus.js';
|
|
26
27
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
|
27
28
|
import { WaitForPromise } from './engine_coroutine.js';
|
|
28
29
|
import { ObjectUtils } from "./engine_create_objects.js";
|
|
@@ -151,7 +152,7 @@ export function registerComponent(script: IComponent, context?: Context) {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
|
-
* The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
|
|
155
|
+
* The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
|
|
155
156
|
* It can be used to access the {@link Context.scene}, {@link Context.renderer}, {@link Context.mainCamera}, {@link Context.input}, {@link Context.physics}, {@link Context.time}, {@link Context.connection} (networking), and more.
|
|
156
157
|
*
|
|
157
158
|
* The context is automatically created when using the `<needle-engine>` web component.
|
|
@@ -522,19 +523,31 @@ export class Context implements IContext {
|
|
|
522
523
|
private _fallbackCamera: PerspectiveCamera | null = null;
|
|
523
524
|
|
|
524
525
|
/** access application state (e.g. if all audio should be muted) */
|
|
525
|
-
application: Application;
|
|
526
|
+
get application(): Application { return this._application; }
|
|
527
|
+
private _application!: Application;
|
|
526
528
|
/** access animation mixer used by components in the scene */
|
|
527
|
-
animations: AnimationsRegistry;
|
|
529
|
+
get animations(): AnimationsRegistry { return this._animations; }
|
|
530
|
+
private _animations!: AnimationsRegistry;
|
|
528
531
|
/** access timings (current frame number, deltaTime, timeScale, ...) */
|
|
529
|
-
time: Time;
|
|
532
|
+
get time(): Time { return this._time; }
|
|
533
|
+
private _time!: Time;
|
|
530
534
|
/** access input data (e.g. click or touch events) */
|
|
531
|
-
input: Input;
|
|
535
|
+
get input(): Input { return this._input; }
|
|
536
|
+
private _input!: Input;
|
|
532
537
|
/** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
|
|
533
|
-
physics: Physics;
|
|
538
|
+
get physics(): Physics { return this._physics; }
|
|
539
|
+
private _physics!: Physics;
|
|
534
540
|
/** access postprocessing effects stack. Add/remove effects and configure adaptive performance settings */
|
|
535
|
-
postprocessing: PostProcessing;
|
|
541
|
+
get postprocessing(): PostProcessing { return this._postprocessing; }
|
|
542
|
+
private _postprocessing!: PostProcessing;
|
|
536
543
|
/** access networking methods (use it to send or listen to messages or join a networking backend) */
|
|
537
|
-
connection: NetworkConnection;
|
|
544
|
+
get connection(): NetworkConnection { return this._connection; }
|
|
545
|
+
private _connection!: NetworkConnection;
|
|
546
|
+
/** context-level event bus for decoupled component communication
|
|
547
|
+
* @see {@link ContextEventMap} for known event types
|
|
548
|
+
*/
|
|
549
|
+
get events(): EventBus { return this._events; }
|
|
550
|
+
private _events = new EventBus();
|
|
538
551
|
/** @deprecated AssetDatabase is deprecated */
|
|
539
552
|
assets: AssetDatabase;
|
|
540
553
|
|
|
@@ -606,12 +619,12 @@ export class Context implements IContext {
|
|
|
606
619
|
else this.scene = new Scene();
|
|
607
620
|
if (args?.camera) this._mainCamera = args.camera;
|
|
608
621
|
|
|
609
|
-
this.
|
|
610
|
-
this.
|
|
611
|
-
this.
|
|
612
|
-
this.
|
|
613
|
-
this.
|
|
614
|
-
this.
|
|
622
|
+
this._application = new Application(this);
|
|
623
|
+
this._time = new Time();
|
|
624
|
+
this._input = new Input(this);
|
|
625
|
+
this._physics = new Physics(this);
|
|
626
|
+
this._postprocessing = new PostProcessing(this);
|
|
627
|
+
this._connection = new NetworkConnection(this);
|
|
615
628
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
616
629
|
this.assets = new AssetDatabase();
|
|
617
630
|
this.sceneLighting = new SceneLighting(this);
|
|
@@ -620,7 +633,7 @@ export class Context implements IContext {
|
|
|
620
633
|
this.players = new PlayerViewManager(this);
|
|
621
634
|
this.menu = new NeedleMenu(this);
|
|
622
635
|
this.lodsManager = new LODsManager(this);
|
|
623
|
-
this.
|
|
636
|
+
this._animations = new AnimationsRegistry(this);
|
|
624
637
|
this.accessibility = new AccessibilityManager(this);
|
|
625
638
|
|
|
626
639
|
|
|
@@ -869,6 +882,8 @@ export class Context implements IContext {
|
|
|
869
882
|
|
|
870
883
|
this._onBeforeRenderListeners.clear();
|
|
871
884
|
this._onAfterRenderListeners.clear();
|
|
885
|
+
this._events.clear();
|
|
886
|
+
this._events = new EventBus();
|
|
872
887
|
|
|
873
888
|
this.lights.length = 0;
|
|
874
889
|
|
|
@@ -1793,6 +1808,7 @@ export class Context implements IContext {
|
|
|
1793
1808
|
? (`${((window.performance as any).memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`)
|
|
1794
1809
|
: "n/a";
|
|
1795
1810
|
|
|
1811
|
+
const gl = this.renderer.getContext();
|
|
1796
1812
|
console.log(this.renderer.info.render.calls + " DrawCalls", "\nRender:",
|
|
1797
1813
|
{
|
|
1798
1814
|
shaders: this.renderer.info.programs?.length,
|
|
@@ -1802,7 +1818,16 @@ export class Context implements IContext {
|
|
|
1802
1818
|
{
|
|
1803
1819
|
usedMemory: usedJSHeapSize,
|
|
1804
1820
|
...this.renderer.info.memory
|
|
1805
|
-
},
|
|
1821
|
+
},
|
|
1822
|
+
"\nRenderer:",
|
|
1823
|
+
{
|
|
1824
|
+
dpr: this.renderer.getPixelRatio(),
|
|
1825
|
+
windowDpr: window.devicePixelRatio,
|
|
1826
|
+
antialias: gl.getContextAttributes()?.antialias,
|
|
1827
|
+
samples: gl.getParameter(gl.SAMPLES),
|
|
1828
|
+
resolution: `${this.renderer.domElement.width}x${this.renderer.domElement.height}`,
|
|
1829
|
+
},
|
|
1830
|
+
"\nTarget Framerate: " + this.targetFrameRate);
|
|
1806
1831
|
}
|
|
1807
1832
|
}
|
|
1808
1833
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Object3D } from "three";
|
|
2
|
+
|
|
3
|
+
import type { IComponent } from "./engine_types.js";
|
|
4
|
+
|
|
5
|
+
/** Typed event map for {@link Context.events}.
|
|
6
|
+
* Known events get full autocomplete; custom events can be typed at the call site via generic parameter.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContextEventMap {
|
|
9
|
+
"scene-content-changed": {
|
|
10
|
+
/** The component that triggered the change (e.g. SceneSwitcher, DropListener) */
|
|
11
|
+
readonly source: IComponent;
|
|
12
|
+
/** The root object that was added/loaded */
|
|
13
|
+
readonly object: Object3D;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Options for {@link EventBus.on}. */
|
|
18
|
+
export interface EventBusListenerOptions {
|
|
19
|
+
/** If true the listener is automatically removed after the first invocation. */
|
|
20
|
+
once?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Typed event bus. Known {@link ContextEventMap} events get full autocomplete.
|
|
24
|
+
* Custom events can be typed at the call site via generic parameter.
|
|
25
|
+
* @example Known events
|
|
26
|
+
* ```ts
|
|
27
|
+
* context.events.on("scene-content-changed", e => e.object);
|
|
28
|
+
* ```
|
|
29
|
+
* @example Custom events — type at call site
|
|
30
|
+
* ```ts
|
|
31
|
+
* context.events.emit<{ pts: number }>("scored", { pts: 10 });
|
|
32
|
+
* context.events.on<{ pts: number }>("scored", e => e.pts);
|
|
33
|
+
* ```
|
|
34
|
+
* @example Once
|
|
35
|
+
* ```ts
|
|
36
|
+
* context.events.on("scene-content-changed", e => { ... }, { once: true });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class EventBus {
|
|
40
|
+
private _listeners = new Map<string, Function[]>();
|
|
41
|
+
|
|
42
|
+
/** Emit a known {@link ContextEventMap} event */
|
|
43
|
+
emit<K extends keyof ContextEventMap & string>(type: K, detail?: ContextEventMap[K]): void;
|
|
44
|
+
/** Emit a custom event with user-provided type */
|
|
45
|
+
emit<T>(type: string, detail?: T): void;
|
|
46
|
+
emit(type: string, detail?: unknown): void {
|
|
47
|
+
const arr = this._listeners.get(type);
|
|
48
|
+
if (arr) for (const cb of [...arr]) cb(detail);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Subscribe to a known {@link ContextEventMap} event. Returns an unsubscribe function. */
|
|
52
|
+
on<K extends keyof ContextEventMap & string>(type: K, callback: (args: ContextEventMap[K]) => void, options?: EventBusListenerOptions): () => void;
|
|
53
|
+
/** Subscribe to a custom event with user-provided type. Returns an unsubscribe function. */
|
|
54
|
+
on<T>(type: string, callback: (args: T) => void, options?: EventBusListenerOptions): () => void;
|
|
55
|
+
on(type: string, callback: Function, options?: EventBusListenerOptions): () => void {
|
|
56
|
+
let arr = this._listeners.get(type);
|
|
57
|
+
if (!arr) { arr = []; this._listeners.set(type, arr); }
|
|
58
|
+
const unsub = () => {
|
|
59
|
+
const i = arr.indexOf(wrapped);
|
|
60
|
+
if (i >= 0) arr.splice(i, 1);
|
|
61
|
+
};
|
|
62
|
+
const wrapped = options?.once
|
|
63
|
+
? (...args: unknown[]) => { unsub(); callback(...args); }
|
|
64
|
+
: callback;
|
|
65
|
+
arr.push(wrapped);
|
|
66
|
+
return unsub;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Remove all listeners. Called when the context is cleared or destroyed. */
|
|
70
|
+
clear(): void {
|
|
71
|
+
this._listeners.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -8,7 +8,7 @@ import { initBuiltinTypes } from "./codegen/register_types.js";
|
|
|
8
8
|
import { initSpatialConsole } from "./debug/debug_spatial_console.js";
|
|
9
9
|
import { initAddressableSerializers } from "./engine_addressables.js";
|
|
10
10
|
import { ensureAudioContextIsResumed } from "./engine_audio.js";
|
|
11
|
-
import {
|
|
11
|
+
import { _$AnFl } from "./engine_license.js";
|
|
12
12
|
import { initNeedleLoader } from "./engine_loaders.js";
|
|
13
13
|
import { initPhysics } from "./engine_physics_rapier.js";
|
|
14
14
|
import { initBuiltinSerializers } from "./engine_serialization_builtin_serializer.js";
|
|
@@ -59,5 +59,5 @@ export function initEngine() {
|
|
|
59
59
|
initPhysics();
|
|
60
60
|
initXR();
|
|
61
61
|
initSpatialConsole();
|
|
62
|
-
|
|
62
|
+
_$AnFl();
|
|
63
63
|
}
|
|
@@ -343,14 +343,33 @@ export class Input implements IInput {
|
|
|
343
343
|
private readonly _eventListeners: Record<string, RegisteredEventListenerValue> = {};
|
|
344
344
|
|
|
345
345
|
/** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
|
|
346
|
+
*
|
|
347
|
+
* Returns an unsubscribe function — call it to remove the listener.
|
|
348
|
+
* Pass it to {@link Behaviour.autoCleanup} for automatic lifecycle management.
|
|
349
|
+
*
|
|
346
350
|
* @param type The event type to listen for
|
|
347
351
|
* @param callback The callback to call when the event is triggered
|
|
348
352
|
* @param options The options for adding the event listener.
|
|
349
|
-
* @
|
|
353
|
+
* @returns A function that removes the event listener when called.
|
|
354
|
+
*
|
|
355
|
+
* @example With autoCleanup (recommended)
|
|
350
356
|
* ```ts
|
|
351
|
-
*
|
|
357
|
+
* export class MyComponent extends Behaviour {
|
|
358
|
+
* onEnable() {
|
|
359
|
+
* this.autoCleanup(this.context.input.addEventListener("pointerdown", (evt) => {
|
|
360
|
+
* console.log("Pointer down", evt.pointerId, evt.pointerType);
|
|
361
|
+
* }));
|
|
362
|
+
* }
|
|
363
|
+
* // Listener is automatically removed on disable — no manual cleanup needed!
|
|
364
|
+
* }
|
|
365
|
+
* ```
|
|
366
|
+
* @example Manual unsubscribe
|
|
367
|
+
* ```ts
|
|
368
|
+
* const off = input.addEventListener("pointerdown", (evt) => {
|
|
352
369
|
* console.log("Pointer down", evt.pointerId, evt.pointerType);
|
|
353
370
|
* });
|
|
371
|
+
* // later
|
|
372
|
+
* off();
|
|
354
373
|
* ```
|
|
355
374
|
* @example Adding a listener that is called after all other listeners
|
|
356
375
|
* By using a higher value for the queue the listener will be called after other listeners (default queue is 0).
|
|
@@ -366,14 +385,14 @@ export class Input implements IInput {
|
|
|
366
385
|
* }, { once: true });
|
|
367
386
|
* ```
|
|
368
387
|
*/
|
|
369
|
-
addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions);
|
|
370
|
-
addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions);
|
|
371
|
-
addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): void {
|
|
388
|
+
addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions): () => void;
|
|
389
|
+
addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions): () => void;
|
|
390
|
+
addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): () => void {
|
|
372
391
|
if (!this._eventListeners[type]) this._eventListeners[type] = [];
|
|
373
392
|
|
|
374
393
|
if (!callback || typeof callback !== "function") {
|
|
375
394
|
console.error("Invalid call to addEventListener: callback is required and must be a function!");
|
|
376
|
-
return;
|
|
395
|
+
return () => {};
|
|
377
396
|
}
|
|
378
397
|
|
|
379
398
|
if (!options) options = {};
|
|
@@ -392,6 +411,8 @@ export class Input implements IInput {
|
|
|
392
411
|
} else {
|
|
393
412
|
queueListeners.listeners.push({ callback, options });
|
|
394
413
|
}
|
|
414
|
+
|
|
415
|
+
return () => this.removeEventListener(type as any, callback as any, options);
|
|
395
416
|
}
|
|
396
417
|
/** Removes the event listener from the specified event type. If no queue is specified the listener will be removed from all queues.
|
|
397
418
|
* @param type The event type to remove the listener from
|