@@ -45,11 +45,17 @@
|
|
45
45
|
}
|
46
46
|
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
/**
|
49
|
+
* Find a registered AssetReference by its URL
|
50
|
+
*/
|
51
|
+
findAssetReference(url: string): AssetReference | null {
|
52
|
+
return this._assetReferences[url] || null;
|
50
53
|
}
|
51
54
|
|
52
|
-
/**
|
55
|
+
/**
|
56
|
+
* Register an asset reference
|
57
|
+
* @internal
|
58
|
+
*/
|
53
59
|
registerAssetReference(ref: AssetReference): AssetReference {
|
54
60
|
if (!ref.uri) return ref;
|
55
61
|
if (!this._assetReferences[ref.uri]) {
|
@@ -83,8 +89,32 @@
|
|
83
89
|
*/
|
84
90
|
export class AssetReference {
|
85
91
|
|
92
|
+
/**
|
93
|
+
* Experimental!
|
94
|
+
* @internal
|
95
|
+
* Get an AssetReference for a URL to be easily loaded.
|
96
|
+
* AssetReferences are cached so calling this method multiple times with the same arguments will always return the same AssetReference.
|
97
|
+
* @param url The URL of the asset to load. The url can be relative or absolute.
|
98
|
+
* @param context The context to use for loading the asset
|
99
|
+
* @returns the AssetReference for the URL
|
100
|
+
*/
|
101
|
+
static getOrCreateFromUrl(url: string, context?: Context): AssetReference {
|
102
|
+
if (!context) {
|
103
|
+
context = Context.Current;
|
104
|
+
if (!context)
|
105
|
+
throw new Error("Context is required when sourceId is a string. When you call this method from a component you can call it with \"getOrCreate(this, url)\" where \"this\" is the component.");
|
106
|
+
}
|
107
|
+
const addressables = context.addressables;
|
108
|
+
const existing = addressables.findAssetReference(url);
|
109
|
+
if (existing) return existing;
|
110
|
+
const ref = new AssetReference(url, context.hash);
|
111
|
+
addressables.registerAssetReference(ref);
|
112
|
+
return ref;
|
113
|
+
}
|
114
|
+
|
86
115
|
/**
|
87
|
-
* Get an AssetReference for a URL to be easily loaded.
|
116
|
+
* Get an AssetReference for a URL to be easily loaded.
|
117
|
+
* AssetReferences are cached so calling this method multiple times with the same arguments will always return the same AssetReference.
|
88
118
|
*/
|
89
119
|
static getOrCreate(sourceId: SourceIdentifier | IComponent, url: string, context?: Context): AssetReference {
|
90
120
|
|
@@ -515,6 +545,7 @@
|
|
515
545
|
}
|
516
546
|
|
517
547
|
|
548
|
+
/** @internal */
|
518
549
|
export class ImageReferenceSerializer extends TypeSerializer {
|
519
550
|
constructor() {
|
520
551
|
super([ImageReference], "ImageReferenceSerializer");
|
@@ -578,6 +609,7 @@
|
|
578
609
|
}
|
579
610
|
|
580
611
|
|
612
|
+
/** @internal */
|
581
613
|
export class FileReferenceSerializer extends TypeSerializer {
|
582
614
|
constructor() {
|
583
615
|
super([FileReference], "FileReferenceSerializer");
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Intersection,Matrix4, Object3D, Ray, Vector2, Vector3 } from 'three';
|
1
|
+
import { Intersection, Matrix4, Object3D, Ray, Vector2, Vector3 } from 'three';
|
2
2
|
|
3
3
|
import { showBalloonMessage, showBalloonWarning } from './debug/debug.js';
|
4
4
|
import { Context } from './engine_setup.js';
|
@@ -207,6 +207,9 @@
|
|
207
207
|
signal?: AbortSignal;
|
208
208
|
}
|
209
209
|
|
210
|
+
/**
|
211
|
+
* The input system is responsible for handling all input events like pointer events (mouse, touch, xr controllers) and keyboard events.
|
212
|
+
*/
|
210
213
|
export class Input implements IInput {
|
211
214
|
|
212
215
|
/** This is a list of event listeners per event type (e.g. pointerdown, pointerup, keydown...). Each entry contains a priority and list of listeners.
|
@@ -649,6 +652,7 @@
|
|
649
652
|
vec2.y = -((vec2.y - this.context.domY) / this.context.domHeight) * 2 + 1;
|
650
653
|
}
|
651
654
|
|
655
|
+
/** @internal */
|
652
656
|
constructor(context: Context) {
|
653
657
|
this.context = context;
|
654
658
|
this.context.post_render_callbacks.push(this.onEndOfFrame);
|
@@ -6,6 +6,9 @@
|
|
6
6
|
export const $instancingRenderer = Symbol("instancingRenderer");
|
7
7
|
export const $instancingAutoUpdateBounds = Symbol("instancingAutoUpdateBounds");
|
8
8
|
|
9
|
+
/**
|
10
|
+
* Utility class for accessing instancing related properties
|
11
|
+
*/
|
9
12
|
export class InstancingUtil {
|
10
13
|
|
11
14
|
/** Is this object rendered using a InstancedMesh */
|
@@ -10,6 +10,7 @@
|
|
10
10
|
let NEEDLE_ENGINE_LICENSE_TYPE: string = "basic";
|
11
11
|
if (debug) console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
|
12
12
|
|
13
|
+
/** @internal */
|
13
14
|
export function hasProLicense() {
|
14
15
|
switch (NEEDLE_ENGINE_LICENSE_TYPE) {
|
15
16
|
case "pro":
|
@@ -19,6 +20,7 @@
|
|
19
20
|
return false;
|
20
21
|
}
|
21
22
|
|
23
|
+
/** @internal */
|
22
24
|
export function hasIndieLicense() {
|
23
25
|
switch (NEEDLE_ENGINE_LICENSE_TYPE) {
|
24
26
|
case "indie":
|
@@ -27,11 +29,13 @@
|
|
27
29
|
return false;
|
28
30
|
}
|
29
31
|
|
32
|
+
/** @internal */
|
30
33
|
export function hasCommercialLicense() {
|
31
34
|
return hasProLicense() || hasIndieLicense();
|
32
35
|
}
|
33
36
|
|
34
37
|
const _licenseCheckResultChangedCallbacks: ((result: boolean) => void)[] = [];
|
38
|
+
/** @internal */
|
35
39
|
export function onLicenseCheckResultChanged(cb: (result: boolean) => void) {
|
36
40
|
if (hasProLicense() || hasIndieLicense())
|
37
41
|
return cb(true);
|
@@ -151,6 +151,8 @@
|
|
151
151
|
|
152
152
|
// when a file is instantiated via some server (e.g. via file drop) we also want to send the info where the file can be downloaded
|
153
153
|
// doing it this route will ensure we have
|
154
|
+
|
155
|
+
/** @internal */
|
154
156
|
export class HostData {
|
155
157
|
filename: string;
|
156
158
|
hash: string;
|
@@ -15,6 +15,7 @@
|
|
15
15
|
import { NEEDLE_components } from "./extensions/NEEDLE_components.js";
|
16
16
|
|
17
17
|
|
18
|
+
/** @internal */
|
18
19
|
export class NeedleGltfLoader implements INeedleGltfLoader {
|
19
20
|
createBuiltinComponents(context: Context, gltfId: string, gltf: any, seed: number | UIDProvider | null, extension?: NEEDLE_components | undefined) {
|
20
21
|
return createBuiltinComponents(context, gltfId, gltf, seed, extension);
|
@@ -103,6 +104,13 @@
|
|
103
104
|
return loader;
|
104
105
|
}
|
105
106
|
|
107
|
+
/** Load a gltf file from a url. This is the core method used by Needle Engine to load gltf files. All known extensions are registered here.
|
108
|
+
* @param context The current context
|
109
|
+
* @param data The gltf data as string or ArrayBuffer
|
110
|
+
* @param path The path to the gltf file
|
111
|
+
* @param seed The seed for generating unique ids
|
112
|
+
* @returns The loaded gltf object
|
113
|
+
*/
|
106
114
|
export async function parseSync(context: Context, data: string | ArrayBuffer, path: string, seed: number | UIDProvider | null): Promise<GLTF | undefined> {
|
107
115
|
if (typeof path !== "string") {
|
108
116
|
console.warn("Parse gltf binary without path, this might lead to errors in resolving extensions. Please provide the source path of the gltf/glb file", path, typeof path);
|
@@ -153,6 +161,15 @@
|
|
153
161
|
});
|
154
162
|
}
|
155
163
|
|
164
|
+
/**
|
165
|
+
* Load a gltf file from a url. This is the core method used by Needle Engine to load gltf files. All known extensions are registered here.
|
166
|
+
* @param context The current context
|
167
|
+
* @param url The url to the gltf file
|
168
|
+
* @param sourceId The source id of the gltf file - this is usually the url
|
169
|
+
* @param seed The seed for generating unique ids
|
170
|
+
* @param prog A progress callback
|
171
|
+
* @returns The loaded gltf object
|
172
|
+
*/
|
156
173
|
export async function loadSync(context: Context, url: string, sourceId: string, seed: number | UIDProvider | null, prog?: (ProgressEvent) => void): Promise<GLTF | undefined> {
|
157
174
|
// better to create new loaders every time
|
158
175
|
// (maybe we can cache them...)
|
@@ -11,6 +11,12 @@
|
|
11
11
|
white[0] = 255; white[1] = 255; white[2] = 255; white[3] = 255;
|
12
12
|
export const whiteDefaultTexture = new DataTexture(white, 1, 1, RGBAFormat);
|
13
13
|
|
14
|
+
/**
|
15
|
+
* Creates a new texture with a single color
|
16
|
+
* @param col Color to use
|
17
|
+
* @param size Size of the texture
|
18
|
+
* @returns A texture with the specified color
|
19
|
+
*/
|
14
20
|
export function createFlatTexture(col: RGBAColor | Color, size: number = 1) {
|
15
21
|
const hasAlpha = "alpha" in col;
|
16
22
|
const length = size * size;
|
@@ -31,6 +37,15 @@
|
|
31
37
|
return tex;
|
32
38
|
}
|
33
39
|
|
40
|
+
/**
|
41
|
+
* Creates a new texture with three colors
|
42
|
+
* @param col0 First color
|
43
|
+
* @param col1 Second color
|
44
|
+
* @param col2 Third color
|
45
|
+
* @param width Width of the texture
|
46
|
+
* @param height Height of the texture
|
47
|
+
* @returns A texture with the specified colors
|
48
|
+
*/
|
34
49
|
export function createTrilightTexture<T extends Color>(col0: T, col1: T, col2: T, width: number = 1, height: number = 3) {
|
35
50
|
const hasAlpha = false;// "alpha" in col0;
|
36
51
|
const channels = 4;
|
@@ -62,11 +77,13 @@
|
|
62
77
|
return tex;
|
63
78
|
}
|
64
79
|
|
80
|
+
/** @internal */
|
65
81
|
export enum Stage {
|
66
82
|
Vertex,
|
67
83
|
Fragment,
|
68
84
|
}
|
69
85
|
|
86
|
+
/** @internal */
|
70
87
|
export class UnityShaderStage {
|
71
88
|
stage: Stage;
|
72
89
|
code: string;
|
@@ -105,9 +122,11 @@
|
|
105
122
|
}
|
106
123
|
}
|
107
124
|
|
125
|
+
/** @internal */
|
108
126
|
export const lib = new ShaderLib();
|
109
127
|
|
110
128
|
|
129
|
+
/** @internal */
|
111
130
|
export function ToUnityMatrixArray(mat: Matrix4, buffer?: Array<Vector4>): Array<Vector4> {
|
112
131
|
const arr = mat.elements;
|
113
132
|
if (!buffer)
|
@@ -126,6 +145,8 @@
|
|
126
145
|
|
127
146
|
const noAmbientLight: Array<number> = [];
|
128
147
|
const copyBuffer: Array<number> = [];
|
148
|
+
|
149
|
+
/** @internal */
|
129
150
|
export function SetUnitySphericalHarmonics(obj: object, array?: number[]) {
|
130
151
|
|
131
152
|
if (noAmbientLight.length === 0) {
|
@@ -147,6 +168,7 @@
|
|
147
168
|
obj["unity_SHC"] = { value: new Vector4(array[24], array[25], array[26], 1) };
|
148
169
|
}
|
149
170
|
|
171
|
+
/** @internal */
|
150
172
|
export class ShaderBundle {
|
151
173
|
readonly vertexShader: string;
|
152
174
|
readonly fragmentShader: string;
|
@@ -159,6 +181,7 @@
|
|
159
181
|
}
|
160
182
|
}
|
161
183
|
|
184
|
+
/** @internal */
|
162
185
|
export async function FindShaderTechniques(shaderData: SHADERDATA.ShaderData, id: number): Promise<ShaderBundle | null> {
|
163
186
|
// console.log(shaderData);
|
164
187
|
if (!shaderData) {
|
@@ -191,6 +214,7 @@
|
|
191
214
|
return null;
|
192
215
|
}
|
193
216
|
|
217
|
+
/** @internal */
|
194
218
|
async function loadShaderCode(shader: SHADERDATA.Shader) {
|
195
219
|
const uri = shader.uri;
|
196
220
|
if (!uri) return;
|
@@ -21,6 +21,9 @@
|
|
21
21
|
*/
|
22
22
|
export class RenderTexture extends WebGLRenderTarget {
|
23
23
|
|
24
|
+
/**
|
25
|
+
* Render the scene to the texture
|
26
|
+
*/
|
24
27
|
render(scene: Object3D, camera: Camera, renderer: WebGLRenderer | EffectComposer) {
|
25
28
|
if (renderer instanceof EffectComposer) {
|
26
29
|
if (!this["_unsupported_effectcomposer_warning"]) {
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { AnimationAction, Euler, Mesh,Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, Texture, Uniform, Vector3 } from "three";
|
2
|
-
import { ShaderMaterial,WebGLRenderer } from "three";
|
1
|
+
import { AnimationAction, Euler, Mesh, Object3D, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, Texture, Uniform, Vector3 } from "three";
|
2
|
+
import { ShaderMaterial, WebGLRenderer } from "three";
|
3
3
|
|
4
4
|
import { Mathf } from "./engine_math.js"
|
5
5
|
import { CircularBuffer } from "./engine_utils.js";
|
@@ -13,6 +13,7 @@
|
|
13
13
|
}
|
14
14
|
|
15
15
|
const flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
16
|
+
|
16
17
|
export function lookAtInverse(obj: Object3D, target: Vector3) {
|
17
18
|
|
18
19
|
obj.lookAt(target);
|
@@ -50,6 +51,14 @@
|
|
50
51
|
|
51
52
|
|
52
53
|
const _tempVecs = new CircularBuffer(() => new Vector3(), 100);
|
54
|
+
|
55
|
+
/** Gets a temporary vector. If a vector is passed in it will be copied to the temporary vector
|
56
|
+
* Temporary vectors are cached and reused internally. Don't store them!
|
57
|
+
* @param vecOrX the vector to copy or the x value
|
58
|
+
* @param y the y value
|
59
|
+
* @param z the z value
|
60
|
+
* @returns a temporary vector
|
61
|
+
*/
|
53
62
|
export function getTempVector(vecOrX?: Vector3 | number | DOMPointReadOnly, y?: number, z?: number) {
|
54
63
|
const vec = _tempVecs.get();
|
55
64
|
if (vecOrX instanceof Vector3) vec.copy(vecOrX);
|
@@ -62,6 +71,13 @@
|
|
62
71
|
return vec;
|
63
72
|
}
|
64
73
|
const _tempQuats = new CircularBuffer(() => new Quaternion(), 100);
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Gets a temporary quaternion. If a quaternion is passed in it will be copied to the temporary quaternion
|
77
|
+
* Temporary quaternions are cached and reused internally. Don't store them!
|
78
|
+
* @param value the quaternion to copy
|
79
|
+
* @returns a temporary quaternion
|
80
|
+
*/
|
65
81
|
export function getTempQuaternion(value?: Quaternion | DOMPointReadOnly) {
|
66
82
|
const val = _tempQuats.get();
|
67
83
|
if (value instanceof Quaternion) val.copy(value);
|
@@ -73,6 +89,13 @@
|
|
73
89
|
const _worldPositions = new CircularBuffer(() => new Vector3(), 100);
|
74
90
|
const _lastMatrixWorldUpdateKey = Symbol("lastMatrixWorldUpdateKey");
|
75
91
|
|
92
|
+
/**
|
93
|
+
* Get the world position of an object
|
94
|
+
* @param obj the object to get the world position from
|
95
|
+
* @param vec a vector to store the result in. If not passed in a temporary vector will be used
|
96
|
+
* @param updateParents if true the parents will be updated before getting the world position
|
97
|
+
* @returns the world position
|
98
|
+
*/
|
76
99
|
export function getWorldPosition(obj: Object3D, vec: Vector3 | null = null, updateParents: boolean = true): Vector3 {
|
77
100
|
const wp = vec ?? _worldPositions.get();
|
78
101
|
if (!obj) return wp.set(0, 0, 0);
|
@@ -87,20 +110,34 @@
|
|
87
110
|
return wp;
|
88
111
|
}
|
89
112
|
|
90
|
-
|
91
|
-
|
113
|
+
/**
|
114
|
+
* Set the world position of an object
|
115
|
+
* @param obj the object to set the world position of
|
116
|
+
* @param val the world position to set
|
117
|
+
*/
|
118
|
+
export function setWorldPosition(obj: Object3D, val: Vector3): Object3D {
|
119
|
+
if (!obj) return obj;
|
92
120
|
const wp = _worldPositions.get();
|
93
121
|
if (val !== wp)
|
94
122
|
wp.copy(val);
|
95
123
|
const obj2 = obj?.parent ?? obj;
|
96
124
|
obj2.worldToLocal(wp);
|
97
125
|
obj.position.set(wp.x, wp.y, wp.z);
|
126
|
+
return obj;
|
98
127
|
}
|
99
128
|
|
100
|
-
|
129
|
+
/**
|
130
|
+
* Set the world position of an object
|
131
|
+
* @param obj the object to set the world position of
|
132
|
+
* @param x the x position
|
133
|
+
* @param y the y position
|
134
|
+
* @param z the z position
|
135
|
+
*/
|
136
|
+
export function setWorldPositionXYZ(obj: Object3D, x: number, y: number, z: number): Object3D {
|
101
137
|
const wp = _worldPositions.get();
|
102
138
|
wp.set(x, y, z);
|
103
139
|
setWorldPosition(obj, wp);
|
140
|
+
return obj;
|
104
141
|
}
|
105
142
|
|
106
143
|
|
@@ -254,7 +291,7 @@
|
|
254
291
|
|
255
292
|
export function getParentHierarchyPath(obj: Object3D): string {
|
256
293
|
let path = obj?.name || "";
|
257
|
-
if(!obj) return path;
|
294
|
+
if (!obj) return path;
|
258
295
|
let parent = obj.parent;
|
259
296
|
while (parent) {
|
260
297
|
path = parent.name + "/" + path;
|
@@ -278,6 +315,9 @@
|
|
278
315
|
|
279
316
|
|
280
317
|
|
318
|
+
/**
|
319
|
+
* Utility class to perform various graphics operations like copying textures to canvas
|
320
|
+
*/
|
281
321
|
export class Graphics {
|
282
322
|
private static planeGeometry = new PlaneGeometry(2, 2, 1, 1);
|
283
323
|
private static renderer: WebGLRenderer | undefined = undefined;
|
@@ -300,6 +340,9 @@
|
|
300
340
|
}`;
|
301
341
|
private static blipMaterial: ShaderMaterial | undefined = undefined;
|
302
342
|
|
343
|
+
/**
|
344
|
+
* Create a blit material for copying textures
|
345
|
+
*/
|
303
346
|
static createBlitMaterial(fragment: string): ShaderMaterial {
|
304
347
|
return new ShaderMaterial({
|
305
348
|
uniforms: { map: new Uniform(null) },
|
@@ -309,7 +352,13 @@
|
|
309
352
|
}
|
310
353
|
private static mesh: Mesh | undefined = undefined;
|
311
354
|
|
312
|
-
|
355
|
+
/**
|
356
|
+
* Copy a texture to a new texture
|
357
|
+
* @param texture the texture to copy
|
358
|
+
* @param blitMaterial the material to use for copying (optional)
|
359
|
+
* @returns the newly created, copied texture
|
360
|
+
*/
|
361
|
+
static copyTexture(texture: Texture, blitMaterial?: ShaderMaterial): Texture {
|
313
362
|
const material = blitMaterial ?? this.blipMaterial!;
|
314
363
|
material.uniforms.map.value = texture;
|
315
364
|
material.needsUpdate = true;
|
@@ -320,6 +320,9 @@
|
|
320
320
|
|
321
321
|
const contactsVectorBuffer = new CircularBuffer(() => new Vector3(), 20);
|
322
322
|
|
323
|
+
/**
|
324
|
+
* Holds information about physics contacts
|
325
|
+
*/
|
323
326
|
export class ContactPoint {
|
324
327
|
|
325
328
|
private readonly _point: Vec3;
|
@@ -350,6 +353,7 @@
|
|
350
353
|
return target.set(this._tangentVelocity.x, this._tangentVelocity.y, this._tangentVelocity.z);
|
351
354
|
}
|
352
355
|
|
356
|
+
/** @internal */
|
353
357
|
constructor(point: Vec3, dist: number, normal: Vec3, impulse: number, friction: number, tangentVelocity: Vec3) {
|
354
358
|
this._point = point;
|
355
359
|
this.distance = dist;
|
@@ -360,12 +364,15 @@
|
|
360
364
|
}
|
361
365
|
}
|
362
366
|
|
363
|
-
|
367
|
+
/**
|
368
|
+
* Holds information about a collision event. Includes a list of contact points and the colliders involved
|
369
|
+
*/
|
364
370
|
export class Collision {
|
365
371
|
|
366
372
|
/** The contact points of this collision. Contains information about positions, normals, distance, friction, impulse... */
|
367
373
|
readonly contacts: ContactPoint[];
|
368
374
|
|
375
|
+
/** @internal */
|
369
376
|
constructor(obj: IGameObject, otherCollider: ICollider, contacts: ContactPoint[]) {
|
370
377
|
this.me = obj;
|
371
378
|
this._collider = otherCollider;
|
@@ -96,8 +96,9 @@
|
|
96
96
|
|
97
97
|
|
98
98
|
|
99
|
-
/**
|
100
|
-
*
|
99
|
+
/** Experimental attribute
|
100
|
+
* Use to hook into another type's methods and run before the other methods run (similar to Harmony prefixes).
|
101
|
+
* Return false to prevent the original method from running.
|
101
102
|
*/
|
102
103
|
export const prefix = function <T>(type: Constructor<T>) {
|
103
104
|
return function (target: IComponent | any, _propertyKey: string | { name: string }, _PropertyDescriptor: PropertyDescriptor) {
|
@@ -319,10 +319,12 @@
|
|
319
319
|
if (pathIndex >= 0) {
|
320
320
|
// Take the source uri as the base path
|
321
321
|
const basePath = source.substring(0, pathIndex + 1);
|
322
|
+
// make sure we don't have double slashes
|
323
|
+
while (basePath.endsWith("/") && uri.startsWith("/")) uri = uri.substring(1);
|
322
324
|
// Append the relative uri
|
323
325
|
const newUri = basePath + uri;
|
324
326
|
// newUri = new URL(newUri, globalThis.location.href).href;
|
325
|
-
if (debugGetPath) console.log("source:", source, "
|
327
|
+
if (debugGetPath) console.log("source:", source, "changed uri \nfrom", uri, "\nto ", newUri, "\nbasePath: " + basePath);
|
326
328
|
return newUri;
|
327
329
|
}
|
328
330
|
return uri;
|
@@ -639,10 +639,17 @@
|
|
639
639
|
expandByObjectRecursive(child);
|
640
640
|
}
|
641
641
|
}
|
642
|
+
let hasAnyObject = false;
|
642
643
|
for (const object of objects) {
|
644
|
+
if (!object) continue;
|
645
|
+
hasAnyObject = true;
|
643
646
|
object.updateMatrixWorld();
|
644
647
|
expandByObjectRecursive(object);
|
645
648
|
}
|
649
|
+
if (!hasAnyObject) {
|
650
|
+
console.warn("No objects to fit camera to...");
|
651
|
+
return;
|
652
|
+
}
|
646
653
|
|
647
654
|
camera.updateMatrixWorld();
|
648
655
|
camera.updateProjectionMatrix();
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Object3D } from "three";
|
2
2
|
|
3
|
-
import { AssetReference } from "../engine/engine_addressables.js";
|
3
|
+
import { Addressables, AssetReference } from "../engine/engine_addressables.js";
|
4
4
|
import { registerObservableAttribute } from "../engine/engine_element_extras.js";
|
5
5
|
import { InputEvents } from "../engine/engine_input.js";
|
6
6
|
import { isLocalNetwork } from "../engine/engine_networking_utils.js";
|
@@ -64,6 +64,7 @@
|
|
64
64
|
* - `loadscene-start`: Called when a scene starts loading
|
65
65
|
* - `loadscene-finished`: Called when a scene finished loading
|
66
66
|
* - `progress`: Called when a scene is loading and the progress changes
|
67
|
+
* - `scene-opened`: Called when a scene is loaded and added to the SceneSwitcher's GameObject
|
67
68
|
* @example
|
68
69
|
* ```ts
|
69
70
|
* sceneSwitcher.addEventListener("loadscene-start", (e) => {
|
@@ -75,6 +76,9 @@
|
|
75
76
|
* sceneSwitcher.addEventListener("progress", (e) => {
|
76
77
|
* console.log("Loading progress", e.loaded, e.total);
|
77
78
|
* });
|
79
|
+
* sceneSwitcher.addEventListener("scene-opened", (e) => {
|
80
|
+
* console.log("Scene opened", e.detail.scene.uri);
|
81
|
+
* });
|
78
82
|
* ```
|
79
83
|
*
|
80
84
|
*/
|
@@ -159,10 +163,14 @@
|
|
159
163
|
|
160
164
|
private _preloadScheduler?: PreLoadScheduler;
|
161
165
|
|
166
|
+
/** @internal */
|
162
167
|
awake(): void {
|
168
|
+
if (this.scenes === undefined) this.scenes = [];
|
169
|
+
|
163
170
|
if (debug) console.log("SceneSwitcher", this);
|
164
171
|
}
|
165
172
|
|
173
|
+
/** @internal */
|
166
174
|
async onEnable() {
|
167
175
|
globalThis.addEventListener("popstate", this.onPopState);
|
168
176
|
this.context.input.addEventListener(InputEvents.KeyDown, this.onInputKeyDown);
|
@@ -210,6 +218,7 @@
|
|
210
218
|
}
|
211
219
|
}
|
212
220
|
|
221
|
+
/** @internal */
|
213
222
|
onDisable(): void {
|
214
223
|
globalThis.removeEventListener("popstate", this.onPopState);
|
215
224
|
this.context.input.removeEventListener(InputEvents.KeyDown, this.onInputKeyDown);
|
@@ -288,14 +297,58 @@
|
|
288
297
|
}
|
289
298
|
}
|
290
299
|
|
300
|
+
/**
|
301
|
+
* Add a scene to the SceneSwitcher.
|
302
|
+
* If the scene is already added it will be added again.
|
303
|
+
* @param urlOrAssetReference The url of the scene or an AssetReference to the scene
|
304
|
+
* @returns The AssetReference of the scene that was added
|
305
|
+
* @example
|
306
|
+
* ```ts
|
307
|
+
* // adding a scene:
|
308
|
+
* sceneSwitcher.addScene("scene1.glb");
|
309
|
+
* // add another scene and load it:
|
310
|
+
* const scene2 = sceneSwitcher.addScene("scene2.glb");
|
311
|
+
* sceneSwitcher.switchScene(scene2).then(res => { console.log("Scene loaded", res); });
|
312
|
+
* ```
|
313
|
+
*/
|
314
|
+
addScene(urlOrAssetReference: string | AssetReference): AssetReference {
|
315
|
+
if (typeof urlOrAssetReference === "string") {
|
316
|
+
let assetReference = this.context.addressables.findAssetReference(urlOrAssetReference);
|
317
|
+
if (!assetReference) {
|
318
|
+
assetReference = new AssetReference(urlOrAssetReference);
|
319
|
+
this.context.addressables.registerAssetReference(assetReference);
|
320
|
+
}
|
321
|
+
this.scenes.push(assetReference);
|
322
|
+
return assetReference;
|
323
|
+
}
|
324
|
+
|
325
|
+
this.scenes.push(urlOrAssetReference);
|
326
|
+
return urlOrAssetReference;
|
327
|
+
}
|
328
|
+
|
329
|
+
/**
|
330
|
+
* Load the next scene in the scenes array ({@link this.currentIndex} + 1)
|
331
|
+
* If the current scene is the last scene in the array and {@link this.clamp} is disabled then the first scene will be loaded.
|
332
|
+
* @returns a promise that resolves to true if the scene was loaded successfully
|
333
|
+
*/
|
291
334
|
selectNext(): Promise<boolean> {
|
292
335
|
return this.select(this._currentIndex + 1);
|
293
336
|
}
|
294
337
|
|
338
|
+
/**
|
339
|
+
* Load the previous scene in the scenes array ({@link this.currentIndex} - 1)
|
340
|
+
* If the current scene is the first scene in the array and {@link this.clamp} is disabled then the last scene will be loaded.
|
341
|
+
* @returns a promise that resolves to true if the scene was loaded successfully
|
342
|
+
*/
|
295
343
|
selectPrev(): Promise<boolean> {
|
296
344
|
return this.select(this._currentIndex - 1);
|
297
345
|
}
|
298
346
|
|
347
|
+
/**
|
348
|
+
* Load a scene by its index in the scenes array.
|
349
|
+
* @param index The index of the scene or a string that represents the scene uri (if the url is not known to the SceneSwitcher it will try to load the scene by its uri but it won't be added to the current scenes array. Use {@link addScene} to add a scene to the SceneSwitcher)
|
350
|
+
* @returns a promise that resolves to true if the scene was loaded successfully
|
351
|
+
*/
|
299
352
|
select(index: number | string): Promise<boolean> {
|
300
353
|
if (debug) console.log("select", index);
|
301
354
|
|
@@ -341,6 +394,19 @@
|
|
341
394
|
private __lastSwitchScene?: AssetReference;
|
342
395
|
private __lastSwitchScenePromise?: Promise<boolean>;
|
343
396
|
|
397
|
+
/**
|
398
|
+
* Switch to a scene by its AssetReference.
|
399
|
+
* If the scene is already loaded it will be unloaded and the new scene will be loaded.
|
400
|
+
* If the scene is already loading it will wait for the scene to be loaded.
|
401
|
+
* If the scene is already loaded and the same scene is requested again it will return the same promise that was returned the first time the scene was requested.
|
402
|
+
* @param scene The AssetReference of the scene to switch to
|
403
|
+
* @returns a promise that resolves to true if the scene was loaded successfully
|
404
|
+
* @example
|
405
|
+
* ```ts
|
406
|
+
* const myAssetReference = new AssetReference("scene1.glb");
|
407
|
+
* sceneSwitcher.switchScene(myAssetReference).then(res => { console.log("Scene loaded", res); });
|
408
|
+
* ```
|
409
|
+
*/
|
344
410
|
async switchScene(scene: AssetReference): Promise<boolean> {
|
345
411
|
if (!(scene instanceof AssetReference)) {
|
346
412
|
const type = typeof scene;
|
@@ -444,6 +510,9 @@
|
|
444
510
|
const res = sceneListener.sceneOpened(this);
|
445
511
|
if (res instanceof Promise) await res;
|
446
512
|
}
|
513
|
+
|
514
|
+
const openedEvt = new CustomEvent<LoadSceneEvent>("scene-opened", { detail: { scene: scene, switcher: this, index: index } });
|
515
|
+
this.dispatchEvent(openedEvt);
|
447
516
|
return true;
|
448
517
|
}
|
449
518
|
}
|