@needle-tools/engine 5.0.2 → 5.1.0-canary.525aa82
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 +24 -0
- package/README.md +6 -7
- package/SKILL.md +39 -21
- package/components.needle.json +1 -1
- package/dist/needle-engine.bundle-DPag02s9.min.js +1732 -0
- package/dist/needle-engine.bundle-IPMzQpe1.umd.cjs +1732 -0
- package/dist/{needle-engine.bundle-BoTyA-Le.js → needle-engine.bundle-qa_NEunk.js} +8881 -8148
- package/dist/needle-engine.d.ts +633 -61
- package/dist/needle-engine.js +576 -565
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-vHLk8sXu.js → vendor-CAcsI0eU.js} +116 -115
- package/dist/{vendor-CntUvmJu.umd.cjs → vendor-CEM38hLE.umd.cjs} +2 -2
- package/dist/{vendor-DPbfJJ4d.min.js → vendor-HRlxIBga.min.js} +2 -2
- package/lib/engine/api.d.ts +2 -0
- package/lib/engine/api.js +2 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_addressables.js +5 -1
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_animation.d.ts +14 -7
- package/lib/engine/engine_animation.js +49 -9
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_components.js +33 -4
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +7 -2
- package/lib/engine/engine_context.js +10 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_gameobject.d.ts +4 -0
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_init.js +4 -0
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.js +4 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.js +1 -20
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +11 -8
- package/lib/engine/engine_networking.js +43 -26
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking_instantiate.d.ts +100 -5
- package/lib/engine/engine_networking_instantiate.js +150 -16
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_prefabs.d.ts +59 -0
- package/lib/engine/engine_networking_prefabs.js +67 -0
- package/lib/engine/engine_networking_prefabs.js.map +1 -0
- package/lib/engine/engine_physics_rapier.d.ts +3 -0
- package/lib/engine/engine_physics_rapier.js +13 -9
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/postprocessing/api.d.ts +2 -0
- package/lib/engine/postprocessing/api.js +2 -0
- package/lib/engine/postprocessing/api.js.map +1 -0
- package/lib/engine/postprocessing/index.d.ts +2 -0
- package/lib/engine/postprocessing/index.js +2 -0
- package/lib/engine/postprocessing/index.js.map +1 -0
- package/lib/engine/postprocessing/postprocessing.d.ts +83 -0
- package/lib/engine/postprocessing/postprocessing.js +280 -0
- package/lib/engine/postprocessing/postprocessing.js.map +1 -0
- package/lib/engine/postprocessing/types.d.ts +39 -0
- package/lib/engine/postprocessing/types.js +2 -0
- package/lib/engine/postprocessing/types.js.map +1 -0
- package/lib/engine/webcomponents/WebXRButtons.js +17 -3
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +3 -1
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +1 -0
- package/lib/engine/xr/NeedleXRSession.js +43 -10
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/init.d.ts +4 -0
- package/lib/engine/xr/init.js +49 -0
- package/lib/engine/xr/init.js.map +1 -0
- package/lib/engine-components/AnimationUtils.d.ts +4 -1
- package/lib/engine-components/AnimationUtils.js +7 -19
- package/lib/engine-components/AnimationUtils.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +135 -2
- package/lib/engine-components/AnimatorController.js +216 -13
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/GroundProjection.d.ts +1 -0
- package/lib/engine-components/GroundProjection.js +184 -48
- package/lib/engine-components/GroundProjection.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +4 -0
- package/lib/engine-components/OrbitControls.js +5 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/SeeThrough.d.ts +0 -2
- package/lib/engine-components/SeeThrough.js +0 -89
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/SyncedRoom.d.ts +4 -0
- package/lib/engine-components/SyncedRoom.js +23 -8
- package/lib/engine-components/SyncedRoom.js.map +1 -1
- package/lib/engine-components/SyncedTransform.js +5 -5
- package/lib/engine-components/SyncedTransform.js.map +1 -1
- package/lib/engine-components/Voip.d.ts +46 -0
- package/lib/engine-components/Voip.js +126 -2
- package/lib/engine-components/Voip.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 +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +5 -2
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js +11 -18
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +3 -4
- package/lib/engine-components/postprocessing/PostProcessingEffect.js +6 -15
- package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +2 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
- package/lib/engine-components/postprocessing/Volume.d.ts +18 -11
- package/lib/engine-components/postprocessing/Volume.js +61 -140
- package/lib/engine-components/postprocessing/Volume.js.map +1 -1
- package/lib/engine-components/postprocessing/index.d.ts +1 -0
- package/lib/engine-components/postprocessing/index.js +1 -0
- package/lib/engine-components/postprocessing/index.js.map +1 -1
- package/lib/engine-components/postprocessing/utils.d.ts +2 -0
- package/lib/engine-components/postprocessing/utils.js +2 -0
- package/lib/engine-components/postprocessing/utils.js.map +1 -1
- package/lib/engine-components/ui/Canvas.js +2 -2
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Graphic.d.ts +3 -3
- package/lib/engine-components/ui/Graphic.js +6 -2
- package/lib/engine-components/ui/Graphic.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +64 -11
- package/lib/engine-components/ui/Text.js +154 -45
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/engine-components/ui/index.d.ts +1 -0
- package/lib/engine-components/ui/index.js +1 -0
- package/lib/engine-components/ui/index.js.map +1 -1
- package/lib/engine-components-experimental/networking/PlayerSync.d.ts +25 -3
- package/lib/engine-components-experimental/networking/PlayerSync.js +60 -11
- package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
- package/package.json +6 -5
- package/plugins/vite/ai.d.ts +11 -10
- package/plugins/vite/ai.js +305 -31
- package/src/engine/api.ts +3 -0
- package/src/engine/engine_addressables.ts +4 -1
- package/src/engine/engine_animation.ts +47 -9
- package/src/engine/engine_components.ts +36 -7
- package/src/engine/engine_context.ts +11 -2
- package/src/engine/engine_gameobject.ts +5 -0
- package/src/engine/engine_init.ts +4 -0
- package/src/engine/engine_input.ts +2 -1
- package/src/engine/engine_materialpropertyblock.ts +1 -20
- package/src/engine/engine_networking.ts +46 -23
- package/src/engine/engine_networking_instantiate.ts +160 -18
- package/src/engine/engine_networking_prefabs.ts +80 -0
- package/src/engine/engine_physics_rapier.ts +14 -9
- package/src/engine/postprocessing/api.ts +2 -0
- package/src/engine/postprocessing/index.ts +2 -0
- package/src/engine/postprocessing/postprocessing.ts +322 -0
- package/src/engine/postprocessing/types.ts +43 -0
- package/src/engine/webcomponents/WebXRButtons.ts +21 -4
- package/src/engine/webcomponents/icons.ts +5 -3
- package/src/engine/xr/NeedleXRSession.ts +50 -15
- package/src/engine/xr/init.ts +56 -0
- package/src/engine-components/AnimationUtils.ts +7 -17
- package/src/engine-components/AnimatorController.ts +288 -18
- package/src/engine-components/GroundProjection.ts +226 -52
- package/src/engine-components/OrbitControls.ts +5 -1
- package/src/engine-components/SeeThrough.ts +0 -116
- package/src/engine-components/SyncedRoom.ts +28 -9
- package/src/engine-components/SyncedTransform.ts +5 -5
- package/src/engine-components/Voip.ts +129 -2
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/postprocessing/Effects/Tonemapping.ts +16 -24
- package/src/engine-components/postprocessing/PostProcessingEffect.ts +9 -16
- package/src/engine-components/postprocessing/PostProcessingHandler.ts +2 -1
- package/src/engine-components/postprocessing/Volume.ts +72 -163
- package/src/engine-components/postprocessing/index.ts +1 -0
- package/src/engine-components/postprocessing/utils.ts +2 -0
- package/src/engine-components/ui/Canvas.ts +2 -2
- package/src/engine-components/ui/Graphic.ts +7 -3
- package/src/engine-components/ui/Text.ts +170 -52
- package/src/engine-components/ui/index.ts +2 -1
- package/src/engine-components-experimental/networking/PlayerSync.ts +64 -11
- package/dist/needle-engine.bundle-B3ywqx5o.min.js +0 -1654
- package/dist/needle-engine.bundle-CzOPcOui.umd.cjs +0 -1654
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import type { EffectComposer } from "postprocessing";
|
|
2
|
+
import type { ToneMapping } from "three";
|
|
3
|
+
import type { EffectComposer as ThreeEffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
4
|
+
|
|
5
|
+
import { isDevEnvironment } from "../debug/index.js";
|
|
6
|
+
import type { Context } from "../engine_context.js";
|
|
7
|
+
import { DeviceUtilities, getParam } from "../engine_utils.js";
|
|
8
|
+
import type { IPostProcessingEffect, IPostProcessingHandler, ITonemappingEffect } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const debug = getParam("debugpost");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Core postprocessing stack accessible via `context.postprocessing`.
|
|
14
|
+
* Manages the effect pipeline independently of any specific component.
|
|
15
|
+
*
|
|
16
|
+
* Volumes and individual PostProcessingEffect components add/remove effects
|
|
17
|
+
* to this stack. The stack builds the EffectComposer pipeline when dirty.
|
|
18
|
+
*
|
|
19
|
+
* @example Add an effect directly
|
|
20
|
+
* ```ts
|
|
21
|
+
* const bloom = new BloomEffect({ intensity: 3 });
|
|
22
|
+
* this.context.postprocessing.addEffect(bloom);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Remove an effect
|
|
26
|
+
* ```ts
|
|
27
|
+
* this.context.postprocessing.removeEffect(bloom);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class PostProcessing {
|
|
31
|
+
|
|
32
|
+
private readonly _context: Context;
|
|
33
|
+
private _handler: IPostProcessingHandler | null = null;
|
|
34
|
+
private readonly _effects: IPostProcessingEffect[] = [];
|
|
35
|
+
private _isDirty: boolean = false;
|
|
36
|
+
|
|
37
|
+
/** Currently active postprocessing effects in the stack */
|
|
38
|
+
get effects(): readonly IPostProcessingEffect[] {
|
|
39
|
+
return this._effects;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get dirty() { return this._isDirty; }
|
|
43
|
+
set dirty(value: boolean) { this._isDirty = value; }
|
|
44
|
+
|
|
45
|
+
/** The internal PostProcessingHandler that manages the EffectComposer pipeline */
|
|
46
|
+
get handler(): IPostProcessingHandler | null { return this._handler; }
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The effect composer used to render postprocessing effects.
|
|
50
|
+
* This is set internally by the PostProcessingHandler when effects are applied.
|
|
51
|
+
*/
|
|
52
|
+
get composer(): EffectComposer | ThreeEffectComposer | null { return this._composer; }
|
|
53
|
+
set composer(value: EffectComposer | ThreeEffectComposer | null) { this._composer = value; }
|
|
54
|
+
private _composer: EffectComposer | ThreeEffectComposer | null = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Set multisampling to "auto" to automatically adjust the multisampling level based on performance.
|
|
58
|
+
* Set to a number to manually set the multisampling level.
|
|
59
|
+
* @default "auto"
|
|
60
|
+
*/
|
|
61
|
+
multisampling: "auto" | number = "auto";
|
|
62
|
+
|
|
63
|
+
/** When enabled, the device pixel ratio will be gradually reduced when FPS is low
|
|
64
|
+
* and restored when performance recovers.
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
adaptiveResolution: boolean = true;
|
|
68
|
+
|
|
69
|
+
constructor(context: Context) {
|
|
70
|
+
this._context = context;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a post processing effect to the stack.
|
|
75
|
+
* The effect stack will be rebuilt on the next update.
|
|
76
|
+
*/
|
|
77
|
+
addEffect(effect: IPostProcessingEffect): void {
|
|
78
|
+
if (this._effects.includes(effect)) return;
|
|
79
|
+
this._effects.push(effect);
|
|
80
|
+
this._isDirty = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remove a post processing effect from the stack.
|
|
85
|
+
* The effect stack will be rebuilt on the next update.
|
|
86
|
+
*/
|
|
87
|
+
removeEffect(effect: IPostProcessingEffect): void {
|
|
88
|
+
const index = this._effects.indexOf(effect);
|
|
89
|
+
if (index !== -1) {
|
|
90
|
+
this._effects.splice(index, 1);
|
|
91
|
+
this._isDirty = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Mark the stack as dirty so the effects are rebuilt on the next update */
|
|
96
|
+
markDirty(): void {
|
|
97
|
+
this._isDirty = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- Adaptive multisampling state ---
|
|
101
|
+
private _enabledTime: number = -1;
|
|
102
|
+
private _multisampleAutoChangeTime: number = 0;
|
|
103
|
+
private _multisampleAutoDecreaseTime: number = 0;
|
|
104
|
+
|
|
105
|
+
/** @internal Called from the context render loop to update the postprocessing pipeline */
|
|
106
|
+
update(): void {
|
|
107
|
+
const context = this._context;
|
|
108
|
+
if (context.isInXR) return;
|
|
109
|
+
|
|
110
|
+
// Wait for a camera before applying
|
|
111
|
+
if (this._isDirty && context.mainCamera) {
|
|
112
|
+
this.apply();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// In tonemapping-only mode, keep renderer values in sync with the active effect
|
|
116
|
+
if (this._tonemappingOnlyActive) {
|
|
117
|
+
const activeEffects = this._effects.filter(e => e.active && e.enabled && e.isToneMapping === true);
|
|
118
|
+
if (activeEffects.length > 0) {
|
|
119
|
+
const effect = activeEffects[activeEffects.length - 1] as ITonemappingEffect;
|
|
120
|
+
context.renderer.toneMapping = effect.threeToneMapping;
|
|
121
|
+
context.renderer.toneMappingExposure = effect.toneMappingExposure;
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!this._handler || !this._composer || this._handler.composer !== this._composer) return;
|
|
127
|
+
|
|
128
|
+
// The composer is always a pmndrs EffectComposer (created by PostProcessingHandler)
|
|
129
|
+
const composer = this._composer as EffectComposer;
|
|
130
|
+
|
|
131
|
+
// Handle context lost
|
|
132
|
+
if (context.renderer.getContext().isContextLost()) {
|
|
133
|
+
context.renderer.forceContextRestore();
|
|
134
|
+
}
|
|
135
|
+
if (composer.getRenderer() !== context.renderer)
|
|
136
|
+
composer.setRenderer(context.renderer);
|
|
137
|
+
|
|
138
|
+
composer.setMainScene(context.scene);
|
|
139
|
+
|
|
140
|
+
// --- Adaptive multisampling ---
|
|
141
|
+
if (this.multisampling === "auto") {
|
|
142
|
+
if (this._handler.hasSmaaEffect) {
|
|
143
|
+
if (this._handler.multisampling !== 0) {
|
|
144
|
+
this._handler.multisampling = 0;
|
|
145
|
+
if (debug || isDevEnvironment()) {
|
|
146
|
+
console.log(`[PostProcessing] multisampling is disabled because it's set to 'auto' and there is an SMAA effect.\n\nIf you need multisampling consider changing 'auto' to a fixed value (e.g. 4).`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const timeSinceLastChange = context.time.realtimeSinceStartup - this._multisampleAutoChangeTime;
|
|
152
|
+
|
|
153
|
+
if (context.time.realtimeSinceStartup - this._enabledTime > 2
|
|
154
|
+
&& timeSinceLastChange > .5
|
|
155
|
+
) {
|
|
156
|
+
const prev = this._handler.multisampling;
|
|
157
|
+
|
|
158
|
+
if (this._handler.multisampling > 0 && context.time.smoothedFps <= 50) {
|
|
159
|
+
this._multisampleAutoChangeTime = context.time.realtimeSinceStartup;
|
|
160
|
+
this._multisampleAutoDecreaseTime = context.time.realtimeSinceStartup;
|
|
161
|
+
let newMultiSample = this._handler.multisampling * .5;
|
|
162
|
+
newMultiSample = Math.floor(newMultiSample);
|
|
163
|
+
if (newMultiSample != this._handler.multisampling) {
|
|
164
|
+
this._handler.multisampling = newMultiSample;
|
|
165
|
+
}
|
|
166
|
+
if (debug) console.debug(`[PostProcessing] Reduced multisampling from ${prev} to ${this._handler.multisampling}`);
|
|
167
|
+
}
|
|
168
|
+
else if (timeSinceLastChange > 1
|
|
169
|
+
&& context.time.smoothedFps >= 59
|
|
170
|
+
&& this._handler.multisampling < context.renderer.capabilities.maxSamples
|
|
171
|
+
&& context.time.realtimeSinceStartup - this._multisampleAutoDecreaseTime > 10
|
|
172
|
+
) {
|
|
173
|
+
this._multisampleAutoChangeTime = context.time.realtimeSinceStartup;
|
|
174
|
+
let newMultiSample = this._handler.multisampling <= 0 ? 1 : this._handler.multisampling * 2;
|
|
175
|
+
newMultiSample = Math.floor(newMultiSample);
|
|
176
|
+
if (newMultiSample !== this._handler.multisampling) {
|
|
177
|
+
this._handler.multisampling = newMultiSample;
|
|
178
|
+
}
|
|
179
|
+
if (debug) console.debug(`[PostProcessing] Increased multisampling from ${prev} to ${this._handler.multisampling}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const newMultiSample = Math.max(0, Math.min(this.multisampling as number, context.renderer.capabilities.maxSamples));
|
|
186
|
+
if (newMultiSample !== this._handler.multisampling)
|
|
187
|
+
this._handler.multisampling = newMultiSample;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Adaptive pixel ratio ---
|
|
191
|
+
this._handler.adaptivePixelRatio = this.adaptiveResolution;
|
|
192
|
+
this._handler.updateAdaptivePixelRatio();
|
|
193
|
+
|
|
194
|
+
// Update camera on passes if needed
|
|
195
|
+
if (context.mainCamera) {
|
|
196
|
+
const passes = composer.passes;
|
|
197
|
+
for (const pass of passes) {
|
|
198
|
+
if (pass.mainCamera && pass.mainCamera !== context.mainCamera) {
|
|
199
|
+
composer.setMainCamera(context.mainCamera);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private _lastApplyTime?: number;
|
|
207
|
+
private _rapidApplyCount = 0;
|
|
208
|
+
|
|
209
|
+
// --- Tonemapping-only state ---
|
|
210
|
+
/** When true, tonemapping is applied directly to the renderer (no full pipeline) */
|
|
211
|
+
private _tonemappingOnlyActive = false;
|
|
212
|
+
private _previousToneMapping?: ToneMapping;
|
|
213
|
+
private _previousToneMappingExposure?: number;
|
|
214
|
+
|
|
215
|
+
private apply() {
|
|
216
|
+
if (debug) console.log(`[PostProcessing] Apply stack (${this._effects.length} effects)`);
|
|
217
|
+
|
|
218
|
+
if (isDevEnvironment()) {
|
|
219
|
+
if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
|
|
220
|
+
this._rapidApplyCount++;
|
|
221
|
+
if (this._rapidApplyCount === 5)
|
|
222
|
+
console.warn("[PostProcessing] Detected rapid post processing modifications - this might be a bug");
|
|
223
|
+
}
|
|
224
|
+
this._lastApplyTime = Date.now();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this._isDirty = false;
|
|
228
|
+
|
|
229
|
+
// Collect active effects
|
|
230
|
+
const activeEffects = this._effects.filter(e => e.active && e.enabled);
|
|
231
|
+
|
|
232
|
+
if (activeEffects.length <= 0) {
|
|
233
|
+
this.restoreTonemapping();
|
|
234
|
+
this._handler?.unapply(false);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if ALL active effects are tonemapping-only
|
|
239
|
+
const allToneMapping = activeEffects.every(e => e.isToneMapping === true);
|
|
240
|
+
|
|
241
|
+
if (allToneMapping) {
|
|
242
|
+
// Use the last tonemapping effect added (last in the array)
|
|
243
|
+
const tonemappingEffect = activeEffects[activeEffects.length - 1] as ITonemappingEffect;
|
|
244
|
+
|
|
245
|
+
if (debug) console.log(`[PostProcessing] Only tonemapping effects in stack — applying directly to renderer`);
|
|
246
|
+
|
|
247
|
+
// Store previous values on first activation
|
|
248
|
+
if (!this._tonemappingOnlyActive) {
|
|
249
|
+
this._previousToneMapping = this._context.renderer.toneMapping as ToneMapping;
|
|
250
|
+
this._previousToneMappingExposure = this._context.renderer.toneMappingExposure;
|
|
251
|
+
this._tonemappingOnlyActive = true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Apply tonemapping directly to renderer
|
|
255
|
+
this._context.renderer.toneMapping = tonemappingEffect.threeToneMapping;
|
|
256
|
+
this._context.renderer.toneMappingExposure = tonemappingEffect.toneMappingExposure;
|
|
257
|
+
|
|
258
|
+
// Tear down any existing postprocessing pipeline
|
|
259
|
+
this._handler?.unapply(false);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// We have non-tonemapping effects — restore renderer tonemapping if we were in tonemapping-only mode
|
|
264
|
+
this.restoreTonemapping();
|
|
265
|
+
|
|
266
|
+
// Build full postprocessing pipeline
|
|
267
|
+
this.ensureHandler()
|
|
268
|
+
.then(handler => {
|
|
269
|
+
if (!handler) return;
|
|
270
|
+
return handler.apply(activeEffects) as Promise<void> | void;
|
|
271
|
+
})
|
|
272
|
+
.then(() => {
|
|
273
|
+
if (this._handler) {
|
|
274
|
+
if (this.multisampling === "auto") {
|
|
275
|
+
this._handler.multisampling = DeviceUtilities.isMobileDevice()
|
|
276
|
+
? 2
|
|
277
|
+
: 4;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
this._handler.multisampling = Math.max(0, Math.min(this.multisampling as number, this._context.renderer.capabilities.maxSamples));
|
|
281
|
+
}
|
|
282
|
+
if (debug) console.debug(`[PostProcessing] Set multisampling to ${this._handler.multisampling} (Is Mobile: ${DeviceUtilities.isMobileDevice()})`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
this._enabledTime = this._context.time.realtimeSinceStartup;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Restore renderer tonemapping to previous values when leaving tonemapping-only mode */
|
|
290
|
+
private restoreTonemapping() {
|
|
291
|
+
if (this._tonemappingOnlyActive) {
|
|
292
|
+
if (this._previousToneMapping !== undefined)
|
|
293
|
+
this._context.renderer.toneMapping = this._previousToneMapping;
|
|
294
|
+
if (this._previousToneMappingExposure !== undefined)
|
|
295
|
+
this._context.renderer.toneMappingExposure = this._previousToneMappingExposure;
|
|
296
|
+
this._tonemappingOnlyActive = false;
|
|
297
|
+
this._previousToneMapping = undefined;
|
|
298
|
+
this._previousToneMappingExposure = undefined;
|
|
299
|
+
if (debug) console.log(`[PostProcessing] Restored renderer tonemapping`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Lazily creates the PostProcessingHandler to avoid loading the postprocessing library until actually needed */
|
|
304
|
+
private async ensureHandler(): Promise<IPostProcessingHandler> {
|
|
305
|
+
if (!this._handler) {
|
|
306
|
+
const { PostProcessingHandler } = await import("../../engine-components/postprocessing/PostProcessingHandler.js");
|
|
307
|
+
if (!this._handler) {
|
|
308
|
+
this._handler = new PostProcessingHandler(this._context);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return this._handler;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** @internal */
|
|
315
|
+
dispose() {
|
|
316
|
+
this.restoreTonemapping();
|
|
317
|
+
this._handler?.dispose();
|
|
318
|
+
this._handler = null;
|
|
319
|
+
this._composer = null;
|
|
320
|
+
this._effects.length = 0;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EffectComposer } from "postprocessing";
|
|
2
|
+
import type { ToneMapping } from "three";
|
|
3
|
+
import type { EffectComposer as ThreeEffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal interface for a postprocessing effect as seen by the core stack.
|
|
7
|
+
* Implemented by `PostProcessingEffect` in engine-components.
|
|
8
|
+
*/
|
|
9
|
+
export interface IPostProcessingEffect {
|
|
10
|
+
readonly active: boolean;
|
|
11
|
+
readonly enabled: boolean;
|
|
12
|
+
/** When true, this effect is a tonemapping effect. The core stack uses this to detect tonemapping-only scenarios. */
|
|
13
|
+
readonly isToneMapping?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extended interface for tonemapping effects.
|
|
18
|
+
* When ONLY tonemapping effects are in the stack, the core applies tonemapping
|
|
19
|
+
* directly to the renderer instead of creating a full postprocessing pipeline.
|
|
20
|
+
*/
|
|
21
|
+
export interface ITonemappingEffect extends IPostProcessingEffect {
|
|
22
|
+
readonly isToneMapping: true;
|
|
23
|
+
/** The three.js ToneMapping enum value to apply to the renderer */
|
|
24
|
+
readonly threeToneMapping: ToneMapping;
|
|
25
|
+
/** The exposure value to apply to the renderer */
|
|
26
|
+
readonly toneMappingExposure: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Interface for the pipeline builder that manages the EffectComposer.
|
|
31
|
+
* Implemented by `PostProcessingHandler` in engine-components.
|
|
32
|
+
*/
|
|
33
|
+
export interface IPostProcessingHandler {
|
|
34
|
+
readonly composer: EffectComposer | ThreeEffectComposer | null;
|
|
35
|
+
readonly hasSmaaEffect: boolean;
|
|
36
|
+
multisampling: number;
|
|
37
|
+
adaptivePixelRatio: boolean;
|
|
38
|
+
|
|
39
|
+
apply(effects: IPostProcessingEffect[]): Promise<void>;
|
|
40
|
+
unapply(dispose?: boolean): void;
|
|
41
|
+
updateAdaptivePixelRatio(): void;
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
|
@@ -6,7 +6,7 @@ import { onXRSessionEnd, onXRSessionStart } from "../xr/events.js";
|
|
|
6
6
|
import { ButtonsFactory } from "./buttons.js";
|
|
7
7
|
import { getIconElement } from "./icons.js";
|
|
8
8
|
import { NeedleMenu } from "./needle menu/needle-menu.js";
|
|
9
|
-
import { getOrCreateQuicklookHandler,type IQuicklookHandler } from "./quicklook-handler.js";
|
|
9
|
+
import { getOrCreateQuicklookHandler, type IQuicklookHandler } from "./quicklook-handler.js";
|
|
10
10
|
|
|
11
11
|
// TODO: move these buttons into their own web components so their logic is encapsulated (e.g. the CSS animation when a xr session is requested)
|
|
12
12
|
|
|
@@ -120,10 +120,27 @@ export class WebXRButtonFactory {
|
|
|
120
120
|
|
|
121
121
|
button.classList.add("webxr-button");
|
|
122
122
|
button.dataset["needle"] = "webxr-ar-button";
|
|
123
|
-
button.innerText = "Enter AR";
|
|
124
|
-
button.prepend(getIconElement("view_in_ar"))
|
|
125
123
|
button.title = "Click to start an AR session";
|
|
126
|
-
|
|
124
|
+
const setDefaultText = () => {
|
|
125
|
+
button.innerText = "Enter AR";
|
|
126
|
+
button.prepend(getIconElement("view_in_ar"));
|
|
127
|
+
};
|
|
128
|
+
setDefaultText();
|
|
129
|
+
|
|
130
|
+
let tapAgainTimeout: ReturnType<typeof setTimeout>;
|
|
131
|
+
button.addEventListener("click", () => {
|
|
132
|
+
|
|
133
|
+
NeedleXRSession.start(mode, init);
|
|
134
|
+
|
|
135
|
+
if (DeviceUtilities.isiOS() && !DeviceUtilities.isVisionOS()) {
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
button.innerHTML = "Tap again";
|
|
138
|
+
button.prepend(getIconElement("view_in_ar"));
|
|
139
|
+
clearTimeout(tapAgainTimeout);
|
|
140
|
+
tapAgainTimeout = setTimeout(setDefaultText, 4000);
|
|
141
|
+
}, 500);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
127
144
|
NeedleMenu.setElementPriority(button, arButtonPriority);
|
|
128
145
|
this.updateSessionSupported(button, mode);
|
|
129
146
|
this.listenToXRSessionState(button, mode);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Texture } from "three";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
const fontname = "Material Symbols Outlined";
|
|
5
4
|
|
|
6
5
|
/** Returns a HTML element containing an icon. Using https://fonts.google.com/icons
|
|
@@ -21,7 +20,10 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
21
20
|
span.setAttribute("aria-label", str + " icon");
|
|
22
21
|
span.setAttribute("aria-hidden", "true");
|
|
23
22
|
fontReady(fontname).then(res => {
|
|
24
|
-
if (res)
|
|
23
|
+
if (res) {
|
|
24
|
+
span.style.visibility = "";
|
|
25
|
+
span.innerText = str; // re-assign, otherise more_vert doesnt show up. Maybe because of layout restructuring on mobile
|
|
26
|
+
}
|
|
25
27
|
else {
|
|
26
28
|
if (str === "more_vert") {
|
|
27
29
|
span.style.visibility = "";
|
|
@@ -31,7 +33,7 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
31
33
|
span.style.display = "none";
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
})
|
|
36
|
+
});
|
|
35
37
|
return span;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -79,13 +79,9 @@ function getDOMOverlayElement(domElement: HTMLElement) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
|
|
83
82
|
handleSessionGranted();
|
|
84
83
|
async function handleSessionGranted() {
|
|
85
84
|
|
|
86
|
-
// await delay(400);
|
|
87
|
-
|
|
88
|
-
|
|
89
85
|
let defaultMode: XRSessionMode = "immersive-vr";
|
|
90
86
|
|
|
91
87
|
try {
|
|
@@ -109,8 +105,6 @@ async function handleSessionGranted() {
|
|
|
109
105
|
return;
|
|
110
106
|
}
|
|
111
107
|
|
|
112
|
-
// showBalloonMessage("sessiongranted: " + defaultMode);
|
|
113
|
-
|
|
114
108
|
// TODO: asap session granted doesnt handle the pre-room yet
|
|
115
109
|
if (getParam("debugasap")) {
|
|
116
110
|
let asapSession = globalThis["needle:XRSession"] as XRSession | undefined | Promise<XRSession>;
|
|
@@ -149,7 +143,6 @@ async function handleSessionGranted() {
|
|
|
149
143
|
navigator.xr?.addEventListener('sessiongranted', async () => {
|
|
150
144
|
// enableSpatialConsole(true);
|
|
151
145
|
|
|
152
|
-
|
|
153
146
|
const lastSessionMode = sessionStorage.getItem("needle_xr_session_mode") as XRSessionMode;
|
|
154
147
|
const lastSessionInit = sessionStorage.getItem("needle_xr_session_init") ?? null;
|
|
155
148
|
const init = lastSessionInit ? JSON.parse(lastSessionInit) : null;
|
|
@@ -331,7 +324,18 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
331
324
|
* @param mode The XRSessionMode to check if it is supported
|
|
332
325
|
* @returns true if the browser supports the given XRSessionMode
|
|
333
326
|
*/
|
|
334
|
-
static isSessionSupported(mode: XRSessionMode) {
|
|
327
|
+
static isSessionSupported(mode: XRSessionMode) {
|
|
328
|
+
// We cache the result of the session support check to avoid unnecessary calls to isSessionSupported. This is especially important for user input events that internally run session support checks during onclick where the event must be resolved as fast as possible.
|
|
329
|
+
if (this._sessionSupportedCache[mode] !== undefined) {
|
|
330
|
+
return Promise.resolve(this._sessionSupportedCache[mode]);
|
|
331
|
+
}
|
|
332
|
+
return this.xrSystem?.isSessionSupported(mode).then(supported => {
|
|
333
|
+
this._sessionSupportedCache[mode] = supported;
|
|
334
|
+
return supported;
|
|
335
|
+
}).catch(err => { if (debug) console.error(err); return false }) ?? Promise.resolve(false);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private static _sessionSupportedCache: { [mode in XRSessionMode]?: boolean } = {};
|
|
335
339
|
|
|
336
340
|
private static _currentSessionRequest?: Promise<XRSession>;
|
|
337
341
|
private static _activeSession: NeedleXRSession | null;
|
|
@@ -466,10 +470,11 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
466
470
|
// handle iOS platform where "immersive-ar" is special:
|
|
467
471
|
// - we either launch QuickLook
|
|
468
472
|
// - or forward to the Needle App Clip experience for WebXR AR
|
|
469
|
-
// TODO: should we add a separate mode (e.g. "AR")? https://linear.app/needle/issue/NE-5303
|
|
470
473
|
if (DeviceUtilities.isiOS()) {
|
|
471
474
|
|
|
472
|
-
|
|
475
|
+
// IMPORTANT: on iOS we should have prefetched immersive-ar support already on app start (this is done in xrInit()) - if not then clicking the Enter AR button for the very first time does not immediately open the AppClip overlay.
|
|
476
|
+
// Without an async await here the overlay *does* however open even when clicking for the very first time. (To test this you need to clear the local experience cache on device)
|
|
477
|
+
const arSupported = this._sessionSupportedCache["immersive-ar"] ?? await this.isARSupported();
|
|
473
478
|
|
|
474
479
|
// On VisionOS, we use QuickLook for AR experiences; no AppClip support for now.
|
|
475
480
|
if (DeviceUtilities.isVisionOS() && !arSupported && (mode === "ar" || mode === "immersive-ar")) {
|
|
@@ -489,11 +494,14 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
489
494
|
|
|
490
495
|
this.invokeSessionRequestStart("immersive-ar", init);
|
|
491
496
|
|
|
497
|
+
// if we are in an iframe, we need to navigate the top window
|
|
498
|
+
const topWindow = window.top || window;
|
|
499
|
+
const originalUrl = topWindow.location.href;
|
|
500
|
+
|
|
492
501
|
// Forward to the AppClip experience (Using the apple.com url the appclip overlay shows immediately)
|
|
493
502
|
// const url =`https://appclip.needle.tools/ar?url=${(location.href)}`;
|
|
494
503
|
const url = new URL("https://appclip.apple.com/id?p=tools.needle.launch-app.Clip");
|
|
495
504
|
url.searchParams.set("url", location.href);
|
|
496
|
-
|
|
497
505
|
const urlStr = url.toString();
|
|
498
506
|
|
|
499
507
|
Telemetry.sendEvent(Context.Current, "xr", {
|
|
@@ -502,16 +510,43 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
502
510
|
url: urlStr,
|
|
503
511
|
});
|
|
504
512
|
|
|
505
|
-
// if we are in an iframe, we need to navigate the top window
|
|
506
|
-
const topWindow = window.top || window;
|
|
507
513
|
try {
|
|
508
514
|
console.debug("iOS device detected - opening Needle App Clip for AR experience", { mode, init, url });
|
|
515
|
+
|
|
516
|
+
// Prewarm the appclip by opening the URL
|
|
517
|
+
// BUT on safari triggering this with multiple URLs causes the
|
|
518
|
+
// Browser to show the dialogue AGAIN in the background
|
|
519
|
+
// SO: we don't do it here...
|
|
520
|
+
const key = "appclip-prewarm:" + new URL(url).origin;
|
|
521
|
+
const allowMultipleNavigations = sessionStorage.getItem(key) === null;
|
|
522
|
+
if (allowMultipleNavigations) {
|
|
523
|
+
const secondUrl = new URL(url);
|
|
524
|
+
secondUrl.searchParams.set("prewarm", "1");
|
|
525
|
+
// eslint-disable-next-line xss/no-location-href-assign
|
|
526
|
+
topWindow.location.href = secondUrl.toString();
|
|
527
|
+
|
|
528
|
+
// wait a bit to give the device time to fetch the app clip metadata - this seems to fix the double tap issue (500 ms seem to work)
|
|
529
|
+
await new Promise(res => setTimeout(res, 300));
|
|
530
|
+
}
|
|
531
|
+
|
|
509
532
|
// navigate to app clip url but keep the current url in history, open in same tab
|
|
510
533
|
// eslint-disable-next-line xss/no-location-href-assign
|
|
511
534
|
topWindow.location.href = urlStr;
|
|
535
|
+
|
|
536
|
+
if (allowMultipleNavigations && !DeviceUtilities.isSafari()) {
|
|
537
|
+
setTimeout(() => {
|
|
538
|
+
const url2 = new URL(url);
|
|
539
|
+
url2.searchParams.set("prewarm", "1");
|
|
540
|
+
// eslint-disable-next-line xss/no-location-href-assign
|
|
541
|
+
topWindow.location.href = url2.toString();
|
|
542
|
+
}, 500);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
sessionStorage.setItem(key, "1");
|
|
546
|
+
|
|
512
547
|
}
|
|
513
548
|
catch (e) {
|
|
514
|
-
console.warn(
|
|
549
|
+
console.warn(`Error navigating to AppClip ${urlStr}\n`, e);
|
|
515
550
|
// if top window navigation fails and we are in an iframe, we try to navigate the top window directly
|
|
516
551
|
const weAreInIframe = window !== window.top;
|
|
517
552
|
if (weAreInIframe) {
|
|
@@ -531,7 +566,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
531
566
|
}
|
|
532
567
|
|
|
533
568
|
if (mode === "quicklook") {
|
|
534
|
-
console.
|
|
569
|
+
console.error("QuickLook mode is only supported on iOS devices.");
|
|
535
570
|
return null;
|
|
536
571
|
}
|
|
537
572
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isDevEnvironment, showBalloonMessage } from "../debug/index.js";
|
|
2
|
+
import { DeviceUtilities, setParamWithoutReload } from "../engine_utils.js";
|
|
3
|
+
import { NeedleXRSession } from "./NeedleXRSession.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize XR subsystem. Called from `initEngine()`
|
|
7
|
+
*/
|
|
8
|
+
export function initXR() {
|
|
9
|
+
// Prewarm AR support check
|
|
10
|
+
NeedleXRSession.isARSupported();
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if (DeviceUtilities.isiOS()) {
|
|
15
|
+
|
|
16
|
+
if (isDevEnvironment()) {
|
|
17
|
+
const randomParameterValue = Date.now().toString();
|
|
18
|
+
setParamWithoutReload("debug_appclip", randomParameterValue);
|
|
19
|
+
showBalloonMessage("iOS appclip debug: " + randomParameterValue);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Prefetch
|
|
23
|
+
const url = new URL("https://appclip.apple.com/id?p=tools.needle.launch-app.Clip");
|
|
24
|
+
url.searchParams.set("url", location.href);
|
|
25
|
+
const urlStr = url.toString();
|
|
26
|
+
fetch(urlStr, { method: "HEAD", mode: "no-cors" }).catch(() => {
|
|
27
|
+
// appclip prefetch - to get metadata faster on iOS devices, this seems to fix the double tap issue when opening the appclip for AR sessions.
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// We add the meta tag here to preload app clip card data for iOS.
|
|
32
|
+
const meta = window.top?.document.querySelector('meta[name="apple-itunes-app"]');
|
|
33
|
+
if(!meta) {
|
|
34
|
+
const metaTag = document.createElement("meta");
|
|
35
|
+
metaTag.name = "apple-itunes-app";
|
|
36
|
+
metaTag.content = "app-id=6757205152, app-clip-bundle-id=tools.needle.launch-app.Clip, app-clip-display=card";
|
|
37
|
+
window.top?.document.head.appendChild(metaTag);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.warn("Error adding apple-itunes-app meta tag for appclip support\n", e);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// We preconnect to apple here to speed up the appclip meta request for the first click. NOT sure if necessary and working but can't hurt either?
|
|
46
|
+
const topWindow = window.top || window;
|
|
47
|
+
const preconnectMeta = topWindow.document.createElement("link");
|
|
48
|
+
preconnectMeta.rel = "preconnect";
|
|
49
|
+
preconnectMeta.href = url.toString();
|
|
50
|
+
topWindow.document.head.appendChild(preconnectMeta);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.warn("Error adding preconnect link for appclip.apple.com\n", e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import { Object3D } from "three";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { AnimationUtils } from "../engine/engine_animation.js";
|
|
4
4
|
|
|
5
5
|
/** Internal method - This marks an object as being animated. Make sure to always call isAnimated=false if you stop animating the object
|
|
6
6
|
* @param obj The object to mark
|
|
7
7
|
* @param isAnimated Whether the object is animated or not
|
|
8
|
+
* @deprecated Use {@link AnimationUtils.setObjectAnimated} instead
|
|
8
9
|
*/
|
|
9
10
|
export function setObjectAnimated(obj: Object3D, animatedBy: object, isAnimated: boolean) {
|
|
10
|
-
|
|
11
|
-
if (obj[$objectAnimationKey] === undefined) {
|
|
12
|
-
if (!isAnimated) return;
|
|
13
|
-
obj[$objectAnimationKey] = new Set<object>();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const set = obj[$objectAnimationKey] as Set<object>;
|
|
17
|
-
if (isAnimated) {
|
|
18
|
-
set.add(animatedBy);
|
|
19
|
-
}
|
|
20
|
-
else if (set.has(animatedBy))
|
|
21
|
-
set.delete(animatedBy);
|
|
11
|
+
return AnimationUtils.setObjectAnimated(obj, animatedBy, isAnimated);
|
|
22
12
|
}
|
|
23
13
|
|
|
24
|
-
/** Get is the object is currently animated. Currently used by the Animator to check if a timeline animationtrack is actively animating an object
|
|
14
|
+
/** Get is the object is currently animated. Currently used by the Animator to check if a timeline animationtrack is actively animating an object
|
|
15
|
+
* @deprecated Use {@link AnimationUtils.getObjectAnimated} instead
|
|
16
|
+
*/
|
|
25
17
|
export function getObjectAnimated(obj: Object3D): boolean {
|
|
26
|
-
|
|
27
|
-
const set = obj[$objectAnimationKey] as Set<object>;
|
|
28
|
-
return set !== undefined && set.size > 0;
|
|
18
|
+
return AnimationUtils.getObjectAnimated(obj) || false;
|
|
29
19
|
}
|