@needle-tools/engine 4.12.0-next.5d44f6c → 4.12.0-next.8d9ac4f
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/components.needle.json +1 -1
- package/dist/{gltf-progressive-Rs-ojtXy.umd.cjs → gltf-progressive-Bfpfaz84.umd.cjs} +1 -1
- package/dist/{gltf-progressive-DnLBuGK5.js → gltf-progressive-DPunMlEM.js} +1 -1
- package/dist/{gltf-progressive-BmSygnAC.min.js → gltf-progressive-hFPACYio.min.js} +1 -1
- package/dist/{needle-engine.bundle-By-ZxucN.js → needle-engine.bundle-BWI5qB1a.js} +7976 -7801
- package/dist/{needle-engine.bundle-3hSMBtBM.umd.cjs → needle-engine.bundle-BsjlUA5m.umd.cjs} +143 -143
- package/dist/{needle-engine.bundle-DLa-Vhrd.min.js → needle-engine.bundle-CtLKRFYk.min.js} +146 -146
- package/dist/needle-engine.d.ts +12 -5
- package/dist/needle-engine.js +3 -3
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-DZtb9Nnn.umd.cjs → postprocessing-BHQvwehB.umd.cjs} +1 -1
- package/dist/{postprocessing-B5ksn9-G.min.js → postprocessing-ClLv0reO.min.js} +1 -1
- package/dist/{postprocessing-__7s9wON.js → postprocessing-DLI2N3LL.js} +1 -1
- package/dist/{three-examples-y2GeYlze.js → three-examples-D4rE49Ui.js} +10 -2
- package/dist/{three-examples-MsJjauyk.min.js → three-examples-DB5Uoja4.min.js} +2 -2
- package/dist/{three-examples-Dho7cuu4.umd.cjs → three-examples-Djbk6WA4.umd.cjs} +2 -2
- package/lib/engine/engine_context.js +2 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_license.d.ts +18 -0
- package/lib/engine/engine_license.js +160 -9
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking.js +15 -0
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_three_utils.js +2 -2
- package/lib/engine/engine_three_utils.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -0
- package/lib/engine/webcomponents/needle menu/needle-menu.js +41 -2
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.js +23 -2
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine-components/DragControls.js +1 -1
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +1 -0
- package/lib/engine-components/DropListener.js +26 -8
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.js +4 -1
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +3 -2
- package/lib/engine-components/SceneSwitcher.js +24 -11
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +8 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/webxr/WebARSessionRoot.d.ts +5 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js +5 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.d.ts +3 -1
- package/lib/engine-components/webxr/WebXR.js +3 -1
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/package.json +2 -2
- package/src/engine/engine_context.ts +2 -0
- package/src/engine/engine_license.ts +177 -9
- package/src/engine/engine_networking.ts +15 -0
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -1
- package/src/engine/engine_three_utils.ts +4 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -1
- package/src/engine/webcomponents/needle menu/needle-menu.ts +44 -3
- package/src/engine/xr/NeedleXRSession.ts +27 -2
- package/src/engine-components/DragControls.ts +1 -1
- package/src/engine-components/DropListener.ts +29 -8
- package/src/engine-components/EventList.ts +5 -1
- package/src/engine-components/SceneSwitcher.ts +26 -13
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +11 -0
- package/src/engine-components/webxr/WebARSessionRoot.ts +7 -3
- package/src/engine-components/webxr/WebXR.ts +4 -2
|
@@ -27,6 +27,7 @@ import { ObjectUtils } from "./engine_create_objects.js";
|
|
|
27
27
|
import { destroy, foreachComponent } from './engine_gameobject.js';
|
|
28
28
|
import { getLoader } from './engine_gltf.js';
|
|
29
29
|
import { Input } from './engine_input.js';
|
|
30
|
+
import { Telemetry } from './engine_license.js';
|
|
30
31
|
import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
|
|
31
32
|
import { type ILightDataRegistry, LightDataRegistry } from './engine_lightdata.js';
|
|
32
33
|
import { LODsManager } from "./engine_lods.js";
|
|
@@ -1403,6 +1404,7 @@ export class Context implements IContext {
|
|
|
1403
1404
|
if (this._renderlooperrors >= 3) {
|
|
1404
1405
|
console.warn("Stopping render loop due to error")
|
|
1405
1406
|
this.renderer.setAnimationLoop(null);
|
|
1407
|
+
Telemetry.sendError(Context.Current, "renderloop", err instanceof Error ? err : new Error(String(err)) );
|
|
1406
1408
|
}
|
|
1407
1409
|
this.domElement.dispatchEvent(new CustomEvent("error", { detail: err }));
|
|
1408
1410
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { dof } from "three/src/nodes/TSL.js";
|
|
2
|
+
|
|
1
3
|
import { isDevEnvironment } from "./debug/index.js";
|
|
2
4
|
import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
|
|
3
5
|
import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
|
|
6
|
+
import { onInitialized } from "./engine_lifecycle_api.js";
|
|
7
|
+
import { isLocalNetwork } from "./engine_networking_utils.js";
|
|
4
8
|
import { Context } from "./engine_setup.js";
|
|
5
9
|
import type { IContext } from "./engine_types.js";
|
|
6
10
|
import { getParam } from "./engine_utils.js";
|
|
11
|
+
import { InternalAttributeUtils } from "./engine_utils_attributes.js";
|
|
7
12
|
|
|
8
13
|
const debug = getParam("debuglicense");
|
|
9
14
|
|
|
@@ -67,6 +72,174 @@ function invokeLicenseCheckResultChanged(result: boolean) {
|
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// #region Telemetry
|
|
78
|
+
export namespace Telemetry {
|
|
79
|
+
|
|
80
|
+
window.addEventListener("error", (event: ErrorEvent) => {
|
|
81
|
+
sendError(Context.Current, "unhandled_error", event);
|
|
82
|
+
});
|
|
83
|
+
window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
|
|
84
|
+
sendError(Context.Current, "unhandled_promise_rejection", {
|
|
85
|
+
message: event.reason?.message,
|
|
86
|
+
stack: event.reason?.stack,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
onInitialized((ctx => sendPageViewEvent(ctx)), { once: true });
|
|
92
|
+
|
|
93
|
+
function sendPageViewEvent(ctx: IContext) {
|
|
94
|
+
if (!isAllowed(ctx)) {
|
|
95
|
+
if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return doFetch({
|
|
99
|
+
site_id: "dabb8317376f",
|
|
100
|
+
type: "pageview",
|
|
101
|
+
pathname: window.location.pathname,
|
|
102
|
+
hostname: window.location.hostname,
|
|
103
|
+
page_title: document.title,
|
|
104
|
+
referrer: document.referrer,
|
|
105
|
+
user_agent: navigator.userAgent,
|
|
106
|
+
querystring: window.location.search,
|
|
107
|
+
language: navigator.language,
|
|
108
|
+
screenWidth: window.screen.width,
|
|
109
|
+
screenHeight: window.screen.height,
|
|
110
|
+
event_name: "page_view"
|
|
111
|
+
}).then(res => {
|
|
112
|
+
if (res instanceof Response && res.ok && isLocalNetwork()) {
|
|
113
|
+
const src = ctx.domElement?.getAttribute("src") || "";
|
|
114
|
+
const sessionKey = src + VERSION + GENERATOR + BUILD_TIME + PUBLIC_KEY;
|
|
115
|
+
if (window.sessionStorage.getItem("session_key") !== sessionKey) {
|
|
116
|
+
window.sessionStorage.setItem("session_key", sessionKey);
|
|
117
|
+
sendEvent(ctx, "info", {
|
|
118
|
+
src: ctx.domElement?.getAttribute("src") || "",
|
|
119
|
+
version: VERSION,
|
|
120
|
+
generator: GENERATOR,
|
|
121
|
+
build_time: BUILD_TIME,
|
|
122
|
+
public_key: PUBLIC_KEY,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isAllowed(context: IContext | null | undefined): boolean {
|
|
130
|
+
let domElement = context?.domElement as HTMLElement | null;
|
|
131
|
+
if (!domElement) domElement = document.querySelector<HTMLElement>("needle-engine");
|
|
132
|
+
if (!domElement && !context) return false;
|
|
133
|
+
|
|
134
|
+
const attribute = domElement?.getAttribute("no-telemetry");
|
|
135
|
+
if (attribute === "" || attribute === "true" || attribute === "1") {
|
|
136
|
+
if (NEEDLE_ENGINE_LICENSE_TYPE === "pro" || NEEDLE_ENGINE_LICENSE_TYPE === "enterprise") {
|
|
137
|
+
if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const id = "dabb8317376f";
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Sends a telemetry event
|
|
148
|
+
*/
|
|
149
|
+
export async function sendEvent(context: IContext | null | undefined, eventName: string, properties?: Record<string, any>) {
|
|
150
|
+
if (!isAllowed(context)) {
|
|
151
|
+
if (debug) console.debug("Telemetry is disabled");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const body = {
|
|
155
|
+
site_id: id,
|
|
156
|
+
type: "custom_event",
|
|
157
|
+
pathname: window.location.pathname,
|
|
158
|
+
event_name: eventName,
|
|
159
|
+
properties: properties ? JSON.stringify(properties) : undefined,
|
|
160
|
+
}
|
|
161
|
+
return doFetch(body);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type ErrorData = {
|
|
165
|
+
message?: string;
|
|
166
|
+
stack?: string;
|
|
167
|
+
filename?: string;
|
|
168
|
+
lineno?: number;
|
|
169
|
+
colno?: number;
|
|
170
|
+
timestamp?: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function sendError(context: IContext, errorName: string, error: ErrorData | ErrorEvent | Error) {
|
|
174
|
+
|
|
175
|
+
if (!isAllowed(context)) {
|
|
176
|
+
if (debug) console.debug("Telemetry is disabled");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (error instanceof ErrorEvent) {
|
|
181
|
+
error = {
|
|
182
|
+
message: error.message,
|
|
183
|
+
stack: error.error?.stack,
|
|
184
|
+
filename: error.filename,
|
|
185
|
+
lineno: error.lineno,
|
|
186
|
+
colno: error.colno,
|
|
187
|
+
timestamp: error.timeStamp || Date.now(),
|
|
188
|
+
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
else if (error instanceof Error) {
|
|
192
|
+
error = {
|
|
193
|
+
message: error.message,
|
|
194
|
+
stack: error.stack,
|
|
195
|
+
timestamp: Date.now(),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const body = {
|
|
199
|
+
site_id: id,
|
|
200
|
+
type: "error",
|
|
201
|
+
event_name: errorName || "error",
|
|
202
|
+
properties: JSON.stringify({
|
|
203
|
+
error_name: errorName,
|
|
204
|
+
message: error.message,
|
|
205
|
+
stack: error.stack,
|
|
206
|
+
filename: error.filename,
|
|
207
|
+
lineno: error.lineno,
|
|
208
|
+
colno: error.colno,
|
|
209
|
+
timestamp: error.timestamp,
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
return doFetch(body);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function doFetch(body: Record<string, any>) {
|
|
216
|
+
try {
|
|
217
|
+
const url = "https://needle.tools/api/v1/rum/t";
|
|
218
|
+
return fetch(url, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
body: JSON.stringify(body),
|
|
221
|
+
headers: {
|
|
222
|
+
'Content-Type': 'application/json'
|
|
223
|
+
},
|
|
224
|
+
// Ensures request completes even if page unloads
|
|
225
|
+
keepalive: true,
|
|
226
|
+
// Allow CORS requests
|
|
227
|
+
mode: 'cors',
|
|
228
|
+
// Low priority to avoid blocking other requests
|
|
229
|
+
// @ts-ignore
|
|
230
|
+
priority: 'low',
|
|
231
|
+
}).catch(e => {
|
|
232
|
+
if (debug) console.error("Failed to send telemetry", e);
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
if (debug) console.error(err);
|
|
237
|
+
}
|
|
238
|
+
return Promise.resolve();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
70
243
|
ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
|
|
71
244
|
showLicenseInfo(evt.context);
|
|
72
245
|
handleForbidden(evt.context);
|
|
@@ -341,14 +514,9 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
|
|
|
341
514
|
// We can't send beacons from cross-origin isolated pages
|
|
342
515
|
if (window.crossOriginIsolated) return;
|
|
343
516
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (attribute === "" || attribute === "true" || attribute === "1") {
|
|
348
|
-
if (debug) console.debug("Telemetry is disabled");
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (debug) console.debug("Telemetry attribute: " + attribute);
|
|
517
|
+
if (!Telemetry.isAllowed(context)) {
|
|
518
|
+
if (debug) console.debug("Telemetry is disabled via no-telemetry attribute");
|
|
519
|
+
return;
|
|
352
520
|
}
|
|
353
521
|
|
|
354
522
|
try {
|
|
@@ -379,4 +547,4 @@ async function sendUsageMessageToAnalyticsBackend(context: IContext) {
|
|
|
379
547
|
if (debug)
|
|
380
548
|
console.log("Failed to send non-commercial usage message to analytics backend", err);
|
|
381
549
|
}
|
|
382
|
-
}
|
|
550
|
+
}
|
|
@@ -6,6 +6,7 @@ import { type Websocket } from 'websocket-ts';
|
|
|
6
6
|
|
|
7
7
|
import * as schemes from "../engine-schemes/schemes.js";
|
|
8
8
|
import { isDevEnvironment } from './debug/index.js';
|
|
9
|
+
import { Telemetry } from './engine_license.js';
|
|
9
10
|
import { PeerNetworking } from './engine_networking_peer.js';
|
|
10
11
|
import { type IModel, type INetworkConnection, SendQueue } from './engine_networking_types.js';
|
|
11
12
|
import { isHostedOnGlitch } from './engine_networking_utils.js';
|
|
@@ -658,6 +659,9 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
658
659
|
.onError((_e) => {
|
|
659
660
|
console.error("Websocket connection failed...");
|
|
660
661
|
resolve(false);
|
|
662
|
+
Telemetry.sendEvent(this.context, "networking", {
|
|
663
|
+
event: "connection_error",
|
|
664
|
+
});
|
|
661
665
|
})
|
|
662
666
|
.onRetry(() => { console.log("Retry connecting to networking websocket") })
|
|
663
667
|
.build();
|
|
@@ -727,6 +731,9 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
727
731
|
"server did not send connection id", connection.id);
|
|
728
732
|
console.debug("Your id is: " + connection.id, this.context.alias ?? "");
|
|
729
733
|
this._connectionId = connection.id;
|
|
734
|
+
Telemetry.sendEvent(this.context, "networking", {
|
|
735
|
+
event: "connected",
|
|
736
|
+
});
|
|
730
737
|
}
|
|
731
738
|
}
|
|
732
739
|
else console.warn("Expected connection id in " + message.key);
|
|
@@ -754,6 +761,10 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
754
761
|
}
|
|
755
762
|
|
|
756
763
|
this.onSendQueued(SendQueue.OnRoomJoin);
|
|
764
|
+
Telemetry.sendEvent(this.context, "networking", {
|
|
765
|
+
event: "joined_room",
|
|
766
|
+
room: this._currentRoomName,
|
|
767
|
+
});
|
|
757
768
|
break;
|
|
758
769
|
|
|
759
770
|
case RoomEvents.LeftRoom:
|
|
@@ -765,6 +776,10 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
765
776
|
this._currentInRoom.length = 0;
|
|
766
777
|
if (debugnetBin || isDevEnvironment()) console.debug("Left Needle Engine Room: " + model.room);
|
|
767
778
|
}
|
|
779
|
+
Telemetry.sendEvent(this.context, "networking", {
|
|
780
|
+
event: "left_room",
|
|
781
|
+
room: model.room,
|
|
782
|
+
});
|
|
768
783
|
break;
|
|
769
784
|
case RoomEvents.UserJoinedRoom:
|
|
770
785
|
if (message.data) {
|
|
@@ -337,7 +337,7 @@ class EventListSerializer extends TypeSerializer {
|
|
|
337
337
|
args = call.arguments.map(deserializeArgument);
|
|
338
338
|
}
|
|
339
339
|
const method = target[call.method];
|
|
340
|
-
if (
|
|
340
|
+
if (method === undefined) {
|
|
341
341
|
console.warn(`EventList method not found: \"${call.method}\" on ${target?.name}`);
|
|
342
342
|
}
|
|
343
343
|
else {
|
|
@@ -242,8 +242,10 @@ export function setWorldPosition(obj: Object3D, val: Vector3): Object3D {
|
|
|
242
242
|
const wp = _worldPositions.get();
|
|
243
243
|
if (val !== wp)
|
|
244
244
|
wp.copy(val);
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
|
|
246
|
+
if (obj.parent !== null)
|
|
247
|
+
obj.parent.worldToLocal(wp);
|
|
248
|
+
|
|
247
249
|
obj.position.set(wp.x, wp.y, wp.z);
|
|
248
250
|
return obj;
|
|
249
251
|
}
|
|
@@ -104,7 +104,8 @@ export class NeedleSpatialMenu {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const xr = this._context.xr;
|
|
107
|
-
|
|
107
|
+
const isImmersiveXR = xr?.running && (xr?.isPassThrough || xr?.isVR)
|
|
108
|
+
if (!isImmersiveXR) {
|
|
108
109
|
if (this._wasInXR) {
|
|
109
110
|
this._wasInXR = false;
|
|
110
111
|
this.onExitXR();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Context } from "../../engine_context.js";
|
|
2
|
-
import { hasCommercialLicense, onLicenseCheckResultChanged } from "../../engine_license.js";
|
|
2
|
+
import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
|
|
3
3
|
import { isLocalNetwork } from "../../engine_networking_utils.js";
|
|
4
4
|
import { DeviceUtilities, getParam } from "../../engine_utils.js";
|
|
5
5
|
import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
|
|
6
6
|
import { ButtonsFactory } from "../buttons.js";
|
|
7
7
|
import { ensureFonts, iconFontUrl, loadFont } from "../fonts.js";
|
|
8
8
|
import { getIconElement } from "../icons.js";
|
|
9
|
+
import { showBalloonMessage } from "../../debug/debug.js";
|
|
9
10
|
import { NeedleLogoElement } from "../logo-element.js";
|
|
10
11
|
import { NeedleSpatialMenu } from "./needle-menu-spatial.js";
|
|
11
12
|
|
|
@@ -149,6 +150,9 @@ export class NeedleMenu {
|
|
|
149
150
|
else console.error("NeedleMenu: onclick is not a valid link", buttoninfo.onclick);
|
|
150
151
|
}
|
|
151
152
|
}
|
|
153
|
+
Telemetry.sendEvent(this._context, "needle-menu", {
|
|
154
|
+
action: "button_added_via_postmessage",
|
|
155
|
+
});
|
|
152
156
|
this._menu.appendChild(button);
|
|
153
157
|
}
|
|
154
158
|
else if (debug) console.error("NeedleMenu: unknown postMessage event", data);
|
|
@@ -935,12 +939,39 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
935
939
|
/** @private foldout container used in compact mode */
|
|
936
940
|
private readonly foldout: HTMLDivElement;
|
|
937
941
|
|
|
942
|
+
|
|
943
|
+
private readonly trackedElements: WeakSet<Node> = new WeakSet();
|
|
944
|
+
private trackElement(el: Node) {
|
|
945
|
+
if (this.trackedElements.has(el)) return;
|
|
946
|
+
this.trackedElements.add(el);
|
|
947
|
+
el.addEventListener("click", (evt) => {
|
|
948
|
+
Telemetry.sendEvent(this._context, "needle-menu", {
|
|
949
|
+
action: "button_clicked",
|
|
950
|
+
element: evt.target instanceof Node ? evt.target.nodeName : el.nodeName,
|
|
951
|
+
label: el.textContent,
|
|
952
|
+
title: (el instanceof HTMLElement) ? el.title : undefined,
|
|
953
|
+
pointerid: (evt instanceof PointerEvent) ? evt.pointerId : undefined,
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
// el.addEventListener("pointerenter", (evt) => {
|
|
957
|
+
// Telemetry.sendEvent(this._context, "needle-menu", {
|
|
958
|
+
// action: "button_hovered",
|
|
959
|
+
// element: evt.target instanceof Node ? evt.target.nodeName : el.nodeName,
|
|
960
|
+
// label: el.textContent,
|
|
961
|
+
// title: (el instanceof HTMLElement) ? el.title : undefined,
|
|
962
|
+
// pointerid: (evt instanceof PointerEvent) ? evt.pointerId : undefined,
|
|
963
|
+
// });
|
|
964
|
+
// });
|
|
965
|
+
}
|
|
966
|
+
|
|
938
967
|
append(...nodes: (string | Node)[]): void {
|
|
939
968
|
for (const node of nodes) {
|
|
940
969
|
if (typeof node === "string") {
|
|
941
970
|
const element = document.createTextNode(node);
|
|
971
|
+
this.trackElement(element);
|
|
942
972
|
this.options.appendChild(element);
|
|
943
973
|
} else {
|
|
974
|
+
this.trackElement(node);
|
|
944
975
|
this.options.appendChild(node);
|
|
945
976
|
}
|
|
946
977
|
}
|
|
@@ -987,9 +1018,10 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
987
1018
|
if (node.class) {
|
|
988
1019
|
button.classList.add(node.class);
|
|
989
1020
|
}
|
|
1021
|
+
|
|
990
1022
|
node = button as unknown as T;
|
|
991
1023
|
}
|
|
992
|
-
|
|
1024
|
+
this.trackElement(node);
|
|
993
1025
|
const res = this.options.appendChild(node);
|
|
994
1026
|
return res;
|
|
995
1027
|
}
|
|
@@ -997,8 +1029,10 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
997
1029
|
for (const node of nodes) {
|
|
998
1030
|
if (typeof node === "string") {
|
|
999
1031
|
const element = document.createTextNode(node);
|
|
1032
|
+
this.trackElement(element);
|
|
1000
1033
|
this.options.prepend(element);
|
|
1001
1034
|
} else {
|
|
1035
|
+
this.trackElement(node);
|
|
1002
1036
|
this.options.prepend(node);
|
|
1003
1037
|
}
|
|
1004
1038
|
}
|
|
@@ -1142,8 +1176,15 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
1142
1176
|
const getCurrentWidth = () => {
|
|
1143
1177
|
return this.options.clientWidth + this.logoContainer.clientWidth;
|
|
1144
1178
|
}
|
|
1179
|
+
|
|
1180
|
+
let lastSpaceLeft = -1;
|
|
1145
1181
|
const getSpaceLeft = () => {
|
|
1146
|
-
|
|
1182
|
+
const spaceLeft = availableWidth - getCurrentWidth();
|
|
1183
|
+
if (debug && spaceLeft !== lastSpaceLeft) {
|
|
1184
|
+
lastSpaceLeft = spaceLeft;
|
|
1185
|
+
showBalloonMessage(`Menu space left: ${spaceLeft.toFixed(0)}px`);
|
|
1186
|
+
}
|
|
1187
|
+
return spaceLeft;
|
|
1147
1188
|
}
|
|
1148
1189
|
}
|
|
1149
1190
|
|
|
@@ -6,6 +6,7 @@ import { Context, FrameEvent } from "../engine_context.js";
|
|
|
6
6
|
import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
|
|
7
7
|
import { isDestroyed } from "../engine_gameobject.js";
|
|
8
8
|
import { Gizmos } from "../engine_gizmos.js";
|
|
9
|
+
import { Telemetry } from "../engine_license.js";
|
|
9
10
|
import { registerFrameEventCallback, unregisterFrameEventCallback } from "../engine_lifecycle_functions_internal.js";
|
|
10
11
|
import { getBoundingBox, getTempQuaternion, getTempVector, getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../engine_three_utils.js";
|
|
11
12
|
import type { ICamera, IComponent, INeedleXRSession } from "../engine_types.js";
|
|
@@ -98,12 +99,12 @@ async function handleSessionGranted() {
|
|
|
98
99
|
}
|
|
99
100
|
// Check if AR is supported, otherwise we can't do anything
|
|
100
101
|
if (!(await navigator.xr?.isSessionSupported("immersive-ar")) && defaultMode === "immersive-ar") {
|
|
101
|
-
console.warn("[NeedleXRSession:granted] Neither VR nor AR supported, aborting session start.");
|
|
102
|
+
// console.warn("[NeedleXRSession:granted] Neither VR nor AR supported, aborting session start.");
|
|
102
103
|
// showBalloonMessage("NeidleXRSession: Neither VR nor AR supported, aborting session start.");
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
106
|
} catch (e) {
|
|
106
|
-
console.
|
|
107
|
+
console.debug("[NeedleXRSession:granted] Error while checking XR support:", e);
|
|
107
108
|
// showBalloonWarning("NeedleXRSession: Error while checking XR support: " + (e as Error).message);
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
@@ -148,6 +149,7 @@ async function handleSessionGranted() {
|
|
|
148
149
|
navigator.xr?.addEventListener('sessiongranted', async () => {
|
|
149
150
|
// enableSpatialConsole(true);
|
|
150
151
|
|
|
152
|
+
|
|
151
153
|
const lastSessionMode = sessionStorage.getItem("needle_xr_session_mode") as XRSessionMode;
|
|
152
154
|
const lastSessionInit = sessionStorage.getItem("needle_xr_session_init") ?? null;
|
|
153
155
|
const init = lastSessionInit ? JSON.parse(lastSessionInit) : null;
|
|
@@ -466,6 +468,10 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
466
468
|
}
|
|
467
469
|
|
|
468
470
|
if (mode === "quicklook") {
|
|
471
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
472
|
+
action: "quicklook_export",
|
|
473
|
+
source: "NeedleXRSession.start",
|
|
474
|
+
});
|
|
469
475
|
InternalUSDZRegistry.exportAndOpen();
|
|
470
476
|
return null;
|
|
471
477
|
}
|
|
@@ -478,6 +484,13 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
478
484
|
url.searchParams.set("url", location.href);
|
|
479
485
|
|
|
480
486
|
const urlStr = url.toString();
|
|
487
|
+
|
|
488
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
489
|
+
action: "app_clip_launch",
|
|
490
|
+
source: "NeedleXRSession.start",
|
|
491
|
+
url: urlStr,
|
|
492
|
+
});
|
|
493
|
+
|
|
481
494
|
// if we are in an iframe, we need to navigate the top window
|
|
482
495
|
const topWindow = window.top || window;
|
|
483
496
|
try {
|
|
@@ -606,6 +619,12 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
606
619
|
listener({ mode, init });
|
|
607
620
|
}
|
|
608
621
|
if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
|
|
622
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
623
|
+
action: "session_request",
|
|
624
|
+
mode: mode,
|
|
625
|
+
features: ((init.requiredFeatures ?? []).concat(init.optionalFeatures ?? [])).join(","),
|
|
626
|
+
source: "NeedleXRSession.start",
|
|
627
|
+
});
|
|
609
628
|
this._currentSessionRequest = navigator?.xr?.requestSession(mode, init);
|
|
610
629
|
this._currentSessionRequestMode = mode;
|
|
611
630
|
/**@type {XRSystem} */
|
|
@@ -1182,6 +1201,12 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1182
1201
|
|
|
1183
1202
|
console.debug("XR Session ended");
|
|
1184
1203
|
|
|
1204
|
+
Telemetry.sendEvent(Context.Current, "xr", {
|
|
1205
|
+
action: "session_end",
|
|
1206
|
+
mode: this.mode,
|
|
1207
|
+
source: "NeedleXRSession.onEnd",
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1185
1210
|
deleteSessionInfo();
|
|
1186
1211
|
|
|
1187
1212
|
this.onAfterRender();
|
|
@@ -447,7 +447,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
|
|
|
447
447
|
if (!this || !this._isDragging) return;
|
|
448
448
|
this._isDragging = false;
|
|
449
449
|
for (const rb of this._draggingRigidbodies) {
|
|
450
|
-
rb.setVelocity(rb.smoothedVelocity);
|
|
450
|
+
rb.setVelocity(rb.smoothedVelocity.multiplyScalar(this.context.time.deltaTime));
|
|
451
451
|
}
|
|
452
452
|
this._draggingRigidbodies.length = 0;
|
|
453
453
|
this._targetObject = null;
|
|
@@ -10,7 +10,7 @@ import { BlobStorage } from "../engine/engine_networking_blob.js";
|
|
|
10
10
|
import { PreviewHelper } from "../engine/engine_networking_files.js";
|
|
11
11
|
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
|
|
12
12
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
13
|
-
import { fitObjectIntoVolume, getBoundingBox, placeOnSurface } from "../engine/engine_three_utils.js";
|
|
13
|
+
import { fitObjectIntoVolume, getBoundingBox, getWorldScale, placeOnSurface } from "../engine/engine_three_utils.js";
|
|
14
14
|
import { Model, Vec3 } from "../engine/engine_types.js";
|
|
15
15
|
import { getParam, setParamWithoutReload } from "../engine/engine_utils.js";
|
|
16
16
|
import { determineMimeTypeFromExtension } from "../engine/engine_utils_format.js";
|
|
@@ -111,6 +111,7 @@ const blobKeyName = "blob";
|
|
|
111
111
|
|
|
112
112
|
/** The DropListener component is used to listen for drag and drop events in the browser and add the dropped files to the scene
|
|
113
113
|
* It can be used to allow users to drag and drop glTF files into the scene to add new objects.
|
|
114
|
+
* Existing child objects will behave like placeholders and will be removed when new files are dropped.
|
|
114
115
|
*
|
|
115
116
|
* If {@link useNetworking} is enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
116
117
|
* Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
|
|
@@ -522,22 +523,42 @@ export class DropListener extends Behaviour {
|
|
|
522
523
|
|
|
523
524
|
const obj = model.scene;
|
|
524
525
|
|
|
525
|
-
|
|
526
|
-
this.gameObject
|
|
527
|
-
obj.position.set(0, 0, 0);
|
|
528
|
-
obj.quaternion.identity();
|
|
526
|
+
obj.position.copy(this.gameObject.worldPosition);
|
|
527
|
+
const scale = getWorldScale(this.gameObject);
|
|
529
528
|
|
|
530
|
-
|
|
531
|
-
|
|
529
|
+
let localPos = new Vector3(0,0,0);
|
|
530
|
+
scale.x = Math.abs(scale.x);
|
|
531
|
+
scale.y = Math.abs(scale.y);
|
|
532
|
+
scale.z = Math.abs(scale.z);
|
|
533
|
+
let localScale =obj.scale.clone();
|
|
534
|
+
|
|
535
|
+
// TODOs: handle rotation when Gizmos APIs has changed to support it
|
|
532
536
|
|
|
533
|
-
const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize);
|
|
537
|
+
const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * scale.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize.clone().multiply(scale));
|
|
534
538
|
if (debug) Gizmos.DrawWireBox3(volume, 0x0000ff, 5);
|
|
535
539
|
if (this.fitIntoVolume) {
|
|
540
|
+
|
|
536
541
|
fitObjectIntoVolume(obj, volume, {
|
|
537
542
|
position: !this.placeAtHitPosition
|
|
538
543
|
});
|
|
544
|
+
|
|
545
|
+
// to match parent scale later, divide by it
|
|
546
|
+
localScale = obj.scale.clone().divide(scale);
|
|
547
|
+
// just take the computed offset from fitting
|
|
548
|
+
localPos = obj.worldPosition.clone().sub(this.gameObject.worldPosition).divide(scale);
|
|
549
|
+
if (debug) Gizmos.DrawSphere(localPos, 0.1, 0xff0000, 5);
|
|
539
550
|
}
|
|
540
551
|
|
|
552
|
+
// use attach to ignore the DropListener scale (e.g. if the parent object scale is not uniform)
|
|
553
|
+
this.gameObject.attach(obj);
|
|
554
|
+
obj.position.copy(localPos);
|
|
555
|
+
obj.quaternion.identity();
|
|
556
|
+
obj.scale.copy(localScale);
|
|
557
|
+
if (debug) Gizmos.DrawArrow(this.gameObject.worldPosition, obj.getWorldPosition(new Vector3()), 0x00ff00, 5);
|
|
558
|
+
|
|
559
|
+
this._addedObjects.push(obj);
|
|
560
|
+
this._addedModels.push(model);
|
|
561
|
+
|
|
541
562
|
if (this.placeAtHitPosition && ctx && ctx.screenposition) {
|
|
542
563
|
obj.visible = false; // < don't raycast on the placed object
|
|
543
564
|
const rc = this.context.physics.raycast({ screenPoint: this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone()) });
|
|
@@ -78,7 +78,11 @@ export class CallInfo {
|
|
|
78
78
|
// If the target is a property
|
|
79
79
|
else {
|
|
80
80
|
if (this.arguments) {
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
if (args !== undefined && args.length > 0)
|
|
83
|
+
this.target[this.methodName] = args[0];
|
|
84
|
+
else
|
|
85
|
+
this.target[this.methodName] = this.arguments[0];
|
|
82
86
|
}
|
|
83
87
|
else {
|
|
84
88
|
this.target[this.methodName] = args[0];
|