@@ -57,8 +57,6 @@
|
|
57
57
|
}
|
58
58
|
if (debug) {
|
59
59
|
const outputFilePath = path.resolve(projectDir, 'node_modules/.vite/needle.alias.log');
|
60
|
-
const outputDirectory = path.dirname(outputFilePath);
|
61
|
-
if (!existsSync(outputDirectory)) mkdirSync(outputDirectory, { recursive: true });
|
62
60
|
outputDebugFile = createWriteStream(outputFilePath, { flags: "a" });
|
63
61
|
const timestamp = new Date().toISOString();
|
64
62
|
outputDebugFile.write("\n\n\n--------------------------\n");
|
@@ -70,7 +68,7 @@
|
|
70
68
|
const aliasPlugin = {
|
71
69
|
name: "needle-alias",
|
72
70
|
config(config) {
|
73
|
-
if (debug)
|
71
|
+
if (debug) console.log('[needle-alias] ProjectDirectory: ' + projectDir);
|
74
72
|
if (!config.resolve) config.resolve = {};
|
75
73
|
if (!config.resolve.alias) config.resolve.alias = {};
|
76
74
|
const aliasDict = config.resolve.alias;
|
@@ -99,7 +97,6 @@
|
|
99
97
|
let lastImporter = "";
|
100
98
|
/** This plugin logs all imports. This helps to find cases where incorrect folders are found/resolved. */
|
101
99
|
|
102
|
-
/** @type {import("vite").Plugin} */
|
103
100
|
const debuggingPlugin = {
|
104
101
|
name: "needle:alias-debug",
|
105
102
|
// needs to run before regular resolver
|
@@ -118,9 +115,9 @@
|
|
118
115
|
// verbose logging for all imports
|
119
116
|
if (lastImporter !== importer) {
|
120
117
|
lastImporter = importer;
|
121
|
-
log(
|
118
|
+
log('[needle-alias] Resolving: ', importer, "(file)");
|
122
119
|
}
|
123
|
-
log(
|
120
|
+
log('[needle-alias] → ' + id);
|
124
121
|
return;
|
125
122
|
},
|
126
123
|
}
|
@@ -11,73 +11,38 @@
|
|
11
11
|
|
12
12
|
const debug = getParam("debuganimator");
|
13
13
|
|
14
|
-
|
15
|
-
* Represents an event emitted by an animation mixer
|
16
|
-
* @category Animation and Sequencing
|
17
|
-
*/
|
14
|
+
|
18
15
|
export declare class MixerEvent {
|
19
|
-
/** The type of event that occurred */
|
20
16
|
type: string;
|
21
|
-
/** The animation action that triggered this event */
|
22
17
|
action: AnimationAction;
|
23
|
-
/** Number of loops completed in this cycle */
|
24
18
|
loopDelta: number;
|
25
|
-
/** The animation mixer that emitted this event */
|
26
19
|
target: AnimationMixer;
|
27
20
|
}
|
28
21
|
|
29
|
-
/**
|
30
|
-
* Configuration options for playing animations
|
31
|
-
* @category Animation and Sequencing
|
32
|
-
*/
|
33
22
|
export declare class PlayOptions {
|
34
|
-
/** Whether the animation should loop, and if so, which loop style to use */
|
35
23
|
loop?: boolean | AnimationActionLoopStyles;
|
36
|
-
/** Whether the final animation state should be maintained after playback completes */
|
37
24
|
clampWhenFinished?: boolean;
|
38
25
|
}
|
39
26
|
|
40
|
-
/**
|
41
|
-
*
|
42
|
-
* It works with an AnimatorController to handle state transitions and animation blending.
|
43
|
-
* A new AnimatorController can be created from code via `AnimatorController.createFromClips`.
|
27
|
+
/** The Animator component is used to play animations on a GameObject. It is used in combination with an AnimatorController (which is a state machine for animations)
|
28
|
+
* A new AnimatorController can be created from code via `AnimatorController.createFromClips`
|
44
29
|
* @category Animation and Sequencing
|
45
30
|
* @group Components
|
46
31
|
*/
|
47
32
|
export class Animator extends Behaviour implements IAnimationComponent {
|
48
33
|
|
49
|
-
/**
|
50
|
-
* Identifies this component as an animation component in the engine
|
51
|
-
*/
|
52
34
|
get isAnimationComponent() {
|
53
35
|
return true;
|
54
36
|
}
|
55
37
|
|
56
|
-
/**
|
57
|
-
* When enabled, animation will affect the root transform position and rotation
|
58
|
-
*/
|
59
38
|
@serializable()
|
60
39
|
applyRootMotion: boolean = false;
|
61
|
-
|
62
|
-
/**
|
63
|
-
* Indicates whether this animator contains root motion data
|
64
|
-
*/
|
65
40
|
@serializable()
|
66
41
|
hasRootMotion: boolean = false;
|
67
|
-
|
68
|
-
/**
|
69
|
-
* When enabled, the animator will maintain its state when the component is disabled
|
70
|
-
*/
|
71
42
|
@serializable()
|
72
43
|
keepAnimatorControllerStateOnDisable: boolean = false;
|
73
44
|
|
74
45
|
// set from needle animator extension
|
75
|
-
/**
|
76
|
-
* Sets or replaces the animator controller for this component.
|
77
|
-
* Handles binding the controller to this animator instance and ensures
|
78
|
-
* proper initialization when the controller changes.
|
79
|
-
* @param val The animator controller model or instance to use
|
80
|
-
*/
|
81
46
|
@serializable()
|
82
47
|
set runtimeAnimatorController(val: AnimatorControllerModel | AnimatorController | undefined | null) {
|
83
48
|
if (this._animatorController && this._animatorController.model === val) {
|
@@ -104,53 +69,41 @@
|
|
104
69
|
}
|
105
70
|
else this._animatorController = null;
|
106
71
|
}
|
107
|
-
|
108
|
-
/**
|
109
|
-
* Gets the current animator controller instance
|
110
|
-
* @returns The current animator controller or null if none is assigned
|
111
|
-
*/
|
112
72
|
get runtimeAnimatorController(): AnimatorController | undefined | null {
|
113
73
|
return this._animatorController;
|
114
74
|
}
|
115
75
|
|
116
|
-
/**
|
117
|
-
*
|
118
|
-
* @returns The current state
|
119
|
-
|
76
|
+
/** The current state info of the animator.
|
77
|
+
* If you just want to access the currently playing animation action you can use currentAction
|
78
|
+
* @returns {AnimatorStateInfo} The current state info of the animator or null if no state is playing
|
79
|
+
*/
|
120
80
|
getCurrentStateInfo() {
|
121
81
|
return this.runtimeAnimatorController?.getCurrentStateInfo();
|
122
82
|
}
|
123
|
-
/**
|
124
|
-
*
|
125
|
-
|
126
|
-
*/
|
83
|
+
/** The current action playing. It can be used to modify the action
|
84
|
+
* @returns {AnimationAction | null} The current action playing or null if no state is playing
|
85
|
+
*/
|
127
86
|
get currentAction() {
|
128
87
|
return this.runtimeAnimatorController?.currentAction || null;
|
129
88
|
}
|
130
89
|
|
131
|
-
/**
|
132
|
-
* Indicates whether animation parameters have been modified since the last update
|
133
|
-
* @returns True if parameters have been changed
|
134
|
-
*/
|
90
|
+
/** @returns {boolean} True if parameters have been changed */
|
135
91
|
get parametersAreDirty() { return this._parametersAreDirty; }
|
136
92
|
private _parametersAreDirty: boolean = false;
|
137
93
|
|
138
|
-
/**
|
139
|
-
* Indicates whether the animator state has changed since the last update
|
140
|
-
* @returns True if the animator has been changed
|
141
|
-
*/
|
94
|
+
/** @returns {boolean} True if the animator has been changed */
|
142
95
|
get isDirty() { return this._isDirty; }
|
143
96
|
private _isDirty: boolean = false;
|
144
97
|
|
145
98
|
/**@deprecated use play() */
|
146
99
|
Play(name: string | number, layer: number = -1, normalizedTime: number = Number.NEGATIVE_INFINITY, transitionDurationInSec: number = 0) { this.play(name, layer, normalizedTime, transitionDurationInSec); }
|
147
|
-
/**
|
148
|
-
*
|
149
|
-
* @param
|
150
|
-
* @param
|
151
|
-
* @param
|
152
|
-
* @
|
153
|
-
*/
|
100
|
+
/** Plays an animation on the animator
|
101
|
+
* @param {string | number} name The name of the animation to play. Can also be the hash of the animation
|
102
|
+
* @param {number} layer The layer to play the animation on. Default is -1
|
103
|
+
* @param {number} normalizedTime The normalized time to start the animation at. Default is Number.NEGATIVE_INFINITY
|
104
|
+
* @param {number} transitionDurationInSec The duration of the transition to the new animation. Default is 0
|
105
|
+
* @returns {void}
|
106
|
+
* */
|
154
107
|
play(name: string | number, layer: number = -1, normalizedTime: number = Number.NEGATIVE_INFINITY, transitionDurationInSec: number = 0) {
|
155
108
|
this.runtimeAnimatorController?.play(name, layer, normalizedTime, transitionDurationInSec);
|
156
109
|
this._isDirty = true;
|
@@ -158,9 +111,7 @@
|
|
158
111
|
|
159
112
|
/**@deprecated use reset */
|
160
113
|
Reset() { this.reset(); }
|
161
|
-
/**
|
162
|
-
* Resets the animator controller to its initial state
|
163
|
-
*/
|
114
|
+
/** Resets the animatorcontroller */
|
164
115
|
reset() {
|
165
116
|
this._animatorController?.reset();
|
166
117
|
this._isDirty = true;
|
@@ -168,12 +119,6 @@
|
|
168
119
|
|
169
120
|
/**@deprecated use setBool */
|
170
121
|
SetBool(name: string | number, val: boolean) { this.setBool(name, val); }
|
171
|
-
|
172
|
-
/**
|
173
|
-
* Sets a boolean parameter in the animator
|
174
|
-
* @param name The name or hash of the parameter
|
175
|
-
* @param value The boolean value to set
|
176
|
-
*/
|
177
122
|
setBool(name: string | number, value: boolean) {
|
178
123
|
if (debug) console.log("setBool", name, value);
|
179
124
|
if (this.runtimeAnimatorController?.getBool(name) !== value)
|
@@ -183,34 +128,18 @@
|
|
183
128
|
|
184
129
|
/**@deprecated use getBool */
|
185
130
|
GetBool(name: string | number) { return this.getBool(name); }
|
186
|
-
|
187
|
-
/**
|
188
|
-
* Gets a boolean parameter from the animator
|
189
|
-
* @param name The name or hash of the parameter
|
190
|
-
* @returns The value of the boolean parameter, or false if not found
|
191
|
-
*/
|
192
131
|
getBool(name: string | number): boolean {
|
193
132
|
const res = this.runtimeAnimatorController?.getBool(name) ?? false;
|
194
133
|
if (debug) console.log("getBool", name, res);
|
195
134
|
return res;
|
196
135
|
}
|
197
136
|
|
198
|
-
/**
|
199
|
-
* Toggles a boolean parameter between true and false
|
200
|
-
* @param name The name or hash of the parameter
|
201
|
-
*/
|
202
137
|
toggleBool(name: string | number) {
|
203
138
|
this.setBool(name, !this.getBool(name));
|
204
139
|
}
|
205
140
|
|
206
141
|
/**@deprecated use setFloat */
|
207
142
|
SetFloat(name: string | number, val: number) { this.setFloat(name, val); }
|
208
|
-
|
209
|
-
/**
|
210
|
-
* Sets a float parameter in the animator
|
211
|
-
* @param name The name or hash of the parameter
|
212
|
-
* @param val The float value to set
|
213
|
-
*/
|
214
143
|
setFloat(name: string | number, val: number) {
|
215
144
|
if (this.runtimeAnimatorController?.getFloat(name) !== val)
|
216
145
|
this._parametersAreDirty = true;
|
@@ -220,12 +149,6 @@
|
|
220
149
|
|
221
150
|
/**@deprecated use getFloat */
|
222
151
|
GetFloat(name: string | number) { return this.getFloat(name); }
|
223
|
-
|
224
|
-
/**
|
225
|
-
* Gets a float parameter from the animator
|
226
|
-
* @param name The name or hash of the parameter
|
227
|
-
* @returns The value of the float parameter, or -1 if not found
|
228
|
-
*/
|
229
152
|
getFloat(name: string | number): number {
|
230
153
|
const res = this.runtimeAnimatorController?.getFloat(name) ?? -1;
|
231
154
|
if (debug) console.log("getFloat", name, res);
|
@@ -234,12 +157,6 @@
|
|
234
157
|
|
235
158
|
/**@deprecated use setInteger */
|
236
159
|
SetInteger(name: string | number, val: number) { this.setInteger(name, val); }
|
237
|
-
|
238
|
-
/**
|
239
|
-
* Sets an integer parameter in the animator
|
240
|
-
* @param name The name or hash of the parameter
|
241
|
-
* @param val The integer value to set
|
242
|
-
*/
|
243
160
|
setInteger(name: string | number, val: number) {
|
244
161
|
if (this.runtimeAnimatorController?.getInteger(name) !== val)
|
245
162
|
this._parametersAreDirty = true;
|
@@ -249,12 +166,6 @@
|
|
249
166
|
|
250
167
|
/**@deprecated use getInteger */
|
251
168
|
GetInteger(name: string | number) { return this.getInteger(name); }
|
252
|
-
|
253
|
-
/**
|
254
|
-
* Gets an integer parameter from the animator
|
255
|
-
* @param name The name or hash of the parameter
|
256
|
-
* @returns The value of the integer parameter, or -1 if not found
|
257
|
-
*/
|
258
169
|
getInteger(name: string | number): number {
|
259
170
|
const res = this.runtimeAnimatorController?.getInteger(name) ?? -1;
|
260
171
|
if (debug) console.log("getInteger", name, res);
|
@@ -263,11 +174,6 @@
|
|
263
174
|
|
264
175
|
/**@deprecated use setTrigger */
|
265
176
|
SetTrigger(name: string | number) { this.setTrigger(name); }
|
266
|
-
|
267
|
-
/**
|
268
|
-
* Activates a trigger parameter in the animator
|
269
|
-
* @param name The name or hash of the trigger parameter
|
270
|
-
*/
|
271
177
|
setTrigger(name: string | number) {
|
272
178
|
this._parametersAreDirty = true;
|
273
179
|
if (debug) console.log("setTrigger", name);
|
@@ -276,11 +182,6 @@
|
|
276
182
|
|
277
183
|
/**@deprecated use resetTrigger */
|
278
184
|
ResetTrigger(name: string | number) { this.resetTrigger(name); }
|
279
|
-
|
280
|
-
/**
|
281
|
-
* Resets a trigger parameter in the animator
|
282
|
-
* @param name The name or hash of the trigger parameter
|
283
|
-
*/
|
284
185
|
resetTrigger(name: string | number) {
|
285
186
|
this._parametersAreDirty = true;
|
286
187
|
if (debug) console.log("resetTrigger", name);
|
@@ -289,12 +190,6 @@
|
|
289
190
|
|
290
191
|
/**@deprecated use getTrigger */
|
291
192
|
GetTrigger(name: string | number) { this.getTrigger(name); }
|
292
|
-
|
293
|
-
/**
|
294
|
-
* Gets the state of a trigger parameter from the animator
|
295
|
-
* @param name The name or hash of the trigger parameter
|
296
|
-
* @returns The state of the trigger parameter
|
297
|
-
*/
|
298
193
|
getTrigger(name: string | number) {
|
299
194
|
const res = this.runtimeAnimatorController?.getTrigger(name);
|
300
195
|
if (debug) console.log("getTrigger", name, res);
|
@@ -303,21 +198,13 @@
|
|
303
198
|
|
304
199
|
/**@deprecated use isInTransition */
|
305
200
|
IsInTransition() { return this.isInTransition(); }
|
306
|
-
/**
|
307
|
-
* Checks if the animator is currently in a transition between states
|
308
|
-
* @returns True if the animator is currently blending between animations
|
309
|
-
*/
|
201
|
+
/** @returns `true` if the animator is currently in a transition */
|
310
202
|
isInTransition(): boolean {
|
311
203
|
return this.runtimeAnimatorController?.isInTransition() ?? false;
|
312
204
|
}
|
313
205
|
|
314
206
|
/**@deprecated use setSpeed */
|
315
207
|
SetSpeed(speed: number) { return this.setSpeed(speed); }
|
316
|
-
|
317
|
-
/**
|
318
|
-
* Sets the playback speed of the animator
|
319
|
-
* @param speed The new playback speed multiplier
|
320
|
-
*/
|
321
208
|
setSpeed(speed: number) {
|
322
209
|
if (speed === this._speed) return;
|
323
210
|
if (debug) console.log("setSpeed", speed);
|
@@ -326,20 +213,13 @@
|
|
326
213
|
this._animatorController.setSpeed(speed);
|
327
214
|
}
|
328
215
|
|
329
|
-
/**
|
330
|
-
* Sets a random playback speed between the min and max values
|
331
|
-
* @param minMax Object with x (minimum) and y (maximum) speed values
|
332
|
-
*/
|
216
|
+
/** Will generate a random speed between the min and max values and set it to the animatorcontroller */
|
333
217
|
set minMaxSpeed(minMax: { x: number, y: number }) {
|
334
218
|
this._speed = Mathf.lerp(minMax.x, minMax.y, Math.random());
|
335
219
|
if (this._animatorController?.animator == this)
|
336
220
|
this._animatorController.setSpeed(this._speed);
|
337
221
|
}
|
338
222
|
|
339
|
-
/**
|
340
|
-
* Sets a random normalized time offset for animations between min (x) and max (y) values
|
341
|
-
* @param minMax Object with x (min) and y (max) values for the offset range
|
342
|
-
*/
|
343
223
|
set minMaxOffsetNormalized(minMax: { x: number, y: number }) {
|
344
224
|
this._normalizedStartOffset = Mathf.lerp(minMax.x, minMax.y, Math.random());
|
345
225
|
if (this.runtimeAnimatorController?.animator == this)
|
@@ -15,11 +15,6 @@
|
|
15
15
|
const debug = getParam("debuganimatorcontroller");
|
16
16
|
const debugRootMotion = getParam("debugrootmotion");
|
17
17
|
|
18
|
-
/**
|
19
|
-
* Generates a hash code for a string
|
20
|
-
* @param str - The string to hash
|
21
|
-
* @returns A numeric hash value
|
22
|
-
*/
|
23
18
|
function stringToHash(str): number {
|
24
19
|
let hash = 0;
|
25
20
|
for (let i = 0; i < str.length; i++) {
|
@@ -30,38 +25,27 @@
|
|
30
25
|
return hash;
|
31
26
|
}
|
32
27
|
|
33
|
-
/**
|
34
|
-
* Configuration options for creating an AnimatorController
|
35
|
-
*/
|
36
28
|
declare type CreateAnimatorControllerOptions = {
|
37
|
-
/** Should each
|
29
|
+
/** Should each animationstate loop */
|
38
30
|
looping?: boolean,
|
39
|
-
/** Set to false to disable generating transitions between
|
31
|
+
/** Set to false to disable generating transitions between animationclips */
|
40
32
|
autoTransition?: boolean,
|
41
|
-
/**
|
33
|
+
/** Set to a positive value in seconds for transition duration between states */
|
42
34
|
transitionDuration?: number,
|
43
35
|
}
|
44
36
|
|
45
37
|
/**
|
46
|
-
*
|
47
|
-
*
|
48
|
-
*
|
49
|
-
|
50
|
-
* component to control animation behavior on 3D models.
|
51
|
-
*
|
52
|
-
* Use the static method {@link AnimatorController.createFromClips} to create
|
53
|
-
* an animator controller from a set of animation clips.
|
54
|
-
*/
|
38
|
+
* The AnimatorController is used to control the playback of animations. It is used by the {@link Animator} component.
|
39
|
+
* It is using a state machine to control the playback of animations.
|
40
|
+
* To create an animator controller use the static method `AnimatorController.createFromClips(clips: AnimationClip[], options: CreateAnimatorControllerOptions)`
|
41
|
+
*/
|
55
42
|
export class AnimatorController {
|
56
43
|
|
57
|
-
/**
|
58
|
-
*
|
59
|
-
*
|
60
|
-
*
|
61
|
-
|
62
|
-
* @param options - Configuration options for the controller including looping behavior and transitions
|
63
|
-
* @returns A new AnimatorController instance
|
64
|
-
*/
|
44
|
+
/** Create an animatorcontroller. States are created from the clips array.
|
45
|
+
* @param clips the clips to assign to the controller
|
46
|
+
* @param options options to control the creation of the controller.
|
47
|
+
* @returns the created animator controller
|
48
|
+
*/
|
65
49
|
static createFromClips(clips: AnimationClip[], options: CreateAnimatorControllerOptions = { looping: false, autoTransition: true, transitionDuration: 0 }): AnimatorController {
|
66
50
|
const states: State[] = [];
|
67
51
|
for (let i = 0; i < clips.length; i++) {
|
@@ -115,14 +99,6 @@
|
|
115
99
|
return controller;
|
116
100
|
}
|
117
101
|
|
118
|
-
/**
|
119
|
-
* Plays an animation state by name or hash.
|
120
|
-
*
|
121
|
-
* @param name - The name or hash identifier of the state to play
|
122
|
-
* @param layerIndex - The layer index (defaults to 0)
|
123
|
-
* @param normalizedTime - The normalized time to start the animation from (0-1)
|
124
|
-
* @param durationInSec - Transition duration in seconds
|
125
|
-
*/
|
126
102
|
play(name: string | number, layerIndex: number = -1, normalizedTime: number = Number.NEGATIVE_INFINITY, durationInSec: number = 0) {
|
127
103
|
if (layerIndex < 0) layerIndex = 0;
|
128
104
|
else if (layerIndex >= this.model.layers.length) {
|
@@ -142,42 +118,20 @@
|
|
142
118
|
console.warn("Could not find " + name + " to play");
|
143
119
|
}
|
144
120
|
|
145
|
-
/**
|
146
|
-
* Resets the controller to its initial state.
|
147
|
-
*/
|
148
121
|
reset() {
|
149
122
|
this.setStartTransition();
|
150
123
|
}
|
151
124
|
|
152
|
-
/**
|
153
|
-
* Sets a boolean parameter value by name or hash.
|
154
|
-
*
|
155
|
-
* @param name - The name or hash identifier of the parameter
|
156
|
-
* @param value - The boolean value to set
|
157
|
-
*/
|
158
125
|
setBool(name: string | number, value: boolean) {
|
159
126
|
const key = typeof name === "string" ? "name" : "hash";
|
160
127
|
return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = value);
|
161
128
|
}
|
162
129
|
|
163
|
-
/**
|
164
|
-
* Gets a boolean parameter value by name or hash.
|
165
|
-
*
|
166
|
-
* @param name - The name or hash identifier of the parameter
|
167
|
-
* @returns The boolean value of the parameter, or false if not found
|
168
|
-
*/
|
169
130
|
getBool(name: string | number): boolean {
|
170
131
|
const key = typeof name === "string" ? "name" : "hash";
|
171
132
|
return this.model?.parameters?.find(p => p[key] === name)?.value as boolean ?? false;
|
172
133
|
}
|
173
134
|
|
174
|
-
/**
|
175
|
-
* Sets a float parameter value by name or hash.
|
176
|
-
*
|
177
|
-
* @param name - The name or hash identifier of the parameter
|
178
|
-
* @param val - The float value to set
|
179
|
-
* @returns True if the parameter was found and set, false otherwise
|
180
|
-
*/
|
181
135
|
setFloat(name: string | number, val: number) {
|
182
136
|
const key = typeof name === "string" ? "name" : "hash";
|
183
137
|
const filtered = this.model?.parameters?.filter(p => p[key] === name);
|
@@ -185,45 +139,21 @@
|
|
185
139
|
return filtered?.length > 0;
|
186
140
|
}
|
187
141
|
|
188
|
-
/**
|
189
|
-
* Gets a float parameter value by name or hash.
|
190
|
-
*
|
191
|
-
* @param name - The name or hash identifier of the parameter
|
192
|
-
* @returns The float value of the parameter, or 0 if not found
|
193
|
-
*/
|
194
142
|
getFloat(name: string | number): number {
|
195
143
|
const key = typeof name === "string" ? "name" : "hash";
|
196
144
|
return this.model?.parameters?.find(p => p[key] === name)?.value as number ?? 0;
|
197
145
|
}
|
198
146
|
|
199
|
-
/**
|
200
|
-
* Sets an integer parameter value by name or hash.
|
201
|
-
*
|
202
|
-
* @param name - The name or hash identifier of the parameter
|
203
|
-
* @param val - The integer value to set
|
204
|
-
*/
|
205
147
|
setInteger(name: string | number, val: number) {
|
206
148
|
const key = typeof name === "string" ? "name" : "hash";
|
207
149
|
return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = val);
|
208
150
|
}
|
209
151
|
|
210
|
-
/**
|
211
|
-
* Gets an integer parameter value by name or hash.
|
212
|
-
*
|
213
|
-
* @param name - The name or hash identifier of the parameter
|
214
|
-
* @returns The integer value of the parameter, or 0 if not found
|
215
|
-
*/
|
216
152
|
getInteger(name: string | number): number {
|
217
153
|
const key = typeof name === "string" ? "name" : "hash";
|
218
154
|
return this.model?.parameters?.find(p => p[key] === name)?.value as number ?? 0;
|
219
155
|
}
|
220
156
|
|
221
|
-
/**
|
222
|
-
* Sets a trigger parameter to active (true).
|
223
|
-
* Trigger parameters are automatically reset after they are consumed by a transition.
|
224
|
-
*
|
225
|
-
* @param name - The name or hash identifier of the trigger parameter
|
226
|
-
*/
|
227
157
|
setTrigger(name: string | number) {
|
228
158
|
if (debug)
|
229
159
|
console.log("SET TRIGGER", name);
|
@@ -231,32 +161,16 @@
|
|
231
161
|
return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = true);
|
232
162
|
}
|
233
163
|
|
234
|
-
/**
|
235
|
-
* Resets a trigger parameter to inactive (false).
|
236
|
-
*
|
237
|
-
* @param name - The name or hash identifier of the trigger parameter
|
238
|
-
*/
|
239
164
|
resetTrigger(name: string | number) {
|
240
165
|
const key = typeof name === "string" ? "name" : "hash";
|
241
166
|
return this.model?.parameters?.filter(p => p[key] === name).forEach(p => p.value = false);
|
242
167
|
}
|
243
168
|
|
244
|
-
/**
|
245
|
-
* Gets the current state of a trigger parameter.
|
246
|
-
*
|
247
|
-
* @param name - The name or hash identifier of the trigger parameter
|
248
|
-
* @returns The boolean state of the trigger, or false if not found
|
249
|
-
*/
|
250
169
|
getTrigger(name: string | number): boolean {
|
251
170
|
const key = typeof name === "string" ? "name" : "hash";
|
252
171
|
return this.model?.parameters?.find(p => p[key] === name)?.value as boolean ?? false;
|
253
172
|
}
|
254
173
|
|
255
|
-
/**
|
256
|
-
* Checks if the controller is currently in a transition between states.
|
257
|
-
*
|
258
|
-
* @returns True if a transition is in progress, false otherwise
|
259
|
-
*/
|
260
174
|
isInTransition(): boolean {
|
261
175
|
return this._activeStates.length > 1;
|
262
176
|
}
|
@@ -268,21 +182,8 @@
|
|
268
182
|
private _speed: number = 1;
|
269
183
|
|
270
184
|
|
271
|
-
/**
|
272
|
-
* Finds an animation state by name or hash.
|
273
|
-
* @deprecated Use findState instead
|
274
|
-
*
|
275
|
-
* @param name - The name or hash identifier of the state to find
|
276
|
-
* @returns The found state or null if not found
|
277
|
-
*/
|
185
|
+
/**@deprecated use findState */
|
278
186
|
FindState(name: string | number | undefined | null): State | null { return this.findState(name); }
|
279
|
-
|
280
|
-
/**
|
281
|
-
* Finds an animation state by name or hash.
|
282
|
-
*
|
283
|
-
* @param name - The name or hash identifier of the state to find
|
284
|
-
* @returns The found state or null if not found
|
285
|
-
*/
|
286
187
|
findState(name: string | number | undefined | null): State | null {
|
287
188
|
if (!name) return null;
|
288
189
|
if (Array.isArray(this.model.layers)) {
|
@@ -295,11 +196,9 @@
|
|
295
196
|
return null;
|
296
197
|
}
|
297
198
|
|
298
|
-
/**
|
299
|
-
*
|
300
|
-
|
301
|
-
* @returns An AnimatorStateInfo object with data about the current state, or null if no state is active
|
302
|
-
*/
|
199
|
+
/** Get the current state info
|
200
|
+
* @returns the current state info or null if no state is active
|
201
|
+
*/
|
303
202
|
getCurrentStateInfo() {
|
304
203
|
if (!this._activeState) return null;
|
305
204
|
const action = this._activeState.motion.action;
|
@@ -309,11 +208,10 @@
|
|
309
208
|
return new AnimatorStateInfo(this._activeState, normalizedTime, dur, this._speed);
|
310
209
|
}
|
311
210
|
|
312
|
-
/**
|
313
|
-
*
|
314
|
-
*
|
315
|
-
*
|
316
|
-
*/
|
211
|
+
/** Get the current action (shorthand for activeState.motion.action)
|
212
|
+
* @returns the current action that is playing. This is the action that is currently transitioning to or playing.
|
213
|
+
* If no action is playing null is returned.
|
214
|
+
**/
|
317
215
|
get currentAction(): AnimationAction | null {
|
318
216
|
if (!this._activeState) return null;
|
319
217
|
const action = this._activeState.motion.action;
|
@@ -321,37 +219,24 @@
|
|
321
219
|
return action;
|
322
220
|
}
|
323
221
|
|
324
|
-
/**
|
325
|
-
* The normalized time (0-1) to start playing the first state at.
|
326
|
-
* This affects the initial state when the animator is first enabled.
|
327
|
-
*/
|
222
|
+
/** The normalized time of the start state. This is used to determine the start time of the first state. */
|
328
223
|
normalizedStartOffset: number = 0;
|
329
224
|
|
330
|
-
/**
|
331
|
-
* The Animator component this controller is bound to.
|
332
|
-
*/
|
225
|
+
/** the animator that this controller is bound to */
|
333
226
|
animator?: Animator;
|
334
|
-
|
335
|
-
/**
|
336
|
-
* The data model describing the animation states and transitions.
|
337
|
-
*/
|
227
|
+
/** the model that this controller is based on */
|
338
228
|
model: AnimatorControllerModel;
|
339
229
|
|
340
|
-
/**
|
341
|
-
* Gets the engine context from the bound animator.
|
342
|
-
*/
|
230
|
+
/** Get the context of the animator */
|
343
231
|
get context(): Context | undefined | null { return this.animator?.context; }
|
344
232
|
|
345
|
-
/**
|
346
|
-
* Gets the animation mixer used by this controller.
|
347
|
-
*/
|
233
|
+
/** Get the animation mixer that is used to play the animations */
|
348
234
|
get mixer() {
|
349
235
|
return this._mixer;
|
350
236
|
}
|
351
237
|
|
352
238
|
/**
|
353
|
-
*
|
354
|
-
* Stops all animations and unregisters the mixer from the animation system.
|
239
|
+
* Clears the animation mixer and unregisters it from the context.
|
355
240
|
*/
|
356
241
|
dispose() {
|
357
242
|
this._mixer.stopAllAction();
|
@@ -369,12 +254,7 @@
|
|
369
254
|
// // this.internalApplyRootMotion(obj);
|
370
255
|
// }
|
371
256
|
|
372
|
-
/**
|
373
|
-
* Binds this controller to an animator component.
|
374
|
-
* Creates a new animation mixer and sets up animation actions.
|
375
|
-
*
|
376
|
-
* @param animator - The animator to bind this controller to
|
377
|
-
*/
|
257
|
+
/** Bind the animator to the controller. Only one animator can be bound to a controller at a time. */
|
378
258
|
bind(animator: Animator) {
|
379
259
|
if (!animator) console.error("AnimatorController.bind: animator is null");
|
380
260
|
else if (this.animator !== animator) {
|
@@ -389,12 +269,7 @@
|
|
389
269
|
}
|
390
270
|
}
|
391
271
|
|
392
|
-
/**
|
393
|
-
* Creates a deep copy of this controller.
|
394
|
-
* Clones the model data but does not copy runtime state.
|
395
|
-
*
|
396
|
-
* @returns A new AnimatorController instance with the same configuration
|
397
|
-
*/
|
272
|
+
/** Create a clone of the controller. This will clone the model but not the runtime state. */
|
398
273
|
clone() {
|
399
274
|
if (typeof this.model === "string") {
|
400
275
|
console.warn("AnimatorController has not been resolved, can not create model from string", this.model);
|
@@ -422,12 +297,7 @@
|
|
422
297
|
return controller;
|
423
298
|
}
|
424
299
|
|
425
|
-
/**
|
426
|
-
* Updates the controller's state machine and animations.
|
427
|
-
* Called each frame by the animator component.
|
428
|
-
*
|
429
|
-
* @param weight - The weight to apply to the animations (for blending)
|
430
|
-
*/
|
300
|
+
/** Called by the animator. This will update the active states and transitions as well as the animation mixer. */
|
431
301
|
update(weight: number) {
|
432
302
|
if (!this.animator) return;
|
433
303
|
this.evaluateTransitions();
|
@@ -448,11 +318,9 @@
|
|
448
318
|
private _mixer!: AnimationMixer;
|
449
319
|
private _activeState?: State;
|
450
320
|
|
451
|
-
/**
|
452
|
-
*
|
453
|
-
*
|
454
|
-
* @returns The active state or undefined if no state is active
|
455
|
-
*/
|
321
|
+
/** Get the currently active state playing
|
322
|
+
* @returns the currently active state or undefined if no state is active
|
323
|
+
**/
|
456
324
|
get activeState(): State | undefined { return this._activeState; }
|
457
325
|
|
458
326
|
constructor(model: AnimatorControllerModel) {
|
@@ -902,10 +770,6 @@
|
|
902
770
|
}
|
903
771
|
}
|
904
772
|
|
905
|
-
/**
|
906
|
-
* Yields all animation actions managed by this controller.
|
907
|
-
* Iterates through all states in all layers and returns their actions.
|
908
|
-
*/
|
909
773
|
*enumerateActions() {
|
910
774
|
if (!this.model.layers) return;
|
911
775
|
for (const layer of this.model.layers) {
|
@@ -943,10 +807,6 @@
|
|
943
807
|
// }
|
944
808
|
}
|
945
809
|
|
946
|
-
/**
|
947
|
-
* Wraps a KeyframeTrack to allow custom evaluation of animation values.
|
948
|
-
* Used internally to modify animation behavior without changing the original data.
|
949
|
-
*/
|
950
810
|
class TrackEvaluationWrapper {
|
951
811
|
|
952
812
|
track?: KeyframeTrack;
|
@@ -984,10 +844,6 @@
|
|
984
844
|
}
|
985
845
|
}
|
986
846
|
|
987
|
-
/**
|
988
|
-
* Handles root motion extraction from animation tracks.
|
989
|
-
* Captures movement from animations and applies it to the root object.
|
990
|
-
*/
|
991
847
|
class RootMotionAction {
|
992
848
|
|
993
849
|
private static lastObjPosition: { [key: string]: Vector3 } = {};
|
@@ -1187,10 +1043,6 @@
|
|
1187
1043
|
}
|
1188
1044
|
}
|
1189
1045
|
|
1190
|
-
/**
|
1191
|
-
* Manages root motion for a character.
|
1192
|
-
* Extracts motion from animation tracks and applies it to the character's transform.
|
1193
|
-
*/
|
1194
1046
|
class RootMotionHandler {
|
1195
1047
|
|
1196
1048
|
private controller: AnimatorController;
|
@@ -1277,10 +1129,8 @@
|
|
1277
1129
|
}
|
1278
1130
|
}
|
1279
1131
|
|
1280
|
-
|
1281
|
-
|
1282
|
-
* Handles conversion between serialized data and runtime objects.
|
1283
|
-
*/
|
1132
|
+
|
1133
|
+
|
1284
1134
|
class AnimatorControllerSerializator extends TypeSerializer {
|
1285
1135
|
onSerialize(_: any, _context: SerializationContext) {
|
1286
1136
|
|
@@ -5,18 +5,15 @@
|
|
5
5
|
import { Behaviour, GameObject } from "./Component.js";
|
6
6
|
|
7
7
|
/**
|
8
|
-
* AudioListener represents a listener that can
|
9
|
-
* This component creates and manages a Three.js {@link three#AudioListener}, automatically connecting it
|
10
|
-
* to the main camera or a Camera in the parent hierarchy.
|
8
|
+
* AudioListener represents a listener that can be attached to a GameObject to listen to audio sources in the scene.
|
11
9
|
* @category Multimedia
|
12
10
|
* @group Components
|
13
11
|
*/
|
14
12
|
export class AudioListener extends Behaviour {
|
15
13
|
|
16
14
|
/**
|
17
|
-
* Gets the existing
|
18
|
-
*
|
19
|
-
* @returns The {@link three#AudioListener} instance
|
15
|
+
* Gets the existing or creates a new {@link ThreeAudioListener} instance
|
16
|
+
* @returns The {@link ThreeAudioListener} instance
|
20
17
|
*/
|
21
18
|
get listener(): ThreeAudioListener {
|
22
19
|
if (this._listener == null)
|
@@ -26,21 +23,13 @@
|
|
26
23
|
|
27
24
|
private _listener: ThreeAudioListener | null = null;
|
28
25
|
|
29
|
-
/**
|
30
|
-
* Registers for interaction events and initializes the audio listener
|
31
|
-
* when this component is enabled.
|
32
|
-
* @internal
|
33
|
-
*/
|
26
|
+
/** @internal */
|
34
27
|
onEnable(): void {
|
35
28
|
Application.registerWaitForInteraction(this.onInteraction);
|
36
29
|
this.addListenerIfItExists();
|
37
30
|
}
|
38
31
|
|
39
|
-
/**
|
40
|
-
* Cleans up event registrations and removes the audio listener
|
41
|
-
* when this component is disabled.
|
42
|
-
* @internal
|
43
|
-
*/
|
32
|
+
/** @internal */
|
44
33
|
onDisable(): void {
|
45
34
|
Application.unregisterWaitForInteraction(this.onInteraction);
|
46
35
|
this.removeListenerIfItExists();
|
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
import { isDevEnvironment } from "../engine/debug/index.js";
|
5
5
|
import { Application, ApplicationEvents } from "../engine/engine_application.js";
|
6
|
-
import { findObjectOfType } from "../engine/engine_components.js";
|
7
6
|
import { Mathf } from "../engine/engine_math.js";
|
8
7
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
9
8
|
import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
|
@@ -14,121 +13,84 @@
|
|
14
13
|
const debug = getParam("debugaudio");
|
15
14
|
|
16
15
|
/**
|
17
|
-
*
|
16
|
+
* The AudioRolloffMode enum describes different ways that audio can attenuate with distance.
|
18
17
|
*/
|
19
18
|
export enum AudioRolloffMode {
|
20
|
-
/
|
21
|
-
|
22
|
-
|
23
|
-
*/
|
19
|
+
/// <summary>
|
20
|
+
/// <para>Use this mode when you want a real-world rolloff.</para>
|
21
|
+
/// </summary>
|
24
22
|
Logarithmic = 0,
|
25
|
-
|
26
|
-
/
|
27
|
-
|
28
|
-
* rate with distance.
|
29
|
-
*/
|
23
|
+
/// <summary>
|
24
|
+
/// <para>Use this mode when you want to lower the volume of your sound over the distance.</para>
|
25
|
+
/// </summary>
|
30
26
|
Linear = 1,
|
31
|
-
|
32
|
-
/
|
33
|
-
|
34
|
-
* Note: Custom rolloff is not fully implemented in this version.
|
35
|
-
*/
|
27
|
+
/// <summary>
|
28
|
+
/// <para>Use this when you want to use a custom rolloff.</para>
|
29
|
+
/// </summary>
|
36
30
|
Custom = 2,
|
37
31
|
}
|
38
32
|
|
39
33
|
|
40
|
-
/**
|
41
|
-
*
|
42
|
-
*
|
43
|
-
* The AudioSource component can play audio files or media streams with
|
44
|
-
* options for spatial blending, volume control, looping, and more.
|
45
|
-
*
|
46
|
-
* When a page loses visibility (tab becomes inactive), audio will automatically
|
47
|
-
* pause unless {@link playInBackground} is set to true. On mobile devices, audio always
|
48
|
-
* pauses regardless of this setting. When the page becomes visible again,
|
49
|
-
* previously playing audio will resume.
|
50
|
-
*
|
51
|
-
* AudioSource also responds to application mute state changes. When the application
|
52
|
-
* is muted, the volume is set to 0. When unmuted, the volume
|
53
|
-
* returns to its previous value.
|
54
|
-
*
|
34
|
+
/** The AudioSource can be used to play audio in the scene.
|
35
|
+
* Use `clip` to set the audio file to play.
|
55
36
|
* @category Multimedia
|
56
37
|
* @group Components
|
57
38
|
*/
|
58
39
|
export class AudioSource extends Behaviour {
|
59
40
|
|
60
|
-
/**
|
61
|
-
*
|
62
|
-
* Audio playback often requires a user gesture first due to browser autoplay policies.
|
63
|
-
* This is the same as calling {@link Application.userInteractionRegistered}.
|
64
|
-
*
|
65
|
-
* @returns Whether user interaction has been registered to allow audio playback
|
41
|
+
/** Check if the user has interacted with the page to allow audio playback.
|
42
|
+
* Internally calling {@link Application.userInteractionRegistered}
|
66
43
|
*/
|
67
44
|
public static get userInteractionRegistered(): boolean {
|
68
45
|
return Application.userInteractionRegistered;
|
69
46
|
}
|
70
47
|
|
71
|
-
/**
|
72
|
-
*
|
73
|
-
* allowing audio playback to begin.
|
74
|
-
* This is the same as calling {@link Application.registerWaitForInteraction}.
|
75
|
-
*
|
76
|
-
* @param cb - The callback function to execute when user interaction is registered
|
48
|
+
/** Register a callback that is called when the user has interacted with the page to allow audio playback.
|
49
|
+
* Internally calling {@link Application.registerWaitForInteraction}
|
77
50
|
*/
|
78
51
|
public static registerWaitForAllowAudio(cb: Function) {
|
79
52
|
Application.registerWaitForInteraction(cb);
|
80
53
|
}
|
81
54
|
|
82
55
|
/**
|
83
|
-
* The audio clip to play. Can be a
|
56
|
+
* The audio clip to play. Can be a string (URL) or a MediaStream.
|
84
57
|
*/
|
85
58
|
@serializable(URL)
|
86
59
|
clip: string | MediaStream = "";
|
87
60
|
|
88
61
|
/**
|
89
|
-
*
|
90
|
-
*
|
62
|
+
* If true, the audio source will start playing as soon as the scene starts.
|
63
|
+
* If false, you can call play() to start the audio.
|
91
64
|
* @default false
|
92
|
-
|
65
|
+
*/
|
93
66
|
@serializable()
|
94
67
|
playOnAwake: boolean = false;
|
95
68
|
|
96
69
|
/**
|
97
|
-
*
|
98
|
-
*
|
70
|
+
* If true, the audio source will start loading the audio clip as soon as the scene starts.
|
71
|
+
* If false, the audio clip will be loaded when play() is called.
|
99
72
|
* @default false
|
100
73
|
*/
|
101
74
|
@serializable()
|
102
75
|
preload: boolean = false;
|
103
76
|
|
104
77
|
/**
|
105
|
-
* When true, audio will continue playing
|
106
|
-
* When false, audio will pause when the tab is minimized or not active.
|
78
|
+
* When true, the audio will play in the background. This means it will continue playing if the browser tab is not focused/active or minimized
|
107
79
|
* @default true
|
108
80
|
*/
|
109
81
|
@serializable()
|
110
82
|
playInBackground: boolean = true;
|
111
83
|
|
112
84
|
/**
|
113
|
-
*
|
114
|
-
*
|
115
|
-
* @returns True if the audio is playing, false otherwise
|
85
|
+
* If true, the audio is currently playing.
|
116
86
|
*/
|
117
87
|
get isPlaying(): boolean { return this.sound?.isPlaying ?? false; }
|
118
88
|
|
119
|
-
/**
|
120
|
-
* The total duration of the current audio clip in seconds.
|
121
|
-
*
|
122
|
-
* @returns Duration in seconds or undefined if no clip is loaded
|
123
|
-
*/
|
89
|
+
/** The duration of the audio clip in seconds. */
|
124
90
|
get duration() {
|
125
91
|
return this.sound?.buffer?.duration;
|
126
92
|
}
|
127
|
-
|
128
|
-
/**
|
129
|
-
* The current playback position as a normalized value between 0 and 1.
|
130
|
-
* Can be set to seek to a specific position in the audio.
|
131
|
-
*/
|
93
|
+
/** The current time of the audio clip in 0-1 range. */
|
132
94
|
get time01() {
|
133
95
|
const duration = this.duration;
|
134
96
|
if (duration && this.sound) {
|
@@ -144,8 +106,7 @@
|
|
144
106
|
}
|
145
107
|
|
146
108
|
/**
|
147
|
-
* The current
|
148
|
-
* Can be set to seek to a specific time in the audio.
|
109
|
+
* The current time of the audio clip in seconds.
|
149
110
|
*/
|
150
111
|
get time(): number { return this.sound?.source ? (this.sound.source?.context.currentTime - this._lastContextTime + this.sound.offset) : 0; }
|
151
112
|
set time(val: number) {
|
@@ -160,8 +121,8 @@
|
|
160
121
|
}
|
161
122
|
|
162
123
|
/**
|
163
|
-
*
|
164
|
-
*
|
124
|
+
* If true, the audio source will loop the audio clip.
|
125
|
+
* If false, the audio clip will play once.
|
165
126
|
* @default false
|
166
127
|
*/
|
167
128
|
@serializable()
|
@@ -173,12 +134,10 @@
|
|
173
134
|
this._loop = val;
|
174
135
|
if (this.sound) this.sound.setLoop(val);
|
175
136
|
}
|
176
|
-
|
177
|
-
|
178
|
-
*
|
179
|
-
*
|
180
|
-
* Note: 2D playback is not fully supported in the current implementation.
|
181
|
-
*/
|
137
|
+
/** Can be used to play the audio clip in 2D or 3D space.
|
138
|
+
* 2D Playback is currently not fully supported.
|
139
|
+
* 0 = 2D, 1 = 3D
|
140
|
+
* */
|
182
141
|
@serializable()
|
183
142
|
get spatialBlend(): number {
|
184
143
|
return this._spatialBlend;
|
@@ -188,11 +147,6 @@
|
|
188
147
|
this._spatialBlend = val;
|
189
148
|
this._needUpdateSpatialDistanceSettings = true;
|
190
149
|
}
|
191
|
-
|
192
|
-
/**
|
193
|
-
* The minimum distance from the audio source at which the volume starts to attenuate.
|
194
|
-
* Within this radius, the audio plays at full volume regardless of distance.
|
195
|
-
*/
|
196
150
|
@serializable()
|
197
151
|
get minDistance(): number {
|
198
152
|
return this._minDistance;
|
@@ -202,11 +156,6 @@
|
|
202
156
|
this._minDistance = val;
|
203
157
|
this._needUpdateSpatialDistanceSettings = true;
|
204
158
|
}
|
205
|
-
|
206
|
-
/**
|
207
|
-
* The maximum distance from the audio source beyond which the volume no longer decreases.
|
208
|
-
* This defines the outer limit of the attenuation curve.
|
209
|
-
*/
|
210
159
|
@serializable()
|
211
160
|
get maxDistance(): number {
|
212
161
|
return this._maxDistance;
|
@@ -221,11 +170,6 @@
|
|
221
170
|
private _minDistance: number = 1;
|
222
171
|
private _maxDistance: number = 100;
|
223
172
|
|
224
|
-
/**
|
225
|
-
* Controls the overall volume/loudness of the audio.
|
226
|
-
* Values range from 0 (silent) to 1 (full volume).
|
227
|
-
* @default 1
|
228
|
-
*/
|
229
173
|
@serializable()
|
230
174
|
get volume(): number { return this._volume; }
|
231
175
|
set volume(val: number) {
|
@@ -237,12 +181,6 @@
|
|
237
181
|
}
|
238
182
|
private _volume: number = 1;
|
239
183
|
|
240
|
-
/**
|
241
|
-
* Controls the playback rate (speed) of the audio.
|
242
|
-
* Values greater than 1 increase speed, values less than 1 decrease it.
|
243
|
-
* This affects both speed and pitch of the audio.
|
244
|
-
* @default 1
|
245
|
-
*/
|
246
184
|
@serializable()
|
247
185
|
set pitch(val: number) {
|
248
186
|
if (this.sound) this.sound.setPlaybackRate(val);
|
@@ -251,11 +189,6 @@
|
|
251
189
|
return this.sound ? this.sound.getPlaybackRate() : 1;
|
252
190
|
}
|
253
191
|
|
254
|
-
/**
|
255
|
-
* Determines how audio volume decreases with distance from the listener.
|
256
|
-
* @default AudioRolloffMode.Logarithmic
|
257
|
-
* @see {@link AudioRolloffMode}
|
258
|
-
*/
|
259
192
|
@serializable()
|
260
193
|
rollOffMode: AudioRolloffMode = 0;
|
261
194
|
|
@@ -271,21 +204,10 @@
|
|
271
204
|
private _lastClipStartedLoading: string | MediaStream | null = null;
|
272
205
|
private _audioElement: HTMLAudioElement | null = null;
|
273
206
|
|
274
|
-
/**
|
275
|
-
* Returns the underlying {@link PositionalAudio} object, creating it if necessary.
|
276
|
-
* The audio source needs a user interaction to be initialized due to browser autoplay policies.
|
277
|
-
*
|
278
|
-
* @returns The three.js PositionalAudio object or null if unavailable
|
279
|
-
*/
|
280
207
|
public get Sound(): PositionalAudio | null {
|
281
208
|
if (!this.sound && AudioSource.userInteractionRegistered) {
|
282
|
-
|
283
|
-
|
284
|
-
?? this.context.mainCamera.getComponent(AudioListener) // AudioListener on current main camera?
|
285
|
-
?? findObjectOfType(AudioListener, this.context, false); // Active AudioListener in scene?
|
286
|
-
|
287
|
-
if (!listener && this.context.mainCamera) listener = this.context.mainCamera.addComponent(AudioListener);
|
288
|
-
|
209
|
+
let listener = GameObject.getComponent(this.context.mainCamera, AudioListener) ?? GameObject.findObjectOfType(AudioListener, this.context);
|
210
|
+
if (!listener && this.context.mainCamera) listener = GameObject.addComponent(this.context.mainCamera, AudioListener);
|
289
211
|
if (listener?.listener) {
|
290
212
|
this.sound = new PositionalAudio(listener.listener);
|
291
213
|
this.gameObject?.add(this.sound);
|
@@ -328,19 +250,9 @@
|
|
328
250
|
// }
|
329
251
|
// }
|
330
252
|
|
331
|
-
/**
|
332
|
-
* Indicates whether the audio source is queued to play when possible.
|
333
|
-
* This may be true before user interaction has been registered.
|
334
|
-
*
|
335
|
-
* @returns Whether the audio source intends to play
|
336
|
-
*/
|
337
253
|
public get ShouldPlay(): boolean { return this.shouldPlay; }
|
338
254
|
|
339
|
-
/**
|
340
|
-
* Returns the Web Audio API context associated with this audio source.
|
341
|
-
*
|
342
|
-
* @returns The {@link AudioContext} or null if not available
|
343
|
-
*/
|
255
|
+
/** Get the audio context from the Sound */
|
344
256
|
public get audioContext() {
|
345
257
|
return this.sound?.context;
|
346
258
|
}
|
@@ -432,9 +344,6 @@
|
|
432
344
|
if (sound.isPlaying)
|
433
345
|
sound.stop();
|
434
346
|
|
435
|
-
// const panner = sound.panner;
|
436
|
-
// panner.coneOuterGain = 1;
|
437
|
-
// sound.setDirectionalCone(360, 360, 1);
|
438
347
|
// const src = sound.context.createBufferSource();
|
439
348
|
// src.buffer = sound.buffer;
|
440
349
|
// src.connect(sound.panner);
|
@@ -515,12 +424,7 @@
|
|
515
424
|
}
|
516
425
|
}
|
517
426
|
|
518
|
-
/**
|
519
|
-
* Plays the audio clip or media stream.
|
520
|
-
* If no argument is provided, plays the currently assigned clip.
|
521
|
-
*
|
522
|
-
* @param clip - Optional audio clip or {@link MediaStream} to play
|
523
|
-
*/
|
427
|
+
/** Play a audioclip or mediastream */
|
524
428
|
play(clip: string | MediaStream | undefined = undefined) {
|
525
429
|
// use audio source's clip when no clip is passed in
|
526
430
|
if (!clip && this.clip)
|
@@ -579,8 +483,7 @@
|
|
579
483
|
}
|
580
484
|
|
581
485
|
/**
|
582
|
-
*
|
583
|
-
* Use play() to resume from the paused position.
|
486
|
+
* Pause the audio
|
584
487
|
*/
|
585
488
|
pause() {
|
586
489
|
if (debug) console.log("Pause", this);
|
@@ -594,8 +497,7 @@
|
|
594
497
|
}
|
595
498
|
|
596
499
|
/**
|
597
|
-
*
|
598
|
-
* Unlike pause(), calling play() after stop() will start from the beginning.
|
500
|
+
* Stop the audio and reset the time to 0
|
599
501
|
*/
|
600
502
|
stop() {
|
601
503
|
if (debug) console.log("Pause", this);
|
@@ -10,37 +10,17 @@
|
|
10
10
|
|
11
11
|
const debug = utils.getParam("debugavatar");
|
12
12
|
|
13
|
-
/**
|
14
|
-
* Represents an avatar model with head and hands references.
|
15
|
-
* Used for representing characters in 3D space.
|
16
|
-
*/
|
17
13
|
export class AvatarModel {
|
18
|
-
/** The root object of the avatar model */
|
19
14
|
root: Object3D;
|
20
|
-
/** The head object of the avatar model */
|
21
15
|
head: Object3D;
|
22
|
-
/** The left hand object of the avatar model, if available */
|
23
16
|
leftHand: Object3D | null;
|
24
|
-
/** The right hand object of the avatar model, if available */
|
25
17
|
rigthHand: Object3D | null;
|
26
18
|
|
27
19
|
|
28
|
-
/**
|
29
|
-
* Checks if the avatar model has a valid configuration.
|
30
|
-
* An avatar is considered valid if it has a head.
|
31
|
-
* @returns Whether the avatar has a valid setup
|
32
|
-
*/
|
33
20
|
get isValid(): boolean {
|
34
21
|
return this.head !== null && this.head !== undefined;
|
35
22
|
}
|
36
23
|
|
37
|
-
/**
|
38
|
-
* Creates a new avatar model.
|
39
|
-
* @param root The root object of the avatar
|
40
|
-
* @param head The head object of the avatar
|
41
|
-
* @param leftHand The left hand object of the avatar
|
42
|
-
* @param rigthHand The right hand object of the avatar
|
43
|
-
*/
|
44
24
|
constructor(root: Object3D, head: Object3D, leftHand: Object3D | null, rigthHand: Object3D | null) {
|
45
25
|
this.root = root;
|
46
26
|
this.head = head;
|
@@ -53,25 +33,12 @@
|
|
53
33
|
}
|
54
34
|
}
|
55
35
|
|
56
|
-
/**
|
57
|
-
* Handles loading and instantiating avatar models from various sources.
|
58
|
-
* Provides functionality to find and extract important parts of an avatar (head, hands).
|
59
|
-
*
|
60
|
-
* Debug mode can be enabled with the URL parameter `?debugavatar`,
|
61
|
-
* which will log detailed information about avatar loading and configuration.
|
62
|
-
*/
|
63
36
|
export class AvatarLoader {
|
64
37
|
|
65
38
|
private readonly avatarRegistryUrl: string | null = null;
|
66
39
|
// private loader: GLTFLoader | null;
|
67
40
|
// private avatarModelCache: Map<string, AvatarModel | null> = new Map<string, AvatarModel | null>();
|
68
41
|
|
69
|
-
/**
|
70
|
-
* Retrieves or creates a new avatar instance from an ID or existing Object3D.
|
71
|
-
* @param context The application context
|
72
|
-
* @param avatarId Either a string ID to load an avatar or an existing Object3D to use as avatar
|
73
|
-
* @returns Promise resolving to an AvatarModel if successful, or null if failed
|
74
|
-
*/
|
75
42
|
public async getOrCreateNewAvatarInstance(context: Context, avatarId: string | Object3D): Promise<AvatarModel | null> {
|
76
43
|
|
77
44
|
if (!avatarId) {
|
@@ -109,12 +76,6 @@
|
|
109
76
|
}
|
110
77
|
|
111
78
|
|
112
|
-
/**
|
113
|
-
* Loads an avatar model from a file or registry using the provided ID.
|
114
|
-
* @param context The engine context
|
115
|
-
* @param avatarId The ID of the avatar to load
|
116
|
-
* @returns Promise resolving to the loaded avatar's Object3D, or null if failed
|
117
|
-
*/
|
118
79
|
private async loadAvatar(context: Context, avatarId: string): Promise<Object3D | null> {
|
119
80
|
|
120
81
|
console.assert(avatarId !== undefined && avatarId !== null && typeof avatarId === "string", "Avatar id must not be null");
|
@@ -179,20 +140,11 @@
|
|
179
140
|
});
|
180
141
|
}
|
181
142
|
|
182
|
-
/**
|
183
|
-
* Caches an avatar model for reuse.
|
184
|
-
* @param _id The ID to associate with the model
|
185
|
-
* @param _model The avatar model to cache
|
186
|
-
*/
|
187
143
|
private cacheModel(_id: string, _model: AvatarModel) {
|
188
144
|
// this.avatarModelCache.set(id, model);
|
189
145
|
}
|
190
146
|
|
191
|
-
/
|
192
|
-
* Analyzes an Object3D to find avatar parts (head, hands) based on naming conventions.
|
193
|
-
* @param obj The Object3D to search for avatar parts
|
194
|
-
* @returns A structured AvatarModel with references to found parts
|
195
|
-
*/
|
147
|
+
// TODO this should be burned to the ground once 🤞 we have proper extras that define object relations.
|
196
148
|
private findAvatar(obj: Object3D): AvatarModel {
|
197
149
|
|
198
150
|
const root: Object3D = obj;
|
@@ -223,12 +175,7 @@
|
|
223
175
|
return model;
|
224
176
|
}
|
225
177
|
|
226
|
-
|
227
|
-
* Recursively searches for an avatar part by name within an Object3D hierarchy.
|
228
|
-
* @param obj The Object3D to search within
|
229
|
-
* @param searchString Array of strings that should all be present in the object name
|
230
|
-
* @returns The found Object3D part or null if not found
|
231
|
-
*/
|
178
|
+
|
232
179
|
private findAvatarPart(obj: Object3D, searchString: string[]): Object3D | null {
|
233
180
|
|
234
181
|
const name = obj.name.toLowerCase();
|
@@ -249,12 +196,6 @@
|
|
249
196
|
return null;
|
250
197
|
}
|
251
198
|
|
252
|
-
/**
|
253
|
-
* Handles HTTP response errors from avatar loading operations.
|
254
|
-
* @param response The fetch API response to check
|
255
|
-
* @returns The response if it was ok
|
256
|
-
* @throws Error with status text if response was not ok
|
257
|
-
*/
|
258
199
|
private handleCustomAvatarErrors(response) {
|
259
200
|
if (!response.ok) {
|
260
201
|
throw Error(response.statusText);
|
@@ -5,37 +5,20 @@
|
|
5
5
|
import { Behaviour } from "./Component.js";
|
6
6
|
|
7
7
|
/**
|
8
|
-
*
|
9
|
-
* Renders colored lines representing the X (red), Y (green) and Z (blue) axes.
|
8
|
+
* AxesHelper is a component that displays the axes of the object in the scene.
|
10
9
|
* @category Helpers
|
11
10
|
* @group Components
|
12
11
|
*/
|
13
12
|
export class AxesHelper extends Behaviour {
|
14
|
-
/**
|
15
|
-
* The length of each axis line in scene units.
|
16
|
-
*/
|
17
13
|
@serializable()
|
18
14
|
public length: number = 1;
|
19
|
-
|
20
|
-
/**
|
21
|
-
* Whether the axes should be occluded by objects in the scene.
|
22
|
-
* When set to false, axes will always appear on top regardless of their depth.
|
23
|
-
*/
|
24
15
|
@serializable()
|
25
16
|
public depthTest: boolean = true;
|
26
|
-
|
27
|
-
/**
|
28
|
-
* When true, this helper will only be visible if the debug flag `?gizmos` is enabled.
|
29
|
-
*/
|
30
17
|
@serializable()
|
31
18
|
public isGizmo: boolean = false;
|
32
19
|
|
33
20
|
private _axes: _AxesHelper | null = null;
|
34
21
|
|
35
|
-
/**
|
36
|
-
* Creates and adds the axes visualization to the scene when the component is enabled.
|
37
|
-
* If marked as a gizmo, it will only be shown when gizmos are enabled in the global parameters.
|
38
|
-
*/
|
39
22
|
onEnable(): void {
|
40
23
|
if (this.isGizmo && !params.showGizmos) return;
|
41
24
|
if (!this._axes)
|
@@ -50,9 +33,6 @@
|
|
50
33
|
}
|
51
34
|
}
|
52
35
|
|
53
|
-
/**
|
54
|
-
* Removes the axes visualization from the scene when the component is disabled.
|
55
|
-
*/
|
56
36
|
onDisable(): void {
|
57
37
|
if (!this._axes) return;
|
58
38
|
this.gameObject.remove(this._axes);
|
@@ -9,17 +9,11 @@
|
|
9
9
|
const debug = getParam("debugboxhelper");
|
10
10
|
|
11
11
|
/**
|
12
|
-
* A component that creates a bounding box around an object and provides intersection testing functionality.
|
13
|
-
*
|
14
|
-
* Debug mode can be enabled with the URL parameter `?debugboxhelper`, which will visualize intersection tests.
|
15
|
-
* Helper visualization can be enabled with the URL parameter `?gizmos`.
|
16
|
-
*
|
17
12
|
* @category Helpers
|
18
13
|
* @group Components
|
19
14
|
*/
|
20
15
|
export class BoxHelperComponent extends Behaviour {
|
21
16
|
|
22
|
-
/** The bounding box for this component */
|
23
17
|
private box: Box3 | null = null;
|
24
18
|
private static testBox: Box3 = new Box3();
|
25
19
|
private _lastMatrixUpdateFrame: number = -1;
|
@@ -27,11 +21,6 @@
|
|
27
21
|
private static _size: Vector3 = new Vector3(.01, .01, .01);
|
28
22
|
private static _emptyObjectSize: Vector3 = new Vector3(.01, .01, .01);
|
29
23
|
|
30
|
-
/**
|
31
|
-
* Tests if an object intersects with this helper's bounding box
|
32
|
-
* @param obj The object to test for intersection
|
33
|
-
* @returns True if objects intersect, false if not, undefined if the provided object is invalid
|
34
|
-
*/
|
35
24
|
public isInBox(obj: Object3D): boolean | undefined {
|
36
25
|
if (!obj) return undefined;
|
37
26
|
|
@@ -55,21 +44,11 @@
|
|
55
44
|
return intersects;
|
56
45
|
}
|
57
46
|
|
58
|
-
/**
|
59
|
-
* Tests if this helper's bounding box intersects with another box
|
60
|
-
* @param box The {@link Box3} to test for intersection
|
61
|
-
* @returns True if boxes intersect, false otherwise
|
62
|
-
*/
|
63
47
|
public intersects(box: Box3): boolean {
|
64
48
|
if (!box) return false;
|
65
49
|
return this.updateBox(false).intersectsBox(box);
|
66
50
|
}
|
67
51
|
|
68
|
-
/**
|
69
|
-
* Updates the helper's bounding box based on the gameObject's position and scale
|
70
|
-
* @param force Whether to force an update regardless of frame count
|
71
|
-
* @returns The updated {@link Box3}
|
72
|
-
*/
|
73
52
|
public updateBox(force: boolean = false): Box3 {
|
74
53
|
if (!this.box) {
|
75
54
|
this.box = new Box3();
|
@@ -95,11 +74,6 @@
|
|
95
74
|
this.box = null;
|
96
75
|
}
|
97
76
|
|
98
|
-
/**
|
99
|
-
* Creates and displays a visual wireframe representation of this box helper
|
100
|
-
* @param col Optional color for the wireframe. If not provided, uses default color
|
101
|
-
* @param force If true, shows the helper even if gizmos are disabled
|
102
|
-
*/
|
103
77
|
public showHelper(col: ColorRepresentation | null = null, force: boolean = false) {
|
104
78
|
if (!gizmos && !force) return;
|
105
79
|
if (this._helper) {
|
@@ -13,11 +13,8 @@
|
|
13
13
|
import { Behaviour, GameObject } from "./Component.js";
|
14
14
|
import { OrbitControls } from "./OrbitControls.js";
|
15
15
|
|
16
|
-
/**
|
17
|
-
* The ClearFlags enum is used to determine how the camera clears the background
|
18
|
-
*/
|
16
|
+
/** The ClearFlags enum is used to determine how the camera clears the background */
|
19
17
|
export enum ClearFlags {
|
20
|
-
/** Don't clear the background */
|
21
18
|
None = 0,
|
22
19
|
/** Clear the background with a skybox */
|
23
20
|
Skybox = 1,
|
@@ -31,28 +28,16 @@
|
|
31
28
|
const debugscreenpointtoray = getParam("debugscreenpointtoray");
|
32
29
|
|
33
30
|
/**
|
34
|
-
* Camera component that handles rendering from a specific viewpoint in the scene.
|
35
|
-
* Supports both perspective and orthographic cameras with various rendering options.
|
36
|
-
* Internally, this component uses {@link PerspectiveCamera} and {@link OrthographicCamera} three.js objects.
|
37
|
-
*
|
38
31
|
* @category Camera Controls
|
39
32
|
* @group Components
|
40
33
|
*/
|
41
34
|
export class Camera extends Behaviour implements ICamera {
|
42
35
|
|
43
|
-
/**
|
44
|
-
* Returns whether this component is a camera
|
45
|
-
* @returns {boolean} Always returns true
|
46
|
-
*/
|
47
36
|
get isCamera() {
|
48
37
|
return true;
|
49
38
|
}
|
50
39
|
|
51
|
-
/**
|
52
|
-
* Gets or sets the camera's aspect ratio (width divided by height).
|
53
|
-
* For perspective cameras, this directly affects the camera's projection matrix.
|
54
|
-
* When set, automatically updates the projection matrix.
|
55
|
-
*/
|
40
|
+
/** The camera's aspect ratio (width divided by height) if it is a perspective camera */
|
56
41
|
get aspect(): number {
|
57
42
|
if (this._cam instanceof PerspectiveCamera) return this._cam.aspect;
|
58
43
|
return (this.context.domWidth / this.context.domHeight);
|
@@ -66,11 +51,7 @@
|
|
66
51
|
}
|
67
52
|
}
|
68
53
|
}
|
69
|
-
|
70
|
-
/**
|
71
|
-
* Gets or sets the camera's field of view in degrees for perspective cameras.
|
72
|
-
* When set, automatically updates the projection matrix.
|
73
|
-
*/
|
54
|
+
/** The camera's field of view in degrees if it is a perspective camera. Calls updateProjectionMatrix when set */
|
74
55
|
get fieldOfView(): number | undefined {
|
75
56
|
if (this._cam instanceof PerspectiveCamera) {
|
76
57
|
return this._cam.fov;
|
@@ -93,11 +74,7 @@
|
|
93
74
|
}
|
94
75
|
}
|
95
76
|
|
96
|
-
/**
|
97
|
-
* Gets or sets the camera's near clipping plane distance.
|
98
|
-
* Objects closer than this distance won't be rendered.
|
99
|
-
* When set, automatically updates the projection matrix.
|
100
|
-
*/
|
77
|
+
/** The camera's near clipping plane. Calls updateProjectionMatrix when set */
|
101
78
|
get nearClipPlane(): number { return this._nearClipPlane; }
|
102
79
|
@serializable()
|
103
80
|
set nearClipPlane(val) {
|
@@ -110,11 +87,7 @@
|
|
110
87
|
}
|
111
88
|
private _nearClipPlane: number = 0.1;
|
112
89
|
|
113
|
-
/**
|
114
|
-
* Gets or sets the camera's far clipping plane distance.
|
115
|
-
* Objects farther than this distance won't be rendered.
|
116
|
-
* When set, automatically updates the projection matrix.
|
117
|
-
*/
|
90
|
+
/** The camera's far clipping plane. Calls updateProjectionMatrix when set */
|
118
91
|
get farClipPlane(): number { return this._farClipPlane; }
|
119
92
|
@serializable()
|
120
93
|
set farClipPlane(val) {
|
@@ -128,8 +101,7 @@
|
|
128
101
|
private _farClipPlane: number = 1000;
|
129
102
|
|
130
103
|
/**
|
131
|
-
*
|
132
|
-
* This ensures rendering occurs only within the specified distance range.
|
104
|
+
* Applys both the camera's near and far plane and calls updateProjectionMatrix on the camera.
|
133
105
|
*/
|
134
106
|
applyClippingPlane() {
|
135
107
|
if (this._cam) {
|
@@ -139,10 +111,7 @@
|
|
139
111
|
}
|
140
112
|
}
|
141
113
|
|
142
|
-
/**
|
143
|
-
* Gets or sets the camera's clear flags that determine how the background is rendered.
|
144
|
-
* Options include skybox, solid color, or transparent background.
|
145
|
-
*/
|
114
|
+
/** The camera's clear flags - determines if the background is a skybox or a solid color or transparent */
|
146
115
|
@serializable()
|
147
116
|
public get clearFlags(): ClearFlags {
|
148
117
|
return this._clearFlags;
|
@@ -152,31 +121,18 @@
|
|
152
121
|
this._clearFlags = val;
|
153
122
|
this.applyClearFlagsIfIsActiveCamera();
|
154
123
|
}
|
155
|
-
|
156
|
-
/**
|
157
|
-
* Determines if the camera should use orthographic projection instead of perspective.
|
158
|
-
*/
|
159
124
|
@serializable()
|
160
125
|
public orthographic: boolean = false;
|
161
|
-
|
162
|
-
/**
|
163
|
-
* The size of the orthographic camera's view volume when in orthographic mode.
|
164
|
-
* Larger values show more of the scene.
|
165
|
-
*/
|
166
126
|
@serializable()
|
167
127
|
public orthographicSize: number = 5;
|
168
128
|
|
169
|
-
/**
|
170
|
-
* Controls the transparency level of the camera background in AR mode on supported devices.
|
171
|
-
* Value from 0 (fully transparent) to 1 (fully opaque).
|
172
|
-
*/
|
173
129
|
@serializable()
|
174
130
|
public ARBackgroundAlpha: number = 0;
|
175
131
|
|
176
132
|
/**
|
177
|
-
*
|
178
|
-
*
|
179
|
-
|
133
|
+
* The [`mask`](https://threejs.org/docs/#api/en/core/Layers.mask) value of the three camera object layers
|
134
|
+
* If you want to just see objects on one layer (e.g. layer 2) then you can use `cullingLayer = 2` on this camera component instead
|
135
|
+
*/
|
180
136
|
@serializable()
|
181
137
|
public set cullingMask(val: number) {
|
182
138
|
this._cullingMask = val;
|
@@ -190,19 +146,14 @@
|
|
190
146
|
}
|
191
147
|
private _cullingMask: number = 0xffffffff;
|
192
148
|
|
193
|
-
/**
|
194
|
-
*
|
195
|
-
*
|
196
|
-
* @param val The layer index to set active
|
197
|
-
*/
|
149
|
+
/** Set only a specific layer active to be rendered by the camera.
|
150
|
+
* This is equivalent to calling `layers.set(val)`
|
151
|
+
**/
|
198
152
|
public set cullingLayer(val: number) {
|
199
153
|
this.cullingMask = (1 << val | 0) >>> 0;
|
200
154
|
}
|
201
155
|
|
202
|
-
/**
|
203
|
-
* Gets or sets the blurriness of the skybox background.
|
204
|
-
* Values range from 0 (sharp) to 1 (maximum blur).
|
205
|
-
*/
|
156
|
+
/** The blurriness of the background texture (when using a skybox) */
|
206
157
|
@serializable()
|
207
158
|
public set backgroundBlurriness(val: number | undefined) {
|
208
159
|
if (val === this._backgroundBlurriness) return;
|
@@ -217,10 +168,7 @@
|
|
217
168
|
}
|
218
169
|
private _backgroundBlurriness?: number = undefined;
|
219
170
|
|
220
|
-
/**
|
221
|
-
* Gets or sets the intensity of the skybox background.
|
222
|
-
* Values range from 0 (dark) to 10 (very bright).
|
223
|
-
*/
|
171
|
+
/** The intensity of the background texture (when using a skybox) */
|
224
172
|
@serializable()
|
225
173
|
public set backgroundIntensity(val: number | undefined) {
|
226
174
|
if (val === this._backgroundIntensity) return;
|
@@ -235,10 +183,7 @@
|
|
235
183
|
}
|
236
184
|
private _backgroundIntensity?: number = undefined;
|
237
185
|
|
238
|
-
/**
|
239
|
-
* Gets or sets the rotation of the skybox background.
|
240
|
-
* Controls the orientation of the environment map.
|
241
|
-
*/
|
186
|
+
/** the rotation of the background texture (when using a skybox) */
|
242
187
|
@serializable(Euler)
|
243
188
|
public set backgroundRotation(val: Euler | undefined) {
|
244
189
|
if (val === this._backgroundRotation) return;
|
@@ -253,10 +198,7 @@
|
|
253
198
|
}
|
254
199
|
private _backgroundRotation?: Euler = undefined;
|
255
200
|
|
256
|
-
/**
|
257
|
-
* Gets or sets the intensity of the environment lighting.
|
258
|
-
* Controls how strongly the environment map affects scene lighting.
|
259
|
-
*/
|
201
|
+
/** The intensity of the environment map */
|
260
202
|
@serializable()
|
261
203
|
public set environmentIntensity(val: number | undefined) {
|
262
204
|
this._environmentIntensity = val;
|
@@ -266,10 +208,7 @@
|
|
266
208
|
}
|
267
209
|
private _environmentIntensity?: number = undefined;
|
268
210
|
|
269
|
-
/**
|
270
|
-
* Gets or sets the background color of the camera when {@link ClearFlags} is set to {@link ClearFlags.SolidColor}.
|
271
|
-
* The alpha component controls transparency.
|
272
|
-
*/
|
211
|
+
/** The background color of the camera when {@link ClearFlags} are set to `SolidColor` */
|
273
212
|
@serializable(RGBAColor)
|
274
213
|
public get backgroundColor(): RGBAColor | null {
|
275
214
|
return this._backgroundColor ?? null;
|
@@ -286,10 +225,9 @@
|
|
286
225
|
this.applyClearFlagsIfIsActiveCamera();
|
287
226
|
}
|
288
227
|
|
289
|
-
/**
|
290
|
-
*
|
291
|
-
|
292
|
-
*/
|
228
|
+
/** The texture that the camera should render to
|
229
|
+
* It can be used to render to a {@link Texture} instead of the screen.
|
230
|
+
*/
|
293
231
|
@serializable(RenderTexture)
|
294
232
|
public set targetTexture(rt: RenderTexture | null) {
|
295
233
|
this._targetTexture = rt;
|
@@ -306,17 +244,16 @@
|
|
306
244
|
private _skybox?: CameraSkybox;
|
307
245
|
|
308
246
|
/**
|
309
|
-
*
|
310
|
-
* @returns {PerspectiveCamera | OrthographicCamera}
|
311
|
-
* @deprecated
|
247
|
+
* Get the three.js camera object. This will create a camera if it does not exist yet.
|
248
|
+
* @returns {PerspectiveCamera | OrthographicCamera} the three camera
|
249
|
+
* @deprecated use {@link threeCamera} instead
|
312
250
|
*/
|
313
251
|
public get cam(): PerspectiveCamera | OrthographicCamera {
|
314
252
|
return this.threeCamera;
|
315
253
|
}
|
316
|
-
|
317
254
|
/**
|
318
|
-
*
|
319
|
-
* @returns {PerspectiveCamera | OrthographicCamera}
|
255
|
+
* Get the three.js camera object. This will create a camera if it does not exist yet.
|
256
|
+
* @returns {PerspectiveCamera | OrthographicCamera} the three camera
|
320
257
|
*/
|
321
258
|
public get threeCamera(): PerspectiveCamera | OrthographicCamera {
|
322
259
|
if (this.activeAndEnabled)
|
@@ -326,16 +263,6 @@
|
|
326
263
|
|
327
264
|
private static _origin: Vector3 = new Vector3();
|
328
265
|
private static _direction: Vector3 = new Vector3();
|
329
|
-
|
330
|
-
/**
|
331
|
-
* Converts screen coordinates to a ray in world space.
|
332
|
-
* Useful for implementing picking or raycasting from screen to world.
|
333
|
-
*
|
334
|
-
* @param x The x screen coordinate
|
335
|
-
* @param y The y screen coordinate
|
336
|
-
* @param ray Optional ray object to reuse instead of creating a new one
|
337
|
-
* @returns {Ray} A ray originating from the camera position pointing through the screen point
|
338
|
-
*/
|
339
266
|
public screenPointToRay(x: number, y: number, ray?: Ray): Ray {
|
340
267
|
const cam = this.threeCamera;
|
341
268
|
const origin = Camera._origin;
|
@@ -358,12 +285,9 @@
|
|
358
285
|
}
|
359
286
|
|
360
287
|
private _frustum?: Frustum;
|
361
|
-
|
362
288
|
/**
|
363
|
-
*
|
364
|
-
*
|
365
|
-
*
|
366
|
-
* @returns {Frustum} The camera's view frustum
|
289
|
+
* Get a frustum - it will be created the first time this method is called and updated every frame in onBeforeRender when it exists.
|
290
|
+
* You can also manually update it using the updateFrustum method.
|
367
291
|
*/
|
368
292
|
public getFrustum(): Frustum {
|
369
293
|
if (!this._frustum) {
|
@@ -372,22 +296,13 @@
|
|
372
296
|
}
|
373
297
|
return this._frustum;
|
374
298
|
}
|
375
|
-
|
376
|
-
/**
|
377
|
-
* Forces an update of the camera's frustum.
|
378
|
-
* This is automatically called every frame in onBeforeRender.
|
379
|
-
*/
|
299
|
+
/** Force frustum update - note that this also happens automatically every frame in onBeforeRender */
|
380
300
|
public updateFrustum() {
|
381
301
|
if (!this._frustum) this._frustum = new Frustum();
|
382
302
|
this._frustum.setFromProjectionMatrix(this.getProjectionScreenMatrix(this._projScreenMatrix, true), this.context.renderer.coordinateSystem);
|
383
303
|
}
|
384
|
-
|
385
304
|
/**
|
386
|
-
*
|
387
|
-
*
|
388
|
-
* @param target Matrix4 object to store the result in
|
389
|
-
* @param forceUpdate Whether to force recalculation of the matrix
|
390
|
-
* @returns {Matrix4} The requested projection screen matrix
|
305
|
+
* @returns {Matrix4} this camera's projection screen matrix.
|
391
306
|
*/
|
392
307
|
public getProjectionScreenMatrix(target: Matrix4, forceUpdate?: boolean) {
|
393
308
|
if (forceUpdate) {
|
@@ -398,6 +313,7 @@
|
|
398
313
|
}
|
399
314
|
private readonly _projScreenMatrix = new Matrix4();
|
400
315
|
|
316
|
+
|
401
317
|
/** @internal */
|
402
318
|
awake() {
|
403
319
|
if (debugscreenpointtoray) {
|
@@ -466,9 +382,8 @@
|
|
466
382
|
}
|
467
383
|
|
468
384
|
/**
|
469
|
-
* Creates a
|
470
|
-
*
|
471
|
-
*/
|
385
|
+
* Creates a {@link PerspectiveCamera} if it does not exist yet and set the camera's properties. This is internally also called when accessing the {@link cam} property.
|
386
|
+
**/
|
472
387
|
buildCamera() {
|
473
388
|
if (this._cam) return;
|
474
389
|
|
@@ -513,21 +428,13 @@
|
|
513
428
|
}
|
514
429
|
}
|
515
430
|
|
516
|
-
/**
|
517
|
-
* Applies clear flags if this is the active main camera.
|
518
|
-
* @param opts Options for applying clear flags
|
519
|
-
*/
|
520
431
|
applyClearFlagsIfIsActiveCamera(opts?: { applySkybox: boolean }) {
|
521
432
|
if (this.context.mainCameraComponent === this) {
|
522
433
|
this.applyClearFlags(opts);
|
523
434
|
}
|
524
435
|
}
|
525
436
|
|
526
|
-
/**
|
527
|
-
* Applies this camera's clear flags and related settings to the renderer.
|
528
|
-
* This controls how the background is rendered (skybox, solid color, transparent).
|
529
|
-
* @param opts Options for applying clear flags
|
530
|
-
*/
|
437
|
+
/** Apply this camera's clear flags and related settings to the renderer */
|
531
438
|
applyClearFlags(opts?: { applySkybox: boolean }) {
|
532
439
|
if (!this._cam) {
|
533
440
|
if (debug) console.log("Camera does not exist (apply clear flags)")
|
@@ -596,7 +503,7 @@
|
|
596
503
|
}
|
597
504
|
|
598
505
|
/**
|
599
|
-
*
|
506
|
+
* Apply the skybox to the scene
|
600
507
|
*/
|
601
508
|
applySceneSkybox() {
|
602
509
|
if (!this._skybox)
|
@@ -604,12 +511,9 @@
|
|
604
511
|
this._skybox.apply();
|
605
512
|
}
|
606
513
|
|
607
|
-
/**
|
608
|
-
*
|
609
|
-
*
|
610
|
-
* @param context The current rendering context
|
611
|
-
* @returns {boolean} True when in XR on a pass through device where the background should be invisible
|
612
|
-
*/
|
514
|
+
/** Used to determine if the background should be transparent when in pass through AR
|
515
|
+
* @returns true when in XR on a pass through device where the background shouldbe invisible
|
516
|
+
**/
|
613
517
|
static backgroundShouldBeTransparent(context: Context) {
|
614
518
|
const session = context.renderer.xr?.getSession();
|
615
519
|
if (!session) return false;
|
@@ -639,10 +543,7 @@
|
|
639
543
|
}
|
640
544
|
}
|
641
545
|
|
642
|
-
|
643
|
-
* Helper class for managing skybox textures for cameras.
|
644
|
-
* Handles retrieving and applying skybox textures to the scene.
|
645
|
-
*/
|
546
|
+
|
646
547
|
class CameraSkybox {
|
647
548
|
|
648
549
|
private _camera: Camera;
|
@@ -654,10 +555,6 @@
|
|
654
555
|
this._camera = camera;
|
655
556
|
}
|
656
557
|
|
657
|
-
/**
|
658
|
-
* Applies the skybox texture to the scene background.
|
659
|
-
* Retrieves the texture based on the camera's source ID.
|
660
|
-
*/
|
661
558
|
apply() {
|
662
559
|
this._skybox = this.context.lightmaps.tryGetSkybox(this._camera.sourceId) as Texture;
|
663
560
|
if (!this._skybox) {
|
@@ -675,16 +572,13 @@
|
|
675
572
|
}
|
676
573
|
}
|
677
574
|
|
678
|
-
|
679
|
-
* Adds orbit controls to the camera if the freecam URL parameter is enabled.
|
680
|
-
*
|
681
|
-
* @param cam The camera to potentially add orbit controls to
|
682
|
-
*/
|
575
|
+
|
683
576
|
function handleFreeCam(cam: Camera) {
|
684
577
|
const isFreecam = getParam("freecam");
|
685
578
|
if (isFreecam) {
|
686
579
|
if (cam.context.mainCameraComponent === cam) {
|
687
580
|
GameObject.getOrAddComponent(cam.gameObject, OrbitControls);
|
688
581
|
}
|
582
|
+
|
689
583
|
}
|
690
584
|
}
|
@@ -13,13 +13,6 @@
|
|
13
13
|
|
14
14
|
const debug = getParam("debugmissingcamera");
|
15
15
|
|
16
|
-
/**
|
17
|
-
* Handler for missing camera events. Creates a default fallback camera when no camera is found in the scene.
|
18
|
-
* Sets up camera properties based on the context and HTML element attributes.
|
19
|
-
*
|
20
|
-
* @param evt The context event containing scene and configuration information
|
21
|
-
* @returns The created camera component
|
22
|
-
*/
|
23
16
|
ContextRegistry.registerCallback(ContextEvent.MissingCamera, (evt) => {
|
24
17
|
if (debug) console.warn("Creating missing camera")
|
25
18
|
const scene = evt.context.scene;
|
@@ -64,12 +57,6 @@
|
|
64
57
|
return cam;
|
65
58
|
});
|
66
59
|
|
67
|
-
/**
|
68
|
-
* Handler for context creation events. Checks if camera controls should be added
|
69
|
-
* to the main camera when the context is created.
|
70
|
-
*
|
71
|
-
* @param evt The context creation event containing the context information
|
72
|
-
*/
|
73
60
|
ContextRegistry.registerCallback(ContextEvent.ContextCreated, (evt) => {
|
74
61
|
if (!evt.context.mainCamera) {
|
75
62
|
if (debug) console.log("Will not auto-fit because a default camera exists");
|
@@ -90,13 +77,6 @@
|
|
90
77
|
}
|
91
78
|
})
|
92
79
|
|
93
|
-
/**
|
94
|
-
* Creates default orbit camera controls for the specified camera.
|
95
|
-
* Configures auto-rotation and auto-fit settings based on HTML attributes.
|
96
|
-
*
|
97
|
-
* @param context The rendering context
|
98
|
-
* @param cam Optional camera component to attach controls to (uses main camera if not specified)
|
99
|
-
*/
|
100
80
|
function createDefaultCameraControls(context: IContext, cam?: ICamera) {
|
101
81
|
|
102
82
|
cam = cam ?? context.mainCameraComponent;
|
@@ -1,63 +1,59 @@
|
|
1
1
|
import { BufferGeometry, Group, Mesh, Object3D, Vector3 } from "three"
|
2
2
|
|
3
|
-
import { isDevEnvironment } from "../engine/debug/index.js";
|
4
3
|
import { addComponent } from "../engine/engine_components.js";
|
5
4
|
import { Gizmos } from "../engine/engine_gizmos.js";
|
6
5
|
import type { PhysicsMaterial } from "../engine/engine_physics.types.js";
|
7
6
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
8
|
-
import { getBoundingBox } from "../engine/engine_three_utils.js";
|
7
|
+
import { getBoundingBox, getWorldScale } from "../engine/engine_three_utils.js";
|
8
|
+
// import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics.js";
|
9
9
|
import type { IBoxCollider, ICollider, ISphereCollider } from "../engine/engine_types.js";
|
10
10
|
import { validate } from "../engine/engine_util_decorator.js";
|
11
|
-
import {
|
11
|
+
import { unwatchWrite, watchWrite } from "../engine/engine_utils.js";
|
12
12
|
import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js";
|
13
13
|
import { Behaviour } from "./Component.js";
|
14
14
|
import { Rigidbody } from "./RigidBody.js";
|
15
15
|
|
16
16
|
/**
|
17
17
|
* Collider is the base class for all colliders. A collider is a physical shape that is used to detect collisions with other objects in the scene.
|
18
|
-
* Colliders are used in combination with a
|
18
|
+
* Colliders are used in combination with a Rigidbody to create physical interactions between objects.
|
19
19
|
* Colliders are registered with the physics engine when they are enabled and removed when they are disabled.
|
20
20
|
* @category Physics
|
21
21
|
* @group Components
|
22
22
|
*/
|
23
23
|
export class Collider extends Behaviour implements ICollider {
|
24
24
|
|
25
|
-
/**
|
26
|
-
* Identifies this component as a collider.
|
27
|
-
* @internal
|
28
|
-
*/
|
25
|
+
/** @internal */
|
29
26
|
get isCollider(): any {
|
30
27
|
return true;
|
31
28
|
}
|
32
29
|
|
33
30
|
/**
|
34
|
-
* The
|
31
|
+
* The Rigidbody that this collider is attached to.
|
35
32
|
*/
|
36
33
|
@serializable(Rigidbody)
|
37
34
|
attachedRigidbody: Rigidbody | null = null;
|
38
35
|
|
39
36
|
/**
|
40
37
|
* When `true` the collider will not be used for collision detection but will still trigger events.
|
41
|
-
* Trigger colliders can trigger events when other colliders enter their space, without creating a physical response/collision.
|
42
38
|
*/
|
43
39
|
@serializable()
|
44
40
|
isTrigger: boolean = false;
|
45
41
|
|
46
42
|
/**
|
47
|
-
* The physics material that defines physical properties of the collider such as friction and bounciness.
|
43
|
+
* The physics material that is used for the collider. This material defines physical properties of the collider such as friction and bounciness.
|
48
44
|
*/
|
49
45
|
@serializable()
|
50
46
|
sharedMaterial?: PhysicsMaterial;
|
51
47
|
|
52
48
|
/**
|
53
|
-
* The layers that
|
54
|
-
* @default [0]
|
49
|
+
* The layers that the collider is assigned to.
|
55
50
|
*/
|
56
51
|
@serializable()
|
57
52
|
membership: number[] = [0];
|
58
53
|
|
59
54
|
/**
|
60
|
-
* The layers that
|
55
|
+
* The layers that the collider will interact with.
|
56
|
+
* @inheritdoc
|
61
57
|
*/
|
62
58
|
@serializable()
|
63
59
|
filter?: number[];
|
@@ -84,91 +80,62 @@
|
|
84
80
|
this.context.physics.engine?.removeBody(this);
|
85
81
|
}
|
86
82
|
|
87
|
-
/**
|
88
|
-
* Returns the underlying physics body from the physics engine.
|
89
|
-
* Only available if the component is enabled and active in the scene.
|
90
|
-
*/
|
83
|
+
/** Returns the underlying physics body from the physics engine (if any) - the component must be enabled and active in the scene */
|
91
84
|
get body() {
|
92
85
|
return this.context.physics.engine?.getBody(this);
|
93
86
|
}
|
94
87
|
|
95
88
|
/**
|
96
|
-
*
|
97
|
-
* Use this when you've changed collider properties and need to sync with the physics engine.
|
89
|
+
* Apply the collider properties to the physics engine.
|
98
90
|
*/
|
99
91
|
updateProperties = () => {
|
100
92
|
this.context.physics.engine?.updateProperties(this);
|
101
93
|
}
|
102
94
|
|
103
|
-
/**
|
104
|
-
* Updates the physics material in the physics engine.
|
105
|
-
* Call this after changing the sharedMaterial property.
|
106
|
-
*/
|
95
|
+
/** Requests an update of the physics material in the physics engine */
|
107
96
|
updatePhysicsMaterial() {
|
108
97
|
this.context.physics.engine?.updatePhysicsMaterial(this);
|
98
|
+
|
109
99
|
}
|
110
100
|
}
|
111
101
|
|
112
102
|
/**
|
113
|
-
* SphereCollider represents a sphere
|
114
|
-
* Useful for objects that are roughly spherical in shape or need a simple collision boundary.
|
103
|
+
* SphereCollider is a collider that represents a sphere shape.
|
115
104
|
* @category Physics
|
116
105
|
* @group Components
|
117
106
|
*/
|
118
107
|
export class SphereCollider extends Collider implements ISphereCollider {
|
119
108
|
|
120
|
-
/**
|
121
|
-
* The radius of the sphere collider.
|
122
|
-
*/
|
123
109
|
@validate()
|
124
110
|
@serializable()
|
125
111
|
radius: number = .5;
|
126
112
|
|
127
|
-
/**
|
128
|
-
* The center position of the sphere collider relative to the transform's position.
|
129
|
-
*/
|
130
113
|
@serializable(Vector3)
|
131
114
|
center: Vector3 = new Vector3(0, 0, 0);
|
132
115
|
|
133
|
-
/**
|
134
|
-
* Registers the sphere collider with the physics engine and sets up scale change monitoring.
|
135
|
-
*/
|
136
116
|
onEnable() {
|
137
117
|
super.onEnable();
|
138
118
|
this.context.physics.engine?.addSphereCollider(this);
|
139
119
|
watchWrite(this.gameObject.scale, this.updateProperties);
|
140
120
|
}
|
141
121
|
|
142
|
-
/**
|
143
|
-
* Removes scale change monitoring when the collider is disabled.
|
144
|
-
*/
|
145
122
|
onDisable(): void {
|
146
123
|
super.onDisable();
|
147
124
|
unwatchWrite(this.gameObject.scale, this.updateProperties);
|
148
125
|
}
|
149
126
|
|
150
|
-
/**
|
151
|
-
* Updates collider properties when validated in the editor or inspector.
|
152
|
-
*/
|
153
127
|
onValidate(): void {
|
154
128
|
this.updateProperties();
|
155
129
|
}
|
156
130
|
}
|
157
131
|
|
158
132
|
/**
|
159
|
-
* BoxCollider represents a box
|
160
|
-
* Ideal for rectangular objects or objects that need a simple cuboid collision boundary.
|
133
|
+
* BoxCollider is a collider that represents a box shape.
|
161
134
|
* @category Physics
|
162
135
|
* @group Components
|
163
136
|
*/
|
164
137
|
export class BoxCollider extends Collider implements IBoxCollider {
|
165
138
|
|
166
|
-
/**
|
167
|
-
* Creates and adds a BoxCollider to the given object.
|
168
|
-
* @param obj The object to add the collider to
|
169
|
-
* @param opts Configuration options for the collider and optional rigidbody
|
170
|
-
* @returns The newly created BoxCollider
|
171
|
-
*/
|
172
139
|
static add(obj: Mesh | Object3D, opts?: { rigidbody: boolean, debug?: boolean }) {
|
173
140
|
const collider = addComponent(obj, BoxCollider);
|
174
141
|
collider.autoFit();
|
@@ -179,51 +146,31 @@
|
|
179
146
|
return collider;
|
180
147
|
}
|
181
148
|
|
182
|
-
/**
|
183
|
-
* The size of the box collider along each axis.
|
184
|
-
*/
|
185
149
|
@validate()
|
186
150
|
@serializable(Vector3)
|
187
151
|
size: Vector3 = new Vector3(1, 1, 1);
|
188
152
|
|
189
|
-
/**
|
190
|
-
* The center position of the box collider relative to the transform's position.
|
191
|
-
*/
|
192
153
|
@serializable(Vector3)
|
193
154
|
center: Vector3 = new Vector3(0, 0, 0);
|
194
155
|
|
195
|
-
/**
|
196
|
-
* Registers the box collider with the physics engine and sets up scale change monitoring.
|
197
|
-
* @internal
|
198
|
-
*/
|
156
|
+
/** @internal */
|
199
157
|
onEnable() {
|
200
158
|
super.onEnable();
|
201
159
|
this.context.physics.engine?.addBoxCollider(this, this.size);
|
202
160
|
watchWrite(this.gameObject.scale, this.updateProperties);
|
203
161
|
}
|
204
162
|
|
205
|
-
/**
|
206
|
-
* Removes scale change monitoring when the collider is disabled.
|
207
|
-
* @internal
|
208
|
-
*/
|
163
|
+
/** @internal */
|
209
164
|
onDisable(): void {
|
210
165
|
super.onDisable();
|
211
166
|
unwatchWrite(this.gameObject.scale, this.updateProperties);
|
212
167
|
}
|
213
168
|
|
214
|
-
/**
|
215
|
-
* Updates collider properties when validated in the editor or inspector.
|
216
|
-
* @internal
|
217
|
-
*/
|
169
|
+
/** @internal */
|
218
170
|
onValidate(): void {
|
219
171
|
this.updateProperties();
|
220
172
|
}
|
221
173
|
|
222
|
-
/**
|
223
|
-
* Automatically fits the collider to the geometry of the object.
|
224
|
-
* Sets the size and center based on the object's bounding box.
|
225
|
-
* @param opts Options object with a debug flag to visualize the bounding box
|
226
|
-
*/
|
227
174
|
autoFit(opts?: { debug?: boolean }) {
|
228
175
|
const obj = this.gameObject;
|
229
176
|
|
@@ -258,31 +205,24 @@
|
|
258
205
|
}
|
259
206
|
|
260
207
|
/**
|
261
|
-
* MeshCollider
|
262
|
-
*
|
208
|
+
* MeshCollider is a collider that represents a mesh shape.
|
209
|
+
* The mesh collider can be used to create a collider from a mesh.
|
263
210
|
* @category Physics
|
264
211
|
* @group Components
|
265
212
|
*/
|
266
213
|
export class MeshCollider extends Collider {
|
267
214
|
|
268
215
|
/**
|
269
|
-
* The mesh that is used
|
270
|
-
* If not set, the collider will try to use the mesh of the object it's attached to.
|
216
|
+
* The mesh that is used for the collider.
|
271
217
|
*/
|
272
218
|
@serializable(Mesh)
|
273
219
|
sharedMesh?: Mesh;
|
274
220
|
|
275
|
-
/**
|
276
|
-
*
|
277
|
-
* Set to `false` if you want this mesh collider to be able to contain other objects.
|
278
|
-
*/
|
221
|
+
/** When `true` the collider won't have holes or entrances.
|
222
|
+
* If you wan't this mesh collider to be able to *contain* other objects this should be set to `false` */
|
279
223
|
@serializable()
|
280
224
|
convex: boolean = false;
|
281
225
|
|
282
|
-
/**
|
283
|
-
* Creates and registers the mesh collider with the physics engine.
|
284
|
-
* Handles both individual meshes and mesh groups.
|
285
|
-
*/
|
286
226
|
onEnable() {
|
287
227
|
super.onEnable();
|
288
228
|
if (!this.context.physics.engine) return;
|
@@ -332,44 +272,29 @@
|
|
332
272
|
});
|
333
273
|
}
|
334
274
|
else {
|
335
|
-
|
336
|
-
console.warn(`[MeshCollider] A MeshCollider mesh is assigned to an unknown object on \"${this.gameObject.name}\", but it's neither a Mesh nor a Group. Please double check that you attached the collider component to the right object and report a bug otherwise!`, this);
|
337
|
-
}
|
275
|
+
console.warn("A MeshCollider mesh is assigned, but it's neither a Mesh nor a Group. Please report a bug!", this, this.sharedMesh);
|
338
276
|
}
|
339
277
|
}
|
340
278
|
}
|
341
279
|
}
|
342
280
|
|
343
281
|
/**
|
344
|
-
* CapsuleCollider
|
345
|
-
* Ideal for character controllers and objects that need a rounded collision shape.
|
282
|
+
* CapsuleCollider is a collider that represents a capsule shape.
|
346
283
|
* @category Physics
|
347
284
|
* @group Components
|
348
285
|
*/
|
349
286
|
export class CapsuleCollider extends Collider {
|
350
|
-
/**
|
351
|
-
* The center position of the capsule collider relative to the transform's position.
|
352
|
-
*/
|
353
287
|
@serializable(Vector3)
|
354
288
|
center: Vector3 = new Vector3(0, 0, 0);
|
355
289
|
|
356
|
-
/**
|
357
|
-
* The radius of the capsule's cylindrical body and hemispherical ends.
|
358
|
-
*/
|
359
290
|
@serializable()
|
360
291
|
radius: number = .5;
|
361
|
-
|
362
|
-
/**
|
363
|
-
* The total height of the capsule including both hemispherical ends.
|
364
|
-
*/
|
365
292
|
@serializable()
|
366
293
|
height: number = 2;
|
367
294
|
|
368
|
-
/**
|
369
|
-
* Registers the capsule collider with the physics engine.
|
370
|
-
*/
|
371
295
|
onEnable() {
|
372
296
|
super.onEnable();
|
373
297
|
this.context.physics.engine?.addCapsuleCollider(this, this.height, this.radius);
|
374
298
|
}
|
299
|
+
|
375
300
|
}
|
@@ -23,167 +23,57 @@
|
|
23
23
|
// }
|
24
24
|
|
25
25
|
/**
|
26
|
-
*
|
27
|
-
*
|
28
|
-
* They manage their components and provide methods to add, remove and get components.
|
29
|
-
*
|
30
|
-
* All {@link Object3D} types loaded in Needle Engine have methods like {@link addComponent}.
|
31
|
-
* These methods are available directly on the GameObject instance:
|
26
|
+
* All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like `addComponent` etc.
|
27
|
+
* Many of the GameObject methods can be imported directly via `@needle-tools/engine` as well:
|
32
28
|
* ```typescript
|
33
|
-
*
|
29
|
+
* import { addComponent } from "@needle-tools/engine";
|
34
30
|
* ```
|
35
|
-
*
|
36
|
-
* And can be called statically on the GameObject class as well:
|
37
|
-
* ```typescript
|
38
|
-
* GameObject.setActive(target, true);
|
39
|
-
* ```
|
40
31
|
*/
|
41
32
|
export abstract class GameObject extends Object3D implements Object3D, IGameObject {
|
42
33
|
|
43
|
-
/
|
44
|
-
* Indicates if the GameObject is currently active. Inactive GameObjects will not be rendered or updated.
|
45
|
-
* When the activeSelf state changes, components will receive {@link Component.onEnable} or {@link Component.onDisable} callbacks.
|
46
|
-
*/
|
34
|
+
// these are implemented via threejs object extensions
|
47
35
|
abstract activeSelf: boolean;
|
48
36
|
|
49
|
-
/** @deprecated
|
37
|
+
/** @deprecated use `addComponent` */
|
50
38
|
// eslint-disable-next-line deprecation/deprecation
|
51
39
|
abstract addNewComponent<T extends IComponent>(type: ConstructorConcrete<T>, init?: ComponentInit<T>): T;
|
52
|
-
|
53
|
-
/**
|
54
|
-
* Creates a new component on this gameObject or adds an existing component instance
|
55
|
-
* @param comp Component type constructor or existing component instance
|
56
|
-
* @param init Optional initialization values for the component
|
57
|
-
* @returns The newly created or added component
|
58
|
-
*/
|
40
|
+
/** creates a new component on this gameObject */
|
59
41
|
abstract addComponent<T extends IComponent>(comp: T | ConstructorConcrete<T>, init?: ComponentInit<T>): T;
|
60
|
-
|
61
|
-
/**
|
62
|
-
* Removes a component from this GameObject
|
63
|
-
* @param comp Component instance to remove
|
64
|
-
* @returns The removed component
|
65
|
-
*/
|
66
42
|
abstract removeComponent<T extends IComponent>(comp: T): T;
|
67
|
-
|
68
|
-
/**
|
69
|
-
* Gets an existing component of the specified type or adds a new one if it doesn't exist
|
70
|
-
* @param typeName Constructor of the component type to get or add
|
71
|
-
* @returns The existing or newly added component
|
72
|
-
*/
|
73
43
|
abstract getOrAddComponent<T>(typeName: ConstructorConcrete<T> | null): T;
|
74
|
-
|
75
|
-
/**
|
76
|
-
* Gets a component of the specified type attached to this GameObject
|
77
|
-
* @param type Constructor of the component type to get
|
78
|
-
* @returns The component if found, otherwise null
|
79
|
-
*/
|
80
44
|
abstract getComponent<T>(type: Constructor<T>): T | null;
|
81
|
-
|
82
|
-
/**
|
83
|
-
* Gets all components of the specified type attached to this GameObject
|
84
|
-
* @param type Constructor of the component type to get
|
85
|
-
* @param arr Optional array to populate with the components
|
86
|
-
* @returns Array of components
|
87
|
-
*/
|
88
45
|
abstract getComponents<T>(type: Constructor<T>, arr?: T[]): Array<T>;
|
89
|
-
|
90
|
-
/**
|
91
|
-
* Gets a component of the specified type in this GameObject's children hierarchy
|
92
|
-
* @param type Constructor of the component type to get
|
93
|
-
* @returns The first matching component if found, otherwise null
|
94
|
-
*/
|
95
46
|
abstract getComponentInChildren<T>(type: Constructor<T>): T | null;
|
96
|
-
|
97
|
-
/**
|
98
|
-
* Gets all components of the specified type in this GameObject's children hierarchy
|
99
|
-
* @param type Constructor of the component type to get
|
100
|
-
* @param arr Optional array to populate with the components
|
101
|
-
* @returns Array of components
|
102
|
-
*/
|
103
47
|
abstract getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>;
|
104
|
-
|
105
|
-
/**
|
106
|
-
* Gets a component of the specified type in this GameObject's parent hierarchy
|
107
|
-
* @param type Constructor of the component type to get
|
108
|
-
* @returns The first matching component if found, otherwise null
|
109
|
-
*/
|
110
48
|
abstract getComponentInParent<T>(type: Constructor<T>): T | null;
|
111
|
-
|
112
|
-
/**
|
113
|
-
* Gets all components of the specified type in this GameObject's parent hierarchy
|
114
|
-
* @param type Constructor of the component type to get
|
115
|
-
* @param arr Optional array to populate with the components
|
116
|
-
* @returns Array of components
|
117
|
-
*/
|
118
49
|
abstract getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;
|
119
50
|
|
120
51
|
|
121
|
-
/**
|
122
|
-
* The position of this GameObject in world space
|
123
|
-
*/
|
124
52
|
abstract get worldPosition(): Vector3
|
125
53
|
abstract set worldPosition(val: Vector3);
|
126
|
-
|
127
|
-
/**
|
128
|
-
* The rotation of this GameObject in world space as a quaternion
|
129
|
-
*/
|
130
54
|
abstract set worldQuaternion(val: Quaternion);
|
131
55
|
abstract get worldQuaternion(): Quaternion;
|
132
|
-
|
133
|
-
/**
|
134
|
-
* The rotation of this GameObject in world space in euler angles (degrees)
|
135
|
-
*/
|
136
56
|
abstract set worldRotation(val: Vector3);
|
137
57
|
abstract get worldRotation(): Vector3;
|
138
|
-
|
139
|
-
/**
|
140
|
-
* The scale of this GameObject in world space
|
141
|
-
*/
|
142
58
|
abstract set worldScale(val: Vector3);
|
143
59
|
abstract get worldScale(): Vector3;
|
144
60
|
|
145
|
-
/**
|
146
|
-
* The forward direction vector of this GameObject in world space
|
147
|
-
*/
|
148
61
|
abstract get worldForward(): Vector3;
|
149
|
-
|
150
|
-
/**
|
151
|
-
* The right direction vector of this GameObject in world space
|
152
|
-
*/
|
153
62
|
abstract get worldRight(): Vector3;
|
154
|
-
|
155
|
-
/**
|
156
|
-
* The up direction vector of this GameObject in world space
|
157
|
-
*/
|
158
63
|
abstract get worldUp(): Vector3;
|
159
64
|
|
160
|
-
/**
|
161
|
-
* Unique identifier for this GameObject
|
162
|
-
*/
|
163
65
|
guid: string | undefined;
|
164
66
|
|
165
|
-
/
|
166
|
-
* Destroys this GameObject and all its components.
|
167
|
-
* Internally, this is added to the three.js {@link Object3D} prototype.
|
168
|
-
*/
|
67
|
+
// Added to the threejs Object3D prototype
|
169
68
|
abstract destroy();
|
170
69
|
|
171
70
|
|
172
|
-
|
173
|
-
|
174
|
-
* @param go The GameObject to check
|
175
|
-
* @returns True if the GameObject has been destroyed
|
176
|
-
*/
|
71
|
+
|
72
|
+
|
177
73
|
public static isDestroyed(go: Object3D): boolean {
|
178
74
|
return isDestroyed(go);
|
179
75
|
}
|
180
76
|
|
181
|
-
/**
|
182
|
-
* Sets the active state of a GameObject
|
183
|
-
* @param go The GameObject to modify
|
184
|
-
* @param active Whether the GameObject should be active
|
185
|
-
* @param processStart Whether to process the start callbacks if being activated
|
186
|
-
*/
|
187
77
|
public static setActive(go: Object3D, active: boolean, processStart: boolean = true) {
|
188
78
|
if (!go) return;
|
189
79
|
setActive(go, active);
|
@@ -194,68 +84,47 @@
|
|
194
84
|
main.processStart(Context.Current, go);
|
195
85
|
}
|
196
86
|
|
197
|
-
/**
|
198
|
-
* Checks if the GameObject itself is active (same as go.visible)
|
199
|
-
* @param go The GameObject to check
|
200
|
-
* @returns True if the GameObject is active
|
201
|
-
*/
|
87
|
+
/** If the object is active (same as go.visible) */
|
202
88
|
public static isActiveSelf(go: Object3D): boolean {
|
203
89
|
return isActiveSelf(go);
|
204
90
|
}
|
205
91
|
|
206
|
-
/**
|
207
|
-
*
|
208
|
-
|
209
|
-
* @returns True if the GameObject is active in the hierarchy
|
210
|
-
*/
|
92
|
+
/** If the object is active in the hierarchy (e.g. if any parent is invisible or not in the scene it will be false)
|
93
|
+
* @param go object to check
|
94
|
+
*/
|
211
95
|
public static isActiveInHierarchy(go: Object3D): boolean {
|
212
96
|
return isActiveInHierarchy(go);
|
213
97
|
}
|
214
98
|
|
215
|
-
/**
|
216
|
-
* Marks a GameObject to be rendered using instancing
|
217
|
-
* @param go The GameObject to mark
|
218
|
-
* @param instanced Whether the GameObject should use instanced rendering
|
219
|
-
*/
|
220
99
|
public static markAsInstancedRendered(go: Object3D, instanced: boolean) {
|
221
100
|
markAsInstancedRendered(go, instanced);
|
222
101
|
}
|
223
102
|
|
224
|
-
/**
|
225
|
-
* Checks if a GameObject is using instanced rendering
|
226
|
-
* @param instance The GameObject to check
|
227
|
-
* @returns True if the GameObject is using instanced rendering
|
228
|
-
*/
|
229
103
|
public static isUsingInstancing(instance: Object3D): boolean { return isUsingInstancing(instance); }
|
230
104
|
|
231
|
-
/**
|
232
|
-
*
|
233
|
-
* @param
|
234
|
-
* @param
|
235
|
-
* @
|
236
|
-
* @returns The last return value of the callback
|
105
|
+
/** Run a callback for all components of the provided type on the provided object and its children (if recursive is true)
|
106
|
+
* @param instance object to run the method on
|
107
|
+
* @param cb callback to run on each component, "return undefined;" to continue and "return <anything>;" to break the loop
|
108
|
+
* @param recursive if true, the method will be run on all children as well
|
109
|
+
* @returns the last return value of the callback
|
237
110
|
*/
|
238
111
|
public static foreachComponent(instance: Object3D, cb: (comp: Component) => any, recursive: boolean = true): any {
|
239
112
|
return foreachComponent(instance, cb as (comp: IComponent) => any, recursive);
|
240
113
|
}
|
241
114
|
|
242
|
-
/**
|
243
|
-
*
|
244
|
-
* @param
|
245
|
-
* @param opts Options for the instantiation
|
246
|
-
* @returns The newly created instance or null if creation failed
|
115
|
+
/** Creates a new instance of the provided object. The new instance will be created on all connected clients
|
116
|
+
* @param instance object to instantiate
|
117
|
+
* @param opts options for the instantiation
|
247
118
|
*/
|
248
119
|
public static instantiateSynced(instance: GameObject | Object3D | null, opts: SyncInstantiateOptions): GameObject | null {
|
249
120
|
if (!instance) return null;
|
250
121
|
return syncInstantiate(instance, opts) as GameObject | null;
|
251
122
|
}
|
252
123
|
|
253
|
-
/**
|
254
|
-
*
|
255
|
-
* @param
|
256
|
-
|
257
|
-
* @returns The newly created instance
|
258
|
-
*/
|
124
|
+
/** Creates a new instance of the provided object (like cloning it including all components and children)
|
125
|
+
* @param instance object to instantiate
|
126
|
+
* @param opts options for the instantiation (e.g. with what parent, position, etc.)
|
127
|
+
*/
|
259
128
|
public static instantiate(instance: AssetReference, opts?: IInstantiateOptions | null | undefined): Promise<Object3D | null>
|
260
129
|
public static instantiate(instance: GameObject | Object3D, opts?: IInstantiateOptions | null | undefined): GameObject
|
261
130
|
public static instantiate(instance: AssetReference | GameObject | Object3D, opts: IInstantiateOptions | null | undefined = null): GameObject | Promise<Object3D | null> {
|
@@ -265,12 +134,9 @@
|
|
265
134
|
return instantiate(instance as GameObject | Object3D, opts) as GameObject;
|
266
135
|
}
|
267
136
|
|
268
|
-
/**
|
269
|
-
*
|
270
|
-
|
271
|
-
* @param context Optional context to use
|
272
|
-
* @param recursive If true, all children will be destroyed as well
|
273
|
-
*/
|
137
|
+
/** Destroys a object on all connected clients (if you are in a networked session)
|
138
|
+
* @param instance object to destroy
|
139
|
+
*/
|
274
140
|
public static destroySynced(instance: Object3D | Component, context?: Context, recursive: boolean = true) {
|
275
141
|
if (!instance) return;
|
276
142
|
const go = instance as GameObject;
|
@@ -278,20 +144,16 @@
|
|
278
144
|
syncDestroy(go as any, context.connection, recursive);
|
279
145
|
}
|
280
146
|
|
281
|
-
/**
|
282
|
-
*
|
283
|
-
* @param
|
284
|
-
* @param recursive If true, all children will be destroyed as well. Default: true
|
147
|
+
/** Destroys a object
|
148
|
+
* @param instance object to destroy
|
149
|
+
* @param recursive if true, all children will be destroyed as well. true by default
|
285
150
|
*/
|
286
151
|
public static destroy(instance: Object3D | Component, recursive: boolean = true) {
|
287
152
|
return destroy(instance, recursive);
|
288
153
|
}
|
289
154
|
|
290
155
|
/**
|
291
|
-
*
|
292
|
-
* @param instance Object to add
|
293
|
-
* @param parent Parent to add the object to
|
294
|
-
* @param context Optional context to use
|
156
|
+
* Add an object to parent and also ensure all components are being registered
|
295
157
|
*/
|
296
158
|
public static add(instance: Object3D | null | undefined, parent: Object3D, context?: Context) {
|
297
159
|
if (!instance || !parent) return;
|
@@ -321,7 +183,6 @@
|
|
321
183
|
|
322
184
|
/**
|
323
185
|
* Removes the object from its parent and deactivates all of its components
|
324
|
-
* @param instance Object to remove
|
325
186
|
*/
|
326
187
|
public static remove(instance: Object3D | null | undefined) {
|
327
188
|
if (!instance) return;
|
@@ -333,22 +194,14 @@
|
|
333
194
|
}, true);
|
334
195
|
}
|
335
196
|
|
336
|
-
/**
|
337
|
-
* Invokes a method on all components including children (if a method with that name exists)
|
338
|
-
* @param go GameObject to invoke the method on
|
339
|
-
* @param functionName Name of the method to invoke
|
340
|
-
* @param args Arguments to pass to the method
|
341
|
-
*/
|
197
|
+
/** Invokes a method on all components including children (if a method with that name exists) */
|
342
198
|
public static invokeOnChildren(go: Object3D | null | undefined, functionName: string, ...args: any) {
|
343
199
|
this.invoke(go, functionName, true, args);
|
344
200
|
}
|
345
201
|
|
346
|
-
/**
|
347
|
-
*
|
348
|
-
* @param
|
349
|
-
* @param functionName Name of the method to invoke
|
350
|
-
* @param children Whether to invoke on children as well
|
351
|
-
* @param args Arguments to pass to the method
|
202
|
+
/** Invokes a method on all components that have a method matching the provided name
|
203
|
+
* @param go object to invoke the method on all components
|
204
|
+
* @param functionName name of the method to invoke
|
352
205
|
*/
|
353
206
|
public static invoke(go: Object3D | null | undefined, functionName: string, children: boolean = false, ...args: any) {
|
354
207
|
if (!go) return;
|
@@ -367,53 +220,38 @@
|
|
367
220
|
}
|
368
221
|
|
369
222
|
/**
|
370
|
-
*
|
371
|
-
* @param go
|
372
|
-
* @param instanceOrType
|
373
|
-
* @param init
|
374
|
-
* @param
|
375
|
-
* @returns The added or moved component
|
223
|
+
* Add a new component (or move an existing component) to the provided object
|
224
|
+
* @param go object to add the component to
|
225
|
+
* @param instanceOrType if an instance is provided it will be moved to the new object, if a type is provided a new instance will be created and moved to the new object
|
226
|
+
* @param init optional init object to initialize the component with
|
227
|
+
* @param callAwake if true, the component will be added and awake will be called immediately
|
376
228
|
*/
|
377
229
|
public static addComponent<T extends IComponent>(go: IGameObject | Object3D, instanceOrType: T | ConstructorConcrete<T>, init?: ComponentInit<T>, opts?: { callAwake: boolean }): T {
|
378
230
|
return addComponent(go, instanceOrType, init, opts);
|
379
231
|
}
|
380
232
|
|
381
233
|
/**
|
382
|
-
* Moves a component to a new object
|
383
|
-
* @param go
|
384
|
-
* @param instance
|
385
|
-
* @returns The moved component
|
234
|
+
* Moves a component to a new object
|
235
|
+
* @param go component to move the component to
|
236
|
+
* @param instance component to move to the GO
|
386
237
|
*/
|
387
238
|
public static moveComponent<T extends IComponent>(go: IGameObject | Object3D, instance: T | ConstructorConcrete<T>): T {
|
388
239
|
return addComponent(go, instance);
|
389
240
|
}
|
390
241
|
|
391
|
-
/**
|
392
|
-
*
|
393
|
-
* @param instance Component to remove
|
394
|
-
* @returns The removed component
|
242
|
+
/** Removes a component from its object
|
243
|
+
* @param instance component to remove
|
395
244
|
*/
|
396
245
|
public static removeComponent<T extends IComponent>(instance: T): T {
|
397
246
|
removeComponent(instance.gameObject, instance as any);
|
398
247
|
return instance;
|
399
248
|
}
|
400
249
|
|
401
|
-
/**
|
402
|
-
* Gets or adds a component of the specified type
|
403
|
-
* @param go GameObject to get or add the component to
|
404
|
-
* @param typeName Constructor of the component type
|
405
|
-
* @returns The existing or newly added component
|
406
|
-
*/
|
407
250
|
public static getOrAddComponent<T extends IComponent>(go: IGameObject | Object3D, typeName: ConstructorConcrete<T>): T {
|
408
251
|
return getOrAddComponent<any>(go, typeName);
|
409
252
|
}
|
410
253
|
|
411
|
-
/**
|
412
|
-
* Gets a component on the provided object
|
413
|
-
* @param go GameObject to get the component from
|
414
|
-
* @param typeName Constructor of the component type
|
415
|
-
* @returns The component if found, otherwise null
|
416
|
-
*/
|
254
|
+
/** Gets a component on the provided object */
|
417
255
|
public static getComponent<T extends IComponent>(go: IGameObject | Object3D | null, typeName: Constructor<T> | null): T | null {
|
418
256
|
if (go === null) return null;
|
419
257
|
// if names are minified we could also use the type store and work with strings everywhere
|
@@ -423,99 +261,42 @@
|
|
423
261
|
return getComponent(go, typeName as any);
|
424
262
|
}
|
425
263
|
|
426
|
-
/**
|
427
|
-
* Gets all components of the specified type on the provided object
|
428
|
-
* @param go GameObject to get the components from
|
429
|
-
* @param typeName Constructor of the component type
|
430
|
-
* @param arr Optional array to populate with the components
|
431
|
-
* @returns Array of components
|
432
|
-
*/
|
433
264
|
public static getComponents<T extends IComponent>(go: IGameObject | Object3D | null, typeName: Constructor<T>, arr: T[] | null = null): T[] {
|
434
265
|
if (go === null) return arr ?? [];
|
435
266
|
return getComponents(go, typeName, arr);
|
436
267
|
}
|
437
268
|
|
438
|
-
/**
|
439
|
-
* Finds an object or component by its unique identifier
|
440
|
-
* @param guid Unique identifier to search for
|
441
|
-
* @param hierarchy Root object to search in
|
442
|
-
* @returns The found GameObject or Component, or null/undefined if not found
|
443
|
-
*/
|
444
269
|
public static findByGuid(guid: string, hierarchy: Object3D): GameObject | Component | null | undefined {
|
445
270
|
const res = findByGuid(guid, hierarchy);
|
446
271
|
return res as GameObject | Component | null | undefined;
|
447
272
|
}
|
448
273
|
|
449
|
-
/**
|
450
|
-
* Finds the first object of the specified component type in the scene
|
451
|
-
* @param typeName Constructor of the component type
|
452
|
-
* @param context Context or root object to search in
|
453
|
-
* @param includeInactive Whether to include inactive objects in the search
|
454
|
-
* @returns The first matching component if found, otherwise null
|
455
|
-
*/
|
456
274
|
public static findObjectOfType<T extends IComponent>(typeName: Constructor<T>, context?: Context | Object3D, includeInactive: boolean = true): T | null {
|
457
275
|
return findObjectOfType(typeName, context ?? Context.Current, includeInactive);
|
458
276
|
}
|
459
277
|
|
460
|
-
/**
|
461
|
-
* Finds all objects of the specified component type in the scene
|
462
|
-
* @param typeName Constructor of the component type
|
463
|
-
* @param context Context or root object to search in
|
464
|
-
* @returns Array of matching components
|
465
|
-
*/
|
466
278
|
public static findObjectsOfType<T extends IComponent>(typeName: Constructor<T>, context?: Context | Object3D): Array<T> {
|
467
279
|
const arr = [];
|
468
280
|
findObjectsOfType(typeName, arr, context);
|
469
281
|
return arr;
|
470
282
|
}
|
471
283
|
|
472
|
-
/**
|
473
|
-
* Gets a component of the specified type in the gameObject's children hierarchy
|
474
|
-
* @param go GameObject to search in
|
475
|
-
* @param typeName Constructor of the component type
|
476
|
-
* @returns The first matching component if found, otherwise null
|
477
|
-
*/
|
478
284
|
public static getComponentInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>): T | null {
|
479
285
|
return getComponentInChildren(go, typeName);
|
480
286
|
}
|
481
287
|
|
482
|
-
/**
|
483
|
-
* Gets all components of the specified type in the gameObject's children hierarchy
|
484
|
-
* @param go GameObject to search in
|
485
|
-
* @param typeName Constructor of the component type
|
486
|
-
* @param arr Optional array to populate with the components
|
487
|
-
* @returns Array of components
|
488
|
-
*/
|
489
288
|
public static getComponentsInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>, arr: T[] | null = null): Array<T> {
|
490
289
|
return getComponentsInChildren<T>(go, typeName, arr ?? undefined) as T[]
|
491
290
|
}
|
492
291
|
|
493
|
-
/**
|
494
|
-
* Gets a component of the specified type in the gameObject's parent hierarchy
|
495
|
-
* @param go GameObject to search in
|
496
|
-
* @param typeName Constructor of the component type
|
497
|
-
* @returns The first matching component if found, otherwise null
|
498
|
-
*/
|
499
292
|
public static getComponentInParent<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>): T | null {
|
500
293
|
return getComponentInParent(go, typeName);
|
501
294
|
}
|
502
295
|
|
503
|
-
/**
|
504
|
-
* Gets all components of the specified type in the gameObject's parent hierarchy
|
505
|
-
* @param go GameObject to search in
|
506
|
-
* @param typeName Constructor of the component type
|
507
|
-
* @param arr Optional array to populate with the components
|
508
|
-
* @returns Array of components
|
509
|
-
*/
|
510
296
|
public static getComponentsInParent<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>, arr: Array<T> | null = null): Array<T> {
|
511
297
|
return getComponentsInParent(go, typeName, arr);
|
512
298
|
}
|
513
299
|
|
514
|
-
/**
|
515
|
-
* Gets all components on the gameObject
|
516
|
-
* @param go GameObject to get components from
|
517
|
-
* @returns Array of all components
|
518
|
-
*/
|
519
300
|
public static getAllComponents(go: IGameObject | Object3D): Component[] {
|
520
301
|
const componentsList = go.userData?.components;
|
521
302
|
if (!componentsList) return [];
|
@@ -523,11 +304,6 @@
|
|
523
304
|
return newList;
|
524
305
|
}
|
525
306
|
|
526
|
-
/**
|
527
|
-
* Iterates through all components on the gameObject
|
528
|
-
* @param go GameObject to iterate components on
|
529
|
-
* @returns Generator yielding each component
|
530
|
-
*/
|
531
307
|
public static *iterateComponents(go: IGameObject | Object3D) {
|
532
308
|
const list = go?.userData?.components;
|
533
309
|
if (list && Array.isArray(list)) {
|
@@ -570,43 +346,27 @@
|
|
570
346
|
Partial<INeedleXRSessionEventReceiver>,
|
571
347
|
Partial<IPointerEventHandler>
|
572
348
|
{
|
573
|
-
/**
|
574
|
-
* Indicates whether this object is a component
|
575
|
-
* @internal
|
576
|
-
*/
|
349
|
+
/** @internal */
|
577
350
|
get isComponent(): boolean { return true; }
|
578
351
|
|
579
352
|
private __context: Context | undefined;
|
580
|
-
|
581
|
-
/**
|
582
|
-
* The context this component belongs to, providing access to the runtime environment
|
583
|
-
* including physics, timing utilities, camera, and scene
|
584
|
-
*/
|
353
|
+
/** Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene */
|
585
354
|
get context(): Context {
|
586
355
|
return this.__context ?? Context.Current;
|
587
356
|
}
|
588
357
|
set context(context: Context) {
|
589
358
|
this.__context = context;
|
590
359
|
}
|
591
|
-
|
592
|
-
|
593
|
-
* Shorthand accessor for the current scene from the context
|
594
|
-
* @returns The scene this component belongs to
|
595
|
-
*/
|
360
|
+
/** shorthand for `this.context.scene`
|
361
|
+
* @returns the scene of the context */
|
596
362
|
get scene(): Scene { return this.context.scene; }
|
597
363
|
|
598
|
-
/**
|
599
|
-
* The layer value of the GameObject this component is attached to
|
600
|
-
* Used for visibility and physics filtering
|
601
|
-
*/
|
364
|
+
/** @returns the layer of the gameObject this component is attached to */
|
602
365
|
get layer(): number {
|
603
366
|
return this.gameObject?.userData?.layer;
|
604
367
|
}
|
605
368
|
|
606
|
-
/**
|
607
|
-
* The name of the GameObject this component is attached to
|
608
|
-
* Used for debugging and finding objects
|
609
|
-
*/
|
369
|
+
/** @returns the name of the gameObject this component is attached to */
|
610
370
|
get name(): string {
|
611
371
|
if (this.gameObject?.name) {
|
612
372
|
return this.gameObject.name;
|
@@ -624,11 +384,7 @@
|
|
624
384
|
this.__name = str;
|
625
385
|
}
|
626
386
|
}
|
627
|
-
|
628
|
-
/**
|
629
|
-
* The tag of the GameObject this component is attached to
|
630
|
-
* Used for categorizing objects and efficient lookup
|
631
|
-
*/
|
387
|
+
/** @returns the tag of the gameObject this component is attached to */
|
632
388
|
get tag(): string {
|
633
389
|
return this.gameObject?.userData.tag;
|
634
390
|
}
|
@@ -638,11 +394,7 @@
|
|
638
394
|
this.gameObject.userData.tag = str;
|
639
395
|
}
|
640
396
|
}
|
641
|
-
|
642
|
-
/**
|
643
|
-
* Indicates whether the GameObject is marked as static
|
644
|
-
* Static objects typically don't move and can be optimized by the engine
|
645
|
-
*/
|
397
|
+
/** Is the gameObject marked as static */
|
646
398
|
get static() {
|
647
399
|
return this.gameObject?.userData.static;
|
648
400
|
}
|
@@ -656,11 +408,7 @@
|
|
656
408
|
// return this.gameObject?.hideFlags;
|
657
409
|
// }
|
658
410
|
|
659
|
-
/**
|
660
|
-
* Checks if this component is currently active (enabled and part of an active GameObject hierarchy)
|
661
|
-
* Components that are inactive won't receive lifecycle method calls
|
662
|
-
* @returns True if the component is enabled and all parent GameObjects are active
|
663
|
-
*/
|
411
|
+
/** @returns true if the object is enabled and active in the hierarchy */
|
664
412
|
get activeAndEnabled(): boolean {
|
665
413
|
if (this.destroyed) return false;
|
666
414
|
if (this.__isEnabled === false) return false;
|
@@ -678,7 +426,6 @@
|
|
678
426
|
private get __isActive(): boolean {
|
679
427
|
return this.gameObject.visible;
|
680
428
|
}
|
681
|
-
|
682
429
|
private get __isActiveInHierarchy(): boolean {
|
683
430
|
if (!this.gameObject) return false;
|
684
431
|
const res = this.gameObject[activeInHierarchyFieldName];
|
@@ -691,286 +438,139 @@
|
|
691
438
|
this.gameObject[activeInHierarchyFieldName] = val;
|
692
439
|
}
|
693
440
|
|
694
|
-
/**
|
695
|
-
* Reference to the GameObject this component is attached to
|
696
|
-
* This is a three.js Object3D with additional GameObject functionality
|
697
|
-
*/
|
441
|
+
/** the object this component is attached to. Note that this is a threejs Object3D with some additional features */
|
698
442
|
gameObject!: GameObject;
|
699
|
-
|
700
|
-
/**
|
701
|
-
* Unique identifier for this component instance,
|
702
|
-
* used for finding and tracking components
|
703
|
-
*/
|
443
|
+
/** the unique identifier for this component */
|
704
444
|
guid: string = "invalid";
|
705
|
-
|
706
|
-
/**
|
707
|
-
* Identifier for the source asset that created this component.
|
708
|
-
* For example, URL to the glTF file this component was loaded from
|
709
|
-
*/
|
445
|
+
/** holds the source identifier this object was created with/from (e.g. if it was part of a glTF file the sourceId holds the url to the glTF) */
|
710
446
|
sourceId?: SourceIdentifier;
|
447
|
+
// transform: Object3D = nullObject;
|
711
448
|
|
712
|
-
/**
|
713
|
-
* Called when this component needs to remap guids after an instantiate operation.
|
714
|
-
* @param guidsMap Mapping from old guids to newly generated guids
|
715
|
-
*/
|
449
|
+
/** called on a component with a map of old to new guids (e.g. when instantiate generated new guids and e.g. timeline track bindings needs to remape them) */
|
716
450
|
resolveGuids?(guidsMap: GuidsMap): void;
|
717
451
|
|
718
|
-
/**
|
719
|
-
*
|
720
|
-
* This is the first lifecycle callback to be invoked
|
721
|
-
*/
|
452
|
+
/** called once when the component becomes active for the first time (once per component)
|
453
|
+
* This is the first callback to be called */
|
722
454
|
awake() { }
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
* Invoked after {@link awake} and before {@link start}.
|
727
|
-
*/
|
455
|
+
/** called every time when the component gets enabled (this is invoked after awake and before start)
|
456
|
+
* or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
|
457
|
+
*/
|
728
458
|
onEnable() { }
|
729
|
-
|
730
|
-
/**
|
731
|
-
* Called every time the component becomes disabled or inactive in the hierarchy.
|
732
|
-
* Invoked when the component or any parent GameObject becomes invisible
|
733
|
-
*/
|
459
|
+
/** called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible */
|
734
460
|
onDisable() { }
|
735
|
-
|
736
|
-
/**
|
737
|
-
* Called when the component is destroyed.
|
738
|
-
* Use for cleanup operations like removing event listeners
|
739
|
-
*/
|
461
|
+
/** Called when the component gets destroyed */
|
740
462
|
onDestroy() {
|
741
463
|
this.__destroyed = true;
|
742
464
|
}
|
743
|
-
|
744
|
-
|
745
|
-
* Called when a field decorated with @validate() is modified.
|
746
|
-
* @param prop The name of the field that was changed
|
465
|
+
/** called when you decorate fields with the @validate() decorator
|
466
|
+
* @param prop the name of the field that was changed
|
747
467
|
*/
|
748
468
|
onValidate?(prop?: string): void;
|
749
469
|
|
750
|
-
/**
|
751
|
-
* Called when the context's pause state changes.
|
752
|
-
* @param isPaused Whether the context is currently paused
|
753
|
-
* @param wasPaused The previous pause state
|
754
|
-
*/
|
470
|
+
/** Called for all scripts when the context gets paused or unpaused */
|
755
471
|
onPausedChanged?(isPaused: boolean, wasPaused: boolean): void;
|
756
472
|
|
757
|
-
/**
|
758
|
-
* Called once at the beginning of the first frame after the component is enabled.
|
759
|
-
* Use for initialization that requires other components to be awake.
|
760
|
-
*/
|
473
|
+
/** called at the beginning of a frame (once per component) */
|
761
474
|
start?(): void;
|
762
|
-
|
763
|
-
/**
|
764
|
-
* Called at the beginning of each frame before regular updates.
|
765
|
-
* Use for logic that needs to run before standard update callbacks.
|
766
|
-
*/
|
475
|
+
/** first callback in a frame (called every frame when implemented) */
|
767
476
|
earlyUpdate?(): void;
|
768
|
-
|
769
|
-
/**
|
770
|
-
* Called once per frame during the main update loop.
|
771
|
-
* The primary location for frame-based game logic.
|
772
|
-
*/
|
477
|
+
/** regular callback in a frame (called every frame when implemented) */
|
773
478
|
update?(): void;
|
774
|
-
|
775
|
-
/**
|
776
|
-
* Called after all update functions have been called.
|
777
|
-
* Use for calculations that depend on other components being updated first.
|
778
|
-
*/
|
479
|
+
/** late callback in a frame (called every frame when implemented) */
|
779
480
|
lateUpdate?(): void;
|
780
|
-
|
781
|
-
/**
|
782
|
-
* Called immediately before the scene is rendered.
|
783
|
-
* @param frame Current XRFrame if in an XR session, null otherwise
|
784
|
-
*/
|
481
|
+
/** called before the scene gets rendered in the main update loop */
|
785
482
|
onBeforeRender?(frame: XRFrame | null): void;
|
786
|
-
|
787
|
-
/**
|
788
|
-
* Called after the scene has been rendered.
|
789
|
-
* Use for post-processing or UI updates that should happen after rendering
|
790
|
-
*/
|
483
|
+
/** called after the scene was rendered */
|
791
484
|
onAfterRender?(): void;
|
792
485
|
|
793
|
-
/**
|
794
|
-
* Called when this component's collider begins colliding with another collider.
|
795
|
-
* @param col Information about the collision that occurred
|
796
|
-
*/
|
797
486
|
onCollisionEnter?(col: Collision);
|
798
|
-
|
799
|
-
/**
|
800
|
-
* Called when this component's collider stops colliding with another collider.
|
801
|
-
* @param col Information about the collision that ended
|
802
|
-
*/
|
803
487
|
onCollisionExit?(col: Collision);
|
804
|
-
|
805
|
-
/**
|
806
|
-
* Called each frame while this component's collider is colliding with another collider
|
807
|
-
* @param col Information about the ongoing collision
|
808
|
-
*/
|
809
488
|
onCollisionStay?(col: Collision);
|
810
489
|
|
811
|
-
/**
|
812
|
-
* Called when this component's trigger collider is entered by another collider
|
813
|
-
* @param col The collider that entered this trigger
|
814
|
-
*/
|
815
490
|
onTriggerEnter?(col: ICollider);
|
816
|
-
|
817
|
-
/**
|
818
|
-
* Called each frame while another collider is inside this component's trigger collider
|
819
|
-
* @param col The collider that is inside this trigger
|
820
|
-
*/
|
821
491
|
onTriggerStay?(col: ICollider);
|
822
|
-
|
823
|
-
/**
|
824
|
-
* Called when another collider exits this component's trigger collider
|
825
|
-
* @param col The collider that exited this trigger
|
826
|
-
*/
|
827
492
|
onTriggerExit?(col: ICollider);
|
828
493
|
|
829
|
-
|
830
|
-
|
831
|
-
* @
|
832
|
-
|
833
|
-
*/
|
494
|
+
|
495
|
+
/** Optional callback, you can implement this to only get callbacks for VR or AR sessions if necessary.
|
496
|
+
* @returns true if the mode is supported (if false the mode is not supported by this component and it will not receive XR callbacks for this mode)
|
497
|
+
*/
|
834
498
|
supportsXR?(mode: XRSessionMode): boolean;
|
835
|
-
|
836
|
-
/**
|
837
|
-
* Called before an XR session is requested
|
838
|
-
* Use to modify session initialization parameters
|
839
|
-
* @param mode The XR session mode being requested
|
840
|
-
* @param args The session initialization parameters that can be modified
|
841
|
-
*/
|
499
|
+
/** Called before the XR session is requested. Use this callback if you want to modify the session init features */
|
842
500
|
onBeforeXR?(mode: XRSessionMode, args: XRSessionInit): void;
|
843
|
-
|
844
|
-
/**
|
845
|
-
* Called when this component joins an XR session or becomes active in a running session
|
846
|
-
* @param args Event data for the XR session
|
847
|
-
*/
|
501
|
+
/** Callback when this component joins a xr session (or becomes active in a running XR session) */
|
848
502
|
onEnterXR?(args: NeedleXREventArgs): void;
|
849
|
-
|
850
|
-
/**
|
851
|
-
* Called each frame while this component is active in an XR session
|
852
|
-
* @param args Event data for the current XR frame
|
853
|
-
*/
|
503
|
+
/** Callback when a xr session updates (while it is still active in XR session) */
|
854
504
|
onUpdateXR?(args: NeedleXREventArgs): void;
|
855
|
-
|
856
|
-
/**
|
857
|
-
* Called when this component exits an XR session or becomes inactive during a session
|
858
|
-
* @param args Event data for the XR session
|
859
|
-
*/
|
505
|
+
/** Callback when this component exists a xr session (or when it becomes inactive in a running XR session) */
|
860
506
|
onLeaveXR?(args: NeedleXREventArgs): void;
|
861
|
-
|
862
|
-
|
863
|
-
*
|
864
|
-
* in a session with existing controllers
|
865
|
-
* @param args Event data for the controller that was added
|
866
|
-
*/
|
507
|
+
/** Callback when a controller is connected/added while in a XR session
|
508
|
+
* OR when the component joins a running XR session that has already connected controllers
|
509
|
+
* OR when the component becomes active during a running XR session that has already connected controllers */
|
867
510
|
onXRControllerAdded?(args: NeedleXRControllerEventArgs): void;
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
* during a session with controllers
|
872
|
-
* @param args Event data for the controller that was removed
|
873
|
-
*/
|
511
|
+
/** callback when a controller is removed while in a XR session
|
512
|
+
* OR when the component becomes inactive during a running XR session
|
513
|
+
*/
|
874
514
|
onXRControllerRemoved?(args: NeedleXRControllerEventArgs): void;
|
875
515
|
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
*/
|
516
|
+
|
517
|
+
/* IPointerEventReceiver */
|
518
|
+
/* @inheritdoc */
|
880
519
|
onPointerEnter?(args: PointerEventData);
|
881
|
-
|
882
|
-
/**
|
883
|
-
* Called when a pointer moves while over this component's GameObject
|
884
|
-
* @param args Data about the pointer event
|
885
|
-
*/
|
886
520
|
onPointerMove?(args: PointerEventData);
|
887
|
-
|
888
|
-
/**
|
889
|
-
* Called when a pointer exits this component's GameObject
|
890
|
-
* @param args Data about the pointer event
|
891
|
-
*/
|
892
521
|
onPointerExit?(args: PointerEventData);
|
893
|
-
|
894
|
-
/**
|
895
|
-
* Called when a pointer button is pressed while over this component's GameObject
|
896
|
-
* @param args Data about the pointer event
|
897
|
-
*/
|
898
522
|
onPointerDown?(args: PointerEventData);
|
899
|
-
|
900
|
-
/**
|
901
|
-
* Called when a pointer button is released while over this component's GameObject
|
902
|
-
* @param args Data about the pointer event
|
903
|
-
*/
|
904
523
|
onPointerUp?(args: PointerEventData);
|
905
|
-
|
906
|
-
/**
|
907
|
-
* Called when a pointer completes a click interaction with this component's GameObject
|
908
|
-
* @param args Data about the pointer event
|
909
|
-
*/
|
910
524
|
onPointerClick?(args: PointerEventData);
|
911
525
|
|
912
|
-
|
913
|
-
|
914
|
-
*
|
915
|
-
*
|
916
|
-
*
|
917
|
-
*
|
918
|
-
* @param
|
919
|
-
* @
|
920
|
-
* @
|
921
|
-
*
|
526
|
+
|
527
|
+
/** starts a coroutine (javascript generator function)
|
528
|
+
* `yield` will wait for the next frame:
|
529
|
+
* - Use `yield WaitForSeconds(1)` to wait for 1 second.
|
530
|
+
* - Use `yield WaitForFrames(10)` to wait for 10 frames.
|
531
|
+
* - Use `yield new Promise(...)` to wait for a promise to resolve.
|
532
|
+
* @param routine generator function to start
|
533
|
+
* @param evt event to register the coroutine for (default: FrameEvent.Update). Note that all coroutine FrameEvent callbacks are invoked after the matching regular component callbacks. For example `FrameEvent.Update` will be called after regular component `update()` methods)
|
534
|
+
* @returns the generator function (use it to stop the coroutine with `stopCoroutine`)
|
535
|
+
* @example
|
922
536
|
* ```ts
|
923
|
-
*
|
924
|
-
* yield WaitForSeconds(1); // wait for 1 second
|
925
|
-
* yield WaitForFrames(10); // wait for 10 frames
|
926
|
-
* yield new Promise(resolve => setTimeout(resolve, 1000)); // wait for a promise to resolve
|
927
|
-
* }
|
928
|
-
* ```
|
929
|
-
* @example
|
930
|
-
* Coroutine that logs a message every 5 frames
|
931
|
-
* ```ts
|
932
|
-
* onEnable() {
|
933
|
-
* this.startCoroutine(this.myCoroutine());
|
934
|
-
* }
|
537
|
+
* onEnable() { this.startCoroutine(this.myCoroutine()); }
|
935
538
|
* private *myCoroutine() {
|
936
|
-
*
|
937
|
-
*
|
938
|
-
*
|
939
|
-
*
|
940
|
-
*
|
539
|
+
* while(this.activeAndEnabled) {
|
540
|
+
* console.log("Hello World", this.context.time.frame);
|
541
|
+
* // wait for 5 frames
|
542
|
+
* for(let i = 0; i < 5; i++) yield;
|
543
|
+
* }
|
941
544
|
* }
|
942
545
|
* ```
|
943
546
|
*/
|
944
547
|
startCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): Generator {
|
945
548
|
return this.context.registerCoroutineUpdate(this, routine, evt);
|
946
549
|
}
|
947
|
-
|
948
550
|
/**
|
949
|
-
*
|
950
|
-
* @param routine
|
951
|
-
* @param evt
|
551
|
+
* Stop a coroutine that was previously started with `startCoroutine`
|
552
|
+
* @param routine the routine to be stopped
|
553
|
+
* @param evt the frame event to unregister the routine from (default: FrameEvent.Update)
|
952
554
|
*/
|
953
555
|
stopCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): void {
|
954
556
|
this.context.unregisterCoroutineUpdate(routine, evt);
|
955
557
|
}
|
956
558
|
|
957
|
-
/**
|
958
|
-
* Checks if this component has been destroyed
|
959
|
-
* @returns True if the component or its GameObject has been destroyed
|
960
|
-
*/
|
559
|
+
/** @returns true if this component was destroyed (`this.destroy()`) or the whole object this component was part of */
|
961
560
|
public get destroyed(): boolean {
|
962
561
|
return this.__destroyed;
|
963
562
|
}
|
964
563
|
|
965
564
|
/**
|
966
|
-
* Destroys this component and removes it from
|
967
|
-
* After destruction, the component will no longer receive lifecycle callbacks
|
565
|
+
* Destroys this component (and removes it from the object)
|
968
566
|
*/
|
969
567
|
public destroy() {
|
970
568
|
if (this.__destroyed) return;
|
971
569
|
this.__internalDestroy();
|
972
570
|
}
|
973
571
|
|
572
|
+
|
573
|
+
|
974
574
|
/** @internal */
|
975
575
|
protected __didAwake: boolean = false;
|
976
576
|
|
@@ -989,6 +589,7 @@
|
|
989
589
|
/** @internal */
|
990
590
|
get __internalDidAwakeAndStart() { return this.__didAwake && this.__didStart; }
|
991
591
|
|
592
|
+
|
992
593
|
/** @internal */
|
993
594
|
constructor(init?: ComponentInit<Component>) {
|
994
595
|
this.__didAwake = false;
|
@@ -1010,11 +611,6 @@
|
|
1010
611
|
return this;
|
1011
612
|
}
|
1012
613
|
|
1013
|
-
/**
|
1014
|
-
* Initializes component properties from an initialization object
|
1015
|
-
* @param init Object with properties to copy to this component
|
1016
|
-
* @internal
|
1017
|
-
*/
|
1018
614
|
_internalInit(init?: ComponentInit<this>) {
|
1019
615
|
if (typeof init === "object") {
|
1020
616
|
for (const key of Object.keys(init)) {
|
@@ -1094,10 +690,7 @@
|
|
1094
690
|
destroyComponentInstance(this as any);
|
1095
691
|
}
|
1096
692
|
|
1097
|
-
|
1098
|
-
* Controls whether this component is enabled
|
1099
|
-
* Disabled components don't receive lifecycle callbacks
|
1100
|
-
*/
|
693
|
+
|
1101
694
|
get enabled(): boolean {
|
1102
695
|
return typeof this.__isEnabled === "boolean" ? this.__isEnabled : true; // if it has no enabled field it is always enabled
|
1103
696
|
}
|
@@ -1127,154 +720,85 @@
|
|
1127
720
|
}
|
1128
721
|
}
|
1129
722
|
|
1130
|
-
/**
|
1131
|
-
* Gets the position of this component's GameObject in world space
|
1132
|
-
*/
|
1133
723
|
get worldPosition(): Vector3 {
|
1134
724
|
return threeutils.getWorldPosition(this.gameObject);
|
1135
725
|
}
|
1136
726
|
|
1137
|
-
/**
|
1138
|
-
* Sets the position of this component's GameObject in world space
|
1139
|
-
* @param val The world position vector to set
|
1140
|
-
*/
|
1141
727
|
set worldPosition(val: Vector3) {
|
1142
728
|
threeutils.setWorldPosition(this.gameObject, val);
|
1143
729
|
}
|
1144
730
|
|
1145
|
-
/**
|
1146
|
-
* Sets the position of this component's GameObject in world space using individual coordinates
|
1147
|
-
* @param x X-coordinate in world space
|
1148
|
-
* @param y Y-coordinate in world space
|
1149
|
-
* @param z Z-coordinate in world space
|
1150
|
-
*/
|
1151
731
|
setWorldPosition(x: number, y: number, z: number) {
|
1152
732
|
threeutils.setWorldPositionXYZ(this.gameObject, x, y, z);
|
1153
733
|
}
|
1154
734
|
|
1155
|
-
|
1156
|
-
* Gets the rotation of this component's GameObject in world space as a quaternion
|
1157
|
-
*/
|
735
|
+
|
1158
736
|
get worldQuaternion(): Quaternion {
|
1159
737
|
return threeutils.getWorldQuaternion(this.gameObject);
|
1160
738
|
}
|
1161
|
-
|
1162
|
-
/**
|
1163
|
-
* Sets the rotation of this component's GameObject in world space using a quaternion
|
1164
|
-
* @param val The world rotation quaternion to set
|
1165
|
-
*/
|
1166
739
|
set worldQuaternion(val: Quaternion) {
|
1167
740
|
threeutils.setWorldQuaternion(this.gameObject, val);
|
1168
741
|
}
|
1169
|
-
|
1170
|
-
/**
|
1171
|
-
* Sets the rotation of this component's GameObject in world space using quaternion components
|
1172
|
-
* @param x X component of the quaternion
|
1173
|
-
* @param y Y component of the quaternion
|
1174
|
-
* @param z Z component of the quaternion
|
1175
|
-
* @param w W component of the quaternion
|
1176
|
-
*/
|
1177
742
|
setWorldQuaternion(x: number, y: number, z: number, w: number) {
|
1178
743
|
threeutils.setWorldQuaternionXYZW(this.gameObject, x, y, z, w);
|
1179
744
|
}
|
1180
745
|
|
1181
|
-
/
|
1182
|
-
* Gets the rotation of this component's GameObject in world space as Euler angles (in radians)
|
1183
|
-
*/
|
746
|
+
// world euler (in radians)
|
1184
747
|
get worldEuler(): Euler {
|
1185
748
|
return threeutils.getWorldEuler(this.gameObject);
|
1186
749
|
}
|
1187
750
|
|
1188
|
-
/
|
1189
|
-
* Sets the rotation of this component's GameObject in world space using Euler angles (in radians)
|
1190
|
-
* @param val The world rotation Euler angles to set
|
1191
|
-
*/
|
751
|
+
// world euler (in radians)
|
1192
752
|
set worldEuler(val: Euler) {
|
1193
753
|
threeutils.setWorldEuler(this.gameObject, val);
|
1194
754
|
}
|
1195
755
|
|
1196
|
-
/
|
1197
|
-
* Gets the rotation of this component's GameObject in world space as Euler angles (in degrees)
|
1198
|
-
*/
|
756
|
+
// returns rotation in degrees
|
1199
757
|
get worldRotation(): Vector3 {
|
1200
|
-
return this.gameObject.worldRotation;
|
758
|
+
return this.gameObject.worldRotation;;
|
1201
759
|
}
|
1202
760
|
|
1203
|
-
/**
|
1204
|
-
* Sets the rotation of this component's GameObject in world space using Euler angles (in degrees)
|
1205
|
-
* @param val The world rotation vector to set (in degrees)
|
1206
|
-
*/
|
1207
761
|
set worldRotation(val: Vector3) {
|
1208
762
|
this.setWorldRotation(val.x, val.y, val.z, true);
|
1209
763
|
}
|
1210
764
|
|
1211
|
-
/**
|
1212
|
-
* Sets the rotation of this component's GameObject in world space using individual Euler angles
|
1213
|
-
* @param x X-axis rotation
|
1214
|
-
* @param y Y-axis rotation
|
1215
|
-
* @param z Z-axis rotation
|
1216
|
-
* @param degrees Whether the values are in degrees (true) or radians (false)
|
1217
|
-
*/
|
1218
765
|
setWorldRotation(x: number, y: number, z: number, degrees: boolean = true) {
|
1219
766
|
threeutils.setWorldRotationXYZ(this.gameObject, x, y, z, degrees);
|
1220
767
|
}
|
1221
768
|
|
1222
769
|
private static _forward: Vector3 = new Vector3();
|
1223
|
-
/**
|
1224
|
-
* Gets the forward direction vector (0,0,-1) of this component's GameObject in world space
|
1225
|
-
*/
|
770
|
+
/** Forward (0,0,-1) vector in world space */
|
1226
771
|
public get forward(): Vector3 {
|
1227
772
|
return Component._forward.set(0, 0, -1).applyQuaternion(this.worldQuaternion);
|
1228
773
|
}
|
1229
774
|
private static _right: Vector3 = new Vector3();
|
1230
|
-
/**
|
1231
|
-
* Gets the right direction vector (1,0,0) of this component's GameObject in world space
|
1232
|
-
*/
|
775
|
+
/** Right (1,0,0) vector in world space */
|
1233
776
|
public get right(): Vector3 {
|
1234
777
|
return Component._right.set(1, 0, 0).applyQuaternion(this.worldQuaternion);
|
1235
778
|
}
|
1236
779
|
private static _up: Vector3 = new Vector3();
|
1237
|
-
/**
|
1238
|
-
* Gets the up direction vector (0,1,0) of this component's GameObject in world space
|
1239
|
-
*/
|
780
|
+
/** Up (0,1,0) vector in world space */
|
1240
781
|
public get up(): Vector3 {
|
1241
782
|
return Component._up.set(0, 1, 0).applyQuaternion(this.worldQuaternion);
|
1242
783
|
}
|
1243
784
|
|
785
|
+
|
786
|
+
|
1244
787
|
// EventTarget implementation:
|
1245
788
|
|
1246
|
-
/**
|
1247
|
-
* Storage for event listeners registered to this component
|
1248
|
-
* @private
|
1249
|
-
*/
|
1250
789
|
private _eventListeners = new Map<string, EventListener[]>();
|
1251
790
|
|
1252
|
-
/**
|
1253
|
-
* Registers an event listener for the specified event type
|
1254
|
-
* @param type The event type to listen for
|
1255
|
-
* @param listener The callback function to execute when the event occurs
|
1256
|
-
*/
|
1257
791
|
addEventListener<T extends Event>(type: string, listener: (evt: T) => any) {
|
1258
792
|
this._eventListeners[type] = this._eventListeners[type] || [];
|
1259
793
|
this._eventListeners[type].push(listener);
|
1260
794
|
}
|
1261
795
|
|
1262
|
-
/**
|
1263
|
-
* Removes a previously registered event listener
|
1264
|
-
* @param type The event type the listener was registered for
|
1265
|
-
* @param listener The callback function to remove
|
1266
|
-
*/
|
1267
796
|
removeEventListener<T extends Event>(type: string, listener: (arg: T) => any) {
|
1268
797
|
if (!this._eventListeners[type]) return;
|
1269
798
|
const index = this._eventListeners[type].indexOf(listener);
|
1270
799
|
if (index >= 0) this._eventListeners[type].splice(index, 1);
|
1271
800
|
}
|
1272
801
|
|
1273
|
-
/**
|
1274
|
-
* Dispatches an event to all registered listeners
|
1275
|
-
* @param evt The event object to dispatch
|
1276
|
-
* @returns Always returns false (standard implementation of EventTarget)
|
1277
|
-
*/
|
1278
802
|
dispatchEvent(evt: Event): boolean {
|
1279
803
|
if (!evt || !this._eventListeners[evt.type]) return false;
|
1280
804
|
const listeners = this._eventListeners[evt.type];
|
@@ -18,10 +18,8 @@
|
|
18
18
|
import type { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
19
19
|
import { ObjectRaycaster } from "./ui/Raycaster.js";
|
20
20
|
|
21
|
-
/** Enable debug visualization and logging for DragControls by using the URL parameter `?debugdrag`. */
|
22
21
|
const debug = getParam("debugdrag");
|
23
22
|
|
24
|
-
/** Buffer to store currently active DragControls components */
|
25
23
|
const dragControlsBuffer: DragControls[] = [];
|
26
24
|
|
27
25
|
/**
|
@@ -36,7 +34,7 @@
|
|
36
34
|
HitNormal = 2,
|
37
35
|
/** Combination of XZ and Screen based on the viewing angle. Low angles result in Screen dragging and higher angles in XZ dragging. */
|
38
36
|
DynamicViewAngle = 3,
|
39
|
-
/** The drag plane is
|
37
|
+
/** The drag plane is adjusted dynamically while dragging. */
|
40
38
|
SnapToSurfaces = 4,
|
41
39
|
/** Don't allow dragging the object */
|
42
40
|
None = 5,
|
@@ -44,24 +42,18 @@
|
|
44
42
|
|
45
43
|
/**
|
46
44
|
* DragControls allows you to drag objects around in the scene. It can be used to move objects in 2D (screen space) or 3D (world space).
|
47
|
-
* Debug mode can be enabled with the URL parameter `?debugdrag`, which shows visual helpers and logs drag operations.
|
48
|
-
*
|
49
45
|
* @category Interactivity
|
50
46
|
* @group Components
|
51
47
|
*/
|
52
48
|
export class DragControls extends Behaviour implements IPointerEventHandler {
|
53
49
|
|
54
50
|
/**
|
55
|
-
* Checks if any DragControls component is currently active with selected objects
|
56
51
|
* @returns True if any DragControls component is currently active
|
57
52
|
*/
|
58
53
|
public static get HasAnySelected(): boolean { return this._active > 0; }
|
59
54
|
private static _active: number = 0;
|
60
55
|
|
61
|
-
/**
|
62
|
-
* Retrieves a list of all DragControl components that are currently dragging objects.
|
63
|
-
* @returns Array of currently active DragControls components
|
64
|
-
*/
|
56
|
+
/** @returns a list of DragControl components that are currently active */
|
65
57
|
public static get CurrentlySelected() {
|
66
58
|
dragControlsBuffer.length = 0;
|
67
59
|
for (const dc of this._instances) {
|
@@ -71,71 +63,51 @@
|
|
71
63
|
}
|
72
64
|
return dragControlsBuffer;
|
73
65
|
}
|
74
|
-
/**
|
66
|
+
/** Currently active and enabled DragControls components */
|
75
67
|
private static _instances: DragControls[] = [];
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
69
|
+
|
70
|
+
|
71
|
+
// dragPlane (floor, object, view)
|
72
|
+
// snap to surface (snap orientation?)
|
73
|
+
// two-handed drag (scale, rotate, move)
|
74
|
+
// keep upright (no tilt)
|
75
|
+
|
76
|
+
/** How and where the object is dragged along. */
|
81
77
|
@serializable()
|
82
78
|
public dragMode: DragMode = DragMode.DynamicViewAngle;
|
83
79
|
|
84
|
-
/**
|
85
|
-
* Snaps dragged objects to a 3D grid with the specified resolution.
|
86
|
-
* Set to 0 to disable snapping.
|
87
|
-
*/
|
80
|
+
/** Snap dragged objects to a XYZ grid – 0 means: no snapping. */
|
88
81
|
@serializable()
|
89
82
|
public snapGridResolution: number = 0.0;
|
90
83
|
|
91
|
-
/**
|
92
|
-
* When true, maintains the original rotation of the dragged object while moving it.
|
93
|
-
* When false, allows the object to rotate freely during dragging.
|
94
|
-
*/
|
84
|
+
/** Keep the original rotation of the dragged object. */
|
95
85
|
@serializable()
|
96
86
|
public keepRotation: boolean = true;
|
97
87
|
|
98
|
-
/**
|
99
|
-
* Determines how and where the object is dragged along while dragging in XR.
|
100
|
-
* Uses a separate setting from regular drag mode for better XR interaction.
|
101
|
-
*/
|
88
|
+
/** How and where the object is dragged along while dragging in XR. */
|
102
89
|
@serializable()
|
103
90
|
public xrDragMode: DragMode = DragMode.Attached;
|
104
91
|
|
105
|
-
/**
|
106
|
-
* When true, maintains the original rotation of the dragged object during XR dragging.
|
107
|
-
* When false, allows the object to rotate freely during XR dragging.
|
108
|
-
*/
|
92
|
+
/** Keep the original rotation of the dragged object while dragging in XR. */
|
109
93
|
@serializable()
|
110
94
|
public xrKeepRotation: boolean = false;
|
111
95
|
|
112
|
-
/**
|
113
|
-
* Multiplier that affects how quickly objects move closer or further away when dragging in XR.
|
114
|
-
* Higher values make distance changes more pronounced.
|
115
|
-
* This is similar to mouse acceleration on a screen.
|
116
|
-
*/
|
96
|
+
/** Accelerate dragging objects closer / further away when in XR */
|
117
97
|
@serializable()
|
118
98
|
public xrDistanceDragFactor: number = 1;
|
119
99
|
|
120
|
-
/**
|
121
|
-
* When enabled, draws a visual line from the dragged object downwards to the next raycast hit,
|
122
|
-
* providing visual feedback about the object's position relative to surfaces below it.
|
123
|
-
*/
|
100
|
+
/** When enabled, draws a line from the dragged object downwards to the next raycast hit. */
|
124
101
|
@serializable()
|
125
102
|
public showGizmo: boolean = false;
|
126
103
|
|
127
|
-
/**
|
128
|
-
* Returns the object currently being dragged by this DragControls component, if any.
|
129
|
-
* @returns The object being dragged or null if no object is currently dragged
|
130
|
-
*/
|
104
|
+
/** The currently dragged object (if any) */
|
131
105
|
get draggedObject() {
|
132
106
|
return this._targetObject;
|
133
107
|
}
|
134
108
|
|
135
109
|
/**
|
136
|
-
*
|
137
|
-
* This can be used to change the target during a drag operation.
|
138
|
-
* @param obj The new object to drag, or null to stop dragging
|
110
|
+
* Use to update the object that is being dragged by the DragControls
|
139
111
|
*/
|
140
112
|
setTargetObject(obj: Object3D | null) {
|
141
113
|
this._targetObject = obj;
|
@@ -161,8 +133,8 @@
|
|
161
133
|
this._rigidbody[wasKinematicKey] = false;
|
162
134
|
}
|
163
135
|
}
|
136
|
+
|
164
137
|
}
|
165
|
-
|
166
138
|
private _rigidbody: Rigidbody | null = null;
|
167
139
|
|
168
140
|
// future:
|
@@ -210,20 +182,11 @@
|
|
210
182
|
DragControls._instances = DragControls._instances.filter(i => i !== this);
|
211
183
|
}
|
212
184
|
|
213
|
-
/**
|
214
|
-
* Checks if editing is allowed for the current networking connection.
|
215
|
-
* @param _obj Optional object to check edit permissions for
|
216
|
-
* @returns True if editing is allowed
|
217
|
-
*/
|
218
185
|
private allowEdit(_obj: Object3D | null = null) {
|
219
186
|
return this.context.connection.allowEditing;
|
220
187
|
}
|
221
188
|
|
222
|
-
/**
|
223
|
-
* Handles pointer enter events. Sets the cursor style and tracks the hovered object.
|
224
|
-
* @param evt Pointer event data containing information about the interaction
|
225
|
-
* @internal
|
226
|
-
*/
|
189
|
+
/** @internal */
|
227
190
|
onPointerEnter?(evt: PointerEventData) {
|
228
191
|
if (!this.allowEdit(this.gameObject)) return;
|
229
192
|
if (evt.mode !== "screen") return;
|
@@ -239,20 +202,12 @@
|
|
239
202
|
this.context.domElement.style.cursor = 'pointer';
|
240
203
|
}
|
241
204
|
|
242
|
-
/**
|
243
|
-
* Handles pointer movement events. Marks the event as used if dragging is active.
|
244
|
-
* @param args Pointer event data containing information about the movement
|
245
|
-
* @internal
|
246
|
-
*/
|
205
|
+
/** @internal */
|
247
206
|
onPointerMove?(args: PointerEventData) {
|
248
207
|
if (this._isDragging || this._potentialDragStartEvt !== null) args.use();
|
249
208
|
}
|
250
209
|
|
251
|
-
/**
|
252
|
-
* Handles pointer exit events. Resets the cursor style when the pointer leaves a draggable object.
|
253
|
-
* @param evt Pointer event data containing information about the interaction
|
254
|
-
* @internal
|
255
|
-
*/
|
210
|
+
/** @internal */
|
256
211
|
onPointerExit?(evt: PointerEventData) {
|
257
212
|
if (!this.allowEdit(this.gameObject)) return;
|
258
213
|
if (evt.mode !== "screen") return;
|
@@ -260,11 +215,7 @@
|
|
260
215
|
this.context.domElement.style.cursor = 'auto';
|
261
216
|
}
|
262
217
|
|
263
|
-
/**
|
264
|
-
* Handles pointer down events. Initiates the potential drag operation if conditions are met.
|
265
|
-
* @param args Pointer event data containing information about the interaction
|
266
|
-
* @internal
|
267
|
-
*/
|
218
|
+
/** @internal */
|
268
219
|
onPointerDown(args: PointerEventData) {
|
269
220
|
if (!this.allowEdit(this.gameObject)) return;
|
270
221
|
if (args.used) return;
|
@@ -311,11 +262,7 @@
|
|
311
262
|
}
|
312
263
|
}
|
313
264
|
|
314
|
-
/**
|
315
|
-
* Handles pointer up events. Finalizes or cancels the drag operation.
|
316
|
-
* @param args Pointer event data containing information about the interaction
|
317
|
-
* @internal
|
318
|
-
*/
|
265
|
+
/** @internal */
|
319
266
|
onPointerUp(args: PointerEventData) {
|
320
267
|
if (debug) Gizmos.DrawLabel(args.point ?? this.gameObject.worldPosition, "POINTERUP:" + args.pointerId + ", " + args.button, .03, 3);
|
321
268
|
if (!this.allowEdit(this.gameObject)) return;
|
@@ -346,12 +293,9 @@
|
|
346
293
|
}
|
347
294
|
}
|
348
295
|
|
349
|
-
/**
|
350
|
-
* Updates the drag operation every frame. Processes pointer movement, accumulates drag distance
|
351
|
-
* and triggers drag start once there's enough movement.
|
352
|
-
* @internal
|
353
|
-
*/
|
296
|
+
/** @internal */
|
354
297
|
update(): void {
|
298
|
+
|
355
299
|
for (const handler of this._dragHandlers.values()) {
|
356
300
|
if (handler.collectMovementInfo) handler.collectMovementInfo();
|
357
301
|
// TODO this doesn't make sense, we should instead just use the max here
|
@@ -380,12 +324,7 @@
|
|
380
324
|
this.onAnyDragUpdate();
|
381
325
|
}
|
382
326
|
|
383
|
-
/**
|
384
|
-
* Called when the first pointer starts dragging on this object.
|
385
|
-
* Sets up network synchronization and marks rigidbodies for dragging.
|
386
|
-
* Not called for subsequent pointers on the same object.
|
387
|
-
* @param evt Pointer event data that initiated the drag
|
388
|
-
*/
|
327
|
+
/** Called when the first pointer starts dragging on this object. Not called for subsequent pointers on the same object. */
|
389
328
|
private onFirstDragStart(evt: PointerEventData) {
|
390
329
|
if (!evt || !evt.object) return;
|
391
330
|
|
@@ -417,10 +356,7 @@
|
|
417
356
|
this._draggingRigidbodies.push(...rbs);
|
418
357
|
}
|
419
358
|
|
420
|
-
/**
|
421
|
-
* Called each frame as long as any pointer is dragging this object.
|
422
|
-
* Updates visuals and keeps rigidbodies awake during the drag.
|
423
|
-
*/
|
359
|
+
/** Called each frame as long as any pointer is dragging this object. */
|
424
360
|
private onAnyDragUpdate() {
|
425
361
|
if (!this._dragHelper) return;
|
426
362
|
this._dragHelper.showGizmo = this.showGizmo;
|
@@ -437,11 +373,7 @@
|
|
437
373
|
InstancingUtil.markDirty(object);
|
438
374
|
}
|
439
375
|
|
440
|
-
/**
|
441
|
-
* Called when the last pointer has been removed from this object.
|
442
|
-
* Cleans up drag state and applies final velocities to rigidbodies.
|
443
|
-
* @param evt Pointer event data for the last pointer that was lifted
|
444
|
-
*/
|
376
|
+
/** Called when the last pointer has been removed from this object. */
|
445
377
|
private onLastDragEnd(evt: PointerEventData | null) {
|
446
378
|
if (!this || !this._isDragging) return;
|
447
379
|
this._isDragging = false;
|
@@ -467,10 +399,7 @@
|
|
467
399
|
}
|
468
400
|
}
|
469
401
|
|
470
|
-
/**
|
471
|
-
* Common interface for pointer handlers (single touch and multi touch).
|
472
|
-
* Defines methods for tracking movement and managing target objects during drag operations.
|
473
|
-
*/
|
402
|
+
/** Common interface for pointer handlers (single touch and multi touch) */
|
474
403
|
interface IDragHandler {
|
475
404
|
/** Used to determine if a drag has happened for this handler */
|
476
405
|
getTotalMovement?(): Vector3;
|
@@ -485,10 +414,7 @@
|
|
485
414
|
onDragUpdate?(numberOfPointers: number): void;
|
486
415
|
}
|
487
416
|
|
488
|
-
/**
|
489
|
-
* Handles two touch points affecting one object.
|
490
|
-
* Enables multi-touch interactions that allow movement, scaling, and rotation of objects.
|
491
|
-
*/
|
417
|
+
/** Handles two touch points affecting one object. Allows movement, scale and rotation of objects. */
|
492
418
|
class MultiTouchDragHandler implements IDragHandler {
|
493
419
|
|
494
420
|
handlerA: DragPointerHandler;
|
@@ -734,27 +660,15 @@
|
|
734
660
|
}
|
735
661
|
|
736
662
|
|
737
|
-
/**
|
738
|
-
*
|
739
|
-
* DragPointerHandlers manage determining if a drag operation has started, tracking pointer movement,
|
740
|
-
* and controlling object translation based on the drag mode.
|
663
|
+
/** Handles a single pointer on an object. DragPointerHandlers are created on pointer enter,
|
664
|
+
* help with determining if there's actually a drag, and then perform operations based on spatial pointer data.
|
741
665
|
*/
|
742
666
|
class DragPointerHandler implements IDragHandler {
|
743
667
|
|
744
|
-
/**
|
745
|
-
*
|
746
|
-
* Used for determining if enough motion has occurred to start a drag.
|
747
|
-
*/
|
668
|
+
/** Absolute movement of the pointer. Used for determining if a motion/drag is happening.
|
669
|
+
* This is in world units, so very small for screens (near-plane space change) */
|
748
670
|
getTotalMovement(): Vector3 { return this._totalMovement; }
|
749
|
-
|
750
|
-
/**
|
751
|
-
* Returns the object that follows the pointer during dragging operations.
|
752
|
-
*/
|
753
671
|
get followObject(): GameObject { return this._followObject; }
|
754
|
-
|
755
|
-
/**
|
756
|
-
* Returns the point where the pointer initially hit the object in local space.
|
757
|
-
*/
|
758
672
|
get hitPointInLocalSpace(): Vector3 { return this._hitPointInLocalSpace; }
|
759
673
|
|
760
674
|
private context: Context;
|
@@ -1335,28 +1249,18 @@
|
|
1335
1249
|
}
|
1336
1250
|
}
|
1337
1251
|
|
1338
|
-
/**
|
1339
|
-
*
|
1340
|
-
* Shows where objects will be placed and their relation to surfaces below them.
|
1252
|
+
/** Currently does _only_ provide visuals support for DragControls operations.
|
1253
|
+
* Previously it also provided the actual drag functionality, but that has been moved to DragControls for now.
|
1341
1254
|
*/
|
1342
1255
|
class LegacyDragVisualsHelper {
|
1343
1256
|
|
1344
|
-
/** Controls whether visual helpers like lines and markers are displayed */
|
1345
1257
|
showGizmo: boolean = true;
|
1346
|
-
|
1347
|
-
/** When true, drag plane alignment changes based on view angle */
|
1348
1258
|
useViewAngle: boolean = true;
|
1349
1259
|
|
1350
|
-
/**
|
1351
|
-
* Checks if there is a currently selected object being visualized
|
1352
|
-
*/
|
1353
1260
|
public get hasSelected(): boolean {
|
1354
1261
|
return this._selected !== null && this._selected !== undefined;
|
1355
1262
|
}
|
1356
1263
|
|
1357
|
-
/**
|
1358
|
-
* Returns the currently selected object being visualized, if any
|
1359
|
-
*/
|
1360
1264
|
public get selected(): Object3D | null {
|
1361
1265
|
return this._selected;
|
1362
1266
|
}
|
@@ -19,104 +19,63 @@
|
|
19
19
|
import { Behaviour } from "./Component.js";
|
20
20
|
import { EventList } from "./EventList.js";
|
21
21
|
|
22
|
-
/**
|
23
|
-
* Debug mode can be enabled with the URL parameter `?debugdroplistener`, which
|
24
|
-
* logs additional information during drag and drop events and visualizes hit points.
|
25
|
-
*/
|
26
22
|
const debug = getParam("debugdroplistener");
|
27
23
|
|
28
|
-
/**
|
29
|
-
* Events dispatched by the DropListener component
|
30
|
-
* @enum {string}
|
31
|
-
*/
|
32
24
|
export enum DropListenerEvents {
|
33
25
|
/**
|
34
|
-
* Dispatched when a file is dropped into the scene. The detail of the event is the
|
35
|
-
* The event is called once for each dropped file.
|
26
|
+
* Dispatched when a file is dropped into the scene. The detail of the event is the file that was dropped.
|
36
27
|
*/
|
37
28
|
FileDropped = "file-dropped",
|
38
29
|
/**
|
39
|
-
* Dispatched when a new object is added to the scene. The detail of the event
|
30
|
+
* Dispatched when a new object is added to the scene. The detail of the event is the glTF that was added.
|
40
31
|
*/
|
41
32
|
ObjectAdded = "object-added",
|
42
33
|
}
|
43
34
|
|
44
|
-
/**
|
45
|
-
* Context information for a drop operation
|
46
|
-
*/
|
47
35
|
declare type DropContext = {
|
48
|
-
/** Position where the file was dropped in screen coordinates */
|
49
36
|
screenposition: Vector2;
|
50
|
-
/** URL of the dropped content, if applicable */
|
51
37
|
url?: string,
|
52
|
-
/** File object of the dropped content, if applicable */
|
53
38
|
file?: File;
|
54
|
-
/** 3D position where the content should be placed */
|
55
39
|
point?: Vec3;
|
56
|
-
/** Size dimensions for the content */
|
57
40
|
size?: Vec3;
|
58
41
|
}
|
59
42
|
|
60
43
|
|
61
|
-
/**
|
62
|
-
* Network event arguments passed between clients when using the DropListener with networking
|
63
|
-
*/
|
44
|
+
/** Networking event arguments for the DropListener component */
|
64
45
|
export declare type DropListenerNetworkEventArguments = {
|
65
|
-
/** Unique identifier of the sender */
|
66
46
|
guid: string,
|
67
|
-
/** Name of the dropped object */
|
68
47
|
name: string,
|
69
|
-
/** URL or array of URLs to the dropped content */
|
70
48
|
url: string | string[],
|
71
49
|
/** Worldspace point where the object was placed in the scene */
|
72
50
|
point: Vec3;
|
73
51
|
/** Bounding box size */
|
74
52
|
size: Vec3;
|
75
|
-
/** MD5 hash of the content for verification */
|
76
53
|
contentMD5: string;
|
77
54
|
}
|
78
55
|
|
79
|
-
/**
|
80
|
-
* Arguments provided to handlers when an object is dropped or added to the scene
|
81
|
-
*/
|
82
56
|
export declare type DropListenerOnDropArguments = {
|
83
|
-
/** The DropListener component that processed the drop event */
|
84
57
|
sender: DropListener,
|
85
|
-
/**
|
58
|
+
/** the root object added to the scene */
|
86
59
|
object: Object3D,
|
87
|
-
/** The
|
60
|
+
/** The whole dropped model */
|
88
61
|
model: Model,
|
89
|
-
/** MD5 hash of the content for verification */
|
90
62
|
contentMD5: string;
|
91
|
-
/** The original dropped URL or File object */
|
92
63
|
dropped: URL | File | undefined;
|
93
64
|
}
|
94
65
|
|
95
|
-
/**
|
96
|
-
* CustomEvent dispatched when an object is added to the scene via the DropListener
|
97
|
-
*/
|
66
|
+
/** Dispatched when an object is dropped/changed */
|
98
67
|
class DropListenerAddedEvent<T extends DropListenerOnDropArguments> extends CustomEvent<T> {
|
99
|
-
/**
|
100
|
-
* Creates a new added event with the provided details
|
101
|
-
* @param detail Information about the added object
|
102
|
-
*/
|
103
68
|
constructor(detail: T) {
|
104
69
|
super(DropListenerEvents.ObjectAdded, { detail });
|
105
70
|
}
|
106
71
|
}
|
107
72
|
|
108
|
-
/**
|
109
|
-
* Key name used for blob storage parameters
|
110
|
-
*/
|
111
73
|
const blobKeyName = "blob";
|
112
74
|
|
113
75
|
/** The DropListener component is used to listen for drag and drop events in the browser and add the dropped files to the scene
|
114
76
|
* It can be used to allow users to drag and drop glTF files into the scene to add new objects.
|
115
77
|
*
|
116
|
-
*
|
117
|
-
* Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
|
118
|
-
*
|
119
|
-
* The following events are dispatched by the DropListener:
|
78
|
+
* ## Events
|
120
79
|
* - **object-added** - dispatched when a new object is added to the scene
|
121
80
|
* - **file-dropped** - dispatched when a file is dropped into the scene
|
122
81
|
*
|
@@ -144,46 +103,42 @@
|
|
144
103
|
export class DropListener extends Behaviour {
|
145
104
|
|
146
105
|
/**
|
147
|
-
* When enabled
|
148
|
-
* When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
|
106
|
+
* When enabled the DropListener will automatically network dropped files to other clients.
|
149
107
|
*/
|
150
108
|
@serializable()
|
151
109
|
useNetworking: boolean = true;
|
152
110
|
|
153
111
|
/**
|
154
|
-
* When assigned
|
155
|
-
* This allows creating designated drop zones in your scene.
|
112
|
+
* When assigned the Droplistener will only accept files that are dropped on this object.
|
156
113
|
*/
|
157
114
|
@serializable(Object3D)
|
158
115
|
dropArea?: Object3D;
|
159
116
|
|
160
117
|
/**
|
161
|
-
* When enabled
|
162
|
-
* Useful for ensuring dropped models appear at an appropriate scale.
|
118
|
+
* When enabled the object will be fitted into a volume. Use {@link fitVolumeSize} to specify the volume size.
|
163
119
|
* @default false
|
164
120
|
*/
|
165
121
|
@serializable()
|
166
122
|
fitIntoVolume: boolean = false;
|
167
123
|
|
168
124
|
/**
|
169
|
-
*
|
170
|
-
* Only used when fitIntoVolume is enabled.
|
125
|
+
* The volume size will be used to fit the object into the volume. Use {@link fitIntoVolume} to enable this feature.
|
171
126
|
*/
|
172
127
|
@serializable(Vector3)
|
173
128
|
fitVolumeSize = new Vector3(1, 1, 1);
|
174
129
|
|
175
|
-
/**
|
176
|
-
* When enabled, dropped objects will be positioned at the point where the cursor hit the scene.
|
177
|
-
* When disabled, objects will be placed at the origin of the DropListener.
|
130
|
+
/** When enabled the object will be placed at the drop position (under the cursor)
|
178
131
|
* @default true
|
179
132
|
*/
|
180
133
|
@serializable()
|
181
134
|
placeAtHitPosition: boolean = true;
|
182
135
|
|
136
|
+
|
183
137
|
/**
|
184
|
-
*
|
185
|
-
*
|
138
|
+
* Invoked after a file has been **added** to the scene.
|
139
|
+
* Arguments are {@link DropListenerOnDropArguments}
|
186
140
|
* @event object-added
|
141
|
+
* @param {DropListenerOnDropArguments} evt
|
187
142
|
* @example
|
188
143
|
* ```typescript
|
189
144
|
* dropListener.onDropped.addEventListener((evt) => {
|
@@ -223,10 +178,6 @@
|
|
223
178
|
this.removePreviouslyAddedObjects(false);
|
224
179
|
}
|
225
180
|
|
226
|
-
/**
|
227
|
-
* Handles network events received from other clients containing information about dropped objects
|
228
|
-
* @param evt Network event data containing object information, position, and content URL
|
229
|
-
*/
|
230
181
|
private onNetworkEvent = (evt: DropListenerNetworkEventArguments) => {
|
231
182
|
if (!this.useNetworking) {
|
232
183
|
if (debug) console.debug("[DropListener] Ignoring networked event because networking is disabled", evt);
|
@@ -248,11 +199,6 @@
|
|
248
199
|
}
|
249
200
|
}
|
250
201
|
|
251
|
-
/**
|
252
|
-
* Handles clipboard paste events and processes them as potential URL drops
|
253
|
-
* Only URLs are processed by this handler, and only when editing is allowed
|
254
|
-
* @param evt The paste event
|
255
|
-
*/
|
256
202
|
private handlePaste = (evt: Event) => {
|
257
203
|
if (this.context.connection.allowEditing === false) return;
|
258
204
|
if (evt.defaultPrevented) return;
|
@@ -271,22 +217,12 @@
|
|
271
217
|
.catch(console.warn);
|
272
218
|
}
|
273
219
|
|
274
|
-
/**
|
275
|
-
* Handles drag events over the renderer's canvas
|
276
|
-
* Prevents default behavior to enable drop events
|
277
|
-
* @param evt The drag event
|
278
|
-
*/
|
279
220
|
private onDrag = (evt: DragEvent) => {
|
280
221
|
if (this.context.connection.allowEditing === false) return;
|
281
222
|
// necessary to get drop event
|
282
223
|
evt.preventDefault();
|
283
224
|
}
|
284
225
|
|
285
|
-
/**
|
286
|
-
* Processes drop events to add files to the scene
|
287
|
-
* Handles both file drops and text/URL drops
|
288
|
-
* @param evt The drop event
|
289
|
-
*/
|
290
226
|
private onDrop = async (evt: DragEvent) => {
|
291
227
|
if (this.context.connection.allowEditing === false) return;
|
292
228
|
|
@@ -330,14 +266,6 @@
|
|
330
266
|
}
|
331
267
|
}
|
332
268
|
|
333
|
-
/**
|
334
|
-
* Processes a dropped or pasted URL and tries to load it as a 3D model
|
335
|
-
* Handles special cases like GitHub URLs and Polyhaven asset URLs
|
336
|
-
* @param url The URL to process
|
337
|
-
* @param ctx Context information about where the drop occurred
|
338
|
-
* @param isRemote Whether this URL was shared from a remote client
|
339
|
-
* @returns The added object or null if loading failed
|
340
|
-
*/
|
341
269
|
private async addFromUrl(url: string, ctx: DropContext, isRemote: boolean) {
|
342
270
|
if (debug) console.log("dropped url", url);
|
343
271
|
|
@@ -387,12 +315,6 @@
|
|
387
315
|
|
388
316
|
private _abort: AbortController | null = null;
|
389
317
|
|
390
|
-
/**
|
391
|
-
* Processes dropped files, loads them as 3D models, and handles networking if enabled
|
392
|
-
* Creates an abort controller to cancel previous uploads if new files are dropped
|
393
|
-
* @param fileList Array of dropped files
|
394
|
-
* @param ctx Context information about where the drop occurred
|
395
|
-
*/
|
396
318
|
private async addDroppedFiles(fileList: Array<File>, ctx: DropContext) {
|
397
319
|
if (debug) console.log("Add files", fileList)
|
398
320
|
if (!Array.isArray(fileList)) return;
|
@@ -439,10 +361,7 @@
|
|
439
361
|
private readonly _addedObjects = new Array<Object3D>();
|
440
362
|
private readonly _addedModels = new Array<Model>();
|
441
363
|
|
442
|
-
/**
|
443
|
-
* Removes all previously added objects from the scene
|
444
|
-
* @param doDestroy When true, destroys the objects; when false, just clears the references
|
445
|
-
*/
|
364
|
+
/** Removes all previously added objects from the scene and removes those object references */
|
446
365
|
private removePreviouslyAddedObjects(doDestroy: boolean = true) {
|
447
366
|
if (doDestroy) {
|
448
367
|
for (const prev of this._addedObjects) {
|
@@ -456,13 +375,7 @@
|
|
456
375
|
}
|
457
376
|
|
458
377
|
/**
|
459
|
-
* Adds
|
460
|
-
* Handles placement based on component settings and raycasting.
|
461
|
-
* If {@link fitIntoVolume} is enabled, the object will be scaled to fit within the volume defined by {@link fitVolumeSize}.
|
462
|
-
* @param data The loaded model data and content hash
|
463
|
-
* @param ctx Context information about where the drop occurred
|
464
|
-
* @param isRemote Whether this object was shared from a remote client
|
465
|
-
* @returns The added object or null if adding failed
|
378
|
+
* Adds the object to the scene and fits it into the volume if {@link fitIntoVolume} is enabled.
|
466
379
|
*/
|
467
380
|
private addObject(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
|
468
381
|
|
@@ -531,13 +444,6 @@
|
|
531
444
|
return obj;
|
532
445
|
}
|
533
446
|
|
534
|
-
/**
|
535
|
-
* Sends a network event to other clients about a dropped object
|
536
|
-
* Only triggered when networking is enabled and the connection is established
|
537
|
-
* @param url The URL to the content that was dropped
|
538
|
-
* @param obj The object that was added to the scene
|
539
|
-
* @param contentmd5 The content hash for verification
|
540
|
-
*/
|
541
447
|
private async sendDropEvent(url: string, obj: Object3D, contentmd5: string) {
|
542
448
|
if (!this.useNetworking) {
|
543
449
|
if (debug) console.debug("[DropListener] Ignoring networked event because networking is disabled", url);
|
@@ -557,20 +463,12 @@
|
|
557
463
|
this.context.connection.send("droplistener", evt);
|
558
464
|
}
|
559
465
|
}
|
560
|
-
|
561
|
-
/**
|
562
|
-
* Deletes remote state for this DropListener's objects
|
563
|
-
* Called when new files are dropped to clean up previous state
|
564
|
-
*/
|
565
466
|
private deleteDropEvent() {
|
566
467
|
this.context.connection.sendDeleteRemoteState(this.guid);
|
567
468
|
}
|
568
469
|
|
569
|
-
|
570
|
-
|
571
|
-
* @param ctx The drop context containing screen position information
|
572
|
-
* @returns True if the drop is valid (either no drop area is set or the drop occurred inside it)
|
573
|
-
*/
|
470
|
+
|
471
|
+
|
574
472
|
private testIfIsInDropArea(ctx: DropContext): boolean {
|
575
473
|
if (this.dropArea) {
|
576
474
|
const screenPoint = this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone());
|
@@ -594,11 +492,7 @@
|
|
594
492
|
|
595
493
|
}
|
596
494
|
|
597
|
-
|
598
|
-
* Attempts to convert a Polyhaven website URL to a direct glTF model download URL
|
599
|
-
* @param urlStr The original Polyhaven URL
|
600
|
-
* @returns The direct download URL for the glTF model if it's a valid Polyhaven asset URL, otherwise returns the original URL
|
601
|
-
*/
|
495
|
+
|
602
496
|
function tryResolvePolyhavenAssetUrl(urlStr: string) {
|
603
497
|
if (!urlStr.startsWith("https://polyhaven.com/")) return urlStr;
|
604
498
|
// Handle dropping polyhaven image url
|
@@ -612,18 +506,10 @@
|
|
612
506
|
return assetUrl;
|
613
507
|
}
|
614
508
|
|
615
|
-
|
616
|
-
|
617
|
-
*/
|
509
|
+
|
510
|
+
|
618
511
|
namespace FileHelper {
|
619
512
|
|
620
|
-
/**
|
621
|
-
* Loads and processes a File object into a 3D model
|
622
|
-
* @param file The file to load (supported formats: gltf, glb, fbx, obj, usdz, vrm)
|
623
|
-
* @param context The application context
|
624
|
-
* @param args Additional arguments including a unique guid for instantiation
|
625
|
-
* @returns Promise containing the loaded model and its content hash, or null if loading failed
|
626
|
-
*/
|
627
513
|
export async function loadFile(file: File, context: Context, args: { guid: string }): Promise<{ model: Model, contentMD5: string } | null> {
|
628
514
|
const name = file.name.toLowerCase();
|
629
515
|
if (name.endsWith(".gltf") ||
|
@@ -658,12 +544,6 @@
|
|
658
544
|
return null;
|
659
545
|
}
|
660
546
|
|
661
|
-
/**
|
662
|
-
* Loads a 3D model from a URL with progress visualization
|
663
|
-
* @param url The URL to load the model from
|
664
|
-
* @param args Arguments including context, parent object, and optional placement information
|
665
|
-
* @returns Promise containing the loaded model and its content hash, or null if loading failed
|
666
|
-
*/
|
667
547
|
export async function loadFileFromURL(url: URL, args: { guid: string, context: Context, parent: Object3D, point?: Vec3, size?: Vec3 }): Promise<{ model: Model, contentMD5: string } | null> {
|
668
548
|
return new Promise(async (resolve, _reject) => {
|
669
549
|
|
@@ -164,11 +164,6 @@
|
|
164
164
|
return this._url;
|
165
165
|
}
|
166
166
|
|
167
|
-
/** The name of the assigned url. This name is deduced from the url and might not reflect the actual name of the asset */
|
168
|
-
get urlName(): string {
|
169
|
-
return this._urlName;
|
170
|
-
};
|
171
|
-
|
172
167
|
/**
|
173
168
|
* @returns true if the uri is a valid URL (http, https, blob)
|
174
169
|
*/
|
@@ -185,7 +180,6 @@
|
|
185
180
|
private _asset: any;
|
186
181
|
private _glbRoot?: Object3D | null;
|
187
182
|
private _url: string;
|
188
|
-
private _urlName: string;
|
189
183
|
private _progressListeners: ProgressCallback[] = [];
|
190
184
|
|
191
185
|
private _hash?: string;
|
@@ -197,27 +191,12 @@
|
|
197
191
|
/** @internal */
|
198
192
|
constructor(uri: string, hash?: string, asset: any = null) {
|
199
193
|
this._url = uri;
|
200
|
-
|
201
|
-
const lastUriPart = uri.lastIndexOf("/");
|
202
|
-
if (lastUriPart >= 0) {
|
203
|
-
this._urlName = uri.substring(lastUriPart + 1);
|
204
|
-
// remove file extension
|
205
|
-
const lastDot = this._urlName.lastIndexOf(".");
|
206
|
-
if (lastDot >= 0) {
|
207
|
-
this._urlName = this._urlName.substring(0, lastDot);
|
208
|
-
}
|
209
|
-
}
|
210
|
-
else {
|
211
|
-
this._urlName = uri;
|
212
|
-
}
|
213
|
-
|
214
194
|
this._hash = hash;
|
215
195
|
if (uri.includes("?v="))
|
216
196
|
this._hashedUri = uri;
|
217
197
|
else
|
218
198
|
this._hashedUri = hash ? uri + "?v=" + hash : uri;
|
219
199
|
if (asset !== null) this.asset = asset;
|
220
|
-
|
221
200
|
registerPrefabProvider(this._url, this.onResolvePrefab.bind(this));
|
222
201
|
}
|
223
202
|
|
@@ -548,20 +527,10 @@
|
|
548
527
|
|
549
528
|
private loader: TextureLoader | null = null;
|
550
529
|
createTexture(): Promise<Texture | null> {
|
551
|
-
if (!this.url)
|
552
|
-
console.error("Can not load texture without url");
|
553
|
-
return failedTexturePromise;
|
554
|
-
}
|
555
|
-
|
530
|
+
if (!this.url) return failedTexturePromise;
|
556
531
|
if (!this.loader) this.loader = new TextureLoader();
|
557
532
|
this.loader.setCrossOrigin("anonymous");
|
558
|
-
return this.loader.loadAsync(this.url)
|
559
|
-
if (res && !res.name?.length) {
|
560
|
-
// default name if no name is defined
|
561
|
-
res.name = this.url.split("/").pop() ?? this.url;
|
562
|
-
}
|
563
|
-
return res;
|
564
|
-
})
|
533
|
+
return this.loader.loadAsync(this.url);
|
565
534
|
// return this.getBitmap().then((bitmap) => {
|
566
535
|
// if (bitmap) {
|
567
536
|
// const texture = new Texture(bitmap);
|
@@ -864,7 +864,7 @@
|
|
864
864
|
if (name.length > 3) offendingComponentName = name;
|
865
865
|
}
|
866
866
|
}
|
867
|
-
console.error(`Needle Engine dependencies failed to load:\n\n# Make sure you don't have circular imports in your scripts!\n
|
867
|
+
console.error(`Needle Engine dependencies failed to load:\n\n# Make sure you don't have circular imports in your scripts!\n→ Possible solution: Replace @serializable(${offendingComponentName}) in your script with @serializable(Behaviour)\n\n---`, err)
|
868
868
|
return;
|
869
869
|
}
|
870
870
|
if (!printedError) {
|
@@ -255,26 +255,7 @@
|
|
255
255
|
/** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
|
256
256
|
* @param type The event type to listen for
|
257
257
|
* @param callback The callback to call when the event is triggered
|
258
|
-
* @param options The options for adding the event listener
|
259
|
-
* @example Basic usage:
|
260
|
-
* ```ts
|
261
|
-
* input.addEventListener("pointerdown", (evt) => {
|
262
|
-
* console.log("Pointer down", evt.pointerId, evt.pointerType);
|
263
|
-
* });
|
264
|
-
* ```
|
265
|
-
* @example Adding a listener that is called after all other listeners
|
266
|
-
* By using a higher value for the queue the listener will be called after other listeners (default queue is 0).
|
267
|
-
* ```ts
|
268
|
-
* input.addEventListener("pointerdown", (evt) => {
|
269
|
-
* console.log("Pointer down", evt.pointerId, evt.pointerType);
|
270
|
-
* }, { queue: 10 });
|
271
|
-
* ```
|
272
|
-
* @example Adding a listener that is only called once
|
273
|
-
* ```ts
|
274
|
-
* input.addEventListener("pointerdown", (evt) => {
|
275
|
-
* console.log("Pointer down", evt.pointerId, evt.pointerType);
|
276
|
-
* }, { once: true });
|
277
|
-
* ```
|
258
|
+
* @param options The options for adding the event listener
|
278
259
|
*/
|
279
260
|
addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions);
|
280
261
|
addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions);
|
@@ -326,7 +326,8 @@
|
|
326
326
|
if (comp.enabled) {
|
327
327
|
safeInvoke(comp.__internalAwake.bind(comp));
|
328
328
|
if (comp.enabled) {
|
329
|
-
comp
|
329
|
+
comp["__didEnable"] = true;
|
330
|
+
comp.onEnable();
|
330
331
|
}
|
331
332
|
}
|
332
333
|
}
|
@@ -342,8 +343,9 @@
|
|
342
343
|
|
343
344
|
let success = true;
|
344
345
|
if (go.children) {
|
346
|
+
const nextLevel = level + 1;
|
345
347
|
for (const ch of go.children) {
|
346
|
-
const res = updateIsActiveInHierarchyRecursiveRuntime(ch, activeInHierarchy, allowEventCall,
|
348
|
+
const res = updateIsActiveInHierarchyRecursiveRuntime(ch, activeInHierarchy, allowEventCall, nextLevel);
|
347
349
|
if (res === false) success = false;
|
348
350
|
}
|
349
351
|
}
|
@@ -43,30 +43,16 @@
|
|
43
43
|
return this.clamp(value, 0, 1);
|
44
44
|
}
|
45
45
|
|
46
|
-
/**
|
47
|
-
* Linear interpolate
|
48
|
-
*/
|
49
46
|
lerp(value1: number, value2: number, t: number) {
|
50
47
|
t = t < 0 ? 0 : t;
|
51
48
|
t = t > 1 ? 1 : t;
|
52
49
|
return value1 + (value2 - value1) * t;
|
53
50
|
}
|
54
51
|
|
55
|
-
/**
|
56
|
-
*
|
57
|
-
*/
|
58
52
|
inverseLerp(value1: number, value2: number, t: number) {
|
59
53
|
return (t - value1) / (value2 - value1);
|
60
54
|
}
|
61
55
|
|
62
|
-
/**
|
63
|
-
* Remaps a value from one range to another.
|
64
|
-
* @param value The value to remap.
|
65
|
-
* @param min1 The minimum value of the current range.
|
66
|
-
* @param max1 The maximum value of the current range.
|
67
|
-
* @param min2 The minimum value of the target range.
|
68
|
-
* @param max2 The maximum value of the target range.
|
69
|
-
*/
|
70
56
|
remap(value: number, min1: number, max1: number, min2: number, max2: number) {
|
71
57
|
return min2 + (max2 - min2) * (value - min1) / (max1 - min1);
|
72
58
|
}
|
@@ -78,24 +64,20 @@
|
|
78
64
|
return value1;
|
79
65
|
}
|
80
66
|
|
81
|
-
|
82
|
-
|
83
|
-
readonly Rad2Deg = 180 / Math.PI;
|
84
|
-
readonly Deg2Rad = Math.PI / 180;
|
85
|
-
readonly Epsilon = 0.00001;
|
86
|
-
/**
|
87
|
-
* Converts radians to degrees
|
88
|
-
*/
|
89
67
|
toDegrees(radians: number) {
|
90
68
|
return radians * 180 / Math.PI;
|
91
69
|
}
|
92
|
-
|
93
|
-
|
94
|
-
|
70
|
+
|
71
|
+
readonly Rad2Deg = 180 / Math.PI;
|
72
|
+
|
95
73
|
toRadians(degrees: number) {
|
96
74
|
return degrees * Math.PI / 180;
|
97
75
|
}
|
98
76
|
|
77
|
+
readonly Deg2Rad = Math.PI / 180;
|
78
|
+
|
79
|
+
readonly Epsilon = 0.00001;
|
80
|
+
|
99
81
|
tan(radians: number) {
|
100
82
|
return Math.tan(radians);
|
101
83
|
}
|
@@ -406,7 +406,7 @@
|
|
406
406
|
}
|
407
407
|
const hasOtherKeys = value !== undefined && Object.keys(value).length > 1;
|
408
408
|
if (!hasOtherKeys) {
|
409
|
-
addLog(LogType.Warn, `<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(Object3D)\n${key}? : Object3D;\n\nin ${typeName}.ts\n<a href="https://docs.needle.tools/serializable" target="_blank">
|
409
|
+
addLog(LogType.Warn, `<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(Object3D)\n${key}? : Object3D;\n\nin script ${typeName}.ts\n<a href="https://docs.needle.tools/serializable" target="_blank">documentation</a>`);
|
410
410
|
console.warn(typeName, key, obj[key], obj);
|
411
411
|
continue;
|
412
412
|
}
|
@@ -398,7 +398,7 @@
|
|
398
398
|
gl_FragColor = texture2D( map, uv);
|
399
399
|
// gl_FragColor = vec4(uv.xy, 0, 1);
|
400
400
|
}`;
|
401
|
-
private static
|
401
|
+
private static blipMaterial: ShaderMaterial | undefined = undefined;
|
402
402
|
|
403
403
|
/**
|
404
404
|
* Create a blit material for copying textures
|
@@ -419,28 +419,27 @@
|
|
419
419
|
* @returns the newly created, copied texture
|
420
420
|
*/
|
421
421
|
static copyTexture(texture: Texture, blitMaterial?: ShaderMaterial): Texture {
|
422
|
+
const material = blitMaterial ?? this.blipMaterial!;
|
423
|
+
|
424
|
+
material.uniforms.map.value = texture;
|
425
|
+
material.needsUpdate = true;
|
426
|
+
material.uniformsNeedUpdate = true;
|
427
|
+
|
422
428
|
// ensure that a blit material exists
|
423
|
-
if (!this.
|
424
|
-
this.
|
429
|
+
if (!this.blipMaterial) {
|
430
|
+
this.blipMaterial = new ShaderMaterial({
|
425
431
|
uniforms: { map: new Uniform(null) },
|
426
432
|
vertexShader: this.vertex,
|
427
433
|
fragmentShader: this.fragment
|
428
434
|
});
|
429
435
|
}
|
430
436
|
|
431
|
-
const material = blitMaterial || this.blitMaterial;
|
432
|
-
|
433
|
-
material.uniforms.map.value = texture;
|
434
|
-
material.needsUpdate = true;
|
435
|
-
material.uniformsNeedUpdate = true;
|
436
|
-
|
437
|
-
|
438
437
|
// ensure that the blit material has the correct vertex shader
|
439
438
|
const origVertexShader = material.vertexShader;
|
440
439
|
material.vertexShader = this.vertex;
|
441
440
|
|
442
441
|
if (!this.mesh)
|
443
|
-
this.mesh = new Mesh(this.planeGeometry, this.
|
442
|
+
this.mesh = new Mesh(this.planeGeometry, this.blipMaterial);
|
444
443
|
const mesh = this.mesh;
|
445
444
|
mesh.material = material;
|
446
445
|
mesh.frustumCulled = false;
|
@@ -475,22 +474,15 @@
|
|
475
474
|
// return new Texture(this.renderer.domElement);
|
476
475
|
// }
|
477
476
|
|
478
|
-
|
479
|
-
|
480
|
-
* @param texture the texture convert
|
481
|
-
* @param force if true the texture will be copied to a new texture before converting
|
482
|
-
* @returns the HTMLCanvasElement with the texture or null if the texture could not be copied
|
483
|
-
*/
|
484
|
-
static textureToCanvas(texture: Texture, force: boolean = false): HTMLCanvasElement | null {
|
485
|
-
if (!texture) {
|
486
|
-
return null;
|
487
|
-
}
|
477
|
+
static textureToCanvas(texture: Texture, force: boolean) {
|
478
|
+
if (!texture) return null;
|
488
479
|
|
489
480
|
if (force === true || texture["isCompressedTexture"] === true) {
|
490
481
|
texture = copyTexture(texture);
|
491
482
|
}
|
492
483
|
const image = texture.image;
|
493
484
|
if (isImageBitmap(image)) {
|
485
|
+
|
494
486
|
const canvas = document.createElement('canvas');
|
495
487
|
canvas.width = image.width;
|
496
488
|
canvas.height = image.height;
|
@@ -502,8 +494,8 @@
|
|
502
494
|
}
|
503
495
|
context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
|
504
496
|
return canvas;
|
497
|
+
|
505
498
|
}
|
506
|
-
|
507
499
|
return null;
|
508
500
|
}
|
509
501
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { QueryFilterFlags
|
1
|
+
import type { QueryFilterFlags } from "@dimforge/rapier3d-compat";
|
2
2
|
import { AnimationClip, Color, Material, Mesh, Object3D, Quaternion } from "three";
|
3
3
|
import { Vector3 } from "three";
|
4
4
|
import { type GLTF as THREE_GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
@@ -442,241 +442,80 @@
|
|
442
442
|
|
443
443
|
|
444
444
|
export interface IPhysicsEngine {
|
445
|
-
/** Initializes the physics engine */
|
446
445
|
initialize(): Promise<boolean>;
|
447
|
-
/** Indicates whether the physics engine has been initialized */
|
448
446
|
get isInitialized(): boolean;
|
449
|
-
/** Advances physics simulation by the given time step */
|
450
447
|
step(dt: number): void;
|
451
448
|
postStep();
|
452
|
-
/** Indicates whether the physics engine is currently updating */
|
453
449
|
get isUpdating(): boolean;
|
454
|
-
/**
|
450
|
+
/** clear all possibly cached data (e.g. mesh data when creating scaled mesh colliders) */
|
455
451
|
clearCaches();
|
456
452
|
|
457
|
-
/** Enables or disables the physics engine */
|
458
453
|
enabled: boolean;
|
459
|
-
|
460
|
-
get world(): World | undefined;
|
454
|
+
get world(): any;
|
461
455
|
|
462
|
-
/** Sets the gravity vector for the physics simulation */
|
463
456
|
set gravity(vec3: Vec3);
|
464
|
-
/** Gets the current gravity vector */
|
465
457
|
get gravity(): Vec3;
|
466
458
|
|
467
|
-
/**
|
468
|
-
* Gets the rapier physics body for a Needle component
|
469
|
-
* @param obj The collider or rigidbody component
|
470
|
-
* @returns The underlying physics body or null if not found
|
471
|
-
*/
|
459
|
+
/** Get the rapier body for a Needle component */
|
472
460
|
getBody(obj: ICollider | IRigidbody): null | any;
|
473
|
-
/**
|
474
|
-
* Gets the Needle Engine component for a rapier physics object
|
475
|
-
* @param rapierObject The rapier physics object
|
476
|
-
* @returns The associated component or null if not found
|
477
|
-
*/
|
461
|
+
/** Get the Needle Engine component for a rapier object */
|
478
462
|
getComponent(rapierObject: object): IComponent | null;
|
479
463
|
|
480
|
-
/
|
481
|
-
|
482
|
-
* @param origin
|
483
|
-
* @param direction
|
484
|
-
* @param options
|
485
|
-
* @returns Raycast result containing hit point and collider, or null if no hit
|
464
|
+
// raycasting
|
465
|
+
/** Fast raycast against physics colliders
|
466
|
+
* @param origin ray origin in screen or worldspace
|
467
|
+
* @param direction ray direction in worldspace
|
468
|
+
* @param options additional options
|
486
469
|
*/
|
487
470
|
raycast(origin?: Vec2 | Vec3, direction?: Vec3, options?: {
|
488
471
|
maxDistance?: number,
|
489
472
|
/** True if you want to also hit objects when the raycast starts from inside a collider */
|
490
473
|
solid?: boolean,
|
491
474
|
queryFilterFlags?: QueryFilterFlags,
|
492
|
-
/**
|
493
|
-
*
|
494
|
-
* The scene query will only consider hits with colliders with collision groups compatible with
|
495
|
-
* this collision group (using the bitwise test described in the collision groups section).
|
496
|
-
* For example membership 0x0001 and filter 0x0002 should be 0x00010002
|
497
|
-
* @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
|
498
|
-
*/
|
475
|
+
/** Raycast filter groups. Groups are used to apply the collision group rules for the scene query. The scene query will only consider hits with colliders with collision groups compatible with this collision group (using the bitwise test described in the collision groups section). See https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
|
476
|
+
* For example membership 0x0001 and filter 0x0002 should be 0x00010002 */
|
499
477
|
filterGroups?: number,
|
500
|
-
/**
|
501
|
-
* Predicate to filter colliders in raycast results
|
502
|
-
* @param collider The collider being tested
|
503
|
-
* @returns False to ignore this collider, true to include it
|
504
|
-
*/
|
478
|
+
/** Return false to ignore this collider */
|
505
479
|
filterPredicate?: (collider: ICollider) => boolean
|
506
480
|
}): RaycastResult;
|
507
|
-
|
508
|
-
/**
|
509
|
-
* Performs a raycast that also returns the normal vector at the hit point
|
510
|
-
* @param origin Ray origin in screen or worldspace
|
511
|
-
* @param direction Ray direction in worldspace
|
512
|
-
* @param options Additional raycast configuration options
|
513
|
-
* @returns Raycast result containing hit point, normal, and collider, or null if no hit
|
514
|
-
*/
|
481
|
+
/** raycast that also gets the normal vector. If you don't need it use raycast() */
|
515
482
|
raycastAndGetNormal(origin?: Vec2 | Vec3, direction?: Vec3, options?: {
|
516
483
|
maxDistance?: number,
|
517
484
|
/** True if you want to also hit objects when the raycast starts from inside a collider */
|
518
485
|
solid?: boolean,
|
519
486
|
queryFilterFlags?: QueryFilterFlags,
|
520
|
-
/**
|
521
|
-
*
|
522
|
-
* The scene query will only consider hits with colliders with collision groups compatible with
|
523
|
-
* this collision group (using the bitwise test described in the collision groups section).
|
524
|
-
* For example membership 0x0001 and filter 0x0002 should be 0x00010002
|
525
|
-
* @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
|
526
|
-
*/
|
487
|
+
/** Raycast filter groups. Groups are used to apply the collision group rules for the scene query. The scene query will only consider hits with colliders with collision groups compatible with this collision group (using the bitwise test described in the collision groups section). See https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
|
488
|
+
* For example membership 0x0001 and filter 0x0002 should be 0x00010002 */
|
527
489
|
filterGroups?: number,
|
528
|
-
/**
|
529
|
-
* Predicate to filter colliders in raycast results
|
530
|
-
* @param collider The collider being tested
|
531
|
-
* @returns False to ignore this collider, true to include it
|
532
|
-
*/
|
490
|
+
/** Return false to ignore this collider */
|
533
491
|
filterPredicate?: (collider: ICollider) => boolean
|
534
492
|
}): RaycastResult;
|
535
|
-
|
536
|
-
/**
|
537
|
-
* Finds all colliders within a sphere
|
538
|
-
* @param point The center point of the sphere
|
539
|
-
* @param radius The radius of the sphere
|
540
|
-
* @returns Array of objects that overlap with the sphere
|
541
|
-
*/
|
542
493
|
sphereOverlap(point: Vector3, radius: number): Array<SphereOverlapResult>;
|
543
494
|
|
544
495
|
// Collider methods
|
545
|
-
/**
|
546
|
-
* Adds a sphere collider to the physics world
|
547
|
-
* @param collider The collider component to add
|
548
|
-
*/
|
549
496
|
addSphereCollider(collider: ICollider);
|
550
|
-
|
551
|
-
/**
|
552
|
-
* Adds a box collider to the physics world
|
553
|
-
* @param collider The collider component to add
|
554
|
-
* @param size The size of the box
|
555
|
-
*/
|
556
497
|
addBoxCollider(collider: ICollider, size: Vector3);
|
557
|
-
|
558
|
-
/**
|
559
|
-
* Adds a capsule collider to the physics world
|
560
|
-
* @param collider The collider component to add
|
561
|
-
* @param radius The radius of the capsule
|
562
|
-
* @param height The height of the capsule
|
563
|
-
*/
|
564
498
|
addCapsuleCollider(collider: ICollider, radius: number, height: number);
|
565
|
-
|
566
|
-
/**
|
567
|
-
* Adds a mesh collider to the physics world
|
568
|
-
* @param collider The collider component to add
|
569
|
-
* @param mesh The mesh to use for collision
|
570
|
-
* @param convex Whether the collision mesh should be treated as convex
|
571
|
-
* @param scale Optional scale to apply to the mesh
|
572
|
-
*/
|
573
499
|
addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale?: Vector3 | undefined);
|
574
500
|
|
575
|
-
/**
|
576
|
-
* Updates the physics material properties of a collider
|
577
|
-
* @param collider The collider to update
|
578
|
-
*/
|
579
501
|
updatePhysicsMaterial(collider: ICollider);
|
580
502
|
|
581
503
|
// Rigidbody methods
|
582
|
-
/**
|
583
|
-
* Wakes up a sleeping rigidbody
|
584
|
-
* @param rb The rigidbody to wake up
|
585
|
-
*/
|
586
504
|
wakeup(rb: IRigidbody);
|
587
|
-
|
588
|
-
/**
|
589
|
-
* Checks if a rigidbody is currently sleeping
|
590
|
-
* @param rb The rigidbody to check
|
591
|
-
* @returns Whether the rigidbody is sleeping or undefined if cannot be determined
|
592
|
-
*/
|
593
505
|
isSleeping(rb: IRigidbody): boolean | undefined;
|
594
|
-
|
595
|
-
/**
|
596
|
-
* Updates the physical properties of a rigidbody or collider
|
597
|
-
* @param rb The rigidbody or collider to update
|
598
|
-
*/
|
599
506
|
updateProperties(rb: IRigidbody | ICollider);
|
600
|
-
|
601
|
-
/**
|
602
|
-
* Resets all forces acting on a rigidbody
|
603
|
-
* @param rb The rigidbody to reset forces on
|
604
|
-
* @param wakeup Whether to wake up the rigidbody
|
605
|
-
*/
|
606
507
|
resetForces(rb: IRigidbody, wakeup: boolean);
|
607
|
-
|
608
|
-
/**
|
609
|
-
* Resets all torques acting on a rigidbody
|
610
|
-
* @param rb The rigidbody to reset torques on
|
611
|
-
* @param wakeup Whether to wake up the rigidbody
|
612
|
-
*/
|
613
508
|
resetTorques(rb: IRigidbody, wakeup: boolean);
|
614
|
-
|
615
|
-
/**
|
616
|
-
* Adds a continuous force to a rigidbody
|
617
|
-
* @param rb The rigidbody to add force to
|
618
|
-
* @param vec The force vector to add
|
619
|
-
* @param wakeup Whether to wake up the rigidbody
|
620
|
-
*/
|
621
509
|
addForce(rb: IRigidbody, vec: Vec3, wakeup: boolean);
|
622
|
-
|
623
|
-
/**
|
624
|
-
* Applies an instantaneous impulse to a rigidbody
|
625
|
-
* @param rb The rigidbody to apply impulse to
|
626
|
-
* @param vec The impulse vector to apply
|
627
|
-
* @param wakeup Whether to wake up the rigidbody
|
628
|
-
*/
|
629
510
|
applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean);
|
630
|
-
|
631
|
-
/**
|
632
|
-
* Gets the linear velocity of a rigidbody or the rigidbody attached to a collider
|
633
|
-
* @param rb The rigidbody or collider to get velocity from
|
634
|
-
* @returns The linear velocity vector or null if not available
|
635
|
-
*/
|
511
|
+
/** Returns the linear velocity of a rigidbody or the rigidbody of a collider */
|
636
512
|
getLinearVelocity(rb: IRigidbody | ICollider): Vec3 | null;
|
637
|
-
|
638
|
-
/**
|
639
|
-
* Gets the angular velocity of a rigidbody
|
640
|
-
* @param rb The rigidbody to get angular velocity from
|
641
|
-
* @returns The angular velocity vector or null if not available
|
642
|
-
*/
|
643
513
|
getAngularVelocity(rb: IRigidbody): Vec3 | null;
|
644
|
-
|
645
|
-
/**
|
646
|
-
* Sets the angular velocity of a rigidbody
|
647
|
-
* @param rb The rigidbody to set angular velocity for
|
648
|
-
* @param vec The angular velocity vector to set
|
649
|
-
* @param wakeup Whether to wake up the rigidbody
|
650
|
-
*/
|
651
514
|
setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean);
|
652
|
-
|
653
|
-
/**
|
654
|
-
* Sets the linear velocity of a rigidbody
|
655
|
-
* @param rb The rigidbody to set linear velocity for
|
656
|
-
* @param vec The linear velocity vector to set
|
657
|
-
* @param wakeup Whether to wake up the rigidbody
|
658
|
-
*/
|
659
515
|
setLinearVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean);
|
660
516
|
|
661
|
-
/**
|
662
|
-
* Updates the position and/or rotation of a physics body
|
663
|
-
* @param comp The collider or rigidbody component to update
|
664
|
-
* @param translation Whether to update the position
|
665
|
-
* @param rotation Whether to update the rotation
|
666
|
-
*/
|
667
517
|
updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean);
|
668
|
-
|
669
|
-
/**
|
670
|
-
* Removes a physics body from the simulation
|
671
|
-
* @param body The component whose physics body should be removed
|
672
|
-
*/
|
673
518
|
removeBody(body: IComponent);
|
674
|
-
|
675
|
-
/**
|
676
|
-
* Gets the physics body for a component
|
677
|
-
* @param obj The collider or rigidbody component
|
678
|
-
* @returns The underlying physics body or null if not found
|
679
|
-
*/
|
680
519
|
getBody(obj: ICollider | IRigidbody): null | any;
|
681
520
|
|
682
521
|
// Joints
|
@@ -10,7 +10,6 @@
|
|
10
10
|
import { registerFrameEventCallback } from "./engine_lifecycle_functions_internal.js";
|
11
11
|
import { Context, FrameEvent } from "./engine_setup.js";
|
12
12
|
import { ICamera } from "./engine_types.js";
|
13
|
-
import { DeviceUtilities } from "./engine_utils.js";
|
14
13
|
import { updateTextureFromXRFrame } from "./engine_utils_screenshot.xr.js";
|
15
14
|
import { RGBAColor } from "./js-extensions/index.js";
|
16
15
|
import { setCustomVisibility } from "./js-extensions/Layers.js";
|
@@ -396,22 +395,8 @@
|
|
396
395
|
|
397
396
|
if ("download_filename" in opts && opts.download_filename) {
|
398
397
|
let download_name = opts.download_filename;
|
399
|
-
|
400
|
-
|
401
|
-
const key = download_name + "_screenshots";
|
402
|
-
const parts = download_name.split(".");
|
403
|
-
const ext = parts.pop()?.toLowerCase();
|
404
|
-
let count = 0;
|
405
|
-
if (localStorage.getItem(key)) {
|
406
|
-
count = parseInt(sessionStorage.getItem(key) || "0");
|
407
|
-
}
|
408
|
-
if (count > 0) {
|
409
|
-
// const timestamp = new Date().toLocaleString();
|
410
|
-
download_name = `${parts.join()}-${count}.${ext}`;
|
411
|
-
}
|
412
|
-
count += 1;
|
413
|
-
sessionStorage.setItem(key, count.toString());
|
414
|
-
}
|
398
|
+
const ext = download_name.split(".").pop()?.toLowerCase();
|
399
|
+
download_name = `${download_name}-${Date.now()}.${ext}`;
|
415
400
|
saveImage(dataUrl, download_name);
|
416
401
|
}
|
417
402
|
return dataUrl;
|
@@ -439,6 +424,7 @@
|
|
439
424
|
|
440
425
|
|
441
426
|
|
427
|
+
|
442
428
|
// trim to transparent pixels
|
443
429
|
function trimCanvas(originalCanvas: HTMLCanvasElement): HTMLCanvasElement | null {
|
444
430
|
if (!("document" in globalThis)) return null;
|
@@ -59,7 +59,6 @@
|
|
59
59
|
return ctx.scene.getComponent(EventSystem);
|
60
60
|
}
|
61
61
|
|
62
|
-
/** Get the currently active event system */
|
63
62
|
static get instance(): EventSystem | null {
|
64
63
|
return this.get(Context.Current);
|
65
64
|
}
|
@@ -76,10 +75,7 @@
|
|
76
75
|
}
|
77
76
|
}
|
78
77
|
|
79
|
-
get hasActiveUI() {
|
80
|
-
return this.currentActiveMeshUIComponents.length > 0;
|
81
|
-
}
|
82
|
-
|
78
|
+
get hasActiveUI() { return this.currentActiveMeshUIComponents.length > 0; }
|
83
79
|
get isHoveringObjects() { return this.hoveredByID.size > 0; }
|
84
80
|
|
85
81
|
awake(): void {
|
@@ -428,6 +424,7 @@
|
|
428
424
|
const res = UIRaycastUtils.isInteractable(actualGo, this.out);
|
429
425
|
if (!res) return false;
|
430
426
|
canvasGroup = this.out.canvasGroup ?? null;
|
427
|
+
|
431
428
|
const handled = this.handleMeshUIIntersection(object, pressedOrClicked);
|
432
429
|
if (!clicked && handled) {
|
433
430
|
// return true;
|
@@ -756,6 +753,7 @@
|
|
756
753
|
}
|
757
754
|
|
758
755
|
private resetMeshUIStates() {
|
756
|
+
|
759
757
|
if (this.context.input.getPointerPressedCount() > 0) {
|
760
758
|
MeshUIHelper.resetLastSelected();
|
761
759
|
}
|
@@ -818,7 +816,7 @@
|
|
818
816
|
let foundBlock: Object3D | null = null;
|
819
817
|
|
820
818
|
if (intersect) {
|
821
|
-
foundBlock = this.
|
819
|
+
foundBlock = this.findBlockInParent(intersect);
|
822
820
|
// console.log(intersect, "-- found block:", foundBlock)
|
823
821
|
if (foundBlock && foundBlock !== this.lastSelected) {
|
824
822
|
const interactable = foundBlock["interactable"];
|
@@ -842,13 +840,13 @@
|
|
842
840
|
this.needsUpdate = true;
|
843
841
|
}
|
844
842
|
|
845
|
-
static
|
843
|
+
static findBlockInParent(elem: any): Object3D | null {
|
846
844
|
if (!elem) return null;
|
847
|
-
if (elem.isBlock
|
845
|
+
if (elem.isBlock) {
|
848
846
|
// @TODO : Replace states managements
|
849
847
|
// if (Object.keys(elem.states).length > 0)
|
850
848
|
return elem;
|
851
849
|
}
|
852
|
-
return this.
|
850
|
+
return this.findBlockInParent(elem.parent);
|
853
851
|
}
|
854
852
|
}
|
@@ -24,86 +24,82 @@
|
|
24
24
|
const debug = getParam("debuglights");
|
25
25
|
|
26
26
|
|
27
|
-
/
|
28
|
-
|
29
|
-
|
27
|
+
/// <summary>
|
28
|
+
/// <para>The type of a Light.</para>
|
29
|
+
/// </summary>
|
30
30
|
export enum LightType {
|
31
|
-
/
|
31
|
+
/// <summary>
|
32
|
+
/// <para>The light is a spot light.</para>
|
33
|
+
/// </summary>
|
32
34
|
Spot = 0,
|
33
|
-
/
|
35
|
+
/// <summary>
|
36
|
+
/// <para>The light is a directional light.</para>
|
37
|
+
/// </summary>
|
34
38
|
Directional = 1,
|
35
|
-
/
|
39
|
+
/// <summary>
|
40
|
+
/// <para>The light is a point light.</para>
|
41
|
+
/// </summary>
|
36
42
|
Point = 2,
|
37
|
-
/** Area light */
|
38
43
|
Area = 3,
|
39
|
-
/
|
44
|
+
/// <summary>
|
45
|
+
/// <para>The light is a rectangle shaped area light. It affects only baked lightmaps and lightprobes.</para>
|
46
|
+
/// </summary>
|
40
47
|
Rectangle = 3,
|
41
|
-
/
|
48
|
+
/// <summary>
|
49
|
+
/// <para>The light is a disc shaped area light. It affects only baked lightmaps and lightprobes.</para>
|
50
|
+
/// </summary>
|
42
51
|
Disc = 4,
|
43
52
|
}
|
44
|
-
|
45
|
-
/**
|
46
|
-
* Defines how a light contributes to the scene lighting.
|
47
|
-
*/
|
48
53
|
export enum LightmapBakeType {
|
49
|
-
/
|
54
|
+
/// <summary>
|
55
|
+
/// <para>Realtime lights cast run time light and shadows. They can change position, orientation, color, brightness, and many other properties at run time. No lighting gets baked into lightmaps or light probes..</para>
|
56
|
+
/// </summary>
|
50
57
|
Realtime = 4,
|
51
|
-
/
|
58
|
+
/// <summary>
|
59
|
+
/// <para>Baked lights cannot move or change in any way during run time. All lighting for static objects gets baked into lightmaps. Lighting and shadows for dynamic objects gets baked into Light Probes.</para>
|
60
|
+
/// </summary>
|
52
61
|
Baked = 2,
|
53
|
-
/
|
62
|
+
/// <summary>
|
63
|
+
/// <para>Mixed lights allow a mix of realtime and baked lighting, based on the Mixed Lighting Mode used. These lights cannot move, but can change color and intensity at run time. Changes to color and intensity only affect direct lighting as indirect lighting gets baked. If using Subtractive mode, changes to color or intensity are not calculated at run time on static objects.</para>
|
64
|
+
/// </summary>
|
54
65
|
Mixed = 1,
|
55
66
|
}
|
56
67
|
|
57
|
-
/
|
58
|
-
|
59
|
-
|
60
|
-
*/
|
68
|
+
/// <summary>
|
69
|
+
/// <para>Shadow casting options for a Light.</para>
|
70
|
+
/// </summary>
|
61
71
|
enum LightShadows {
|
62
|
-
/
|
72
|
+
/// <summary>
|
73
|
+
/// <para>Do not cast shadows (default).</para>
|
74
|
+
/// </summary>
|
63
75
|
None = 0,
|
64
|
-
/
|
76
|
+
/// <summary>
|
77
|
+
/// <para>Cast "hard" shadows (with no shadow filtering).</para>
|
78
|
+
/// </summary>
|
65
79
|
Hard = 1,
|
66
|
-
/
|
80
|
+
/// <summary>
|
81
|
+
/// <para>Cast "soft" shadows (with 4x PCF filtering).</para>
|
82
|
+
/// </summary>
|
67
83
|
Soft = 2,
|
68
84
|
}
|
69
85
|
|
70
|
-
/**
|
71
|
-
*
|
72
|
-
*
|
73
|
-
*
|
74
|
-
*
|
75
|
-
* Debug mode can be enabled with the URL parameter `?debuglights`, which shows
|
76
|
-
* additional console output and visual helpers for lights.
|
77
|
-
*
|
86
|
+
/** The Light component can be used to create a light source in the scene.
|
87
|
+
* It can be used to create a directional light, a spot light or a point light.
|
88
|
+
* The light can be set to cast shadows and the shadow type can be set to hard or soft shadows.
|
89
|
+
* The light can be set to be baked or realtime.
|
90
|
+
* The light can be set to be a main light which will be used for the main directional light in the scene.
|
78
91
|
* @category Rendering
|
79
92
|
* @group Components
|
80
93
|
*/
|
81
94
|
export class Light extends Behaviour implements ILight {
|
82
95
|
|
83
|
-
/**
|
84
|
-
* The type of light (spot, directional, point, etc.)
|
85
|
-
*/
|
86
96
|
@serializable()
|
87
97
|
private type: LightType = 0;
|
88
98
|
|
89
|
-
/**
|
90
|
-
* The maximum distance the light affects
|
91
|
-
*/
|
92
99
|
public range: number = 1;
|
93
|
-
|
94
|
-
/**
|
95
|
-
* The full outer angle of the spotlight cone in degrees
|
96
|
-
*/
|
97
100
|
public spotAngle: number = 1;
|
98
|
-
|
99
|
-
/**
|
100
|
-
* The angle of the inner cone in degrees for soft-edge spotlights
|
101
|
-
*/
|
102
101
|
public innerSpotAngle: number = 1;
|
103
102
|
|
104
|
-
/**
|
105
|
-
* The color of the light
|
106
|
-
*/
|
107
103
|
@serializable(Color)
|
108
104
|
set color(val: Color) {
|
109
105
|
this._color = val;
|
@@ -117,9 +113,6 @@
|
|
117
113
|
}
|
118
114
|
public _color: Color = new Color(0xffffff);
|
119
115
|
|
120
|
-
/**
|
121
|
-
* The near plane distance for shadow projection
|
122
|
-
*/
|
123
116
|
@serializable()
|
124
117
|
set shadowNearPlane(val: number) {
|
125
118
|
if (val === this._shadowNearPlane) return;
|
@@ -132,9 +125,6 @@
|
|
132
125
|
get shadowNearPlane(): number { return this._shadowNearPlane; }
|
133
126
|
private _shadowNearPlane: number = .1;
|
134
127
|
|
135
|
-
/**
|
136
|
-
* Shadow bias value to reduce shadow acne and peter-panning
|
137
|
-
*/
|
138
128
|
@serializable()
|
139
129
|
set shadowBias(val: number) {
|
140
130
|
if (val === this._shadowBias) return;
|
@@ -147,9 +137,6 @@
|
|
147
137
|
get shadowBias(): number { return this._shadowBias; }
|
148
138
|
private _shadowBias: number = 0;
|
149
139
|
|
150
|
-
/**
|
151
|
-
* Shadow normal bias to reduce shadow acne on sloped surfaces
|
152
|
-
*/
|
153
140
|
@serializable()
|
154
141
|
set shadowNormalBias(val: number) {
|
155
142
|
if (val === this._shadowNormalBias) return;
|
@@ -165,9 +152,6 @@
|
|
165
152
|
/** when enabled this will remove the multiplication when setting the shadow bias settings initially */
|
166
153
|
private _overrideShadowBiasSettings: boolean = false;
|
167
154
|
|
168
|
-
/**
|
169
|
-
* Shadow casting mode (None, Hard, or Soft)
|
170
|
-
*/
|
171
155
|
@serializable()
|
172
156
|
set shadows(val: LightShadows) {
|
173
157
|
this._shadows = val;
|
@@ -179,16 +163,9 @@
|
|
179
163
|
get shadows(): LightShadows { return this._shadows; }
|
180
164
|
private _shadows: LightShadows = 1;
|
181
165
|
|
182
|
-
/**
|
183
|
-
* Determines if the light contributes to realtime lighting, baked lighting, or a mix
|
184
|
-
*/
|
185
166
|
@serializable()
|
186
167
|
private lightmapBakeType: LightmapBakeType = LightmapBakeType.Realtime;
|
187
168
|
|
188
|
-
/**
|
189
|
-
* Brightness of the light. In WebXR experiences, the intensity is automatically
|
190
|
-
* adjusted based on the AR session scale to maintain consistent lighting.
|
191
|
-
*/
|
192
169
|
@serializable()
|
193
170
|
set intensity(val: number) {
|
194
171
|
this._intensity = val;
|
@@ -207,9 +184,6 @@
|
|
207
184
|
get intensity(): number { return this._intensity; }
|
208
185
|
private _intensity: number = -1;
|
209
186
|
|
210
|
-
/**
|
211
|
-
* Maximum distance the shadow is projected
|
212
|
-
*/
|
213
187
|
@serializable()
|
214
188
|
get shadowDistance(): number {
|
215
189
|
const light = this.light;
|
@@ -233,9 +207,6 @@
|
|
233
207
|
private shadowWidth?: number;
|
234
208
|
private shadowHeight?: number;
|
235
209
|
|
236
|
-
/**
|
237
|
-
* Resolution of the shadow map in pixels (width and height)
|
238
|
-
*/
|
239
210
|
@serializable()
|
240
211
|
get shadowResolution(): number {
|
241
212
|
const light = this.light;
|
@@ -255,16 +226,10 @@
|
|
255
226
|
}
|
256
227
|
private _shadowResolution?: number = undefined;
|
257
228
|
|
258
|
-
/**
|
259
|
-
* Whether this light's illumination is entirely baked into lightmaps
|
260
|
-
*/
|
261
229
|
get isBaked() {
|
262
230
|
return this.lightmapBakeType === LightmapBakeType.Baked;
|
263
231
|
}
|
264
232
|
|
265
|
-
/**
|
266
|
-
* Checks if the GameObject itself is a {@link ThreeLight} object
|
267
|
-
*/
|
268
233
|
private get selfIsLight(): boolean {
|
269
234
|
if (this.gameObject["isLight"] === true) return true;
|
270
235
|
switch (this.gameObject.type) {
|
@@ -276,16 +241,8 @@
|
|
276
241
|
return false;
|
277
242
|
}
|
278
243
|
|
279
|
-
/**
|
280
|
-
* The underlying three.js {@link ThreeLight} instance
|
281
|
-
*/
|
282
244
|
private light: ThreeLight | undefined = undefined;
|
283
245
|
|
284
|
-
/**
|
285
|
-
* Gets the world position of the light
|
286
|
-
* @param vec Vector3 to store the result
|
287
|
-
* @returns The world position as a Vector3
|
288
|
-
*/
|
289
246
|
public getWorldPosition(vec: Vector3): Vector3 {
|
290
247
|
if (this.light) {
|
291
248
|
if (this.type === LightType.Directional) {
|
@@ -354,10 +311,6 @@
|
|
354
311
|
// this.updateIntensity();
|
355
312
|
}
|
356
313
|
|
357
|
-
/**
|
358
|
-
* Creates the appropriate three.js light based on the configured light type
|
359
|
-
* and applies all settings like shadows, intensity, and color.
|
360
|
-
*/
|
361
314
|
createLight() {
|
362
315
|
const lightAlreadyCreated = this.selfIsLight;
|
363
316
|
|
@@ -494,10 +447,6 @@
|
|
494
447
|
|
495
448
|
}
|
496
449
|
|
497
|
-
/**
|
498
|
-
* Coroutine that updates the main light reference in the context
|
499
|
-
* if this directional light should be the main light
|
500
|
-
*/
|
501
450
|
*updateMainLightRoutine() {
|
502
451
|
while (true) {
|
503
452
|
if (this.type === LightType.Directional) {
|
@@ -510,14 +459,8 @@
|
|
510
459
|
}
|
511
460
|
}
|
512
461
|
|
513
|
-
/**
|
514
|
-
* Controls whether the renderer's shadow map type can be changed when soft shadows are used
|
515
|
-
*/
|
516
462
|
static allowChangingRendererShadowMapType: boolean = true;
|
517
463
|
|
518
|
-
/**
|
519
|
-
* Updates shadow settings based on whether the shadows are set to hard or soft
|
520
|
-
*/
|
521
464
|
private updateShadowSoftHard() {
|
522
465
|
if (!this.light) return;
|
523
466
|
if (!this.light.shadow) return;
|
@@ -543,10 +486,6 @@
|
|
543
486
|
}
|
544
487
|
}
|
545
488
|
|
546
|
-
/**
|
547
|
-
* Configures a directional light by adding and positioning its target
|
548
|
-
* @param dirLight The directional light to set up
|
549
|
-
*/
|
550
489
|
private setDirectionalLight(dirLight: DirectionalLight) {
|
551
490
|
dirLight.add(dirLight.target);
|
552
491
|
dirLight.target.position.set(0, 0, -1);
|
@@ -4,68 +4,50 @@
|
|
4
4
|
import { Behaviour } from './Component.js';
|
5
5
|
|
6
6
|
/**
|
7
|
-
*
|
8
|
-
* Needle Menu uses HTML in 2D mode, and automatically switches to a 3D menu in VR/AR mode.
|
9
|
-
*
|
10
|
-
* Controls display options, button visibility, and menu positioning.
|
7
|
+
* Exposes options to customize the built-in Needle Menu.
|
11
8
|
* From code, you can access the menu via {@link Context.menu}.
|
12
9
|
* @category User Interface
|
13
10
|
* @group Components
|
14
11
|
**/
|
15
12
|
export class NeedleMenu extends Behaviour {
|
16
13
|
|
17
|
-
/**
|
18
|
-
* Determines the vertical positioning of the menu on the screen
|
19
|
-
*/
|
20
14
|
@serializable()
|
21
15
|
position: "top" | "bottom" = "bottom";
|
22
16
|
|
23
|
-
/**
|
24
|
-
* Controls the visibility of the Needle logo in the menu (requires PRO license)
|
25
|
-
*/
|
17
|
+
/** Show the Needle logo in the menu (requires PRO license) */
|
26
18
|
@serializable()
|
27
19
|
showNeedleLogo: boolean = true;
|
28
20
|
|
29
|
-
/**
|
30
|
-
* When enabled, displays the menu in VR/AR mode when the user looks up
|
21
|
+
/** When enabled the menu will also be visible in VR/AR when you look up
|
31
22
|
* @default undefined
|
32
|
-
|
23
|
+
*/
|
33
24
|
@serializable()
|
34
25
|
showSpatialMenu?: boolean;
|
35
26
|
|
36
|
-
/**
|
37
|
-
* When enabled, adds a fullscreen toggle button to the menu
|
27
|
+
/** When enabled a button to enter fullscreen will be added to the menu
|
38
28
|
* @default undefined
|
39
|
-
|
29
|
+
*/
|
40
30
|
@serializable()
|
41
31
|
createFullscreenButton?: boolean;
|
42
|
-
|
43
|
-
/**
|
44
|
-
* When enabled, adds an audio mute/unmute button to the menu
|
32
|
+
/** When enabled a button to mute the application will be added to the menu
|
45
33
|
* @default undefined
|
46
|
-
|
34
|
+
*/
|
47
35
|
@serializable()
|
48
36
|
createMuteButton?: boolean;
|
49
37
|
|
50
38
|
/**
|
51
|
-
* When enabled
|
52
|
-
* The QR code is only displayed on desktop devices.
|
39
|
+
* When enabled a button to show a QR code will be added to the menu.
|
53
40
|
* @default undefined
|
54
41
|
*/
|
55
42
|
@serializable()
|
56
43
|
createQRCodeButton?: boolean;
|
57
44
|
|
58
|
-
/**
|
59
|
-
* Applies the configured menu options when the component is enabled
|
60
|
-
* @hidden
|
61
|
-
*/
|
45
|
+
/** @hidden */
|
62
46
|
onEnable() {
|
63
47
|
this.applyOptions();
|
64
48
|
}
|
65
49
|
|
66
|
-
/**
|
67
|
-
* Applies all configured options to the active {@link Context.menu}.
|
68
|
-
*/
|
50
|
+
/** applies the options to `this.context.menu` */
|
69
51
|
applyOptions() {
|
70
52
|
this.context.menu.setPosition(this.position);
|
71
53
|
this.context.menu.showNeedleLogo(this.showNeedleLogo);
|
@@ -693,13 +693,7 @@
|
|
693
693
|
}
|
694
694
|
private _rigScale: number = 1;
|
695
695
|
private _lastRigScaleUpdate: number = -1;
|
696
|
-
|
697
|
-
/** Get the XR Rig worldscale.
|
698
|
-
*
|
699
|
-
* **For AR**
|
700
|
-
* If you want to modify the scale in AR at runtime get the WebARSessionRoot component via `findObjectOfType(WebARSessionRoot)` and then set the `arScale` value.
|
701
|
-
*
|
702
|
-
*/
|
696
|
+
/** get the XR rig worldscale */
|
703
697
|
get rigScale() {
|
704
698
|
if (!this._rigs[0]) return 1;
|
705
699
|
if (this._lastRigScaleUpdate !== this.context.time.frame) {
|
@@ -7,33 +7,21 @@
|
|
7
7
|
const debug = getParam("debugnet");
|
8
8
|
|
9
9
|
/**
|
10
|
-
*
|
11
|
-
* This component supplies websocket URLs for establishing connections.
|
12
|
-
* It implements the {@link INetworkingWebsocketUrlProvider} interface.
|
13
|
-
*
|
10
|
+
* The networking component is used to provide a websocket url to the networking system. It implements the {@link INetworkingWebsocketUrlProvider} interface.
|
14
11
|
* @category Networking
|
15
12
|
* @group Components
|
16
13
|
*/
|
17
14
|
export class Networking extends Behaviour implements INetworkingWebsocketUrlProvider {
|
18
15
|
|
19
|
-
/**
|
20
|
-
* The websocket URL to connect to for networking functionality.
|
21
|
-
* Can be a complete URL or a relative path that will be resolved against the current origin.
|
22
|
-
*/
|
16
|
+
/** The url that should be used for the websocket connection */
|
23
17
|
@serializable()
|
24
18
|
url: string | null = null;
|
25
19
|
|
26
|
-
/**
|
27
|
-
* Name of the URL parameter that can override the websocket connection URL.
|
28
|
-
* When set, the URL will be overridden by the parameter value from the browser URL.
|
29
|
-
* For example, with `urlParameterName="ws"`, adding `?ws=ws://localhost:8080` to the browser URL will override the connection URL.
|
30
|
-
*/
|
20
|
+
/** The name of the url parameter that should be used to override the url. When set the url will be overridden by the url parameter e.g. when `urlParameterName=ws` `?ws=ws://localhost:8080` */
|
31
21
|
@serializable()
|
32
22
|
urlParameterName: string | null = null;
|
33
23
|
|
34
|
-
/**
|
35
|
-
* Alternative URL to use when running on a local network.
|
36
|
-
* This is particularly useful for development, when the server is running on the same machine as the client.
|
24
|
+
/** Thie localhost url that should be used when the networking is running on a local network. This is useful when the server is running on the same machine as the client.
|
37
25
|
*/
|
38
26
|
@serializable()
|
39
27
|
localhost: string | null = null;
|
@@ -45,13 +33,7 @@
|
|
45
33
|
this.context.connection.registerProvider(this);
|
46
34
|
}
|
47
35
|
|
48
|
-
/**
|
49
|
-
* Determines the websocket URL to use for networking connections.
|
50
|
-
* Processes the configured URL, applying localhost fallbacks when appropriate and
|
51
|
-
* handling URL parameter overrides if specified.
|
52
|
-
* @returns The formatted websocket URL string or null if no valid URL could be determined
|
53
|
-
* @internal
|
54
|
-
*/
|
36
|
+
/** @internal */
|
55
37
|
getWebsocketUrl(): string | null {
|
56
38
|
|
57
39
|
let socketurl = this.url ? Networking.GetUrl(this.url, this.localhost) : null;
|
@@ -76,13 +58,7 @@
|
|
76
58
|
return "wss://" + match?.groups["url"];
|
77
59
|
}
|
78
60
|
|
79
|
-
|
80
|
-
* Processes a URL string applying various transformations based on network environment.
|
81
|
-
* Handles relative paths and localhost fallbacks for local network environments.
|
82
|
-
* @param url The original URL to process
|
83
|
-
* @param localhostFallback Alternative URL to use when on a local network
|
84
|
-
* @returns The processed URL string or null/undefined if input was invalid
|
85
|
-
*/
|
61
|
+
|
86
62
|
public static GetUrl(url: string | null | undefined, localhostFallback?: string | null): string | null | undefined {
|
87
63
|
|
88
64
|
let result = url;
|
@@ -102,13 +78,6 @@
|
|
102
78
|
return result;
|
103
79
|
}
|
104
80
|
|
105
|
-
/**
|
106
|
-
* Determines if the current connection is on a local network.
|
107
|
-
* Useful for applying different networking configurations in local development environments.
|
108
|
-
* This is the same as calling {@link isLocalNetwork}.
|
109
|
-
* @param hostname Optional hostname to check instead of the current window location
|
110
|
-
* @returns True if the connection is on a local network, false otherwise
|
111
|
-
*/
|
112
81
|
public static IsLocalNetwork(hostname = window.location.hostname) {
|
113
82
|
return isLocalNetwork(hostname);
|
114
83
|
}
|
@@ -94,7 +94,7 @@
|
|
94
94
|
else this.particleMaterial = newMaterial;
|
95
95
|
}
|
96
96
|
|
97
|
-
|
97
|
+
|
98
98
|
if (material.map) {
|
99
99
|
material.map.colorSpace = LinearSRGBColorSpace;
|
100
100
|
material.map.premultiplyAlpha = false;
|
@@ -916,7 +916,7 @@
|
|
916
916
|
if (particleSystemBehaviour instanceof ParticleSystemBaseBehaviour) {
|
917
917
|
particleSystemBehaviour.system = this;
|
918
918
|
}
|
919
|
-
if (debug) console.debug("Add custom ParticleSystem Behaviour", particleSystemBehaviour);
|
919
|
+
if (isDevEnvironment() || debug) console.debug("Add custom ParticleSystem Behaviour", particleSystemBehaviour);
|
920
920
|
this._particleSystem.addBehavior(particleSystemBehaviour);
|
921
921
|
return true;
|
922
922
|
}
|
@@ -12,7 +12,6 @@
|
|
12
12
|
|
13
13
|
const debug = getParam("debugplayersync");
|
14
14
|
|
15
|
-
/** Type definition for a PlayerSync instance with a guaranteed asset property */
|
16
15
|
declare type PlayerSyncWithAsset = PlayerSync & Required<Pick<PlayerSync, "asset">>;
|
17
16
|
|
18
17
|
/**
|
@@ -24,10 +23,7 @@
|
|
24
23
|
|
25
24
|
/**
|
26
25
|
* This API is experimental and may change or be removed in the future.
|
27
|
-
*
|
28
|
-
* @param url Path to the asset that should be instantiated for each player
|
29
|
-
* @param init Optional initialization parameters for the PlayerSync component
|
30
|
-
* @returns Promise resolving to a PlayerSync instance with a guaranteed asset property
|
26
|
+
* Create a PlayerSync instance at runtime from a given URL
|
31
27
|
* @example
|
32
28
|
* ```typescript
|
33
29
|
* const res = await PlayerSync.setupFrom("/assets/demo.glb");
|
@@ -51,22 +47,15 @@
|
|
51
47
|
return ps as PlayerSyncWithAsset;
|
52
48
|
}
|
53
49
|
|
54
|
-
/**
|
55
|
-
* When enabled, PlayerSync will automatically load and instantiate the assigned asset when joining a networked room
|
56
|
-
*/
|
50
|
+
/** when enabled PlayerSync will automatically load and instantiate the assigned asset when joining a networked room */
|
57
51
|
@serializable()
|
58
52
|
autoSync: boolean = true;
|
59
53
|
|
60
|
-
/**
|
61
|
-
* The asset that will be loaded and instantiated when PlayerSync becomes active and joins a networked room
|
62
|
-
*/
|
54
|
+
/** This asset will be loaded and instantiated when PlayerSync becomes active and joins a networked room */
|
63
55
|
@serializable(AssetReference)
|
64
56
|
asset?: AssetReference;
|
65
57
|
|
66
|
-
/**
|
67
|
-
* Event invoked when a player instance is spawned with the spawned {@link Object3D} as parameter
|
68
|
-
* @serializable
|
69
|
-
*/
|
58
|
+
/** Event called when an instance is spawned */
|
70
59
|
@serializable(EventList)
|
71
60
|
onPlayerSpawned?: EventList<Object3D>;
|
72
61
|
|
@@ -97,10 +86,6 @@
|
|
97
86
|
if (this.autoSync) this.getInstance();
|
98
87
|
}
|
99
88
|
|
100
|
-
/**
|
101
|
-
* Gets or creates an instance of the assigned asset for the local player
|
102
|
-
* @returns Promise resolving to the instantiated player object or null if creation failed
|
103
|
-
*/
|
104
89
|
async getInstance() {
|
105
90
|
if (this._localInstance) return this._localInstance;
|
106
91
|
|
@@ -138,9 +123,6 @@
|
|
138
123
|
return this._localInstance;
|
139
124
|
}
|
140
125
|
|
141
|
-
/**
|
142
|
-
* Destroys the current player instance and cleans up networking state
|
143
|
-
*/
|
144
126
|
destroyInstance = () => {
|
145
127
|
this._localInstance?.then(go => {
|
146
128
|
if (debug) console.log("PlayerSync.destroyInstance", go);
|
@@ -149,9 +131,8 @@
|
|
149
131
|
this._localInstance = undefined;
|
150
132
|
}
|
151
133
|
|
152
|
-
|
153
|
-
|
154
|
-
*/
|
134
|
+
|
135
|
+
|
155
136
|
private watchTabVisible() {
|
156
137
|
window.addEventListener("visibilitychange", _ => {
|
157
138
|
if (document.visibilityState === "visible") {
|
@@ -166,50 +147,32 @@
|
|
166
147
|
}
|
167
148
|
}
|
168
149
|
|
169
|
-
/**
|
170
|
-
* Enum defining events that can be triggered by PlayerState
|
171
|
-
*/
|
172
150
|
export enum PlayerStateEvent {
|
173
|
-
/** Triggered when a PlayerState's owner property changes */
|
174
151
|
OwnerChanged = "ownerChanged",
|
175
152
|
}
|
176
153
|
|
177
|
-
/** Arguments passed when a PlayerState's owner changes */
|
178
154
|
export declare interface PlayerStateOwnerChangedArgs {
|
179
|
-
/** The PlayerState instance that changed */
|
180
155
|
playerState: PlayerState;
|
181
|
-
/** Previous owner's connection ID */
|
182
156
|
oldValue: string;
|
183
|
-
/** New owner's connection ID */
|
184
157
|
newValue: string;
|
185
158
|
}
|
186
159
|
|
187
|
-
/** Callback type for PlayerState events */
|
188
160
|
export declare type PlayerStateEventCallback = (args: CustomEvent<PlayerStateOwnerChangedArgs>) => void;
|
189
161
|
|
190
|
-
/**
|
191
|
-
* Represents a player instance in the networked environment.
|
192
|
-
* Handles ownership, synchronization, and lifecycle management of player objects.
|
193
|
-
*/
|
194
162
|
export class PlayerState extends Behaviour {
|
195
163
|
|
196
164
|
private static _all: PlayerState[] = [];
|
197
|
-
/**
|
165
|
+
/** all instances for all players */
|
198
166
|
static get all(): PlayerState[] {
|
199
167
|
return PlayerState._all;
|
200
168
|
};
|
201
169
|
|
202
170
|
private static _local: PlayerState[] = [];
|
203
|
-
/**
|
171
|
+
/** all instances for the local player */
|
204
172
|
static get local(): PlayerState[] {
|
205
173
|
return PlayerState._local;
|
206
174
|
}
|
207
175
|
|
208
|
-
/**
|
209
|
-
* Gets the PlayerState component for a given object or component
|
210
|
-
* @param obj Object3D or Component to find the PlayerState for
|
211
|
-
* @returns The PlayerState component if found, undefined otherwise
|
212
|
-
*/
|
213
176
|
static getFor(obj: Object3D | Component) {
|
214
177
|
if (obj instanceof Object3D) {
|
215
178
|
return GameObject.getComponentInParent(obj, PlayerState);
|
@@ -220,34 +183,22 @@
|
|
220
183
|
return undefined;
|
221
184
|
}
|
222
185
|
|
223
|
-
/**
|
224
|
-
* Checks if a given object or component belongs to the local player
|
225
|
-
* @param obj Object3D or Component to check
|
226
|
-
* @returns True if the object belongs to the local player, false otherwise
|
227
|
-
*/
|
186
|
+
//** use to check if a component or gameobject is part of a instance owned by the local player */
|
228
187
|
static isLocalPlayer(obj: Object3D | Component): boolean {
|
229
188
|
const state = PlayerState.getFor(obj);
|
230
189
|
return state?.isLocalPlayer ?? false;
|
231
190
|
}
|
232
191
|
|
192
|
+
// static Callback
|
233
193
|
private static _callbacks: { [key: string]: PlayerStateEventCallback[] } = {};
|
234
194
|
/**
|
235
|
-
*
|
236
|
-
* @param event The event to listen for
|
237
|
-
* @param cb Callback function that will be invoked when the event occurs
|
238
|
-
* @returns The provided callback function for chaining
|
195
|
+
* Add a callback for a PlayerStateEvent
|
239
196
|
*/
|
240
197
|
static addEventListener(event: PlayerStateEvent, cb: PlayerStateEventCallback) {
|
241
198
|
if (!this._callbacks[event]) this._callbacks[event] = [];
|
242
199
|
this._callbacks[event].push(cb);
|
243
200
|
return cb;
|
244
201
|
}
|
245
|
-
|
246
|
-
/**
|
247
|
-
* Removes a previously registered event callback
|
248
|
-
* @param event The event type to remove the callback from
|
249
|
-
* @param cb The callback function to remove
|
250
|
-
*/
|
251
202
|
static removeEventListener(event: PlayerStateEvent, cb: PlayerStateEventCallback) {
|
252
203
|
if (!this._callbacks[event]) return;
|
253
204
|
const index = this._callbacks[event].indexOf(cb);
|
@@ -261,39 +212,20 @@
|
|
261
212
|
}
|
262
213
|
|
263
214
|
|
264
|
-
/** Event triggered when the owner of this PlayerState changes */
|
265
215
|
public onOwnerChangeEvent = new EventList();
|
266
|
-
|
267
|
-
/** Event triggered the first time an owner is assigned to this PlayerState */
|
268
216
|
public onFirstOwnerChangeEvent = new EventList();
|
269
|
-
|
270
|
-
/** Indicates if this PlayerState has an owner assigned */
|
271
217
|
public hasOwner = false;
|
272
218
|
|
273
|
-
/**
|
274
|
-
* The connection ID of the player who owns this PlayerState instance
|
275
|
-
* @syncField Synchronized across the network
|
276
|
-
*/
|
277
219
|
@syncField(PlayerState.prototype.onOwnerChange)
|
278
220
|
owner?: string;
|
279
221
|
|
280
|
-
/**
|
281
|
-
* When enabled, PlayerState will not destroy itself when the owner is not connected anymore
|
282
|
-
*/
|
222
|
+
/** when enabled PlayerSync will not destroy itself when not connected anymore */
|
283
223
|
dontDestroy: boolean = false;
|
284
224
|
|
285
|
-
/**
|
286
|
-
* Indicates if this PlayerState belongs to the local player
|
287
|
-
*/
|
288
225
|
get isLocalPlayer(): boolean {
|
289
226
|
return this.owner === this.context.connection.connectionId;
|
290
227
|
}
|
291
228
|
|
292
|
-
/**
|
293
|
-
* Handles owner change events and updates relevant state
|
294
|
-
* @param newOwner The new owner's connection ID
|
295
|
-
* @param oldOwner The previous owner's connection ID
|
296
|
-
*/
|
297
229
|
private onOwnerChange(newOwner: string, oldOwner: string) {
|
298
230
|
if (debug) console.log(`PlayerSync.onOwnerChange: ${oldOwner} → ${newOwner} (me: ${this.context.connection.connectionId})`);
|
299
231
|
|
@@ -368,7 +300,7 @@
|
|
368
300
|
}
|
369
301
|
}
|
370
302
|
|
371
|
-
/**
|
303
|
+
/** this tells the server that this client has been destroyed and the networking message for the instantiate will be removed */
|
372
304
|
doDestroy() {
|
373
305
|
if (debug) console.log("PlayerSync.doDestroy → syncDestroy", this.name);
|
374
306
|
syncDestroy(this.gameObject, this.context.connection, true, { saveInRoom: false });
|
@@ -387,10 +319,6 @@
|
|
387
319
|
}
|
388
320
|
}
|
389
321
|
|
390
|
-
/**
|
391
|
-
* Handler for when a user leaves the networked room
|
392
|
-
* @param model Object containing the ID of the user who left
|
393
|
-
*/
|
394
322
|
private onUserLeftRoom = (model: { userId: string }) => {
|
395
323
|
if (model.userId === this.owner) {
|
396
324
|
if (debug)
|
@@ -75,10 +75,6 @@
|
|
75
75
|
|
76
76
|
abstract get typeName(): string;
|
77
77
|
|
78
|
-
/**
|
79
|
-
* Whether the effect is active or not. Prefer using `enabled` instead.
|
80
|
-
* @deprecated
|
81
|
-
*/
|
82
78
|
@serializable()
|
83
79
|
active: boolean = true;
|
84
80
|
|
@@ -86,16 +82,14 @@
|
|
86
82
|
|
87
83
|
onEnable(): void {
|
88
84
|
super.onEnable();
|
89
|
-
|
85
|
+
this.onEffectEnabled();
|
90
86
|
// Dont override the serialized value by enabling (we could also just disable this component / map enabled to active)
|
91
87
|
if (this.__internalDidAwakeAndStart)
|
92
88
|
this.active = true;
|
93
|
-
this.onEffectEnabled();
|
94
89
|
}
|
95
90
|
|
96
91
|
onDisable(): void {
|
97
92
|
super.onDisable();
|
98
|
-
if (debug) console.warn("onDisable effect", this)
|
99
93
|
this._manager?.removeEffect(this);
|
100
94
|
this.active = false;
|
101
95
|
}
|
@@ -219,12 +219,12 @@
|
|
219
219
|
if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect)
|
220
220
|
effects.push(ef as Effect);
|
221
221
|
else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
222
|
+
const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ...effects);
|
223
|
+
pass.mainScene = scene;
|
224
|
+
pass.name = effects.map(e => e.constructor.name).join(", ");
|
225
|
+
pass.enabled = true;
|
226
226
|
// composer.addPass(pass);
|
227
|
-
|
227
|
+
effects.length = 0;
|
228
228
|
composer.addPass(ef as Pass);
|
229
229
|
}
|
230
230
|
else {
|
@@ -262,7 +262,7 @@
|
|
262
262
|
}
|
263
263
|
|
264
264
|
if (debug)
|
265
|
-
console.log("
|
265
|
+
console.log("PostProcessing Passes", effectsOrPasses, "->", composer.passes);
|
266
266
|
}
|
267
267
|
|
268
268
|
private orderEffects() {
|
@@ -498,9 +498,7 @@
|
|
498
498
|
else this.setAngularVelocity(x);
|
499
499
|
}
|
500
500
|
|
501
|
-
/**
|
502
|
-
* Returns the rigidbody velocity smoothed over ~ 10 frames
|
503
|
-
*/
|
501
|
+
/** Returns the rigidbody velocity smoothed over ~ 10 frames */
|
504
502
|
public get smoothedVelocity(): Vector3 {
|
505
503
|
this._smoothedVelocityGetter.copy(this._smoothedVelocity);
|
506
504
|
return this._smoothedVelocityGetter.multiplyScalar(1 / this.context.time.deltaTime);
|
@@ -514,9 +512,9 @@
|
|
514
512
|
|
515
513
|
|
516
514
|
private captureVelocity() {
|
517
|
-
const
|
518
|
-
|
519
|
-
const vel =
|
515
|
+
const wp = getWorldPosition(this.gameObject);
|
516
|
+
this.gameObject.matrixWorld.decompose(Rigidbody.tempPosition, tempQuaternion, tempScale);
|
517
|
+
const vel = wp.sub(this._lastPosition);
|
520
518
|
this._lastPosition.copy(Rigidbody.tempPosition);
|
521
519
|
this._smoothedVelocity.lerp(vel, this.context.time.deltaTime / .1);
|
522
520
|
}
|
@@ -46,10 +46,6 @@
|
|
46
46
|
index: number;
|
47
47
|
}
|
48
48
|
|
49
|
-
export type LoadSceneProgressEvent = LoadSceneEvent & {
|
50
|
-
progress: number,
|
51
|
-
}
|
52
|
-
|
53
49
|
/**
|
54
50
|
* The ISceneEventListener is called by the {@link SceneSwitcher} when a scene is loaded or unloaded.
|
55
51
|
* It must be added to the root object of your scene (that is being loaded) or on the same object as the SceneSwitcher
|
@@ -230,15 +226,6 @@
|
|
230
226
|
get currentlyLoadedScene() { return this._currentScene; }
|
231
227
|
|
232
228
|
/**
|
233
|
-
* Called when a scene starts loading
|
234
|
-
*/
|
235
|
-
@serializable(EventList)
|
236
|
-
sceneLoadingStart: EventList<LoadSceneEvent> = new EventList();
|
237
|
-
|
238
|
-
@serializable(EventList)
|
239
|
-
sceneLoadingProgress: EventList<ProgressEvent> = new EventList();
|
240
|
-
|
241
|
-
/**
|
242
229
|
* The sceneLoaded event is called when a scene/glTF is loaded and added to the scene
|
243
230
|
*/
|
244
231
|
@serializable(EventList)
|
@@ -294,7 +281,7 @@
|
|
294
281
|
this._preloadScheduler.maxLoadAhead = this.preloadNext;
|
295
282
|
this._preloadScheduler.maxLoadBehind = this.preloadPrevious;
|
296
283
|
this._preloadScheduler.maxConcurrent = this.preloadConcurrent;
|
297
|
-
this._preloadScheduler.begin(
|
284
|
+
this._preloadScheduler.begin();
|
298
285
|
|
299
286
|
// Begin loading the loading scene
|
300
287
|
if (this.autoLoadFirstScene && this._currentIndex === -1 && !await this.tryLoadFromQueryParam()) {
|
@@ -615,13 +602,11 @@
|
|
615
602
|
|
616
603
|
const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
|
617
604
|
this.dispatchEvent(loadStartEvt);
|
618
|
-
this.sceneLoadingStart?.invoke(loadStartEvt.detail);
|
619
605
|
await this.onStartLoading();
|
620
606
|
// start loading and wait for the scene to be loaded
|
621
607
|
await scene.loadAssetAsync((_, prog) => {
|
622
608
|
this._currentLoadingProgress = prog;
|
623
609
|
this.dispatchEvent(prog);
|
624
|
-
this.sceneLoadingProgress?.invoke(prog);
|
625
610
|
}).catch(console.error);
|
626
611
|
await this.onEndLoading();
|
627
612
|
const finishedEvt = new CustomEvent<LoadSceneEvent>("loadscene-finished", { detail: { scene: scene, switcher: this, index: index } });
|
@@ -857,18 +842,9 @@
|
|
857
842
|
|
858
843
|
|
859
844
|
|
860
|
-
/**
|
861
|
-
* PreLoadScheduler is responsible for managing preloading of scenes.
|
862
|
-
* It handles scheduling and limiting concurrent downloads of scenes based on specified parameters.
|
863
|
-
*/
|
864
845
|
class PreLoadScheduler {
|
865
|
-
/** Maximum number of scenes to preload ahead of the current scene */
|
866
846
|
maxLoadAhead: number;
|
867
|
-
|
868
|
-
/** Maximum number of scenes to preload behind the current scene */
|
869
847
|
maxLoadBehind: number;
|
870
|
-
|
871
|
-
/** Maximum number of scenes that can be preloaded concurrently */
|
872
848
|
maxConcurrent: number;
|
873
849
|
|
874
850
|
private _isRunning: boolean = false;
|
@@ -876,13 +852,6 @@
|
|
876
852
|
private _loadTasks: LoadTask[] = [];
|
877
853
|
private _maxConcurrentLoads: number = 1;
|
878
854
|
|
879
|
-
/**
|
880
|
-
* Creates a new PreLoadScheduler instance
|
881
|
-
* @param rooms The SceneSwitcher that this scheduler belongs to
|
882
|
-
* @param ahead Number of scenes to preload ahead of current scene
|
883
|
-
* @param behind Number of scenes to preload behind current scene
|
884
|
-
* @param maxConcurrent Maximum number of concurrent preloads allowed
|
885
|
-
*/
|
886
855
|
constructor(rooms: SceneSwitcher, ahead: number = 1, behind: number = 1, maxConcurrent: number = 2) {
|
887
856
|
this._switcher = rooms;
|
888
857
|
this.maxLoadAhead = ahead;
|
@@ -890,34 +859,26 @@
|
|
890
859
|
this.maxConcurrent = maxConcurrent;
|
891
860
|
}
|
892
861
|
|
893
|
-
|
894
|
-
* Starts the preloading process after a specified delay
|
895
|
-
* @param delay Time in milliseconds to wait before starting preload
|
896
|
-
*/
|
897
|
-
begin(delay: number) {
|
862
|
+
begin() {
|
898
863
|
if (this._isRunning) return;
|
899
|
-
if (debug) console.log("Preload begin"
|
864
|
+
if (debug) console.log("Preload begin")
|
900
865
|
this._isRunning = true;
|
901
|
-
let lastRoom: number = -
|
866
|
+
let lastRoom: number = -1;
|
902
867
|
let searchDistance: number;
|
903
868
|
let searchCall: number;
|
904
869
|
const array = this._switcher.scenes;
|
905
|
-
const startTime = Date.now() + delay;
|
906
870
|
const interval = setInterval(() => {
|
907
871
|
if (this.allLoaded()) {
|
908
872
|
if (debug)
|
909
|
-
console.log("All scenes
|
873
|
+
console.log("All scenes loaded");
|
910
874
|
this.stop();
|
911
875
|
}
|
912
876
|
if (!this._isRunning) {
|
913
877
|
clearInterval(interval);
|
914
878
|
return;
|
915
879
|
}
|
916
|
-
|
917
|
-
if (Date.now() < startTime) return;
|
918
|
-
|
919
880
|
if (this.canLoadNewScene() === false) return;
|
920
|
-
if (lastRoom
|
881
|
+
if (lastRoom !== this._switcher.currentIndex) {
|
921
882
|
lastRoom = this._switcher.currentIndex;
|
922
883
|
searchCall = 0;
|
923
884
|
searchDistance = 0;
|
@@ -931,33 +892,19 @@
|
|
931
892
|
if (roomIndex < 0) return;
|
932
893
|
// if (roomIndex < 0) roomIndex = array.length + roomIndex;
|
933
894
|
if (roomIndex < 0 || roomIndex >= array.length) return;
|
934
|
-
|
935
|
-
|
936
|
-
if (debug) console.log("Preload scene", { roomIndex, searchForward, lastRoom, currentIndex: this._switcher.currentIndex, tasks: this._loadTasks.length }, scene?.url);
|
937
|
-
new LoadTask(roomIndex, scene, this._loadTasks);
|
938
|
-
}
|
895
|
+
const scene = array[roomIndex];
|
896
|
+
new LoadTask(roomIndex, scene, this._loadTasks);
|
939
897
|
}, 200);
|
940
898
|
}
|
941
899
|
|
942
|
-
/**
|
943
|
-
* Stops the preloading process
|
944
|
-
*/
|
945
900
|
stop() {
|
946
901
|
this._isRunning = false;
|
947
902
|
}
|
948
903
|
|
949
|
-
/**
|
950
|
-
* Checks if a new scene can be loaded based on current load limits
|
951
|
-
* @returns True if a new scene can be loaded, false otherwise
|
952
|
-
*/
|
953
904
|
canLoadNewScene(): boolean {
|
954
905
|
return this._loadTasks.length < this._maxConcurrentLoads;
|
955
906
|
}
|
956
907
|
|
957
|
-
/**
|
958
|
-
* Checks if all scenes in the SceneSwitcher have been loaded
|
959
|
-
* @returns True if all scenes are loaded, false otherwise
|
960
|
-
*/
|
961
908
|
allLoaded(): boolean {
|
962
909
|
if (this._switcher.scenes) {
|
963
910
|
for (const scene of this._switcher.scenes) {
|
@@ -968,25 +915,12 @@
|
|
968
915
|
}
|
969
916
|
}
|
970
917
|
|
971
|
-
/**
|
972
|
-
* Represents a single preloading task for a scene
|
973
|
-
*/
|
974
918
|
class LoadTask {
|
975
|
-
|
919
|
+
|
976
920
|
index: number;
|
977
|
-
|
978
|
-
/** The AssetReference to be loaded */
|
979
921
|
asset: AssetReference;
|
980
|
-
|
981
|
-
/** The collection of active load tasks this task belongs to */
|
982
922
|
tasks: LoadTask[];
|
983
923
|
|
984
|
-
/**
|
985
|
-
* Creates a new LoadTask and begins loading immediately
|
986
|
-
* @param index The index of the scene in the scenes array
|
987
|
-
* @param asset The AssetReference to preload
|
988
|
-
* @param tasks The collection of active load tasks
|
989
|
-
*/
|
990
924
|
constructor(index: number, asset: AssetReference, tasks: LoadTask[]) {
|
991
925
|
this.index = index;
|
992
926
|
this.asset = asset;
|
@@ -995,9 +929,6 @@
|
|
995
929
|
this.awaitLoading();
|
996
930
|
}
|
997
931
|
|
998
|
-
/**
|
999
|
-
* Asynchronously loads the asset and removes this task from the active tasks list when complete
|
1000
|
-
*/
|
1001
932
|
private async awaitLoading() {
|
1002
933
|
if (this.asset && !this.asset.isLoaded()) {
|
1003
934
|
if (debug)
|
@@ -1,16 +1,13 @@
|
|
1
1
|
import type { N8AOPostPass } from "n8ao";
|
2
|
-
import { Color,
|
2
|
+
import { Color, PerspectiveCamera } from "three";
|
3
3
|
|
4
4
|
import { MODULES } from "../../../engine/engine_modules.js";
|
5
5
|
import { serializable } from "../../../engine/engine_serialization.js";
|
6
6
|
import { validate } from "../../../engine/engine_util_decorator.js";
|
7
|
-
import { getParam } from "../../../engine/engine_utils.js";
|
8
7
|
import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
|
9
8
|
import { VolumeParameter } from "../VolumeParameter.js";
|
10
9
|
import { registerCustomEffectType } from "../VolumeProfile.js";
|
11
10
|
|
12
|
-
const debugN8AO = getParam("debugN8AO");
|
13
|
-
|
14
11
|
// https://github.com/N8python/n8ao
|
15
12
|
|
16
13
|
/**See (N8AO documentation)[https://github.com/N8python/n8ao] */
|
@@ -33,10 +30,6 @@
|
|
33
30
|
return "ScreenSpaceAmbientOcclusionN8";
|
34
31
|
}
|
35
32
|
|
36
|
-
get pass(): N8AOPostPass {
|
37
|
-
return this._ssao;
|
38
|
-
}
|
39
|
-
|
40
33
|
@validate()
|
41
34
|
@serializable()
|
42
35
|
gammaCorrection: boolean = true;
|
@@ -116,51 +109,19 @@
|
|
116
109
|
|
117
110
|
const cam = this.context.mainCamera! as PerspectiveCamera;
|
118
111
|
|
119
|
-
const width = this.context.domWidth;
|
120
|
-
const height = this.context.domHeight;
|
121
|
-
|
122
112
|
const ssao = this._ssao = new MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass(
|
123
113
|
this.context.scene,
|
124
114
|
cam,
|
125
|
-
|
126
|
-
|
127
|
-
|
115
|
+
this.context.domWidth,
|
116
|
+
this.context.domHeight
|
117
|
+
);
|
128
118
|
const mode = ScreenSpaceAmbientOcclusionN8QualityMode[this.quality];
|
129
119
|
ssao.setQualityMode(mode);
|
130
120
|
|
131
|
-
ssao.configuration.
|
132
|
-
// ssao.needsSwap = false;
|
133
|
-
|
121
|
+
ssao.configuration.gammaCorrection = this.gammaCorrection;
|
134
122
|
|
135
|
-
const renderTarget = new WebGLRenderTarget(width, height);
|
136
|
-
ssao.configuration.beautyRenderTarget = renderTarget;
|
137
|
-
ssao.configuration.autoRenderBeauty = false;
|
138
|
-
// // If you just want a depth buffer
|
139
|
-
// renderTarget.depthTexture = new DepthTexture(width, height, UnsignedIntType);
|
140
|
-
// renderTarget.depthTexture.format = DepthFormat;
|
141
|
-
// // If you want a depth buffer and a stencil buffer
|
142
|
-
// renderTarget.depthTexture = new DepthTexture(width, height, UnsignedInt248Type);
|
143
|
-
// renderTarget.depthTexture.format = DepthStencilFormat;
|
144
|
-
|
145
|
-
|
146
|
-
ssao.configuration.gammaCorrection = this.gammaCorrection;
|
147
123
|
ssao.configuration.screenSpaceRadius = this.screenspaceRadius;
|
148
124
|
|
149
|
-
|
150
|
-
if (debugN8AO) {
|
151
|
-
// ssao.debug = debugN8AO;
|
152
|
-
ssao.enableDebugMode();
|
153
|
-
console.log(ssao);
|
154
|
-
setInterval(() => {
|
155
|
-
console.log("SSAO", ssao.lastTime);
|
156
|
-
}, 1000);
|
157
|
-
setInterval(() => {
|
158
|
-
// ssao.enabled = !ssao.enabled;
|
159
|
-
console.log("SSAO", ssao.enabled, { ssao, autoRenderBeauty: ssao.configuration.autoRenderBeauty });
|
160
|
-
}, 4000)
|
161
|
-
}
|
162
|
-
|
163
|
-
|
164
125
|
this.intensity.onValueChanged = newValue => {
|
165
126
|
ssao.configuration.intensity = newValue;
|
166
127
|
}
|
@@ -174,18 +135,10 @@
|
|
174
135
|
if (!ssao.color) ssao.color = new Color();
|
175
136
|
ssao.configuration.color.copy(newValue);
|
176
137
|
}
|
177
|
-
|
178
138
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
// resolutionScale: .1
|
183
|
-
// });
|
184
|
-
// const arr = new Array();
|
185
|
-
// arr.push(normalPass);
|
186
|
-
// arr.push(depthDownsamplingPass);
|
187
|
-
// arr.push(ssao);
|
188
|
-
return ssao;
|
139
|
+
const arr = new Array();
|
140
|
+
arr.push(ssao);
|
141
|
+
return arr;
|
189
142
|
}
|
190
143
|
|
191
144
|
}
|
@@ -8,15 +8,8 @@
|
|
8
8
|
|
9
9
|
const debug = getParam("debugspatialtrigger");
|
10
10
|
|
11
|
-
/** Layer instances used for mask comparison */
|
12
11
|
const layer1 = new Layers();
|
13
12
|
const layer2 = new Layers();
|
14
|
-
/**
|
15
|
-
* Tests if two layer masks intersect
|
16
|
-
* @param mask1 First layer mask
|
17
|
-
* @param mask2 Second layer mask
|
18
|
-
* @returns True if the layers intersect
|
19
|
-
*/
|
20
13
|
function testMask(mask1, mask2) {
|
21
14
|
layer1.mask = mask1;
|
22
15
|
layer2.mask = mask2;
|
@@ -24,45 +17,25 @@
|
|
24
17
|
}
|
25
18
|
|
26
19
|
/**
|
27
|
-
* Component that receives and responds to spatial events, like entering or exiting a trigger zone.
|
28
|
-
* Used in conjunction with {@link SpatialTrigger} to create interactive spatial events.
|
29
20
|
* @category Interactivity
|
30
21
|
* @group Components
|
31
22
|
*/
|
32
23
|
export class SpatialTriggerReceiver extends Behaviour {
|
33
24
|
|
34
|
-
/
|
35
|
-
* Bitmask determining which triggers this receiver responds to
|
36
|
-
* Only triggers with matching masks will interact with this receiver
|
37
|
-
*/
|
25
|
+
// currently Layers in unity but maybe this should be a string or plane number? Or should it be a bitmask to allow receivers use multiple triggers?
|
38
26
|
@serializable()
|
39
27
|
triggerMask: number = 0;
|
40
|
-
|
41
|
-
/** Event invoked when this object enters a trigger zone */
|
42
28
|
@serializable(EventList)
|
43
29
|
onEnter?: EventList<any>;
|
44
|
-
|
45
|
-
/** Event invoked continuously while this object is inside a trigger zone */
|
46
30
|
@serializable(EventList)
|
47
31
|
onStay?: EventList<any>;
|
48
|
-
|
49
|
-
/** Event invoked when this object exits a trigger zone */
|
50
32
|
@serializable(EventList)
|
51
33
|
onExit?: EventList<any>;
|
52
34
|
|
53
|
-
/**
|
54
|
-
* Initializes the receiver and logs debug info if enabled
|
55
|
-
* @internal
|
56
|
-
*/
|
57
35
|
start() {
|
58
36
|
if (debug) console.log(this.name, this.triggerMask, this);
|
59
37
|
}
|
60
38
|
|
61
|
-
/**
|
62
|
-
* Checks for intersections with spatial triggers and fires appropriate events
|
63
|
-
* Handles enter, stay, and exit events for all relevant triggers
|
64
|
-
* @internal
|
65
|
-
*/
|
66
39
|
update(): void {
|
67
40
|
this.currentIntersected.length = 0;
|
68
41
|
for (const trigger of SpatialTrigger.triggers) {
|
@@ -86,38 +59,23 @@
|
|
86
59
|
}
|
87
60
|
this.lastIntersected.length = 0;
|
88
61
|
this.lastIntersected.push(...this.currentIntersected);
|
62
|
+
|
89
63
|
}
|
90
64
|
|
91
|
-
/** Array of triggers currently intersecting with this receiver */
|
92
65
|
readonly currentIntersected: SpatialTrigger[] = [];
|
93
|
-
|
94
|
-
/** Array of triggers that intersected with this receiver in the previous frame */
|
95
66
|
readonly lastIntersected: SpatialTrigger[] = [];
|
96
67
|
|
97
|
-
/**
|
98
|
-
* Handles trigger enter events.
|
99
|
-
* @param trigger The spatial trigger that was entered
|
100
|
-
*/
|
101
68
|
onEnterTrigger(trigger: SpatialTrigger): void {
|
102
69
|
if(debug) console.log("ENTER TRIGGER", this.name, trigger.name, this, trigger);
|
103
70
|
trigger.raiseOnEnterEvent(this);
|
104
71
|
this.onEnter?.invoke();
|
105
72
|
}
|
106
|
-
|
107
|
-
/**
|
108
|
-
* Handles trigger exit events.
|
109
|
-
* @param trigger The spatial trigger that was exited
|
110
|
-
*/
|
111
73
|
onExitTrigger(trigger: SpatialTrigger): void {
|
112
74
|
if(debug) console.log("EXIT TRIGGER", this.name, trigger.name, );
|
113
75
|
trigger.raiseOnExitEvent(this);
|
114
76
|
this.onExit?.invoke();
|
115
77
|
}
|
116
78
|
|
117
|
-
/**
|
118
|
-
* Handles trigger stay events.
|
119
|
-
* @param trigger The spatial trigger that the receiver is staying in
|
120
|
-
*/
|
121
79
|
onStayTrigger(trigger: SpatialTrigger): void {
|
122
80
|
trigger.raiseOnStayEvent(this);
|
123
81
|
this.onStay?.invoke();
|
@@ -125,38 +83,24 @@
|
|
125
83
|
}
|
126
84
|
|
127
85
|
/**
|
128
|
-
* A
|
129
|
-
* Used to trigger events when objects enter, stay in, or exit the defined area
|
86
|
+
* A trigger that can be used to detect if an object is inside a box.
|
130
87
|
* @category Interactivity
|
131
88
|
* @group Components
|
132
89
|
*/
|
133
90
|
export class SpatialTrigger extends Behaviour {
|
134
91
|
|
135
|
-
/** Global registry of all active spatial triggers in the scene */
|
136
92
|
static triggers: SpatialTrigger[] = [];
|
137
93
|
|
138
|
-
/**
|
139
|
-
* Bitmask determining which receivers this trigger affects.
|
140
|
-
* Only receivers with matching masks will be triggered.
|
141
|
-
*/
|
142
|
-
// currently Layers in unity but maybe this should be a string or plane number? Or should it be a bitmask to allow receivers use multiple triggers?
|
143
94
|
@serializable()
|
144
95
|
triggerMask?: number;
|
145
96
|
|
146
|
-
/** Box helper component used to visualize and calculate the trigger area */
|
147
97
|
private boxHelper?: BoxHelperComponent;
|
148
98
|
|
149
|
-
/**
|
150
|
-
* Initializes the trigger and logs debug info if enabled
|
151
|
-
*/
|
152
99
|
start() {
|
153
100
|
if (debug)
|
154
101
|
console.log(this.name, this.triggerMask, this);
|
155
102
|
}
|
156
103
|
|
157
|
-
/**
|
158
|
-
* Registers this trigger in the global registry and sets up debug visualization if enabled
|
159
|
-
*/
|
160
104
|
onEnable(): void {
|
161
105
|
SpatialTrigger.triggers.push(this);
|
162
106
|
if (!this.boxHelper) {
|
@@ -164,19 +108,10 @@
|
|
164
108
|
this.boxHelper?.showHelper(null, debug as boolean);
|
165
109
|
}
|
166
110
|
}
|
167
|
-
|
168
|
-
/**
|
169
|
-
* Removes this trigger from the global registry when disabled
|
170
|
-
*/
|
171
111
|
onDisable(): void {
|
172
112
|
SpatialTrigger.triggers.splice(SpatialTrigger.triggers.indexOf(this), 1);
|
173
113
|
}
|
174
114
|
|
175
|
-
/**
|
176
|
-
* Tests if an object is inside this trigger's box
|
177
|
-
* @param obj The object to test against this trigger
|
178
|
-
* @returns True if the object is inside the trigger box
|
179
|
-
*/
|
180
115
|
test(obj: Object3D): boolean {
|
181
116
|
if (!this.boxHelper) return false;
|
182
117
|
return this.boxHelper.isInBox(obj) ?? false;
|
@@ -184,10 +119,6 @@
|
|
184
119
|
|
185
120
|
// private args: SpatialTriggerEventArgs = new SpatialTriggerEventArgs();
|
186
121
|
|
187
|
-
/**
|
188
|
-
* Raises the onEnter event on any SpatialTriggerReceiver components attached to this trigger's GameObject
|
189
|
-
* @param rec The receiver that entered this trigger
|
190
|
-
*/
|
191
122
|
raiseOnEnterEvent(rec: SpatialTriggerReceiver) {
|
192
123
|
// this.args.trigger = this;
|
193
124
|
// this.args.source = rec;
|
@@ -199,10 +130,6 @@
|
|
199
130
|
}, false);
|
200
131
|
}
|
201
132
|
|
202
|
-
/**
|
203
|
-
* Raises the onStay event on any SpatialTriggerReceiver components attached to this trigger's GameObject
|
204
|
-
* @param rec The receiver that is staying in this trigger
|
205
|
-
*/
|
206
133
|
raiseOnStayEvent(rec: SpatialTriggerReceiver) {
|
207
134
|
// this.args.trigger = this;
|
208
135
|
// this.args.source = rec;
|
@@ -214,10 +141,6 @@
|
|
214
141
|
}, false);
|
215
142
|
}
|
216
143
|
|
217
|
-
/**
|
218
|
-
* Raises the onExit event on any SpatialTriggerReceiver components attached to this trigger's GameObject
|
219
|
-
* @param rec The receiver that exited this trigger
|
220
|
-
*/
|
221
144
|
raiseOnExitEvent(rec: SpatialTriggerReceiver) {
|
222
145
|
GameObject.foreachComponent(this.gameObject, c => {
|
223
146
|
if (c === rec) return;
|
@@ -17,77 +17,50 @@
|
|
17
17
|
import { XRStateFlag } from "./webxr/XRFlag.js";
|
18
18
|
|
19
19
|
|
20
|
-
/**
|
21
|
-
* Defines the viewing perspective in spectator mode
|
22
|
-
*/
|
23
20
|
export enum SpectatorMode {
|
24
|
-
/** View from the perspective of the followed player */
|
25
21
|
FirstPerson = 0,
|
26
|
-
/** Freely view from a third-person perspective */
|
27
22
|
ThirdPerson = 1,
|
28
23
|
}
|
29
24
|
|
30
25
|
const debug = getParam("debugspectator");
|
31
26
|
|
32
27
|
/**
|
33
|
-
* Provides functionality to follow and spectate other users in a networked environment.
|
34
|
-
* Handles camera switching, following behavior, and network synchronization for spectator mode.
|
35
|
-
*
|
36
|
-
* Debug mode can be enabled with the URL parameter `?debugspectator`, which provides additional console output.
|
37
|
-
*
|
38
28
|
* @category Networking
|
39
29
|
* @group Components
|
40
30
|
*/
|
41
31
|
export class SpectatorCamera extends Behaviour {
|
42
32
|
|
43
|
-
/** Reference to the Camera component on this GameObject */
|
44
33
|
cam: Camera | null = null;
|
45
34
|
|
46
|
-
/**
|
47
|
-
* When enabled, pressing F will send a request to all connected users to follow the local player.
|
48
|
-
* Pressing ESC will stop spectating.
|
49
|
-
*/
|
35
|
+
/** when enabled pressing F will send a request to all connected users to follow me, ESC to stop */
|
50
36
|
@serializable()
|
51
37
|
useKeys: boolean = true;
|
52
38
|
|
53
39
|
private _mode: SpectatorMode = SpectatorMode.FirstPerson;
|
54
40
|
|
55
|
-
/** Gets the current spectator perspective mode */
|
56
41
|
get mode() { return this._mode; }
|
57
|
-
/** Sets the current spectator perspective mode */
|
58
42
|
set mode(val: SpectatorMode) {
|
59
43
|
this._mode = val;
|
60
44
|
}
|
61
45
|
|
62
|
-
/**
|
46
|
+
/** if this user is currently spectating someone else */
|
63
47
|
get isSpectating(): boolean {
|
64
48
|
return this._handler?.currentTarget !== undefined;
|
65
49
|
}
|
66
50
|
|
67
|
-
/**
|
68
|
-
* Checks if this instance is spectating the user with the given ID
|
69
|
-
* @param userId The user ID to check
|
70
|
-
* @returns True if spectating the specified user, false otherwise
|
71
|
-
*/
|
72
51
|
isSpectatingUser(userId: string): boolean {
|
73
52
|
return this.target?.userId === userId;
|
74
53
|
}
|
75
54
|
|
76
|
-
/**
|
77
|
-
* Checks if the user with the specified ID is following this user
|
78
|
-
* @param userId The user ID to check
|
79
|
-
* @returns True if the specified user is following this user, false otherwise
|
80
|
-
*/
|
81
55
|
isFollowedBy(userId: string): boolean {
|
82
56
|
return this.followers?.includes(userId);
|
83
57
|
}
|
84
58
|
|
85
|
-
/**
|
59
|
+
/** list of other users that are following me */
|
86
60
|
get followers(): string[] {
|
87
61
|
return this._networking.followers;
|
88
62
|
}
|
89
63
|
|
90
|
-
/** Stops the current spectating session */
|
91
64
|
stopSpectating() {
|
92
65
|
if (this.context.isInXR) {
|
93
66
|
this.followSelf();
|
@@ -96,15 +69,11 @@
|
|
96
69
|
this.target = undefined;
|
97
70
|
}
|
98
71
|
|
99
|
-
/** Gets the local player's connection ID */
|
100
72
|
private get localId() : string {
|
101
73
|
return this.context.connection.connectionId ?? "local";
|
102
74
|
}
|
103
75
|
|
104
|
-
/**
|
105
|
-
* Sets the player view to follow
|
106
|
-
* @param target The PlayerView to follow, or undefined to stop spectating
|
107
|
-
*/
|
76
|
+
/** player view to follow */
|
108
77
|
set target(target: PlayerView | undefined) {
|
109
78
|
if (this._handler) {
|
110
79
|
|
@@ -137,17 +106,14 @@
|
|
137
106
|
}
|
138
107
|
}
|
139
108
|
|
140
|
-
/** Gets the currently followed player view */
|
141
109
|
get target(): PlayerView | undefined {
|
142
110
|
return this._handler?.currentTarget;
|
143
111
|
}
|
144
112
|
|
145
|
-
/** Sends a network request for all users to follow this player */
|
146
113
|
requestAllFollowMe() {
|
147
114
|
this._networking.onRequestFollowMe();
|
148
115
|
}
|
149
116
|
|
150
|
-
/** Determines if the camera is spectating the local player */
|
151
117
|
private get isSpectatingSelf() {
|
152
118
|
return this.isSpectating && this.target?.currentObject === this.context.players.getPlayerView(this.localId)?.currentObject;
|
153
119
|
}
|
@@ -165,6 +131,7 @@
|
|
165
131
|
private _networking!: SpectatorCamNetworking;
|
166
132
|
|
167
133
|
awake(): void {
|
134
|
+
|
168
135
|
this._debug = new SpectatorSelectionController(this.context, this);
|
169
136
|
this._networking = new SpectatorCamNetworking(this.context, this);
|
170
137
|
this._networking.awake();
|
@@ -177,6 +144,7 @@
|
|
177
144
|
return;
|
178
145
|
}
|
179
146
|
|
147
|
+
|
180
148
|
if (!this._handler && this.cam)
|
181
149
|
this._handler = new SpectatorHandler(this.context, this.cam, this);
|
182
150
|
|
@@ -189,10 +157,6 @@
|
|
189
157
|
this._networking?.destroy();
|
190
158
|
}
|
191
159
|
|
192
|
-
/**
|
193
|
-
* Checks if the current platform supports spectator mode
|
194
|
-
* @returns True if the platform is supported, false otherwise
|
195
|
-
*/
|
196
160
|
private isSupportedPlatform() {
|
197
161
|
const ua = window.navigator.userAgent;
|
198
162
|
const standalone = /Windows|MacOS/.test(ua);
|
@@ -200,19 +164,12 @@
|
|
200
164
|
return standalone && !isHololens;
|
201
165
|
}
|
202
166
|
|
203
|
-
/**
|
204
|
-
* Called before entering WebXR mode
|
205
|
-
* @param _evt The WebXR event
|
206
|
-
*/
|
207
167
|
onBeforeXR(_evt) {
|
208
168
|
if (!this.isSupportedPlatform()) return;
|
209
169
|
GameObject.setActive(this.gameObject, true);
|
210
170
|
}
|
211
171
|
|
212
|
-
|
213
|
-
* Called when entering WebXR mode
|
214
|
-
* @param _evt The WebXR event
|
215
|
-
*/
|
172
|
+
|
216
173
|
onEnterXR(_evt) {
|
217
174
|
if (!this.isSupportedPlatform()) return;
|
218
175
|
if (debug) console.log(this.context.mainCamera);
|
@@ -221,10 +178,6 @@
|
|
221
178
|
}
|
222
179
|
}
|
223
180
|
|
224
|
-
/**
|
225
|
-
* Called when exiting WebXR mode
|
226
|
-
* @param _evt The WebXR event
|
227
|
-
*/
|
228
181
|
onLeaveXR(_evt) {
|
229
182
|
this.context.removeCamera(this.cam as ICamera);
|
230
183
|
GameObject.setActive(this.gameObject, false);
|
@@ -235,9 +188,7 @@
|
|
235
188
|
this.stopSpectating();
|
236
189
|
}
|
237
190
|
|
238
|
-
|
239
|
-
* Sets the target to follow the local player
|
240
|
-
*/
|
191
|
+
|
241
192
|
private followSelf() {
|
242
193
|
this.target = this.context.players.getPlayerView(this.context.connection.connectionId);
|
243
194
|
if (!this.target) {
|
@@ -250,9 +201,6 @@
|
|
250
201
|
// TODO: only show Spectator cam for DesktopVR;
|
251
202
|
// don't show for AR, don't show on Quest
|
252
203
|
// TODO: properly align cameras on enter/exit VR, seems currently spectator cam breaks alignment
|
253
|
-
/**
|
254
|
-
* Called after the main rendering pass to render the spectator view
|
255
|
-
*/
|
256
204
|
onAfterRender(): void {
|
257
205
|
if (!this.cam) return;
|
258
206
|
|
@@ -330,9 +278,6 @@
|
|
330
278
|
this.resetAvatarFlags();
|
331
279
|
}
|
332
280
|
|
333
|
-
/**
|
334
|
-
* Updates avatar visibility flags for rendering in spectator mode
|
335
|
-
*/
|
336
281
|
private setAvatarFlagsBeforeRender() {
|
337
282
|
const isFirstPersonMode = this._mode === SpectatorMode.FirstPerson;
|
338
283
|
|
@@ -350,9 +295,6 @@
|
|
350
295
|
}
|
351
296
|
}
|
352
297
|
|
353
|
-
/**
|
354
|
-
* Restores avatar visibility flags after spectator rendering
|
355
|
-
*/
|
356
298
|
private resetAvatarFlags() {
|
357
299
|
for (const av of AvatarMarker.instances) {
|
358
300
|
if (av.avatar && "flags" in av.avatar) {
|
@@ -371,9 +313,6 @@
|
|
371
313
|
}
|
372
314
|
}
|
373
315
|
|
374
|
-
/**
|
375
|
-
* Interface for handling spectator camera behavior
|
376
|
-
*/
|
377
316
|
interface ISpectatorHandler {
|
378
317
|
context: Context;
|
379
318
|
get currentTarget(): PlayerView | undefined;
|
@@ -383,9 +322,6 @@
|
|
383
322
|
destroy();
|
384
323
|
}
|
385
324
|
|
386
|
-
/**
|
387
|
-
* Handles the smooth following behavior for the spectator camera
|
388
|
-
*/
|
389
325
|
class SpectatorHandler implements ISpectatorHandler {
|
390
326
|
|
391
327
|
readonly context: Context;
|
@@ -397,7 +333,6 @@
|
|
397
333
|
private view?: PlayerView;
|
398
334
|
private currentObject: Object3D | undefined;
|
399
335
|
|
400
|
-
/** Gets the currently targeted player view */
|
401
336
|
get currentTarget(): PlayerView | undefined {
|
402
337
|
return this.view;
|
403
338
|
}
|
@@ -408,10 +343,6 @@
|
|
408
343
|
this.spectator = spectator;
|
409
344
|
}
|
410
345
|
|
411
|
-
/**
|
412
|
-
* Sets the target player view to follow
|
413
|
-
* @param view The PlayerView to follow
|
414
|
-
*/
|
415
346
|
set(view?: PlayerView): void {
|
416
347
|
const followObject = view?.currentObject;
|
417
348
|
if (!followObject) {
|
@@ -437,7 +368,6 @@
|
|
437
368
|
else this.context.removeCamera(this.cam as ICamera);
|
438
369
|
}
|
439
370
|
|
440
|
-
/** Disables the spectator following behavior */
|
441
371
|
disable() {
|
442
372
|
if (debug) console.log("STOP FOLLOW", this.currentObject);
|
443
373
|
this.view = undefined;
|
@@ -447,17 +377,12 @@
|
|
447
377
|
this.follow.enabled = false;
|
448
378
|
}
|
449
379
|
|
450
|
-
/** Cleans up resources used by the handler */
|
451
380
|
destroy() {
|
452
381
|
this.target?.removeFromParent();
|
453
382
|
if (this.follow)
|
454
383
|
GameObject.destroy(this.follow);
|
455
384
|
}
|
456
385
|
|
457
|
-
/**
|
458
|
-
* Updates the camera position and orientation based on the spectator mode
|
459
|
-
* @param mode The current spectator mode (first or third person)
|
460
|
-
*/
|
461
386
|
update(mode: SpectatorMode) {
|
462
387
|
if (this.currentTarget?.isConnected === false || this.currentTarget?.removed === true) {
|
463
388
|
if (debug) console.log("Target disconnected or timeout", this.currentTarget);
|
@@ -506,13 +431,13 @@
|
|
506
431
|
target.quaternion.copy(_inverseYQuat);
|
507
432
|
else target.quaternion.identity();
|
508
433
|
}
|
434
|
+
|
435
|
+
|
509
436
|
}
|
510
437
|
|
511
438
|
const _inverseYQuat = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
512
439
|
|
513
|
-
|
514
|
-
* Handles user input for selecting targets to spectate
|
515
|
-
*/
|
440
|
+
|
516
441
|
class SpectatorSelectionController {
|
517
442
|
|
518
443
|
private readonly context: Context;
|
@@ -543,9 +468,6 @@
|
|
543
468
|
});
|
544
469
|
}
|
545
470
|
|
546
|
-
/**
|
547
|
-
* Attempts to select an avatar to spectate through raycasting
|
548
|
-
*/
|
549
471
|
private trySelectObject() {
|
550
472
|
const opts = new RaycastOptions();
|
551
473
|
opts.setMask(0xffffff);
|
@@ -569,17 +491,17 @@
|
|
569
491
|
}
|
570
492
|
}
|
571
493
|
|
572
|
-
|
573
|
-
|
574
|
-
|
494
|
+
|
495
|
+
|
496
|
+
|
497
|
+
|
575
498
|
class SpectatorFollowerChangedEventModel implements IModel {
|
576
|
-
/**
|
499
|
+
/** the user that is following */
|
577
500
|
guid: string;
|
578
501
|
readonly dontSave: boolean = true;
|
579
502
|
|
580
|
-
/**
|
503
|
+
/** the user being followed */
|
581
504
|
targetUserId: string | undefined;
|
582
|
-
/** Indicates if the user stopped following */
|
583
505
|
stoppedFollowing: boolean;
|
584
506
|
|
585
507
|
constructor(connectionId: string, userId: string | undefined, stoppedFollowing: boolean) {
|
@@ -589,9 +511,6 @@
|
|
589
511
|
}
|
590
512
|
}
|
591
513
|
|
592
|
-
/**
|
593
|
-
* Network model for requesting users to follow a specific player
|
594
|
-
*/
|
595
514
|
class SpectatorFollowEventModel implements IModel {
|
596
515
|
guid: string;
|
597
516
|
userId: string | undefined;
|
@@ -602,14 +521,11 @@
|
|
602
521
|
}
|
603
522
|
}
|
604
523
|
|
605
|
-
/**
|
606
|
-
* Handles network communication for spectator functionality
|
607
|
-
*/
|
608
524
|
class SpectatorCamNetworking {
|
609
525
|
|
610
|
-
/** List of user IDs currently following this player */
|
611
526
|
readonly followers: string[] = [];
|
612
527
|
|
528
|
+
|
613
529
|
private readonly context: Context;
|
614
530
|
private readonly spectator: SpectatorCamera;
|
615
531
|
private _followerEventMethod: Function;
|
@@ -624,9 +540,6 @@
|
|
624
540
|
this._joinedRoomMethod = this.onUserJoinedRoom.bind(this);
|
625
541
|
}
|
626
542
|
|
627
|
-
/**
|
628
|
-
* Initializes network event listeners
|
629
|
-
*/
|
630
543
|
awake() {
|
631
544
|
this.context.connection.beginListen("spectator-follower-changed", this._followerEventMethod);
|
632
545
|
this.context.connection.beginListen("spectator-request-follow", this._requestFollowMethod);
|
@@ -642,20 +555,12 @@
|
|
642
555
|
});
|
643
556
|
}
|
644
557
|
|
645
|
-
/**
|
646
|
-
* Removes network event listeners
|
647
|
-
*/
|
648
558
|
destroy() {
|
649
559
|
this.context.connection.stopListen("spectator-follower-changed", this._followerEventMethod);
|
650
560
|
this.context.connection.stopListen("spectator-request-follow", this._requestFollowMethod);
|
651
561
|
this.context.connection.stopListen(RoomEvents.JoinedRoom, this._joinedRoomMethod);
|
652
562
|
}
|
653
563
|
|
654
|
-
/**
|
655
|
-
* Notifies other users about spectating target changes
|
656
|
-
* @param target The new target being spectated
|
657
|
-
* @param _prevId The previous target's user ID
|
658
|
-
*/
|
659
564
|
onSpectatedObjectChanged(target: PlayerView | undefined, _prevId?: string) {
|
660
565
|
if (debug)
|
661
566
|
console.log(this.context.connection.connectionId, "onSpectatedObjectChanged", target, _prevId);
|
@@ -667,10 +572,6 @@
|
|
667
572
|
}
|
668
573
|
}
|
669
574
|
|
670
|
-
/**
|
671
|
-
* Requests other users to follow this player or stop following
|
672
|
-
* @param stop Whether to request users to stop following
|
673
|
-
*/
|
674
575
|
onRequestFollowMe(stop: boolean = false) {
|
675
576
|
if (debug)
|
676
577
|
console.log("Request follow", this.context.connection.connectionId);
|
@@ -682,19 +583,12 @@
|
|
682
583
|
}
|
683
584
|
}
|
684
585
|
|
685
|
-
/**
|
686
|
-
* Handles room join events
|
687
|
-
*/
|
688
586
|
private onUserJoinedRoom() {
|
689
587
|
if (getParam("followme")) {
|
690
588
|
this.onRequestFollowMe();
|
691
589
|
}
|
692
590
|
}
|
693
591
|
|
694
|
-
/**
|
695
|
-
* Processes follower status change events from the network
|
696
|
-
* @param evt The follower change event data
|
697
|
-
*/
|
698
592
|
private onFollowerEvent(evt: SpectatorFollowerChangedEventModel) {
|
699
593
|
const userBeingFollowed = evt.targetUserId;
|
700
594
|
const userThatIsFollowing = evt.guid;
|
@@ -721,9 +615,6 @@
|
|
721
615
|
}
|
722
616
|
}
|
723
617
|
|
724
|
-
/**
|
725
|
-
* Removes followers that are no longer connected to the room
|
726
|
-
*/
|
727
618
|
private removeDisconnectedFollowers() {
|
728
619
|
for (let i = this.followers.length - 1; i >= 0; i--) {
|
729
620
|
const id = this.followers[i];
|
@@ -735,11 +626,6 @@
|
|
735
626
|
|
736
627
|
private _lastRequestFollowUser: SpectatorFollowEventModel | undefined;
|
737
628
|
|
738
|
-
/**
|
739
|
-
* Handles follow requests from other users
|
740
|
-
* @param evt The follow request event
|
741
|
-
* @returns True if the request was handled successfully
|
742
|
-
*/
|
743
629
|
private onRequestFollowEvent(evt: SpectatorFollowEventModel) {
|
744
630
|
this._lastRequestFollowUser = evt;
|
745
631
|
|
@@ -766,10 +652,6 @@
|
|
766
652
|
}
|
767
653
|
|
768
654
|
private _enforceFollowInterval: any;
|
769
|
-
|
770
|
-
/**
|
771
|
-
* Periodically retries following a user if the initial attempt failed
|
772
|
-
*/
|
773
655
|
private enforceFollow() {
|
774
656
|
if (this._enforceFollowInterval) return;
|
775
657
|
this._enforceFollowInterval = setInterval(() => {
|
@@ -155,11 +155,7 @@
|
|
155
155
|
export class SpriteSheet {
|
156
156
|
|
157
157
|
@serializable(Sprite)
|
158
|
-
sprites: Sprite[];
|
159
|
-
|
160
|
-
constructor() {
|
161
|
-
this.sprites = [];
|
162
|
-
}
|
158
|
+
sprites!: Sprite[];
|
163
159
|
}
|
164
160
|
|
165
161
|
export class SpriteData {
|
@@ -175,13 +171,6 @@
|
|
175
171
|
// hence the spriteSheet field is undefined by default
|
176
172
|
constructor() { }
|
177
173
|
|
178
|
-
clone() {
|
179
|
-
const i = new SpriteData();
|
180
|
-
i.index = this.index;
|
181
|
-
i.spriteSheet = this.spriteSheet;
|
182
|
-
return i;
|
183
|
-
}
|
184
|
-
|
185
174
|
/**
|
186
175
|
* Set the sprite to be rendered in the currently assigned sprite sheet at the currently active index {@link index}
|
187
176
|
*/
|
@@ -331,6 +320,7 @@
|
|
331
320
|
if (typeof value === "number") {
|
332
321
|
const index = Math.floor(value);
|
333
322
|
this.spriteIndex = index;
|
323
|
+
return;
|
334
324
|
}
|
335
325
|
else if (value instanceof Sprite) {
|
336
326
|
if (!this._spriteSheet) {
|
@@ -338,8 +328,8 @@
|
|
338
328
|
}
|
339
329
|
if (this._spriteSheet.sprite != value) {
|
340
330
|
this._spriteSheet.sprite = value;
|
331
|
+
this.updateSprite();
|
341
332
|
}
|
342
|
-
this.updateSprite();
|
343
333
|
}
|
344
334
|
else if (value != this._spriteSheet) {
|
345
335
|
this._spriteSheet = value;
|
@@ -352,6 +342,7 @@
|
|
352
342
|
*/
|
353
343
|
set spriteIndex(value: number) {
|
354
344
|
if (!this._spriteSheet) return;
|
345
|
+
if (value === this.spriteIndex) return;
|
355
346
|
this._spriteSheet.index = value;
|
356
347
|
this.updateSprite();
|
357
348
|
}
|
@@ -368,19 +359,13 @@
|
|
368
359
|
private _spriteSheet?: SpriteData;
|
369
360
|
private _currentSprite?: Mesh;
|
370
361
|
|
371
|
-
|
372
362
|
/** @internal */
|
373
363
|
awake(): void {
|
374
364
|
this._currentSprite = undefined;
|
375
|
-
|
376
365
|
if (!this._spriteSheet) {
|
377
|
-
this._spriteSheet = SpriteData
|
366
|
+
this._spriteSheet = new SpriteData();
|
367
|
+
this._spriteSheet.spriteSheet = new SpriteSheet();
|
378
368
|
}
|
379
|
-
else {
|
380
|
-
// Ensure each SpriteRenderer has a unique spritesheet instance for cases where sprite renderer are cloned at runtime and then different sprites are assigned to each instance
|
381
|
-
this._spriteSheet = this._spriteSheet.clone();
|
382
|
-
}
|
383
|
-
|
384
369
|
if (debug) {
|
385
370
|
console.log("Awake", this.name, this, this.sprite);
|
386
371
|
}
|
@@ -423,7 +408,6 @@
|
|
423
408
|
}
|
424
409
|
mat.transparent = true;
|
425
410
|
mat.toneMapped = this.toneMapped;
|
426
|
-
mat.depthWrite = false;
|
427
411
|
|
428
412
|
if (sprite.texture && !mat.wireframe) {
|
429
413
|
let tex = sprite.texture;
|
@@ -19,12 +19,7 @@
|
|
19
19
|
|
20
20
|
const builder = new flatbuffers.Builder();
|
21
21
|
|
22
|
-
/**
|
23
|
-
* Creates a flatbuffer model containing the transform data of a game object. Used by {@link SyncedTransform}
|
24
|
-
* @param guid The unique identifier of the object to sync
|
25
|
-
* @param b The behavior component containing transform data
|
26
|
-
* @param fast Whether to use fast mode synchronization (syncs more frequently)
|
27
|
-
* @returns A Uint8Array containing the serialized transform data
|
22
|
+
/** Creates a flatbuffer model containing the transform data of a game object. Used by {@link SyncedTransform}
|
28
23
|
*/
|
29
24
|
export function createTransformModel(guid: string, b: Behaviour, fast: boolean = true): Uint8Array {
|
30
25
|
builder.clear();
|
@@ -55,28 +50,18 @@
|
|
55
50
|
})
|
56
51
|
|
57
52
|
/**
|
58
|
-
* SyncedTransform
|
59
|
-
* It handles ownership transfer, interpolation, and network state updates automatically.
|
53
|
+
* SyncedTransform is a behaviour that syncs the transform of a game object over the network.
|
60
54
|
* @category Networking
|
61
55
|
* @group Components
|
62
56
|
*/
|
63
57
|
export class SyncedTransform extends Behaviour {
|
64
58
|
|
65
|
-
|
59
|
+
|
66
60
|
// public autoOwnership: boolean = true;
|
67
|
-
/** When true, overrides physics behavior when this object is owned by the local user */
|
68
61
|
public overridePhysics: boolean = true
|
69
|
-
|
70
|
-
/** Whether to smoothly interpolate position changes when receiving updates */
|
71
62
|
public interpolatePosition: boolean = true;
|
72
|
-
|
73
|
-
/** Whether to smoothly interpolate rotation changes when receiving updates */
|
74
63
|
public interpolateRotation: boolean = true;
|
75
|
-
|
76
|
-
/** When true, sends updates at a higher frequency, useful for fast-moving objects */
|
77
64
|
public fastMode: boolean = false;
|
78
|
-
|
79
|
-
/** When true, notifies other clients when this object is destroyed */
|
80
65
|
public syncDestroy: boolean = false;
|
81
66
|
|
82
67
|
// private _state!: SyncedTransformModel;
|
@@ -92,10 +77,7 @@
|
|
92
77
|
private _receivedFastUpdate: boolean = false;
|
93
78
|
private _shouldRequestOwnership: boolean = false;
|
94
79
|
|
95
|
-
/**
|
96
|
-
* Requests ownership of this object on the network.
|
97
|
-
* You need to be connected to a room for this to work.
|
98
|
-
*/
|
80
|
+
/** Request ownership of an object - you need to be connected to a room */
|
99
81
|
public requestOwnership() {
|
100
82
|
if (debug)
|
101
83
|
console.log("Request ownership");
|
@@ -107,18 +89,10 @@
|
|
107
89
|
this._model.requestOwnership();
|
108
90
|
}
|
109
91
|
|
110
|
-
/**
|
111
|
-
* Checks if this client has ownership of the object
|
112
|
-
* @returns true if this client has ownership, false if not, undefined if ownership state is unknown
|
113
|
-
*/
|
114
92
|
public hasOwnership(): boolean | undefined {
|
115
93
|
return this._model?.hasOwnership ?? undefined;
|
116
94
|
}
|
117
95
|
|
118
|
-
/**
|
119
|
-
* Checks if the object is owned by any client
|
120
|
-
* @returns true if the object is owned, false if not, undefined if ownership state is unknown
|
121
|
-
*/
|
122
96
|
public isOwned(): boolean | undefined {
|
123
97
|
return this._model?.isOwned;
|
124
98
|
}
|
@@ -166,9 +140,6 @@
|
|
166
140
|
this.context.connection.stopListenBinary(SyncedTransformIdentifier, this.receivedDataCallback);
|
167
141
|
}
|
168
142
|
|
169
|
-
/**
|
170
|
-
* Attempts to retrieve and apply the last known network state for this transform
|
171
|
-
*/
|
172
143
|
private tryGetLastState() {
|
173
144
|
const model = this.context.connection.tryGetState(this.guid) as unknown as SyncedTransformModel;
|
174
145
|
if (model) this.onReceivedData(model);
|
@@ -176,10 +147,6 @@
|
|
176
147
|
|
177
148
|
private tempEuler: Euler = new Euler();
|
178
149
|
|
179
|
-
/**
|
180
|
-
* Handles incoming network data for this transform
|
181
|
-
* @param data The model containing transform information
|
182
|
-
*/
|
183
150
|
private onReceivedData(data: SyncedTransformModel) {
|
184
151
|
if (this.destroyed) return;
|
185
152
|
if (typeof data.guid === "function" && data.guid() === this.guid) {
|
@@ -216,10 +183,7 @@
|
|
216
183
|
}
|
217
184
|
}
|
218
185
|
|
219
|
-
/**
|
220
|
-
* @internal
|
221
|
-
* Initializes tracking of position and rotation when component is enabled
|
222
|
-
*/
|
186
|
+
/** @internal */
|
223
187
|
onEnable(): void {
|
224
188
|
this.lastWorldPos.copy(this.worldPosition);
|
225
189
|
this.lastWorldRotation.copy(this.worldQuaternion);
|
@@ -230,10 +194,7 @@
|
|
230
194
|
}
|
231
195
|
}
|
232
196
|
|
233
|
-
/**
|
234
|
-
* @internal
|
235
|
-
* Releases ownership when component is disabled
|
236
|
-
*/
|
197
|
+
/** @internal */
|
237
198
|
onDisable(): void {
|
238
199
|
if (this._model)
|
239
200
|
this._model.freeOwnership();
|
@@ -244,11 +205,7 @@
|
|
244
205
|
private lastWorldPos!: Vector3;
|
245
206
|
private lastWorldRotation!: Quaternion;
|
246
207
|
|
247
|
-
/**
|
248
|
-
* @internal
|
249
|
-
* Handles transform synchronization before each render frame
|
250
|
-
* Sends updates when owner, receives and applies updates when not owner
|
251
|
-
*/
|
208
|
+
/** @internal */
|
252
209
|
onBeforeRender() {
|
253
210
|
if (!this.activeAndEnabled || !this.context.connection.isConnected) return;
|
254
211
|
// console.log("BEFORE RENDER", this.destroyed, this.guid, this._model?.isOwned, this.name, this.gameObject);
|
@@ -8,43 +8,27 @@
|
|
8
8
|
import { SyncedTransform } from "./SyncedTransform.js";
|
9
9
|
|
10
10
|
/**
|
11
|
-
* TransformGizmo
|
12
|
-
* It wraps three.js {@link TransformControls} and provides keyboard shortcuts for changing modes and settings.
|
11
|
+
* TransformGizmo is a component that displays a gizmo for transforming the object in the scene.
|
13
12
|
* @category Helpers
|
14
13
|
* @group Components
|
15
14
|
*/
|
16
15
|
export class TransformGizmo extends Behaviour {
|
17
16
|
|
18
|
-
/**
|
19
|
-
* When true, this is considered a helper gizmo and will only be shown if showGizmos is enabled in engine parameters.
|
20
|
-
*/
|
21
17
|
@serializable()
|
22
18
|
public isGizmo: boolean = false;
|
23
19
|
|
24
|
-
/**
|
25
|
-
* Specifies the translation grid snap value in world units.
|
26
|
-
* Applied when holding Shift while translating an object.
|
27
|
-
*/
|
28
20
|
@serializable()
|
29
21
|
public translationSnap: number = 1;
|
30
22
|
|
31
|
-
/**
|
32
|
-
* Specifies the rotation snap angle in degrees.
|
33
|
-
* Applied when holding Shift while rotating an object.
|
34
|
-
*/
|
35
23
|
@serializable()
|
36
24
|
public rotationSnapAngle: number = 15;
|
37
25
|
|
38
|
-
/**
|
39
|
-
* Specifies the scale snapping value.
|
40
|
-
* Applied when holding Shift while scaling an object.
|
41
|
-
*/
|
42
26
|
@serializable()
|
43
27
|
public scaleSnap: number = .25;
|
44
28
|
|
45
29
|
/**
|
46
|
-
*
|
47
|
-
* @returns The TransformControls instance
|
30
|
+
* Get the underlying three.js TransformControls instance.
|
31
|
+
* @returns The TransformControls instance.
|
48
32
|
*/
|
49
33
|
get control() {
|
50
34
|
return this._control;
|
@@ -97,10 +81,6 @@
|
|
97
81
|
window.removeEventListener('keyup', this.windowKeyUpListener);
|
98
82
|
}
|
99
83
|
|
100
|
-
/**
|
101
|
-
* Enables grid snapping for transform operations according to set snap values.
|
102
|
-
* This applies the translationSnap, rotationSnapAngle, and scaleSnap properties to the controls.
|
103
|
-
*/
|
104
84
|
enableSnapping() {
|
105
85
|
if (this._control) {
|
106
86
|
this._control.setTranslationSnap(this.translationSnap);
|
@@ -109,10 +89,6 @@
|
|
109
89
|
}
|
110
90
|
}
|
111
91
|
|
112
|
-
/**
|
113
|
-
* Disables grid snapping for transform operations.
|
114
|
-
* Removes all snapping constraints from the transform controls.
|
115
|
-
*/
|
116
92
|
disableSnapping() {
|
117
93
|
if (this._control) {
|
118
94
|
this._control.setTranslationSnap(null);
|
@@ -121,11 +97,6 @@
|
|
121
97
|
}
|
122
98
|
}
|
123
99
|
|
124
|
-
/**
|
125
|
-
* Event handler for when dragging state changes.
|
126
|
-
* Disables orbit controls during dragging and requests ownership of the transform if it's synchronized.
|
127
|
-
* @param event The drag change event
|
128
|
-
*/
|
129
100
|
private onControlChangedEvent = (event) => {
|
130
101
|
const orbit = this.orbit;
|
131
102
|
if (orbit) orbit.enabled = !event.value;
|
@@ -138,18 +109,7 @@
|
|
138
109
|
}
|
139
110
|
}
|
140
111
|
|
141
|
-
|
142
|
-
* Handles keyboard shortcuts for transform operations:
|
143
|
-
* - Q: Toggle local/world space
|
144
|
-
* - W: Translation mode
|
145
|
-
* - E: Rotation mode
|
146
|
-
* - R: Scale mode
|
147
|
-
* - Shift: Enable snapping (while held)
|
148
|
-
* - +/-: Adjust gizmo size
|
149
|
-
* - X/Y/Z: Toggle visibility of respective axis
|
150
|
-
* - Spacebar: Toggle controls enabled state
|
151
|
-
* @param event The keyboard event
|
152
|
-
*/
|
112
|
+
|
153
113
|
private windowKeyDownListener = (event) => {
|
154
114
|
if (!this.enabled) return;
|
155
115
|
if (!this._control) return;
|
@@ -202,11 +162,6 @@
|
|
202
162
|
}
|
203
163
|
}
|
204
164
|
|
205
|
-
/**
|
206
|
-
* Handles keyboard key release events.
|
207
|
-
* Currently only handles releasing Shift key to disable snapping.
|
208
|
-
* @param event The keyboard event
|
209
|
-
*/
|
210
165
|
private windowKeyUpListener = (event) => {
|
211
166
|
if (!this.enabled) return;
|
212
167
|
switch (event.keyCode) {
|
@@ -62,11 +62,11 @@
|
|
62
62
|
* Add a post processing effect to the stack and schedules the effect stack to be re-created.
|
63
63
|
*/
|
64
64
|
addEffect<T extends PostProcessingEffect | Effect>(effect: T): T {
|
65
|
+
|
65
66
|
let entry = effect as PostProcessingEffect;
|
66
67
|
if (!(entry instanceof PostProcessingEffect)) {
|
67
68
|
entry = new EffectWrapper(entry);
|
68
69
|
}
|
69
|
-
if(entry.gameObject === undefined) this.gameObject.addComponent(entry);
|
70
70
|
if (this._effects.includes(entry)) return effect;
|
71
71
|
this._effects.push(entry);
|
72
72
|
this._isDirty = true;
|
@@ -125,7 +125,7 @@
|
|
125
125
|
}
|
126
126
|
|
127
127
|
// ensure the profile is initialized
|
128
|
-
this.sharedProfile?.
|
128
|
+
this.sharedProfile?.init();
|
129
129
|
}
|
130
130
|
|
131
131
|
onEnable(): void {
|
@@ -190,7 +190,7 @@
|
|
190
190
|
private _isDirty: boolean = false;
|
191
191
|
|
192
192
|
private apply() {
|
193
|
-
if (debug) console.log(
|
193
|
+
if (debug) console.log("Apply PostProcessing " + this.name);
|
194
194
|
|
195
195
|
if (isDevEnvironment()) {
|
196
196
|
if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
|
@@ -209,16 +209,18 @@
|
|
209
209
|
if (this.sharedProfile?.components) {
|
210
210
|
const comps = this.sharedProfile.components;
|
211
211
|
for (const effect of comps) {
|
212
|
-
if (effect.active &&
|
212
|
+
if (effect.active && !this._activeEffects.includes(effect))
|
213
213
|
this._activeEffects.push(effect);
|
214
214
|
}
|
215
215
|
}
|
216
216
|
// add effects registered via code
|
217
217
|
for (const effect of this._effects) {
|
218
|
-
if (effect.active &&
|
218
|
+
if (effect.active && !this._activeEffects.includes(effect))
|
219
219
|
this._activeEffects.push(effect);
|
220
220
|
}
|
221
221
|
|
222
|
+
if (debug) console.log("Apply PostProcessing", this._activeEffects);
|
223
|
+
|
222
224
|
if (this._activeEffects.length > 0) {
|
223
225
|
if (!this._postprocessing)
|
224
226
|
this._postprocessing = new PostProcessingHandler(this.context);
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import { serializeable } from "../../engine/engine_serialization_decorator.js";
|
2
2
|
import { getParam } from "../../engine/engine_utils.js";
|
3
|
-
import { Component } from "../Component.js";
|
4
3
|
import { PostProcessingEffect } from "./PostProcessingEffect.js";
|
5
|
-
import type { Volume } from "./Volume.js";
|
6
4
|
|
7
5
|
const debug = getParam("debugpost");
|
8
6
|
|
@@ -40,14 +38,8 @@
|
|
40
38
|
* call init on all components
|
41
39
|
* @hidden
|
42
40
|
**/
|
43
|
-
|
44
|
-
this.components?.forEach(c =>
|
45
|
-
// Make sure all components are added to the gameobject (this is not the case when e.g. effects are serialized from Unity)
|
46
|
-
if (c.gameObject === undefined) {
|
47
|
-
owner.gameObject.addComponent(c);
|
48
|
-
}
|
49
|
-
c.init()
|
50
|
-
});
|
41
|
+
init() {
|
42
|
+
this.components?.forEach(c => c.init());
|
51
43
|
}
|
52
44
|
|
53
45
|
addEffect(effect: PostProcessingEffect) {
|
@@ -49,25 +49,18 @@
|
|
49
49
|
}
|
50
50
|
}
|
51
51
|
|
52
|
-
private static _hasPlaced: boolean = false;
|
53
|
-
/**
|
54
|
-
* @returns true if the scene has been placed in AR by the user or automatic placement
|
55
|
-
*/
|
56
|
-
static get hasPlaced(): boolean {
|
57
|
-
return this._hasPlaced;
|
58
|
-
}
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
* **NOTE**: a large value makes the scene appear smaller
|
53
|
+
/** The scale of a user in AR:
|
54
|
+
* Note: a large value makes the scene appear smaller
|
63
55
|
* @default 1
|
64
56
|
*/
|
65
57
|
get arScale(): number {
|
66
58
|
return this._arScale;
|
67
59
|
}
|
68
60
|
set arScale(val: number) {
|
69
|
-
|
70
|
-
this.
|
61
|
+
if (val === this._arScale) return;
|
62
|
+
this._arScale = val;
|
63
|
+
this.onScaleChanged();
|
71
64
|
}
|
72
65
|
private _arScale: number = 1;
|
73
66
|
|
@@ -141,7 +134,6 @@
|
|
141
134
|
if (debug) console.log("ENTER WEBXR: SessionRoot start...");
|
142
135
|
|
143
136
|
this._anchor = null;
|
144
|
-
WebARSessionRoot._hasPlaced = false;
|
145
137
|
|
146
138
|
// if (_args.xr.session.enabledFeatures?.includes("image-tracking")) {
|
147
139
|
// console.warn("Image tracking is enabled - will not place scene");
|
@@ -201,7 +193,6 @@
|
|
201
193
|
this.onRevertSceneChanges();
|
202
194
|
// this._anchor?.delete();
|
203
195
|
this._anchor = null;
|
204
|
-
WebARSessionRoot._hasPlaced = false;
|
205
196
|
this._rigPlacementMatrix = undefined;
|
206
197
|
}
|
207
198
|
onUpdateXR(args: NeedleXREventArgs): void {
|
@@ -414,7 +405,6 @@
|
|
414
405
|
reticle.quaternion.copy(reticle["lastQuat"]);
|
415
406
|
|
416
407
|
this.onApplyPose(reticle);
|
417
|
-
WebARSessionRoot._hasPlaced = true;
|
418
408
|
|
419
409
|
if (this.useXRAnchor) {
|
420
410
|
this.onCreateAnchor(NeedleXRSession.active!, hit);
|
@@ -427,16 +417,8 @@
|
|
427
417
|
}
|
428
418
|
}
|
429
419
|
|
430
|
-
private
|
431
|
-
|
432
|
-
const rig = NeedleXRSession.active?.rig?.gameObject;
|
433
|
-
if (rig) {
|
434
|
-
const currentScale = NeedleXRSession.active?.rigScale || 1;
|
435
|
-
const newScale = (1 / this._arScale) * currentScale;
|
436
|
-
const scaleMatrix = new Matrix4().makeScale(newScale, newScale, newScale).invert();
|
437
|
-
rig.matrix.premultiply(scaleMatrix);
|
438
|
-
rig.matrix.decompose(rig.position, rig.quaternion, rig.scale);
|
439
|
-
}
|
420
|
+
private onScaleChanged() {
|
421
|
+
// TODO: implement
|
440
422
|
}
|
441
423
|
|
442
424
|
private onRevertSceneChanges() {
|
@@ -534,7 +516,7 @@
|
|
534
516
|
console.warn("No rig object to place");
|
535
517
|
return;
|
536
518
|
}
|
537
|
-
|
519
|
+
const rigScale = NeedleXRSession.active?.rigScale || 1;
|
538
520
|
|
539
521
|
// save the previous rig parent
|
540
522
|
const previousParent = rigObject.parent || this.context.scene;
|
@@ -596,10 +578,6 @@
|
|
596
578
|
private _scale: number = 1;
|
597
579
|
private _hasChanged: boolean = false;
|
598
580
|
|
599
|
-
get scale() {
|
600
|
-
return this._scale;
|
601
|
-
}
|
602
|
-
|
603
581
|
// readonly translate: Vector3 = new Vector3();
|
604
582
|
// readonly rotation: Quaternion = new Quaternion();
|
605
583
|
// readonly scale: Vector3 = new Vector3(1, 1, 1);
|
@@ -616,7 +594,6 @@
|
|
616
594
|
reset() {
|
617
595
|
this._scale = 1;
|
618
596
|
this.offset.identity();
|
619
|
-
this._hasChanged = true;
|
620
597
|
}
|
621
598
|
get hasChanged() { return this._hasChanged; }
|
622
599
|
|
@@ -31,127 +31,74 @@
|
|
31
31
|
export class WebXR extends Behaviour {
|
32
32
|
|
33
33
|
// UI
|
34
|
-
/**
|
35
|
-
* When enabled, a button will be automatically added to {@link NeedleMenu} that allows users to enter VR mode.
|
36
|
-
*/
|
34
|
+
/** When enabled a button will be added to the UI to enter VR */
|
37
35
|
@serializable()
|
38
36
|
createVRButton: boolean = true;
|
39
|
-
|
40
|
-
/**
|
41
|
-
* When enabled, a button will be automatically added to {@link NeedleMenu} that allows users to enter AR mode.
|
42
|
-
*/
|
37
|
+
/** When enabled a button will be added to the UI to enter AR */
|
43
38
|
@serializable()
|
44
39
|
createARButton: boolean = true;
|
45
|
-
|
46
|
-
/**
|
47
|
-
* When enabled, a button to send the experience to an Oculus Quest will be shown if the current device does not support VR.
|
48
|
-
* This helps direct users to compatible devices for optimal VR experiences.
|
49
|
-
*/
|
40
|
+
/** When enabled a send to quest button will be shown if the device does not support VR */
|
50
41
|
@serializable()
|
51
42
|
createSendToQuestButton: boolean = true;
|
52
|
-
|
53
|
-
/**
|
54
|
-
* When enabled, a QR code will be generated and displayed on desktop devices to allow easy opening of the experience on mobile devices.
|
55
|
-
*/
|
43
|
+
/** When enabled a QRCode will be created to open the website on a mobile device */
|
56
44
|
@serializable()
|
57
45
|
createQRCode: boolean = true;
|
58
46
|
|
59
47
|
// VR Settings
|
60
|
-
/**
|
61
|
-
* When enabled, default movement controls will be automatically added to the scene when entering VR.
|
62
|
-
* This includes teleportation and smooth locomotion options for VR controllers.
|
63
|
-
*/
|
48
|
+
/** When enabled default movement behaviour will be added */
|
64
49
|
@serializable()
|
65
50
|
useDefaultControls: boolean = true;
|
66
|
-
|
67
|
-
/**
|
68
|
-
* When enabled, 3D models representing the user's VR controllers will be automatically created and rendered in the scene.
|
69
|
-
*/
|
51
|
+
/** When enabled controller models will automatically be created and updated when you are using controllers in WebXR */
|
70
52
|
@serializable()
|
71
53
|
showControllerModels: boolean = true;
|
72
|
-
|
73
|
-
/**
|
74
|
-
* When enabled, 3D models representing the user's hands will be automatically created and rendered when hand tracking is available.
|
75
|
-
*/
|
54
|
+
/** When enabled hand models will automatically be created and updated when you are using hands in WebXR */
|
76
55
|
@serializable()
|
77
56
|
showHandModels: boolean = true;
|
78
57
|
|
79
58
|
// AR Settings
|
80
|
-
/**
|
81
|
-
* When enabled, a reticle will be displayed to help place the scene in AR. The user must tap on a detected surface to position the scene.
|
82
|
-
*/
|
59
|
+
/** When enabled the scene must be placed in AR */
|
83
60
|
@serializable()
|
84
61
|
usePlacementReticle: boolean = true;
|
85
|
-
|
86
|
-
/**
|
87
|
-
* Optional custom 3D object to use as the AR placement reticle instead of the default one.
|
88
|
-
*/
|
62
|
+
/** When assigned this object will be used as the AR placement reticle */
|
89
63
|
@serializable(AssetReference)
|
90
64
|
customARPlacementReticle?: AssetReference;
|
91
|
-
|
92
|
-
/**
|
93
|
-
* When enabled, users can adjust the position, rotation, and scale of the AR scene with one or two fingers after initial placement.
|
94
|
-
*/
|
65
|
+
/** When enabled you can position, rotate or scale your AR scene with one or two fingers */
|
95
66
|
@serializable()
|
96
67
|
usePlacementAdjustment: boolean = true;
|
97
|
-
|
98
|
-
/**
|
99
|
-
* Determines the scale of the user relative to the scene in AR. Larger values make the 3D content appear smaller.
|
100
|
-
* Only applies when `usePlacementReticle` is enabled.
|
101
|
-
*/
|
68
|
+
/** Used when `usePlacementReticle` is enabled. This is the scale of the user in the scene in AR. Larger values make the 3D content appear smaller */
|
102
69
|
@serializable()
|
103
70
|
arScale: number = 1;
|
104
|
-
|
105
|
-
/**
|
106
|
-
* When enabled, an XRAnchor will be created for the AR scene and its position will be regularly updated to match the anchor.
|
107
|
-
* This can help with spatial persistence in AR experiences.
|
108
|
-
* @experimental
|
109
|
-
*/
|
71
|
+
/** Experimental: When enabled an XRAnchor will be created for the AR scene and the position will be updated to the anchor position every few frames */
|
110
72
|
@serializable()
|
111
73
|
useXRAnchor: boolean = false;
|
112
|
-
|
113
74
|
/**
|
114
|
-
* When enabled
|
115
|
-
* without requiring the user to tap to confirm placement.
|
75
|
+
* When enabled the scene will be placed automatically when a point in the real world is found
|
116
76
|
*/
|
117
77
|
@serializable()
|
118
|
-
autoPlace: boolean =
|
119
|
-
|
120
|
-
/**
|
121
|
-
* When enabled, the AR session root center will be automatically adjusted to place the center of the scene.
|
122
|
-
* This helps ensure the scene is properly aligned with detected surfaces.
|
123
|
-
*/
|
78
|
+
autoPlace: boolean = true;
|
79
|
+
/** When enabled the AR session root center will be automatically adjusted to place the center of the scene */
|
124
80
|
@serializable()
|
125
81
|
autoCenter: boolean = false;
|
126
82
|
|
127
|
-
/**
|
128
|
-
* When enabled, a USDZExporter component will be automatically added to the scene if none is found.
|
129
|
-
* This allows iOS and visionOS devices to view 3D content using Apple's AR QuickLook.
|
130
|
-
*/
|
83
|
+
/** When enabled a USDZExporter component will be added to the scene (if none is found) */
|
131
84
|
@serializable()
|
132
85
|
useQuicklookExport: boolean = false;
|
133
86
|
|
134
|
-
|
135
|
-
|
136
|
-
*
|
137
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/XRDepthInformation
|
138
|
-
* @experimental
|
87
|
+
|
88
|
+
/** Preview feature enabling occlusion (when available: https://github.com/cabanier/three.js/commit/b6ee92bcd8f20718c186120b7f19a3b68a1d4e47)
|
89
|
+
* Enables the 'depth-sensing' WebXR feature to provide realtime depth occlusion. Only supported on Oculus Quest right now.
|
139
90
|
*/
|
140
91
|
@serializable()
|
141
92
|
useDepthSensing: boolean = false;
|
142
93
|
|
143
94
|
/**
|
144
|
-
* When enabled
|
145
|
-
* allowing users to interact with objects at a distance in VR/AR.
|
95
|
+
* When enabled the spatial grab raycaster will be added or enabled in the scene
|
146
96
|
* @default true
|
147
97
|
*/
|
148
98
|
@serializable()
|
149
99
|
useSpatialGrab: boolean = true;
|
150
100
|
|
151
|
-
/**
|
152
|
-
* Specifies the avatar representation that will be created when entering a WebXR session.
|
153
|
-
* Can be a reference to a 3D model or a boolean to use the default avatar.
|
154
|
-
*/
|
101
|
+
/** This avatar representation will be spawned when you enter a webxr session */
|
155
102
|
@serializable(AssetReference)
|
156
103
|
defaultAvatar?: AssetReference | boolean;
|
157
104
|
|
@@ -164,19 +111,10 @@
|
|
164
111
|
|
165
112
|
static activeWebXRComponent: WebXR | null = null;
|
166
113
|
|
167
|
-
/**
|
168
|
-
* Initializes the WebXR component by obtaining the XR sync object for this context.
|
169
|
-
* @internal
|
170
|
-
*/
|
171
114
|
awake() {
|
172
115
|
NeedleXRSession.getXRSync(this.context);
|
173
116
|
}
|
174
117
|
|
175
|
-
/**
|
176
|
-
* Sets up the WebXR component when it's enabled. Checks for HTTPS connection,
|
177
|
-
* sets up USDZ export if enabled, creates UI buttons, and configures avatar settings.
|
178
|
-
* @internal
|
179
|
-
*/
|
180
118
|
onEnable(): void {
|
181
119
|
// check if we're on a secure connection:
|
182
120
|
if (window.location.protocol !== "https:") {
|
@@ -215,21 +153,11 @@
|
|
215
153
|
}
|
216
154
|
}
|
217
155
|
|
218
|
-
/**
|
219
|
-
* Cleans up resources when the component is disabled.
|
220
|
-
* Destroys the USDZ exporter if one was created and removes UI buttons.
|
221
|
-
* @internal
|
222
|
-
*/
|
223
156
|
onDisable(): void {
|
224
157
|
this._usdzExporter?.destroy();
|
225
158
|
this.removeButtons();
|
226
159
|
}
|
227
160
|
|
228
|
-
/**
|
229
|
-
* Checks if WebXR is supported and offers an appropriate session.
|
230
|
-
* This is used to show the WebXR session joining prompt in browsers that support it.
|
231
|
-
* @returns A Promise that resolves to true if a session was offered, false otherwise
|
232
|
-
*/
|
233
161
|
private async handleOfferSession() {
|
234
162
|
if (this.createVRButton) {
|
235
163
|
const hasVRSupport = await NeedleXRSession.isVRSupported();
|
@@ -254,32 +182,16 @@
|
|
254
182
|
get sessionMode(): XRSessionMode | null {
|
255
183
|
return NeedleXRSession.activeMode ?? null;;
|
256
184
|
}
|
257
|
-
/** While AR: this will return the currently active WebARSessionRoot component.
|
258
|
-
* You can also query this component in your scene with `findObjectOfType(WebARSessionRoot)`
|
259
|
-
*/
|
260
|
-
get arSessionRoot() {
|
261
|
-
return this._activeWebARSessionRoot;
|
262
|
-
}
|
263
185
|
|
264
|
-
/** Call to start an WebVR session
|
265
|
-
*
|
266
|
-
* This is a shorthand for `NeedleXRSession.start("immersive-vr", init, this.context)`
|
267
|
-
*/
|
186
|
+
/** Call to start an WebVR session */
|
268
187
|
async enterVR(init?: XRSessionInit): Promise<NeedleXRSession | null> {
|
269
188
|
return NeedleXRSession.start("immersive-vr", init, this.context);
|
270
189
|
}
|
271
|
-
/** Call to start an WebAR session
|
272
|
-
*
|
273
|
-
* This is a shorthand for `NeedleXRSession.start("immersive-ar", init, this.context)`
|
274
|
-
*/
|
190
|
+
/** Call to start an WebAR session */
|
275
191
|
async enterAR(init?: XRSessionInit): Promise<NeedleXRSession | null> {
|
276
192
|
return NeedleXRSession.start("immersive-ar", init, this.context);
|
277
193
|
}
|
278
|
-
|
279
|
-
/** Call to end a WebXR (AR or VR) session.
|
280
|
-
*
|
281
|
-
* This is a shorthand for `NeedleXRSession.stop()`
|
282
|
-
*/
|
194
|
+
/** Call to end a WebXR (AR or VR) session */
|
283
195
|
exitXR() {
|
284
196
|
NeedleXRSession.stop();
|
285
197
|
}
|
@@ -287,18 +199,11 @@
|
|
287
199
|
private _exitXRMenuButton?: HTMLElement;
|
288
200
|
private _previousXRState: number = 0;
|
289
201
|
private _spatialGrabRaycaster?: SpatialGrabRaycaster;
|
290
|
-
private _activeWebARSessionRoot: WebARSessionRoot | null = null;
|
291
202
|
|
292
203
|
private get isActiveWebXR() {
|
293
204
|
return !WebXR.activeWebXRComponent || WebXR.activeWebXRComponent === this;
|
294
205
|
}
|
295
206
|
|
296
|
-
/**
|
297
|
-
* Called before entering a WebXR session. Sets up optional features like depth sensing, if needed.
|
298
|
-
* @param _mode The XR session mode being requested (immersive-ar or immersive-vr)
|
299
|
-
* @param args The XRSessionInit object that will be passed to the WebXR API
|
300
|
-
* @internal
|
301
|
-
*/
|
302
207
|
onBeforeXR(_mode: XRSessionMode, args: XRSessionInit): void {
|
303
208
|
if (!this.isActiveWebXR) {
|
304
209
|
console.warn(`WebXR: another WebXR component is already active (${WebXR.activeWebXRComponent?.name}). This is ignored: ${this.name}`);
|
@@ -312,12 +217,6 @@
|
|
312
217
|
}
|
313
218
|
}
|
314
219
|
|
315
|
-
/**
|
316
|
-
* Called when a WebXR session begins. Sets up the scene for XR by configuring controllers,
|
317
|
-
* AR placement, and other features based on component settings.
|
318
|
-
* @param args Event arguments containing information about the started XR session
|
319
|
-
* @internal
|
320
|
-
*/
|
321
220
|
async onEnterXR(args: NeedleXREventArgs) {
|
322
221
|
if (!this.isActiveWebXR) return;
|
323
222
|
|
@@ -345,7 +244,6 @@
|
|
345
244
|
}
|
346
245
|
}
|
347
246
|
|
348
|
-
this._activeWebARSessionRoot = sessionroot;
|
349
247
|
if (sessionroot) {
|
350
248
|
// sessionroot.enabled = this.usePlacementReticle; // < not sure if we want to disable the session root when placement reticle if OFF...
|
351
249
|
sessionroot.customReticle = this.customARPlacementReticle;
|
@@ -390,12 +288,6 @@
|
|
390
288
|
}
|
391
289
|
}
|
392
290
|
|
393
|
-
/**
|
394
|
-
* Called every frame during an active WebXR session.
|
395
|
-
* Updates components that depend on the current XR state.
|
396
|
-
* @param _args Event arguments containing information about the current XR session frame
|
397
|
-
* @internal
|
398
|
-
*/
|
399
291
|
onUpdateXR(_args: NeedleXREventArgs): void {
|
400
292
|
if (!this.isActiveWebXR) return;
|
401
293
|
if (this._spatialGrabRaycaster) {
|
@@ -403,12 +295,6 @@
|
|
403
295
|
}
|
404
296
|
}
|
405
297
|
|
406
|
-
/**
|
407
|
-
* Called when a WebXR session ends. Restores pre-session state,
|
408
|
-
* removes temporary components, and cleans up resources.
|
409
|
-
* @param _ Event arguments containing information about the ended XR session
|
410
|
-
* @internal
|
411
|
-
*/
|
412
298
|
onLeaveXR(_: NeedleXREventArgs): void {
|
413
299
|
this._exitXRMenuButton?.remove();
|
414
300
|
|
@@ -424,8 +310,6 @@
|
|
424
310
|
}
|
425
311
|
this._createdComponentsInSession.length = 0;
|
426
312
|
|
427
|
-
this._activeWebARSessionRoot = null;
|
428
|
-
|
429
313
|
this.handleOfferSession();
|
430
314
|
|
431
315
|
delayForFrames(1).then(() => WebXR.activeWebXRComponent = null);
|
@@ -455,10 +339,8 @@
|
|
455
339
|
return models;
|
456
340
|
}
|
457
341
|
|
458
|
-
|
459
|
-
|
460
|
-
* @param xr The active session
|
461
|
-
*/
|
342
|
+
|
343
|
+
|
462
344
|
protected async createLocalAvatar(xr: NeedleXRSession) {
|
463
345
|
if (this._playerSync && xr.running && typeof this.defaultAvatar != "boolean") {
|
464
346
|
this._playerSync.asset = this.defaultAvatar;
|
@@ -466,11 +348,6 @@
|
|
466
348
|
}
|
467
349
|
}
|
468
350
|
|
469
|
-
/**
|
470
|
-
* Event handler called when a player avatar is spawned.
|
471
|
-
* Ensures the avatar has the necessary Avatar component.
|
472
|
-
* @param instance The spawned avatar 3D object
|
473
|
-
*/
|
474
351
|
private onAvatarSpawned = (instance: Object3D) => {
|
475
352
|
// spawned webxr avatars must have a avatar component
|
476
353
|
if (debug) console.log("WebXR.onAvatarSpawned", instance);
|
@@ -483,16 +360,13 @@
|
|
483
360
|
|
484
361
|
// HTML UI
|
485
362
|
|
486
|
-
/** @deprecated use
|
363
|
+
/** @deprecated use `getButtonsFactory()` or access `WebXRButtonFactory.getOrCreate()` directory */
|
487
364
|
getButtonsContainer(): WebXRButtonFactory {
|
488
365
|
return this.getButtonsFactory();
|
489
366
|
}
|
490
367
|
|
491
|
-
/**
|
492
|
-
*
|
493
|
-
* Use this to access and modify WebXR UI buttons.
|
494
|
-
* @returns The WebXRButtonFactory instance
|
495
|
-
*/
|
368
|
+
/** Calling this function will get the Needle WebXR button factory (it will be created if it doesnt exist yet)
|
369
|
+
* @returns the Needle WebXR button factory */
|
496
370
|
getButtonsFactory(): WebXRButtonFactory {
|
497
371
|
if (!this._buttonFactory) {
|
498
372
|
this._buttonFactory = WebXRButtonFactory.getOrCreate();
|
@@ -500,15 +374,8 @@
|
|
500
374
|
return this._buttonFactory;
|
501
375
|
}
|
502
376
|
|
503
|
-
/**
|
504
|
-
* Reference to the WebXR button factory used by this component.
|
505
|
-
*/
|
506
377
|
private _buttonFactory?: WebXRButtonFactory;
|
507
378
|
|
508
|
-
/**
|
509
|
-
* Creates and sets up UI elements for WebXR interaction based on component settings
|
510
|
-
* and device capabilities. Handles creating AR, VR, QuickLook buttons and utility buttons like QR codes.
|
511
|
-
*/
|
512
379
|
private handleCreatingHTML() {
|
513
380
|
const xrButtonsPriority = 50;
|
514
381
|
|
@@ -556,25 +423,14 @@
|
|
556
423
|
}
|
557
424
|
}
|
558
425
|
|
559
|
-
/**
|
560
|
-
* Storage for UI buttons created by this component.
|
561
|
-
*/
|
562
426
|
private readonly _buttons: HTMLElement[] = [];
|
563
427
|
|
564
|
-
/**
|
565
|
-
* Adds a button to the UI with the specified priority.
|
566
|
-
* @param button The HTML element to add
|
567
|
-
* @param priority The button's priority value (lower numbers appear first)
|
568
|
-
*/
|
569
428
|
private addButton(button: HTMLElement, priority: number) {
|
570
429
|
this._buttons.push(button);
|
571
430
|
button.setAttribute("priority", priority.toString());
|
572
431
|
this.context.menu.appendChild(button);
|
573
432
|
}
|
574
433
|
|
575
|
-
/**
|
576
|
-
* Removes all buttons created by this component from the UI.
|
577
|
-
*/
|
578
434
|
private removeButtons() {
|
579
435
|
for (const button of this._buttons) {
|
580
436
|
button.remove();
|