@@ -180,6 +180,7 @@
|
|
180
180
|
text-align: center;
|
181
181
|
background: #ffffff5c;
|
182
182
|
backdrop-filter: blur(16px);
|
183
|
+
-webkit-backdrop-filter: blur(16px);
|
183
184
|
user-select: none;
|
184
185
|
pointer-events: auto;
|
185
186
|
transition: transform .2s ease-in-out;
|
@@ -297,6 +297,7 @@
|
|
297
297
|
element.style.maxWidth = "250px";
|
298
298
|
element.style.whiteSpace = "pre-wrap";
|
299
299
|
element.style["backdrop-filter"] = "blur(10px)";
|
300
|
+
element.style["-webkit-backdrop-filter"] = "blur(10px)";
|
300
301
|
element.style.backgroundColor = "rgba(20,20,20,.8)";
|
301
302
|
element.style.boxShadow = "inset 0 0 80px rgba(0,0,0,.2), 0 0 5px rgba(0,0,0,.2)";
|
302
303
|
element.style.border = "1px solid rgba(160,160,160,.2)";
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, BufferGeometry, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Matrix4, Mesh, MeshBasicMaterial,Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
1
|
+
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, BufferGeometry, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Matrix4, Mesh, MeshBasicMaterial, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
2
2
|
import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
|
3
3
|
import { type Options } from 'three-mesh-ui/build/types/core/elements/MeshUIBaseElement.js';
|
4
4
|
|
5
5
|
import { isDestroyed } from './engine_gameobject.js';
|
6
|
-
import { Context } from './engine_setup.js';
|
6
|
+
import { Context, FrameEvent } from './engine_setup.js';
|
7
7
|
import { getWorldPosition, lookAtObject, setWorldPositionXYZ } from './engine_three_utils.js';
|
8
8
|
import type { Vec3, Vec4 } from './engine_types.js';
|
9
9
|
import { getParam } from './engine_utils.js';
|
@@ -380,7 +380,14 @@
|
|
380
380
|
object.renderOrder = 999999;
|
381
381
|
object[$cacheSymbol] = cache;
|
382
382
|
this.timedObjectsBuffer.push(object);
|
383
|
-
|
383
|
+
|
384
|
+
// Ensure if duration is 0 it is rendered for one frame only
|
385
|
+
if (duration <= 0 && context.currentFrameEvent >= FrameEvent.OnBeforeRender) {
|
386
|
+
this.timesBuffer.push(0);
|
387
|
+
}
|
388
|
+
else {
|
389
|
+
this.timesBuffer.push(Context.Current.time.realtimeSinceStartup + duration);
|
390
|
+
}
|
384
391
|
context.scene.add(object);
|
385
392
|
}
|
386
393
|
|
@@ -109,6 +109,7 @@
|
|
109
109
|
zIndex: 2147483647;
|
110
110
|
line-height: 1.5;
|
111
111
|
backdrop-filter: blur(15px);
|
112
|
+
-webkit-backdrop-filter: blur(15px);
|
112
113
|
`;
|
113
114
|
const expectedStyle = div.style.cssText;
|
114
115
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { LODsManager as _LODsManager, NEEDLE_progressive, NEEDLE_progressive_mesh_model, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";
|
2
|
+
import { LOD_Results } from "@needle-tools/gltf-progressive/src/lods_manager.js";
|
2
3
|
import { Box3, BufferGeometry, Camera, Mesh, PerspectiveCamera, Scene, Sphere, Vector3, WebGLRenderer } from "three";
|
3
4
|
|
4
5
|
import { findResourceUsers } from "./engine_assetdatabase.js";
|
@@ -14,13 +15,23 @@
|
|
14
15
|
const _tempSphere: Sphere = new Sphere();
|
15
16
|
|
16
17
|
/**
|
17
|
-
* Needle Engine LODs manager. Wrapper around the internal LODs manager.
|
18
|
+
* Needle Engine LODs manager. Wrapper around the internal LODs manager.
|
19
|
+
* It uses the @needle-tools/gltf-progressive package to manage LODs.
|
20
|
+
* @link https://npmjs.com/package/@needle-tools/gltf-progressive
|
18
21
|
*/
|
19
22
|
export class LODsManager implements NEEDLE_progressive_plugin {
|
20
23
|
readonly context: Context;
|
21
24
|
private _lodsManager?: _LODsManager;
|
22
25
|
|
23
26
|
/**
|
27
|
+
* The internal LODs manager. See @needle-tools/gltf-progressive for more information.
|
28
|
+
* @link https://npmjs.com/package/@needle-tools/gltf-progressive
|
29
|
+
*/
|
30
|
+
get manager() {
|
31
|
+
return this._lodsManager;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
24
35
|
* The target triangle density is the desired max amount of triangles on screen when the mesh is filling the screen.
|
25
36
|
* @default 200_000
|
26
37
|
*/
|
@@ -50,20 +61,21 @@
|
|
50
61
|
|
51
62
|
|
52
63
|
/** @internal */
|
53
|
-
onAfterUpdatedLOD(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, mesh: Mesh, level:
|
64
|
+
onAfterUpdatedLOD(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, mesh: Mesh, level: LOD_Results): void {
|
54
65
|
if (debug) this.onRenderDebug(camera, mesh, level);
|
55
66
|
}
|
56
67
|
|
57
|
-
private onRenderDebug(camera: Camera, mesh: Mesh,
|
68
|
+
private onRenderDebug(camera: Camera, mesh: Mesh, results: LOD_Results) {
|
58
69
|
|
59
70
|
if (!mesh.geometry) return;
|
60
|
-
if (!NEEDLE_progressive.hasLODLevelAvailable(mesh.geometry)) return;
|
71
|
+
if (!NEEDLE_progressive.hasLODLevelAvailable(mesh.geometry) && !NEEDLE_progressive.hasLODLevelAvailable(mesh.material)) return;
|
61
72
|
|
62
73
|
const state = _LODsManager.getObjectLODState(mesh);
|
63
74
|
if (!state) return;
|
64
75
|
|
65
76
|
|
66
|
-
|
77
|
+
let level = results.mesh_lod;
|
78
|
+
const changed = results.mesh_lod != state.lastLodLevel_Mesh || results.texture_lod != state.lasLodLevel_Texture;
|
67
79
|
|
68
80
|
if (debug && mesh.geometry.boundingSphere) {
|
69
81
|
const bounds = mesh.geometry.boundingSphere;
|
@@ -105,7 +117,7 @@
|
|
105
117
|
// Area is squared, so both maxBoxSize and wsMedian are squared here
|
106
118
|
// Here, we're basically reverting the calculations that have happened in the pipeline for debugging.
|
107
119
|
// const surfaceArea = 1 / density * triangleCount * (maxBoxSize * maxBoxSize) * (wsMedian * wsMedian);
|
108
|
-
let text = "LOD " +
|
120
|
+
let text = "LOD " + results.mesh_lod + "\nTEX " + results.texture_lod;
|
109
121
|
if (debug == "density") {
|
110
122
|
text +=
|
111
123
|
"\n" + triangleCount + " tris" +
|
@@ -141,8 +153,7 @@
|
|
141
153
|
// const size = Math.min(10, radius);
|
142
154
|
const windowScale = this.context.domHeight > 0 ? screen.height / this.context.domHeight : 1;
|
143
155
|
const fieldOfViewScale = (camera as PerspectiveCamera).isPerspectiveCamera ? Math.tan((camera as PerspectiveCamera).fov * Math.PI / 180 / 2) : 1;
|
144
|
-
Gizmos.DrawLabel(pos, text, distance * .
|
145
|
-
// mesh["LOD_level_label"] = helper;
|
156
|
+
Gizmos.DrawLabel(pos, text, distance * .012 * windowScale * fieldOfViewScale, undefined, 0xffffff, col);
|
146
157
|
}
|
147
158
|
|
148
159
|
}
|
@@ -148,13 +148,37 @@
|
|
148
148
|
}
|
149
149
|
}
|
150
150
|
|
151
|
+
/**
|
152
|
+
* OneEuroFilter is a simple low-pass filter for noisy signals. It uses a one-euro filter to smooth the signal.
|
153
|
+
*/
|
151
154
|
export class OneEuroFilter {
|
155
|
+
/**
|
156
|
+
* An estimate of the frequency in Hz of the signal (> 0), if timestamps are not available.
|
157
|
+
*/
|
152
158
|
freq: number;
|
159
|
+
/**
|
160
|
+
* Min cutoff frequency in Hz (> 0). Lower values allow to remove more jitter.
|
161
|
+
*/
|
153
162
|
minCutOff: number;
|
163
|
+
/**
|
164
|
+
* Parameter to reduce latency (> 0). Higher values make the filter react faster to changes.
|
165
|
+
*/
|
154
166
|
beta: number;
|
167
|
+
/**
|
168
|
+
* Used to filter the derivates. 1 Hz by default. Change this parameter if you know what you are doing.
|
169
|
+
*/
|
155
170
|
dCutOff: number;
|
171
|
+
/**
|
172
|
+
* The low-pass filter for the signal.
|
173
|
+
*/
|
156
174
|
x: LowPassFilter;
|
175
|
+
/**
|
176
|
+
* The low-pass filter for the derivates.
|
177
|
+
*/
|
157
178
|
dx: LowPassFilter;
|
179
|
+
/**
|
180
|
+
* The last time the filter was called.
|
181
|
+
*/
|
158
182
|
lasttime: number | null;
|
159
183
|
|
160
184
|
/** Create a new OneEuroFilter
|
@@ -10,13 +10,14 @@
|
|
10
10
|
|
11
11
|
const debug = getParam("debugenvlight");
|
12
12
|
|
13
|
-
|
13
|
+
/** @internal */
|
14
14
|
export declare type SphericalHarmonicsData = {
|
15
15
|
array: number[],
|
16
16
|
texture: WebGLCubeRenderTarget | Texture,
|
17
17
|
lightProbe?: LightProbe
|
18
18
|
}
|
19
19
|
|
20
|
+
/** @internal */
|
20
21
|
export enum AmbientMode {
|
21
22
|
Skybox = 0,
|
22
23
|
Trilight = 1,
|
@@ -24,11 +25,16 @@
|
|
24
25
|
Custom = 4,
|
25
26
|
}
|
26
27
|
|
28
|
+
/** @internal */
|
27
29
|
export enum DefaultReflectionMode {
|
28
30
|
Skybox = 0,
|
29
31
|
Custom = 1,
|
30
32
|
}
|
31
33
|
|
34
|
+
/**
|
35
|
+
* The RendererData class is used to manage the lighting settings of a scene.
|
36
|
+
* It is created and used within the Needle Engine Context.
|
37
|
+
*/
|
32
38
|
export class RendererData {
|
33
39
|
|
34
40
|
private context: Context;
|
@@ -1,5 +1,9 @@
|
|
1
|
+
|
2
|
+
|
1
3
|
// REMOVE once we switch to TypeScript 5.x
|
2
4
|
// OffscreenCanvas has a convertToBlob method, but TypeScript 4.x doesn't have the proper types for it.
|
5
|
+
|
6
|
+
/** @internal */
|
3
7
|
export declare type OffscreenCanvasExt = OffscreenCanvas & {
|
4
8
|
convertToBlob: (options?: any) => Promise<Blob>;
|
5
9
|
}
|
@@ -23,6 +23,9 @@
|
|
23
23
|
|
24
24
|
/**
|
25
25
|
* Render the scene to the texture
|
26
|
+
* @param scene The scene to render
|
27
|
+
* @param camera The camera to render from
|
28
|
+
* @param renderer The renderer or effectcomposer to use
|
26
29
|
*/
|
27
30
|
render(scene: Object3D, camera: Camera, renderer: WebGLRenderer | EffectComposer) {
|
28
31
|
if (renderer instanceof EffectComposer) {
|
@@ -6,7 +6,9 @@
|
|
6
6
|
import { Mathf } from "./engine_math.js"
|
7
7
|
import { CircularBuffer } from "./engine_utils.js";
|
8
8
|
|
9
|
-
|
9
|
+
/**
|
10
|
+
* Slerp between two vectors
|
11
|
+
*/
|
10
12
|
export function slerp(vec: Vector3, end: Vector3, t: number) {
|
11
13
|
const len1 = vec.length();
|
12
14
|
const len2 = end.length();
|
@@ -18,7 +20,6 @@
|
|
18
20
|
const flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
19
21
|
|
20
22
|
export function lookAtInverse(obj: Object3D, target: Vector3) {
|
21
|
-
|
22
23
|
obj.lookAt(target);
|
23
24
|
obj.quaternion.multiply(flipYQuat);
|
24
25
|
}
|
@@ -76,6 +77,15 @@
|
|
76
77
|
* @param y the y value
|
77
78
|
* @param z the z value
|
78
79
|
* @returns a temporary vector
|
80
|
+
*
|
81
|
+
* @example
|
82
|
+
* ``` javascript
|
83
|
+
* const vec = getTempVector(1, 2, 3);
|
84
|
+
* const vec2 = getTempVector(vec);
|
85
|
+
* const vec3 = getTempVector(new Vector3(1, 2, 3));
|
86
|
+
* const vec4 = getTempVector(new DOMPointReadOnly(1, 2, 3));
|
87
|
+
* const vec5 = getTempVector();
|
88
|
+
* ```
|
79
89
|
*/
|
80
90
|
export function getTempVector(vecOrX?: Vector3 | number | DOMPointReadOnly, y?: number, z?: number) {
|
81
91
|
const vec = _tempVecs.get();
|
@@ -7,6 +7,10 @@
|
|
7
7
|
let timeScale = 1;
|
8
8
|
if (typeof timescaleUrl === "number") timeScale = timescaleUrl;
|
9
9
|
|
10
|
+
/**
|
11
|
+
* Time is a class that provides time-related information.
|
12
|
+
* It is created and used within the Needle Engine Context.
|
13
|
+
*/
|
10
14
|
export class Time implements ITime {
|
11
15
|
|
12
16
|
/** The time in seconds since the start of Needle Engine. */
|
@@ -19,6 +23,7 @@
|
|
19
23
|
private set deltaTime(value: number) { this._deltaTime = value; }
|
20
24
|
private _deltaTime = 0;
|
21
25
|
|
26
|
+
/** The time in seconds it took to complete the last frame (Read Only). Timescale is not applied. */
|
22
27
|
get deltaTimeUnscaled() { return this._deltaTimeUnscaled; }
|
23
28
|
private _deltaTimeUnscaled = 0;
|
24
29
|
|
@@ -54,6 +59,9 @@
|
|
54
59
|
this.timeScale = timeScale;
|
55
60
|
}
|
56
61
|
|
62
|
+
/** Step the time. This is called automatically by the Needle Engine Context.
|
63
|
+
* @internal
|
64
|
+
*/
|
57
65
|
update() {
|
58
66
|
this.deltaTime = this.clock.getDelta();
|
59
67
|
// clamp delta time because if tab is not active clock.getDelta can get pretty big
|
@@ -8,7 +8,10 @@
|
|
8
8
|
import { type SourceIdentifier } from "./engine_types.js";
|
9
9
|
|
10
10
|
// https://schneidenbach.gitbooks.io/typescript-cookbook/content/nameof-operator.html
|
11
|
+
/** @internal */
|
11
12
|
export const nameofFactory = <T>() => (name: keyof T) => name;
|
13
|
+
|
14
|
+
/** @internal */
|
12
15
|
export function nameof<T>(name: keyof T) {
|
13
16
|
return nameofFactory<T>()(name);
|
14
17
|
}
|
@@ -16,12 +19,28 @@
|
|
16
19
|
type ParseNumber<T> = T extends `${infer U extends number}` ? U : never;
|
17
20
|
export type EnumToPrimitiveUnion<T> = `${T & string}` | ParseNumber<`${T & number}`>;
|
18
21
|
|
22
|
+
/** @internal */
|
19
23
|
export function isDebugMode(): boolean {
|
20
24
|
return getParam("debug") ? true : false;
|
21
25
|
}
|
22
26
|
|
23
27
|
|
24
|
-
|
28
|
+
/**
|
29
|
+
* The circular buffer class can be used to cache objects that don't need to be created every frame.
|
30
|
+
* This structure is used for e.g. Vector3 or Quaternion objects in the engine when calling `getTempVector3` or `getTempQuaternion`.
|
31
|
+
*
|
32
|
+
* @example Create a circular buffer that caches Vector3 objects. Max size is 10.
|
33
|
+
* ```typescript
|
34
|
+
* const buffer = new CircularBuffer(() => new Vector3(), 10);
|
35
|
+
* const vec = buffer.get();
|
36
|
+
* ```
|
37
|
+
*
|
38
|
+
* @example Create a circular buffer that caches Quaternion objects. Max size is 1000.
|
39
|
+
* ```typescript
|
40
|
+
* const buffer = new CircularBuffer(() => new Quaternion(), 1000);
|
41
|
+
* const quat = buffer.get();
|
42
|
+
* ```
|
43
|
+
*/
|
25
44
|
export class CircularBuffer<T> {
|
26
45
|
private _factory: () => T;
|
27
46
|
private _cache: T[] = [];
|
@@ -43,29 +62,36 @@
|
|
43
62
|
}
|
44
63
|
}
|
45
64
|
|
46
|
-
let
|
65
|
+
let showHelp: Param<"help"> = false;
|
47
66
|
const requestedParams: Array<string> = new Array();
|
48
67
|
|
49
68
|
if (typeof window !== "undefined") {
|
50
69
|
setTimeout(() => {
|
51
|
-
const debugHelp = getParam("debughelp");
|
52
|
-
if (
|
70
|
+
// const debugHelp = getParam("debughelp");
|
71
|
+
if (showHelp) {
|
53
72
|
const params = {};
|
54
73
|
const url = new URL(window.location.href);
|
55
|
-
const
|
56
|
-
|
57
|
-
const
|
74
|
+
const exampleUrl = new URL(url);
|
75
|
+
exampleUrl.searchParams.append("console", "");
|
76
|
+
const exampleUrlStr = exampleUrl.toString().replace(/=$|=(?=&)/g, '');
|
77
|
+
// Filter the params we're interested in
|
58
78
|
for (const param of requestedParams) {
|
59
79
|
const url2 = new URL(url);
|
60
80
|
url2.searchParams.append(param, "");
|
81
|
+
// Save url with clean parameters (remove trailing = and empty parameters)
|
61
82
|
params[param] = url2.toString().replace(/=$|=(?=&)/g, '');
|
62
83
|
}
|
63
|
-
console.log(
|
64
|
-
"🌵 ?help: Debug Options for Needle Engine.\n" +
|
84
|
+
console.log(
|
85
|
+
"🌵 ?help: Debug Options for Needle Engine.\n" +
|
65
86
|
"Append any of these parameters to the URL to enable specific debug options.\n" +
|
66
|
-
`Example: ${
|
67
|
-
|
87
|
+
`Example: ${exampleUrlStr} will show an onscreen console window.`);
|
88
|
+
const postfix = showHelp === true ? "" : ` (containing "${showHelp}")`;
|
89
|
+
console.group("Available URL parameters:" + postfix);
|
68
90
|
for (const key in params) {
|
91
|
+
// If ?help= is a string we only want to show the parameters that contain the string
|
92
|
+
if (typeof showHelp === "string") {
|
93
|
+
if (!key.toLowerCase().includes(showHelp.toLowerCase())) continue;
|
94
|
+
}
|
69
95
|
console.groupCollapsed(key);
|
70
96
|
// Needs to be a separate log, otherwise Safari doesn't turn the next line into a URL:
|
71
97
|
console.log("Reload with this flag enabled:");
|
@@ -92,7 +118,7 @@
|
|
92
118
|
* Returns the value if it exists e.g. ?message=hello
|
93
119
|
*/
|
94
120
|
export function getParam<T extends string>(paramName: T): Param<T> {
|
95
|
-
if (
|
121
|
+
if (showHelp && !requestedParams.includes(paramName))
|
96
122
|
requestedParams.push(paramName);
|
97
123
|
const urlParams = getUrlParams();
|
98
124
|
if (urlParams.has(paramName)) {
|
@@ -106,7 +132,7 @@
|
|
106
132
|
}
|
107
133
|
return false;
|
108
134
|
}
|
109
|
-
|
135
|
+
showHelp = getParam("help");
|
110
136
|
|
111
137
|
export function setParam(paramName: string, paramValue: string): void {
|
112
138
|
const urlParams = getUrlParams();
|
@@ -242,14 +242,14 @@
|
|
242
242
|
private _camera: Camera | null = null;
|
243
243
|
private _syncedTransform?: SyncedTransform;
|
244
244
|
private _didStart = false;
|
245
|
-
private _didSetTarget =
|
245
|
+
private _didSetTarget = 0;
|
246
246
|
|
247
247
|
targetElement: HTMLElement | null = null;
|
248
248
|
|
249
249
|
/** @internal */
|
250
250
|
awake(): void {
|
251
251
|
this._didStart = false;
|
252
|
-
this._didSetTarget =
|
252
|
+
this._didSetTarget = 0;
|
253
253
|
this._startedListeningToKeyEvents = false;
|
254
254
|
}
|
255
255
|
|
@@ -356,7 +356,7 @@
|
|
356
356
|
if (!evt.hasRay) {
|
357
357
|
evt.intersections.push(...this.context.physics.raycast());
|
358
358
|
}
|
359
|
-
|
359
|
+
|
360
360
|
if (evt.intersections.length <= 0) {
|
361
361
|
const dt = this.context.time.time - this._lastTimeClickOnBackground;
|
362
362
|
this._lastTimeClickOnBackground = this.context.time.time;
|
@@ -508,8 +508,9 @@
|
|
508
508
|
}
|
509
509
|
|
510
510
|
private __handleSetTargetWhenBecomingActiveTheFirstTime() {
|
511
|
-
|
512
|
-
|
511
|
+
// we want to wait one frame so all matrixWorlds are updated
|
512
|
+
// otherwise raycasting will not work correctly
|
513
|
+
if (this._didSetTarget++ != 1) return;
|
513
514
|
if (this.autoTarget) {
|
514
515
|
|
515
516
|
const camGo = GameObject.getComponent(this.gameObject, Camera);
|
@@ -593,6 +594,11 @@
|
|
593
594
|
}
|
594
595
|
this._lookTargetEndPosition.copy(position);
|
595
596
|
|
597
|
+
if (debug) {
|
598
|
+
console.warn("OrbitControls: setLookTargetPosition", position, immediateOrDuration);
|
599
|
+
Gizmos.DrawWireSphere(this._lookTargetEndPosition, .2, 0xff0000, 2);
|
600
|
+
}
|
601
|
+
|
596
602
|
if (immediateOrDuration === true) {
|
597
603
|
this._controls.target.copy(this._lookTargetEndPosition);
|
598
604
|
}
|
@@ -41,9 +41,10 @@
|
|
41
41
|
|
42
42
|
applyToObject(object: Object3D, t01: number | undefined = undefined) {
|
43
43
|
this.ensureTransformData();
|
44
|
-
// check if position/_position or rotation/_rotation changed more than just a little bit
|
45
|
-
const
|
46
|
-
if (t01
|
44
|
+
// check if position/_position or rotation/_rotation changed more than just a little bit and adjust smoothing accordingly
|
45
|
+
const changeAmount = object.position.distanceToSquared(this._position) / 0.05 + object.quaternion.angleTo(this._rotation) / 0.05;
|
46
|
+
if (t01) t01 *= Math.max(1, changeAmount);
|
47
|
+
if (t01 === undefined || t01 >= 1) {
|
47
48
|
object.position.copy(this._position);
|
48
49
|
object.quaternion.copy(this._rotation);
|
49
50
|
// InstancingUtil.markDirty(object);
|
@@ -231,6 +232,9 @@
|
|
231
232
|
@serializable(WebXRImageTrackingModel)
|
232
233
|
trackedImages?: WebXRImageTrackingModel[];
|
233
234
|
|
235
|
+
/** Applies smoothing based on detected jitter to the tracked image. */
|
236
|
+
smooth: boolean = true;
|
237
|
+
|
234
238
|
private readonly trackedImageIndexMap: Map<number, WebXRImageTrackingModel> = new Map();
|
235
239
|
private static _imageElements: Map<string, ImageBitmap | null> = new Map();
|
236
240
|
|
@@ -481,7 +485,7 @@
|
|
481
485
|
|
482
486
|
xr.rig.gameObject.add(trackedData.object);
|
483
487
|
|
484
|
-
image.applyToObject(trackedData.object);
|
488
|
+
image.applyToObject(trackedData.object, this.smooth ? this.context.time.deltaTimeUnscaled * 3 : undefined);
|
485
489
|
if (!trackedData.object.activeSelf) {
|
486
490
|
GameObject.setActive(trackedData.object, true);
|
487
491
|
}
|