@@ -2,6 +2,7 @@
|
|
2
2
|
import path from 'path';
|
3
3
|
|
4
4
|
const projectDir = process.cwd() + "/";
|
5
|
+
const debug = false;
|
5
6
|
|
6
7
|
/** these are alias callbacks as in the vite.alias dictionary
|
7
8
|
* the first argument is the already resoled absolute path (it is only invoked if the path was found in node_modules)
|
@@ -19,10 +20,12 @@
|
|
19
20
|
return res + "/lib";
|
20
21
|
}
|
21
22
|
},
|
23
|
+
/* Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
|
22
24
|
'three': null,
|
23
25
|
'peerjs': null,
|
24
26
|
'websocket-ts': null,
|
25
27
|
'md5': null,
|
28
|
+
*/
|
26
29
|
}
|
27
30
|
|
28
31
|
/**
|
@@ -33,21 +36,77 @@
|
|
33
36
|
if (config?.noAlias === true || userSettings?.noAlias === true)
|
34
37
|
return;
|
35
38
|
|
36
|
-
|
39
|
+
const aliasPlugin = {
|
37
40
|
name: "needle-alias",
|
38
41
|
config(config) {
|
39
|
-
console.log('[needle-alias] ProjectDirectory: ' + projectDir);
|
42
|
+
if (debug) console.log('[needle-alias] ProjectDirectory: ' + projectDir);
|
40
43
|
if (!config.resolve) config.resolve = {};
|
41
44
|
if (!config.resolve.alias) config.resolve.alias = {};
|
42
45
|
const aliasDict = config.resolve.alias;
|
46
|
+
|
47
|
+
addThreeJSResolvers(aliasDict);
|
48
|
+
|
43
49
|
for (const name in packages_to_resolve) {
|
44
50
|
if (!aliasDict[name]) {
|
45
51
|
addPathResolver(name, aliasDict, packages_to_resolve[name]);
|
46
52
|
}
|
47
53
|
}
|
48
|
-
|
54
|
+
|
55
|
+
if (debug) {
|
56
|
+
const testResults = [];
|
57
|
+
for (const name in aliasDict) {
|
58
|
+
testResults.push({name, entry: aliasDict[name](name, 0, name)});
|
59
|
+
}
|
60
|
+
console.log('[needle-alias] Aliases: ', testResults);
|
61
|
+
}
|
62
|
+
},
|
49
63
|
}
|
50
64
|
|
65
|
+
let lastImporter = "";
|
66
|
+
/** This plugin logs all imports. This helps to find cases where incorrect folders are found/resolved. */
|
67
|
+
const debuggingPlugin = {
|
68
|
+
name: "needle-alias-debug",
|
69
|
+
resolveId(id, importer, options) {
|
70
|
+
// simplify paths for better readability
|
71
|
+
if (importer.includes("js/package~")) importer = "package~" + importer.split("js/package~")[1];
|
72
|
+
if (importer.includes("node_modules/@needle-tools")) importer = "node_modules/@needle-tools" + importer.split("node_modules/@needle-tools")[1];
|
73
|
+
if (importer.includes("node_modules/.vite")) importer = ".vite" + importer.split("node_modules/.vite")[1];
|
74
|
+
|
75
|
+
// could filter here, e.g. for things related to three
|
76
|
+
// if (id.includes("three")) return;
|
77
|
+
|
78
|
+
// verbose logging for all imports
|
79
|
+
if (lastImporter !== importer) {
|
80
|
+
lastImporter = importer;
|
81
|
+
console.log('[needle-alias] Resolving: ', importer);
|
82
|
+
}
|
83
|
+
console.log('[needle-alias] ' + ' → ' + id);
|
84
|
+
return;
|
85
|
+
},
|
86
|
+
// needs to run before regular resolver
|
87
|
+
enforce: 'pre',
|
88
|
+
}
|
89
|
+
|
90
|
+
if (debug) return [debuggingPlugin, aliasPlugin];
|
91
|
+
return [aliasPlugin];
|
92
|
+
|
93
|
+
function addThreeJSResolvers(aliasDict) {
|
94
|
+
// We are currently overriding "three" resolution to ensure that all dependencies resolve to the same three.js version.
|
95
|
+
// This is hacky, but the alternative is potentially having conflicting three.js versions since some packages are
|
96
|
+
// stubborn with their peer dependencies or just slow (slower as we) with updating.
|
97
|
+
// NOT adding this allows node.js to correctly resolve `exports` specified in three.js package.json;
|
98
|
+
// since we're overriding resolution here we need to manually resolve the subset of exports that we use.
|
99
|
+
aliasDict['three/addons'] = (res, packageName, index, path) => {
|
100
|
+
return "three/examples/jsm";
|
101
|
+
};
|
102
|
+
aliasDict['three'] = (res, packageName, index, _path) => {
|
103
|
+
return path.resolve(projectDir, 'node_modules', 'three');
|
104
|
+
};
|
105
|
+
aliasDict['three/nodes'] = (res, packageName, index, path) => {
|
106
|
+
return "three/examples/jsm/nodes/Nodes.js";
|
107
|
+
};
|
108
|
+
}
|
109
|
+
|
51
110
|
function addPathResolver(name, aliasDict, cb) {
|
52
111
|
// If a package at the node_modules path exist we resolve the request there
|
53
112
|
// introduced in 89a50718c38940abb99ee16c5e029065e41d7d65
|
@@ -4,6 +4,8 @@
|
|
4
4
|
|
5
5
|
// see https://linear.app/needle/issue/NE-3798
|
6
6
|
|
7
|
+
export let buildPipelineTask;
|
8
|
+
|
7
9
|
/**
|
8
10
|
* Runs the needle build pipeline as part of the vite build process
|
9
11
|
* @param {import('../types').userSettings} userSettings
|
@@ -38,6 +40,7 @@
|
|
38
40
|
taskFinished = true;
|
39
41
|
taskSucceeded = res;
|
40
42
|
});
|
43
|
+
buildPipelineTask = task;
|
41
44
|
},
|
42
45
|
closeBundle() {
|
43
46
|
// this is the last hook that is called, so we can wait for the task to finish here
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import fs from 'fs';
|
2
|
+
import crypto from 'crypto';
|
2
3
|
|
3
4
|
|
4
5
|
/** Create a file containing information about the build inside the build directory
|
@@ -25,7 +26,7 @@
|
|
25
26
|
|
26
27
|
/** Recursively collect all files in a directory
|
27
28
|
* @param {String} directory to search
|
28
|
-
* @param {{ files: Array<string>, totalsize:number }} info
|
29
|
+
* @param {{ files: Array<{path:string, hash:string}>, totalsize:number }} info
|
29
30
|
*/
|
30
31
|
function recursivelyCollectFiles(directory, path, info) {
|
31
32
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
@@ -42,7 +43,12 @@
|
|
42
43
|
recursivelyCollectFiles(newDirectory, newPath, info);
|
43
44
|
} else {
|
44
45
|
const relpath = `${path}/${entry.name}`;
|
45
|
-
|
46
|
+
const filehash = crypto.createHash('sha256');
|
47
|
+
filehash.update(fs.readFileSync(`${directory}/${entry.name}`));
|
48
|
+
info.files.push({
|
49
|
+
path: relpath,
|
50
|
+
hash: filehash.digest('hex')
|
51
|
+
});
|
46
52
|
try {
|
47
53
|
const fullpath = `${directory}/${entry.name}`;
|
48
54
|
const stats = fs.statSync(fullpath);
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { createBuildInfoFile } from '../common/buildinfo.js';
|
2
|
+
import { buildPipelineTask } from './build-pipeline.js';
|
2
3
|
import { getOutputDirectory } from './config.js';
|
3
4
|
|
4
5
|
|
@@ -15,9 +16,20 @@
|
|
15
16
|
name: 'needle-buildinfo',
|
16
17
|
apply: "build",
|
17
18
|
enforce: "post",
|
18
|
-
closeBundle: () => {
|
19
|
-
|
20
|
-
|
19
|
+
closeBundle: async () => {
|
20
|
+
return new Promise(async res => {
|
21
|
+
if (buildPipelineTask) {
|
22
|
+
await buildPipelineTask;
|
23
|
+
}
|
24
|
+
// wait for gzip
|
25
|
+
await delay(500);
|
26
|
+
const buildDirectory = getOutputDirectory();
|
27
|
+
createBuildInfoFile(buildDirectory);
|
28
|
+
});
|
21
29
|
}
|
22
30
|
}
|
31
|
+
}
|
32
|
+
|
33
|
+
function delay(ms) {
|
34
|
+
return new Promise(res => setTimeout(res, ms));
|
23
35
|
}
|
@@ -1,3 +1,6 @@
|
|
1
|
+
import { needleAsap } from "./asap.js";
|
2
|
+
export { needleAsap } from "./asap.js";
|
3
|
+
|
1
4
|
import { needleDefines } from "./defines.js";
|
2
5
|
export { needleDefines } from "./defines.js";
|
3
6
|
|
@@ -90,6 +93,7 @@
|
|
90
93
|
// ensure we have user settings initialized with defaults
|
91
94
|
userSettings = { ...defaultUserSettings, ...userSettings }
|
92
95
|
const array = [
|
96
|
+
needleAsap(command, config, userSettings),
|
93
97
|
needleDefines(command, config, userSettings),
|
94
98
|
needleLicense(command, config, userSettings),
|
95
99
|
needleViteAlias(command, config, userSettings),
|
@@ -1,7 +1,8 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
|
3
|
+
import { tryGetNeedleEngineVersion } from '../common/version.js';
|
1
4
|
import { loadConfig } from './config.js';
|
2
|
-
import fs from 'fs';
|
3
5
|
import { getPosterPath } from './poster.js';
|
4
|
-
import { tryGetNeedleEngineVersion } from '../common/version.js';
|
5
6
|
|
6
7
|
/**
|
7
8
|
* @param {import('../types').userSettings} userSettings
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import { Object3D, Vector3 } from "three";
|
2
2
|
|
3
3
|
import { OwnershipModel } from "../../engine/engine_networking.js";
|
4
4
|
import type { IModel } from "../../engine/engine_networking_types.js";
|
@@ -10,10 +10,10 @@
|
|
10
10
|
|
11
11
|
export class Avatar_POI {
|
12
12
|
|
13
|
-
public static Pois: { obj:
|
13
|
+
public static Pois: { obj: Object3D, avatar: AvatarMarker | null }[] = [];
|
14
14
|
public static LastChangeTime: number = 0;
|
15
15
|
|
16
|
-
public static Add(context: Context, obj:
|
16
|
+
public static Add(context: Context, obj: Object3D, ignoredBy: AvatarMarker | null = null) {
|
17
17
|
if (!obj) return;
|
18
18
|
for (const e of this.Pois) {
|
19
19
|
if (e.obj === obj) return;
|
@@ -23,7 +23,7 @@
|
|
23
23
|
// console.log("Added", obj?.name);
|
24
24
|
}
|
25
25
|
|
26
|
-
public static Remove(context: Context | null, obj:
|
26
|
+
public static Remove(context: Context | null, obj: Object3D | null) {
|
27
27
|
|
28
28
|
if (!obj) return;
|
29
29
|
for (const e of this.Pois) {
|
@@ -43,12 +43,12 @@
|
|
43
43
|
|
44
44
|
class TargetModel implements IModel {
|
45
45
|
public guid!: string;
|
46
|
-
public position:
|
46
|
+
public position: Vector3 = new Vector3();
|
47
47
|
}
|
48
48
|
|
49
49
|
export class Avatar_Brain_LookAt extends Behaviour {
|
50
50
|
|
51
|
-
public set controlledTarget(target:
|
51
|
+
public set controlledTarget(target: Object3D) {
|
52
52
|
this.target = target;
|
53
53
|
// HACK
|
54
54
|
const r = TypeStore.get("MoveRandom");
|
@@ -59,16 +59,16 @@
|
|
59
59
|
}
|
60
60
|
}
|
61
61
|
|
62
|
-
// this.target.add(new
|
62
|
+
// this.target.add(new AxesHelper(.1));
|
63
63
|
}
|
64
64
|
|
65
65
|
// that target to copy positions into
|
66
|
-
private target:
|
66
|
+
private target: Object3D | null = null;
|
67
67
|
|
68
68
|
private avatar: AvatarMarker | null = null;
|
69
69
|
private _model: OwnershipModel | null = null;
|
70
70
|
private _targetModel: TargetModel = new TargetModel();
|
71
|
-
private _currentTargetObject:
|
71
|
+
private _currentTargetObject: Object3D | null = null;
|
72
72
|
private _lastUpdateTime: number = 0;
|
73
73
|
private _lookDuration: number = 0;
|
74
74
|
private _lastPoiChangedTime: number = 0;
|
@@ -10,9 +10,9 @@
|
|
10
10
|
|
11
11
|
export class Avatar_MouthShapes extends Behaviour {
|
12
12
|
@serializable(Object3D)
|
13
|
-
public idle:
|
13
|
+
public idle: Object3D[] = [];
|
14
14
|
@serializable(Object3D)
|
15
|
-
public talking:
|
15
|
+
public talking: Object3D[] = [];
|
16
16
|
|
17
17
|
private marker: AvatarMarker | null = null;
|
18
18
|
private voip: Voip | null = null;
|
@@ -54,7 +54,7 @@
|
|
54
54
|
}
|
55
55
|
}
|
56
56
|
|
57
|
-
private setMouthShapeActive(arr:
|
57
|
+
private setMouthShapeActive(arr: Object3D[], index: number) {
|
58
58
|
if (!arr) return;
|
59
59
|
|
60
60
|
// hide other
|
@@ -75,7 +75,7 @@
|
|
75
75
|
// this.head?.traverse(o => {
|
76
76
|
// if (o && o.type === "Mesh") {
|
77
77
|
// if (o.name.lastIndexOf("mouth") > 0) {
|
78
|
-
// this.mouthShapes.push(o as
|
78
|
+
// this.mouthShapes.push(o as Mesh);
|
79
79
|
// }
|
80
80
|
// }
|
81
81
|
// });
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import { Vector3 } from "three";
|
2
|
+
|
1
3
|
import { Behaviour, GameObject } from "../Component.js";
|
2
4
|
import { Voip } from "../Voip.js";
|
3
5
|
import { AvatarMarker } from "../webxr/WebXRAvatar.js";
|
@@ -6,7 +8,7 @@
|
|
6
8
|
private voip: Voip | null = null;
|
7
9
|
private marker: AvatarMarker | null = null;
|
8
10
|
|
9
|
-
private _startPosition :
|
11
|
+
private _startPosition : Vector3 | null = null;
|
10
12
|
|
11
13
|
awake() {
|
12
14
|
this.voip = GameObject.findObjectOfType(Voip, this.context);
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
4
4
|
import { Behaviour, GameObject } from "../Component.js";
|
5
|
-
import { XRFlag
|
5
|
+
import { XRFlag } from "../webxr/XRFlag.js";
|
6
6
|
|
7
7
|
|
8
8
|
export class AvatarBlink_Simple extends Behaviour {
|
9
9
|
|
10
10
|
@serializable(Object3D)
|
11
|
-
private eyes:
|
11
|
+
private eyes: Object3D[] = [];
|
12
12
|
@serializable()
|
13
13
|
private lastBlinkTime: number = 0;
|
14
14
|
@serializable()
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Object3D } from "three";
|
1
|
+
import { Object3D, Vector3 } from "three";
|
3
2
|
|
4
3
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
5
4
|
import * as utils from "../../engine/engine_three_utils.js"
|
@@ -13,12 +12,11 @@
|
|
13
12
|
@serializable(Object3D)
|
14
13
|
public eyes: GameObject[] | null = null;
|
15
14
|
@serializable(Object3D)
|
16
|
-
public target:
|
15
|
+
public target: Object3D | null = null;
|
17
16
|
|
18
17
|
private brain: Avatar_Brain_LookAt | null = null;
|
19
18
|
|
20
19
|
awake(): void {
|
21
|
-
// console.log(this);
|
22
20
|
if (!this.brain) {
|
23
21
|
this.brain = GameObject.getComponentInParent(this.gameObject, Avatar_Brain_LookAt);
|
24
22
|
}
|
@@ -29,16 +27,12 @@
|
|
29
27
|
if (this.brain && this.target) {
|
30
28
|
this.brain.controlledTarget = this.target;
|
31
29
|
}
|
32
|
-
// console.log(this);
|
33
|
-
// if(this.head){
|
34
|
-
// this.head.add(new THREE.AxesHelper(1));
|
35
|
-
// }
|
36
30
|
}
|
37
31
|
|
38
32
|
|
39
|
-
private vec:
|
40
|
-
private static forward:
|
41
|
-
private currentTargetPoint:
|
33
|
+
private vec: Vector3 = new Vector3();
|
34
|
+
private static forward: Vector3 = new Vector3(0, 0, 1);
|
35
|
+
private currentTargetPoint: Vector3 = new Vector3();
|
42
36
|
|
43
37
|
update(): void {
|
44
38
|
// if(!this.activeAndEnabled) return;
|
@@ -168,7 +168,7 @@
|
|
168
168
|
// emptyParent.add(model);
|
169
169
|
|
170
170
|
|
171
|
-
// const geometry = new
|
171
|
+
// const geometry = new SphereGeometry(.6, 32, 16);
|
172
172
|
// const modelVariant = new USDZObject(model.name + "_variant", identityMatrix, geometry, new MeshStandardMaterial({ color: 0xff0000 }));
|
173
173
|
// emptyParent.add(modelVariant);
|
174
174
|
|
@@ -53,10 +53,10 @@
|
|
53
53
|
}
|
54
54
|
});
|
55
55
|
// xr session started?
|
56
|
-
|
56
|
+
globalThis.addEventListener("needle-xrsession-start", () => {
|
57
57
|
button.style.display = "none";
|
58
58
|
});
|
59
|
-
|
59
|
+
globalThis.addEventListener("needle-xrsession-end", () => {
|
60
60
|
button.style.display = "";
|
61
61
|
});
|
62
62
|
return button;
|
@@ -439,7 +439,6 @@
|
|
439
439
|
else if (this.context.scene.background !== this._skybox) {
|
440
440
|
if (debug)
|
441
441
|
console.log(`Camera \"${this._camera.name}\" set skybox`, this._camera, this._skybox);
|
442
|
-
this._skybox.colorSpace = SRGBColorSpace;
|
443
442
|
this._skybox.mapping = EquirectangularReflectionMapping;
|
444
443
|
this.context.scene.background = this._skybox;
|
445
444
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { BrightnessContrastEffect, HueSaturationEffect, ToneMappingEffect, ToneMappingMode } from "postprocessing";
|
2
|
-
import { ACESFilmicToneMapping, AgXToneMapping, LinearToneMapping, NoToneMapping } from "three";
|
2
|
+
import { ACESFilmicToneMapping, AgXToneMapping, LinearToneMapping, NeutralToneMapping, NoToneMapping } from "three";
|
3
3
|
|
4
4
|
import { serializable } from "../../../engine/engine_serialization.js";
|
5
5
|
import { GameObject } from "../../Component.js";
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import { Volume } from "../Volume.js";
|
8
8
|
import { VolumeParameter } from "../VolumeParameter.js";
|
9
9
|
import { registerCustomEffectType } from "../VolumeProfile.js";
|
10
|
-
import { ToneMapping } from "./Tonemapping.js";
|
10
|
+
import { ToneMapping, TonemappingMode } from "./Tonemapping.js";
|
11
11
|
|
12
12
|
|
13
13
|
export class ColorAdjustments extends PostProcessingEffect {
|
@@ -63,12 +63,13 @@
|
|
63
63
|
this.context.renderer.toneMapping = newMode ?? LinearToneMapping;
|
64
64
|
}
|
65
65
|
|
66
|
-
private threeToneMappingToEffectMode(mode: number | undefined) {
|
66
|
+
private threeToneMappingToEffectMode(mode: number | undefined): ToneMappingMode {
|
67
67
|
switch (mode) {
|
68
|
-
case LinearToneMapping: return ToneMappingMode.
|
68
|
+
case LinearToneMapping: return ToneMappingMode.LINEAR;
|
69
69
|
case ACESFilmicToneMapping: return ToneMappingMode.ACES_FILMIC;
|
70
70
|
case AgXToneMapping: return ToneMappingMode.AGX;
|
71
|
-
|
71
|
+
case NeutralToneMapping: return ToneMappingMode.NEUTRAL;
|
72
|
+
default: return ToneMappingMode.LINEAR;
|
72
73
|
}
|
73
74
|
}
|
74
75
|
|
@@ -93,19 +94,18 @@
|
|
93
94
|
// averageLuminance: 1,
|
94
95
|
});
|
95
96
|
|
96
|
-
|
97
|
+
this.postExposure!.onValueChanged = (v) => {
|
97
98
|
if (this.postExposure.overrideState)
|
98
99
|
this.context.renderer.toneMappingExposure = v;
|
99
|
-
|
100
|
+
|
100
101
|
// this is a workaround so that we can apply tonemapping options – no access to the ToneMappingEffect instance from the Tonemapping effect right now...
|
101
102
|
const currentMode = this.toneMappingEffect?.mode.value;
|
102
|
-
|
103
|
-
|
103
|
+
const threeMode = this.toneMappingEffect?.getThreeToneMapping(currentMode);
|
104
|
+
const mappedMode = this.threeToneMappingToEffectMode(threeMode);
|
105
|
+
if (mappedMode !== undefined)
|
106
|
+
tonemapping.mode = mappedMode;
|
107
|
+
};
|
104
108
|
|
105
|
-
this.postExposure!.onValueChanged = v => {
|
106
|
-
apply(v);
|
107
|
-
}
|
108
|
-
|
109
109
|
const brightnesscontrast = new BrightnessContrastEffect();
|
110
110
|
this.contrast!.onValueChanged = v => brightnesscontrast.contrast = v;
|
111
111
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
import
|
2
|
+
import { Mesh } from "three";
|
3
3
|
|
4
4
|
import { syncDestroy } from "../engine/engine_networking_instantiate.js";
|
5
5
|
import { getParam } from "../engine/engine_utils.js";
|
@@ -22,7 +22,7 @@
|
|
22
22
|
|
23
23
|
update(): void {
|
24
24
|
for (const box of this.deleteBoxes) {
|
25
|
-
const obj = this.gameObject as unknown as
|
25
|
+
const obj = this.gameObject as unknown as Mesh;
|
26
26
|
const res = box.isInBox(obj);
|
27
27
|
if (res === true) {
|
28
28
|
const marker = GameObject.getComponentInParent(this.gameObject, UsageMarker);
|
@@ -26,8 +26,8 @@
|
|
26
26
|
limitInterval = 60;
|
27
27
|
|
28
28
|
private _currentCount = 0;
|
29
|
-
private _startPosition:
|
30
|
-
private _startQuaternion:
|
29
|
+
private _startPosition: Vector3 | null = null;
|
30
|
+
private _startQuaternion: Quaternion | null = null;
|
31
31
|
|
32
32
|
start(): void {
|
33
33
|
if (this.object) {
|
@@ -92,7 +92,7 @@
|
|
92
92
|
}, (this.limitInterval / this.limitCount) * 1000);
|
93
93
|
}
|
94
94
|
|
95
|
-
private handleDuplication():
|
95
|
+
private handleDuplication(): Object3D | null {
|
96
96
|
if (!this.object) return null;
|
97
97
|
if (this._currentCount >= this.limitCount) return null;
|
98
98
|
if (this.object as any === this.gameObject) return null;
|
@@ -1,18 +1,21 @@
|
|
1
1
|
import { AudioContext } from "three";
|
2
2
|
|
3
|
+
import { Application } from "./engine_application.js";
|
3
4
|
|
4
5
|
/** Ensure the audio context is resumed if it gets suspended or interrupted */
|
5
6
|
export function ensureAudioContextIsResumed() {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
.
|
14
|
-
|
15
|
-
|
7
|
+
Application.registerWaitForAllowAudio(() => {
|
8
|
+
// this is a fix for https://github.com/mrdoob/three.js/issues/27779 & https://linear.app/needle/issue/NE-4257
|
9
|
+
const ctx = AudioContext.getContext();
|
10
|
+
ctx.addEventListener("statechange", () => {
|
11
|
+
// on iOS the audiocontext can be interrupted: https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/state#resuming_interrupted_play_states_in_ios_safari
|
12
|
+
const state = ctx.state as AudioContextState | "interrupted";
|
13
|
+
if (state === "suspended" || state === "interrupted") {
|
14
|
+
ctx.resume()
|
15
|
+
.then(() => { console.log("AudioContext resumed successfully"); })
|
16
|
+
.catch((e) => { console.log("Failed to resume AudioContext: " + e); });
|
17
|
+
}
|
18
|
+
});
|
16
19
|
});
|
17
20
|
}
|
18
21
|
setTimeout(ensureAudioContextIsResumed, 1000);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { needleLogoOnlySVG } from "./assets/index.js"
|
2
|
-
import { showBalloonWarning } from "./debug/index.js";
|
2
|
+
import { isDevEnvironment, showBalloonWarning } from "./debug/index.js";
|
3
3
|
import { hasCommercialLicense, hasProLicense, runtimeLicenseCheckPromise } from "./engine_license.js";
|
4
4
|
import { Mathf } from "./engine_math.js";
|
5
5
|
import { LoadingProgressArgs } from "./engine_setup.js";
|
@@ -357,7 +357,7 @@
|
|
357
357
|
nonCommercialContainer.style.paddingTop = ".6em";
|
358
358
|
nonCommercialContainer.style.fontSize = ".8em";
|
359
359
|
nonCommercialContainer.style.textTransform = "uppercase";
|
360
|
-
nonCommercialContainer.innerText = "NEEDLE ENGINE COMMERCIAL
|
360
|
+
nonCommercialContainer.innerText = "NEEDLE ENGINE NON COMMERCIAL VERSION\nCLICK HERE TO GET A LICENSE";
|
361
361
|
nonCommercialContainer.style.cursor = "pointer";
|
362
362
|
nonCommercialContainer.style.userSelect = "none";
|
363
363
|
nonCommercialContainer.style.textAlign = "center";
|
@@ -367,7 +367,7 @@
|
|
367
367
|
loadingElement.appendChild(nonCommercialContainer);
|
368
368
|
|
369
369
|
// Use the runtime license check
|
370
|
-
if (runtimeLicenseCheckPromise) {
|
370
|
+
if (!isDevEnvironment() && runtimeLicenseCheckPromise) {
|
371
371
|
if (debugLicense) console.log("Waiting for runtime license check");
|
372
372
|
await runtimeLicenseCheckPromise;
|
373
373
|
commercialLicense = hasCommercialLicense();
|
@@ -182,6 +182,10 @@
|
|
182
182
|
* For removeEventListener: The queue to remove the listener from. If no queue is specified the listener will be removed from all queues
|
183
183
|
*/
|
184
184
|
queue?: InputEventQueue;
|
185
|
+
/** If true, the listener will be removed after it is invoked once. */
|
186
|
+
once?: boolean;
|
187
|
+
/** The listener will be removed when the given AbortSignal object's `abort()` method is called. If not specified, no AbortSignal is associated with the listener. */
|
188
|
+
signal?: AbortSignal;
|
185
189
|
}
|
186
190
|
|
187
191
|
export class Input implements IInput {
|
@@ -190,7 +194,7 @@
|
|
190
194
|
* That way users can control if they want to receive events before or after other listeners (e.g subscribe to pointer events before the EventSystem receives them) - this allows certain listeners to be always invoked first (or last) and stop propagation
|
191
195
|
* Listeners per event are sorted
|
192
196
|
*/
|
193
|
-
private readonly _eventListeners: { [key: string]: Array<{ priority: number, listeners: InputEventListener
|
197
|
+
private readonly _eventListeners: { [key: string]: Array<{ priority: number, listeners: Array<{ callback: InputEventListener, options: EventListenerOptions }> }> } = {};
|
194
198
|
|
195
199
|
/** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
|
196
200
|
* @param type The event type to listen for
|
@@ -200,17 +204,21 @@
|
|
200
204
|
addEventListener(type: InputEvents | InputEventNames, callback: PointerEventListener, options?: EventListenerOptions): void {
|
201
205
|
if (!this._eventListeners[type]) this._eventListeners[type] = [];
|
202
206
|
|
207
|
+
if (!options) options = {};
|
208
|
+
// create a copy of the options object to avoid the original object being modified
|
209
|
+
else options = { ...options };
|
210
|
+
|
203
211
|
let queue = 0;
|
204
212
|
if (options?.queue != undefined) queue = options.queue;
|
205
213
|
|
206
214
|
const listeners = this._eventListeners[type];
|
207
215
|
const queueListeners = listeners.find(l => l.priority === queue);
|
208
216
|
if (!queueListeners) {
|
209
|
-
listeners.push({ priority: queue, listeners: [callback] });
|
217
|
+
listeners.push({ priority: queue, listeners: [{ callback, options }] });
|
210
218
|
// ensure we sort the listeners by priority
|
211
219
|
listeners.sort((a, b) => a.priority - b.priority);
|
212
220
|
} else {
|
213
|
-
queueListeners.listeners.push(callback);
|
221
|
+
queueListeners.listeners.push({ callback, options });
|
214
222
|
}
|
215
223
|
}
|
216
224
|
/** Removes the event listener from the specified event type. If no queue is specified the listener will be removed from all queues.
|
@@ -225,13 +233,13 @@
|
|
225
233
|
if (options?.queue != undefined) {
|
226
234
|
const queueListeners = listeners.find(l => l.priority === options.queue);
|
227
235
|
if (!queueListeners) return;
|
228
|
-
const index = queueListeners.listeners.
|
236
|
+
const index = queueListeners.listeners.findIndex(l => l.callback === callback);
|
229
237
|
if (index >= 0) queueListeners.listeners.splice(index, 1);
|
230
238
|
}
|
231
239
|
// if no queue is requested the callback will be removed from all queues
|
232
240
|
else {
|
233
241
|
for (const l of listeners) {
|
234
|
-
const index = l.listeners.
|
242
|
+
const index = l.listeners.findIndex(l => l.callback === callback);
|
235
243
|
if (index >= 0) l.listeners.splice(index, 1);
|
236
244
|
}
|
237
245
|
}
|
@@ -245,8 +253,21 @@
|
|
245
253
|
const listeners = this._eventListeners[evt.type];
|
246
254
|
if (listeners) {
|
247
255
|
for (const queue of listeners) {
|
248
|
-
for (
|
249
|
-
|
256
|
+
for (let i = 0; i < queue.listeners.length; i++) {
|
257
|
+
const entry = queue.listeners[i];
|
258
|
+
// if the abort signal is aborted we remove the listener and will not invoke it
|
259
|
+
if (entry.options?.signal?.aborted) {
|
260
|
+
queue.listeners.splice(i, 1);
|
261
|
+
i--;
|
262
|
+
continue;
|
263
|
+
}
|
264
|
+
// if the event should only be invoked once then we remove the listener before invoking it
|
265
|
+
if (entry.options.once) {
|
266
|
+
queue.listeners.splice(i, 1);
|
267
|
+
i--;
|
268
|
+
}
|
269
|
+
(entry.callback as KeyboardEventListener)(evt);
|
270
|
+
}
|
250
271
|
}
|
251
272
|
}
|
252
273
|
}
|
@@ -257,18 +278,34 @@
|
|
257
278
|
if (listeners) {
|
258
279
|
for (const queue of listeners) {
|
259
280
|
if (preventNextEventQueue) break;
|
260
|
-
for (
|
281
|
+
for (let i = 0; i < queue.listeners.length; i++) {
|
282
|
+
const entry = queue.listeners[i];
|
283
|
+
|
284
|
+
// if the abort signal is aborted we remove the listener and will not invoke it
|
285
|
+
if (entry.options?.signal?.aborted) {
|
286
|
+
queue.listeners.splice(i, 1);
|
287
|
+
i--;
|
288
|
+
continue;
|
289
|
+
}
|
290
|
+
// if immediatePropagationStopped is true we stop propagation altogether
|
261
291
|
if (evt.immediatePropagationStopped) {
|
262
292
|
preventNextEventQueue = true;
|
263
293
|
if (debug) console.log("immediatePropagationStopped", evt.type);
|
264
294
|
break;
|
265
295
|
}
|
296
|
+
// if propagationStopped is true we continue invoking the current queue but then not invoke the next queue
|
266
297
|
else if (evt.propagationStopped) {
|
267
298
|
preventNextEventQueue = true;
|
268
299
|
if (debug) console.log("propagationStopped", evt.type);
|
269
300
|
// we do not break here but continue invoking the listeners in the queue
|
270
301
|
}
|
271
|
-
|
302
|
+
|
303
|
+
// if the event should only be invoked once then we remove the listener before invoking it
|
304
|
+
if (entry.options.once) {
|
305
|
+
queue.listeners.splice(i, 1);
|
306
|
+
i--;
|
307
|
+
}
|
308
|
+
(entry.callback as PointerEventListener)(evt);
|
272
309
|
}
|
273
310
|
}
|
274
311
|
}
|
@@ -76,7 +76,7 @@
|
|
76
76
|
|
77
77
|
|
78
78
|
// all the chunks we can patch
|
79
|
-
// console.log(
|
79
|
+
// console.log(ShaderChunk);
|
80
80
|
// Unity: ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw; ambientOrLightmapUV.zw = 0;
|
81
81
|
ShaderChunk.lights_fragment_maps = ShaderChunk.lights_fragment_maps.replace("vec4 lightMapTexel = texture2D( lightMap, vLightMapUv );", `
|
82
82
|
vec2 lUv = vLightMapUv.xy * lightmapScaleOffset.xy + vec2(lightmapScaleOffset.z, (1. - (lightmapScaleOffset.y + lightmapScaleOffset.w)));
|
@@ -1,7 +1,9 @@
|
|
1
|
-
import type {
|
1
|
+
import type { Quaternion, Vector2, Vector3, Vector4 } from "three";
|
2
2
|
|
3
3
|
import type { Vec3 } from "./engine_types.js";
|
4
4
|
|
5
|
+
declare type Vector = Vector3 | Vector4 | Vector2 | Quaternion;
|
6
|
+
|
5
7
|
class MathHelper {
|
6
8
|
|
7
9
|
random(min?: number, max?: number): number {
|
@@ -1,13 +1,14 @@
|
|
1
1
|
// import { SyncedTransform } from "../engine-components/SyncedTransform.js";
|
2
2
|
// import { DragControls } from "../engine-components/DragControls.js"
|
3
3
|
// import { ObjectRaycaster } from "../engine-components/ui/Raycaster.js";
|
4
|
+
import { Object3D } from "three";
|
4
5
|
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
5
6
|
|
6
7
|
import type { UIDProvider } from "./engine_types.js";
|
7
8
|
// import { Animation } from "../engine-components/Animation.js";
|
8
9
|
|
9
10
|
|
10
|
-
export function onDynamicObjectAdded(_obj:
|
11
|
+
export function onDynamicObjectAdded(_obj: Object3D, _idProv: UIDProvider, _gltf?: GLTF) {
|
11
12
|
|
12
13
|
console.warn("Adding components on object has been temporarily disabled");
|
13
14
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
// import { IModel, NetworkConnection } from "./engine_networking.js"
|
2
2
|
import * as THREE from "three";
|
3
|
-
import { Object3D } from "three";
|
3
|
+
import { Object3D, Quaternion, Vector3 } from "three";
|
4
4
|
// https://github.com/uuidjs/uuid
|
5
5
|
// v5 takes string and namespace
|
6
6
|
import { v5 } from 'uuid';
|
@@ -261,11 +261,11 @@
|
|
261
261
|
}
|
262
262
|
const options = new InstantiateOptions();
|
263
263
|
if (model.position)
|
264
|
-
options.position = new
|
264
|
+
options.position = new Vector3(model.position.x, model.position.y, model.position.z);
|
265
265
|
if (model.rotation)
|
266
|
-
options.rotation = new
|
266
|
+
options.rotation = new Quaternion(model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w);
|
267
267
|
if (model.scale)
|
268
|
-
options.scale = new
|
268
|
+
options.scale = new Vector3(model.scale.x, model.scale.y, model.scale.z);
|
269
269
|
options.parent = model.parent;
|
270
270
|
if (model.seed)
|
271
271
|
options.idProvider = new InstantiateIdProvider(model.seed);
|
@@ -307,16 +307,16 @@
|
|
307
307
|
registeredPrefabProviders[key] = fn;
|
308
308
|
}
|
309
309
|
|
310
|
-
async function tryResolvePrefab(guid: string, obj:
|
310
|
+
async function tryResolvePrefab(guid: string, obj: Object3D): Promise<Object3D | null> {
|
311
311
|
const prov = registeredPrefabProviders[guid];
|
312
312
|
if (prov !== null && prov !== undefined) {
|
313
313
|
const res = await prov(guid);
|
314
314
|
if (res) return res;
|
315
315
|
}
|
316
|
-
return tryFindObjectByGuid(guid, obj) as
|
316
|
+
return tryFindObjectByGuid(guid, obj) as Object3D;
|
317
317
|
}
|
318
318
|
|
319
|
-
function tryFindObjectByGuid(guid: string, obj:
|
319
|
+
function tryFindObjectByGuid(guid: string, obj: Object3D): Object3D | null {
|
320
320
|
if (obj === null) return null;
|
321
321
|
if (!guid) return null;
|
322
322
|
if (obj["guid"] === guid) {
|
@@ -1021,7 +1021,7 @@
|
|
1021
1021
|
const material = new LineBasicMaterial({
|
1022
1022
|
color: 0x77dd77,
|
1023
1023
|
fog: false,
|
1024
|
-
// vertexColors:
|
1024
|
+
// vertexColors: VertexColors
|
1025
1025
|
});
|
1026
1026
|
const geometry = new BufferGeometry();
|
1027
1027
|
this.lines = new LineSegments(geometry, material);
|
@@ -173,7 +173,6 @@
|
|
173
173
|
if (debug) console.log("Setting environment reflection", existing);
|
174
174
|
const scene = this.context.scene;
|
175
175
|
const tex = existing.Source;
|
176
|
-
tex.colorSpace = SRGBColorSpace;
|
177
176
|
tex.mapping = EquirectangularReflectionMapping;
|
178
177
|
scene.environment = tex;
|
179
178
|
return;
|
@@ -227,21 +226,20 @@
|
|
227
226
|
export class LightData {
|
228
227
|
|
229
228
|
get Source(): Texture { return this._source; }
|
230
|
-
get Array(): number[] | undefined { return this._sphericalHarmonicsArray; }
|
229
|
+
// get Array(): number[] | undefined { return this._sphericalHarmonicsArray; }
|
231
230
|
|
232
|
-
private _context: Context;
|
233
231
|
private _source: Texture;
|
234
|
-
private
|
235
|
-
private
|
236
|
-
private
|
237
|
-
private
|
232
|
+
// private _sphericalHarmonicsArray?: number[];
|
233
|
+
// private _context: Context;
|
234
|
+
// private _sphericalHarmonics: SphericalHarmonics3 | null = null;
|
235
|
+
// private _ambientScale: number = 1;
|
236
|
+
// private _lightProbe?: LightProbe;
|
238
237
|
|
239
|
-
constructor(
|
240
|
-
this._context = context;
|
238
|
+
constructor(_context: Context, tex: Texture, _ambientScale: number = 1) {
|
239
|
+
// this._context = context;
|
241
240
|
this._source = tex;
|
242
|
-
this._ambientScale = ambientScale;
|
241
|
+
// this._ambientScale = ambientScale;
|
243
242
|
tex.mapping = EquirectangularReflectionMapping;
|
244
|
-
tex.colorSpace = SRGBColorSpace;
|
245
243
|
}
|
246
244
|
|
247
245
|
/* REMOVED, no LightProbe / custom shader lighting support for now
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import * as THREE from "three";
|
2
|
-
import { Color, CompressedTexture, Object3D, Texture, WebGLRenderTarget } from "three";
|
2
|
+
import { Color, CompressedTexture, LinearSRGBColorSpace, Object3D, Texture, WebGLRenderTarget } from "three";
|
3
3
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../engine/debug/index.js";
|
5
5
|
import { Behaviour, Component, GameObject } from "../engine-components/Component.js";
|
@@ -34,7 +34,7 @@
|
|
34
34
|
constructor() {
|
35
35
|
super([Color, RGBAColor], "ColorSerializer")
|
36
36
|
}
|
37
|
-
onDeserialize(data: any):
|
37
|
+
onDeserialize(data: any): Color | RGBAColor | void {
|
38
38
|
if (data === undefined || data === null) return;
|
39
39
|
if (data.a !== undefined) {
|
40
40
|
return new RGBAColor(data.r, data.g, data.b, data.a);
|
@@ -42,7 +42,7 @@
|
|
42
42
|
else if (data.alpha !== undefined) {
|
43
43
|
return new RGBAColor(data.r, data.g, data.b, data.alpha);
|
44
44
|
}
|
45
|
-
return new
|
45
|
+
return new Color(data.r, data.g, data.b);
|
46
46
|
}
|
47
47
|
onSerialize(data: any): any | void {
|
48
48
|
if (data === undefined || data === null) return;
|
@@ -195,7 +195,7 @@
|
|
195
195
|
return undefined;
|
196
196
|
}
|
197
197
|
|
198
|
-
findObjectForGuid(guid: string, root:
|
198
|
+
findObjectForGuid(guid: string, root: Object3D): any {
|
199
199
|
// recursively search root
|
200
200
|
// need to check the root object too
|
201
201
|
if (root["guid"] === guid) return root;
|
@@ -383,7 +383,7 @@
|
|
383
383
|
if (data instanceof Texture && context.type === RenderTexture) {
|
384
384
|
const tex = data as Texture;
|
385
385
|
const rt = new RenderTexture(tex.image.width, tex.image.height, {
|
386
|
-
colorSpace:
|
386
|
+
colorSpace: LinearSRGBColorSpace,
|
387
387
|
});
|
388
388
|
rt.texture = tex;
|
389
389
|
tex.isRenderTargetTexture = true;
|
@@ -399,7 +399,7 @@
|
|
399
399
|
//@ts-ignore
|
400
400
|
tex["isCompressedTexture"] = false;
|
401
401
|
//@ts-ignore
|
402
|
-
tex.format =
|
402
|
+
tex.format = RGBAFormat;
|
403
403
|
}
|
404
404
|
|
405
405
|
return rt;
|
@@ -152,12 +152,12 @@
|
|
152
152
|
|
153
153
|
// passed to serializers
|
154
154
|
export class SerializationContext {
|
155
|
-
root:
|
155
|
+
root: Object3D;
|
156
156
|
|
157
157
|
gltf?: GLTF;
|
158
158
|
/** the url of the glb that is currently being loaded */
|
159
159
|
gltfId?: SourceIdentifier;
|
160
|
-
object!:
|
160
|
+
object!: Object3D;
|
161
161
|
target?: object;
|
162
162
|
nodeId?: number;
|
163
163
|
nodeToObject?: NodeToObjectMap;
|
@@ -170,7 +170,7 @@
|
|
170
170
|
/** holds information if a field was undefined before serialization. This gives us info if we might want to warn the user about missing attributes */
|
171
171
|
implementationInformation?: ImplementationInformation;
|
172
172
|
|
173
|
-
constructor(root:
|
173
|
+
constructor(root: Object3D) {
|
174
174
|
this.root = root;
|
175
175
|
}
|
176
176
|
}
|
@@ -190,7 +190,7 @@
|
|
190
190
|
|
191
191
|
// it can be an object containing a field type that has a constructor
|
192
192
|
// this is just so we have some flexibility later if we need superspecialcustom overrides
|
193
|
-
myFieldName : { type:
|
193
|
+
myFieldName : { type: Color },
|
194
194
|
}
|
195
195
|
*/
|
196
196
|
|
@@ -571,7 +571,7 @@
|
|
571
571
|
}
|
572
572
|
try {
|
573
573
|
// the fallback - this assumes that the type has a constructor that accepts the serialized arguments
|
574
|
-
// made originally with
|
574
|
+
// made originally with Vector3 in mind but SHOULD actually not be used/called anymore
|
575
575
|
instance = new type(...setBuffer(data));
|
576
576
|
}
|
577
577
|
catch (err) {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
import { Color,DataTexture, FileLoader, RGBAFormat, Vector4 } from "three";
|
2
|
+
import { Color,DataTexture, FileLoader, Matrix4, RGBAFormat, Vector4 } from "three";
|
3
3
|
|
4
4
|
import { RGBAColor } from "../engine-components/js-extensions/RGBAColor.js";
|
5
5
|
import * as loader from "./engine_fileloader.js"
|
@@ -108,7 +108,7 @@
|
|
108
108
|
export const lib = new ShaderLib();
|
109
109
|
|
110
110
|
|
111
|
-
export function ToUnityMatrixArray(mat:
|
111
|
+
export function ToUnityMatrixArray(mat: Matrix4, buffer?: Array<Vector4>): Array<Vector4> {
|
112
112
|
const arr = mat.elements;
|
113
113
|
if (!buffer)
|
114
114
|
buffer = [];
|
@@ -136,8 +136,8 @@
|
|
136
136
|
for (let i = 0; i < 27; i++)
|
137
137
|
copyBuffer[i] = array[i];//Math.sqrt(Math.pow(Math.PI,2)*6);//1 / Math.PI;
|
138
138
|
array = copyBuffer;
|
139
|
-
// 18 is too bright with probe.sh.coefficients[6] = new
|
140
|
-
// 24 is too bright with probe.sh.coefficients[8] = new
|
139
|
+
// 18 is too bright with probe.sh.coefficients[6] = new Vector3(1,0,0);
|
140
|
+
// 24 is too bright with probe.sh.coefficients[8] = new Vector3(1,0,0);
|
141
141
|
obj["unity_SHAr"] = { value: new Vector4(array[9], array[3], array[6], array[0]) };
|
142
142
|
obj["unity_SHBr"] = { value: new Vector4(array[12], array[15], array[18], array[21]) };
|
143
143
|
obj["unity_SHAg"] = { value: new Vector4(array[10], array[4], array[7], array[1]) };
|
@@ -9,24 +9,41 @@
|
|
9
9
|
|
10
10
|
export class Time implements ITime {
|
11
11
|
|
12
|
-
|
13
|
-
time
|
12
|
+
/** The time in seconds since the start of Needle Engine. */
|
13
|
+
get time() { return this._time; }
|
14
|
+
private set time(value: number) { this._time = value; }
|
15
|
+
private _time = 0;
|
16
|
+
|
17
|
+
/** The time in seconds it took to complete the last frame (Read Only). */
|
18
|
+
get deltaTime() { return this._deltaTime; }
|
19
|
+
private set deltaTime(value: number) { this._deltaTime = value; }
|
20
|
+
private _deltaTime = 0;
|
21
|
+
|
22
|
+
get deltaTimeUnscaled() { return this._deltaTimeUnscaled; }
|
23
|
+
private _deltaTimeUnscaled = 0;
|
24
|
+
|
25
|
+
/** The scale at which time passes. This can be used for slow motion effects or to speed up time. */
|
14
26
|
timeScale = 1;
|
15
27
|
|
16
28
|
/** same as frameCount */
|
17
|
-
frame
|
29
|
+
get frame() { return this._frame; }
|
30
|
+
private set frame(value: number) { this._frame = value; }
|
31
|
+
private _frame = 0;
|
32
|
+
/** The total number of frames that have passed (Read Only). Same as frame */
|
33
|
+
get frameCount() { return this.frame; }
|
18
34
|
|
35
|
+
/** The time in seconds it took to complete the last frame (Read Only). */
|
19
36
|
get realtimeSinceStartup(): number {
|
20
37
|
return this.clock.elapsedTime;
|
21
38
|
}
|
22
39
|
|
23
|
-
|
40
|
+
/** Approximated frames per second (Read Only). */
|
24
41
|
get smoothedFps() { return this._smoothedFps; }
|
42
|
+
/** The smoothed time in seconds it took to complete the last frame (Read Only). */
|
25
43
|
get smoothedDeltaTime() { return 1 / this._smoothedFps; }
|
26
44
|
|
27
45
|
|
28
46
|
private clock = new Clock();
|
29
|
-
|
30
47
|
private _smoothedFps: number = 0;
|
31
48
|
private _smoothedDeltaTime: number = 0;
|
32
49
|
private _fpsSamples: number[] = [];
|
@@ -41,8 +58,9 @@
|
|
41
58
|
this.deltaTime = this.clock.getDelta();
|
42
59
|
// clamp delta time because if tab is not active clock.getDelta can get pretty big
|
43
60
|
this.deltaTime = Math.min(.1, this.deltaTime);
|
61
|
+
this._deltaTimeUnscaled = this.deltaTime;
|
62
|
+
if (this.deltaTime <= 0) this.deltaTime = 0.000000000001;
|
44
63
|
this.deltaTime *= this.timeScale;
|
45
|
-
if (this.deltaTime <= 0) this.deltaTime = 0.000000000001;
|
46
64
|
this.frame += 1;
|
47
65
|
this.time += this.deltaTime;
|
48
66
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
// use for typesafe interface method calls
|
2
|
-
import {
|
2
|
+
import { Quaternion, Vector2, Vector3, Vector4 } from "three";
|
3
3
|
|
4
|
+
declare type Vector = Vector2 | Vector3 | Vector4 | Quaternion;
|
5
|
+
|
4
6
|
import { type Context } from "./engine_context.js";
|
5
7
|
import { ContextRegistry } from "./engine_context_registry.js";
|
6
8
|
import { type SourceIdentifier } from "./engine_types.js";
|
@@ -15,13 +15,15 @@
|
|
15
15
|
onXRSessionStartListeners.splice(index, 1);
|
16
16
|
}
|
17
17
|
}
|
18
|
+
|
19
|
+
|
18
20
|
export function invokeXRSessionStart(evt: XRSessionEventArgs) {
|
19
|
-
|
21
|
+
globalThis.dispatchEvent(new CustomEvent("needle-xrsession-start", { detail: evt }));
|
20
22
|
for (let i = 0; i < onXRSessionStartListeners.length; i++) {
|
21
23
|
onXRSessionStartListeners[i](evt);
|
22
24
|
}
|
23
25
|
}
|
24
26
|
|
25
27
|
export function invokeXRSessionEnd(evt: XRSessionEventArgs) {
|
26
|
-
|
28
|
+
globalThis.dispatchEvent(new CustomEvent("needle-xrsession-end", { detail: evt }));
|
27
29
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { type Intersection, Object3D } from "three";
|
1
|
+
import { type Intersection, Mesh,Object3D } from "three";
|
2
2
|
|
3
3
|
import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
|
4
4
|
import { type InputEventNames, InputEvents, NEPointerEvent, PointerType } from "../../engine/engine_input.js";
|
@@ -201,7 +201,7 @@
|
|
201
201
|
this.dispatchEvent(new CustomEvent<AfterHandleInputEvent>(EventSystemEvents.AfterHandleInput, { detail: evt }));
|
202
202
|
}
|
203
203
|
|
204
|
-
private readonly _sortedHits:
|
204
|
+
private readonly _sortedHits: Intersection[] = [];
|
205
205
|
|
206
206
|
/**
|
207
207
|
* cache for objects that we want to raycast against. It's cleared before each call to performRaycast invoking raycasters
|
@@ -274,7 +274,7 @@
|
|
274
274
|
}
|
275
275
|
|
276
276
|
/** the raycast filter is always overriden */
|
277
|
-
private performRaycast(opts: RaycastOptions | null):
|
277
|
+
private performRaycast(opts: RaycastOptions | null): Intersection[] | null {
|
278
278
|
if (!this.raycaster) return null;
|
279
279
|
// we clear the cache of previously seen objects
|
280
280
|
this._testObjectsCache.clear();
|
@@ -355,10 +355,10 @@
|
|
355
355
|
return false;
|
356
356
|
}
|
357
357
|
|
358
|
-
private _sortingBuffer:
|
359
|
-
private _noDepthTestingResults:
|
358
|
+
private _sortingBuffer: Intersection[] = [];
|
359
|
+
private _noDepthTestingResults: Intersection[] = [];
|
360
360
|
|
361
|
-
private sortCandidates(hits:
|
361
|
+
private sortCandidates(hits: Intersection[]): Intersection[] {
|
362
362
|
// iterate over all hits and filter for nodepth objects and normal hit objects
|
363
363
|
// the no-depth objects will be handled first starting from the closest
|
364
364
|
// assuming the hits array is sorted by distance (closest > furthest)
|
@@ -366,7 +366,7 @@
|
|
366
366
|
this._noDepthTestingResults.length = 0;
|
367
367
|
for (let i = 0; i < hits.length; i++) {
|
368
368
|
const hit = hits[i];
|
369
|
-
const object = hit.object as
|
369
|
+
const object = hit.object as Mesh;
|
370
370
|
if (object.material) {
|
371
371
|
if (object.material["depthTest"] === false) {
|
372
372
|
this._noDepthTestingResults.push(hit);
|
@@ -387,7 +387,7 @@
|
|
387
387
|
* Handle hit result by preparing all needed information before propagation.
|
388
388
|
* Then calling propagate.
|
389
389
|
*/
|
390
|
-
private handleEventOnObject(object:
|
390
|
+
private handleEventOnObject(object: Object3D, args: PointerEventData): boolean {
|
391
391
|
// ensures that invisible objects are ignored
|
392
392
|
if (!this.testIsVisible(object)) {
|
393
393
|
if (args.isClick && debug)
|
@@ -739,7 +739,7 @@
|
|
739
739
|
|
740
740
|
private currentActiveMeshUIComponents: Object3D[] = [];
|
741
741
|
|
742
|
-
private handleMeshUIIntersection(meshUiObject:
|
742
|
+
private handleMeshUIIntersection(meshUiObject: Object3D, pressed: boolean): boolean {
|
743
743
|
const res = MeshUIHelper.updateState(meshUiObject, pressed);
|
744
744
|
if (res) {
|
745
745
|
this.currentActiveMeshUIComponents.push(res);
|
@@ -761,7 +761,7 @@
|
|
761
761
|
this.currentActiveMeshUIComponents.length = 0;
|
762
762
|
}
|
763
763
|
|
764
|
-
private testIsVisible(obj:
|
764
|
+
private testIsVisible(obj: Object3D | null): boolean {
|
765
765
|
if (!obj) return true;
|
766
766
|
if (!GameObject.isActiveSelf(obj)) return false;
|
767
767
|
return this.testIsVisible(obj.parent);
|
@@ -771,7 +771,7 @@
|
|
771
771
|
|
772
772
|
class MeshUIHelper {
|
773
773
|
|
774
|
-
private static lastSelected:
|
774
|
+
private static lastSelected: Object3D | null = null;
|
775
775
|
private static lastUpdateFrame: { context: Context, frame: number, nextUpdate: number }[] = [];
|
776
776
|
private static needsUpdate: boolean = false;
|
777
777
|
|
@@ -805,7 +805,7 @@
|
|
805
805
|
threeMeshUI.update();
|
806
806
|
}
|
807
807
|
|
808
|
-
static updateState(intersect:
|
808
|
+
static updateState(intersect: Object3D, _selectState: boolean): Object3D | null {
|
809
809
|
let foundBlock: Object3D | null = null;
|
810
810
|
|
811
811
|
if (intersect) {
|
@@ -109,7 +109,7 @@
|
|
109
109
|
let name = str.substring(prefix.length);
|
110
110
|
const endIndex = name.indexOf("/");
|
111
111
|
if (endIndex >= 0) name = name.substring(0, endIndex);
|
112
|
-
const ext = parser.plugins[name] as IExtensionReferenceResolver;
|
112
|
+
const ext = parser.plugins[name] as any as IExtensionReferenceResolver;
|
113
113
|
if (debugExtension)
|
114
114
|
console.log(name, ext);
|
115
115
|
if (typeof ext?.resolve === "function") {
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { BoxHelper, Color } from "three";
|
1
|
+
import { BoxHelper, Color, Object3D } from "three";
|
3
2
|
|
4
3
|
import * as params from "../engine/engine_default_parameters.js";
|
5
4
|
import * as Gizmos from "../engine/engine_gizmos.js";
|
@@ -12,18 +11,18 @@
|
|
12
11
|
@serializable()
|
13
12
|
objectBounds: boolean = false;
|
14
13
|
@serializable(Color)
|
15
|
-
color?:
|
14
|
+
color?: Color;
|
16
15
|
@serializable()
|
17
16
|
isGizmo : boolean = true;
|
18
17
|
|
19
|
-
private _gizmoObject:
|
18
|
+
private _gizmoObject: Object3D | null | BoxHelper = null;
|
20
19
|
private _boxHelper: BoxHelper | null = null;
|
21
20
|
|
22
21
|
onEnable(): void {
|
23
22
|
if (this.isGizmo && !params.showGizmos) return;
|
24
23
|
if (!this._gizmoObject) {
|
25
24
|
if (this.objectBounds) {
|
26
|
-
this._gizmoObject = new
|
25
|
+
this._gizmoObject = new BoxHelper(this.gameObject, this.color ?? 0xffff00);
|
27
26
|
}
|
28
27
|
else {
|
29
28
|
this.objectBounds = false;
|
@@ -16,14 +16,14 @@
|
|
16
16
|
const debugExport = getParam("debuggltfexport");
|
17
17
|
|
18
18
|
declare type ExportOptions = GLTFExporterOptions & {
|
19
|
-
pivot?:
|
19
|
+
pivot?: Vector3
|
20
20
|
}
|
21
21
|
|
22
22
|
export const componentsArrayExportKey = "$___Export_Components";
|
23
23
|
|
24
24
|
// @generate-component
|
25
25
|
export class GltfExportBox extends BoxHelperComponent {
|
26
|
-
sceneRoot?:
|
26
|
+
sceneRoot?: Object3D;
|
27
27
|
}
|
28
28
|
|
29
29
|
export class GltfExport extends Behaviour {
|
@@ -181,7 +181,7 @@
|
|
181
181
|
// URL.revokeObjectURL( url ); breaks Firefox...
|
182
182
|
}
|
183
183
|
|
184
|
-
private static collectAnimations(objs:
|
184
|
+
private static collectAnimations(objs: Object3D[], target?: Array<AnimationClip>): Array<AnimationClip> {
|
185
185
|
target = target || [];
|
186
186
|
for (const obj of objs) {
|
187
187
|
if (!obj) continue;
|
@@ -194,7 +194,7 @@
|
|
194
194
|
}
|
195
195
|
|
196
196
|
|
197
|
-
private static calculateCenter(objs:
|
197
|
+
private static calculateCenter(objs: Object3D[], target?: Vector3): Vector3 {
|
198
198
|
const center = target || new Vector3();
|
199
199
|
center.set(0, 0, 0);
|
200
200
|
objs.forEach(obj => {
|
@@ -206,7 +206,7 @@
|
|
206
206
|
return center;
|
207
207
|
}
|
208
208
|
|
209
|
-
private static filterTopmostParent(objs:
|
209
|
+
private static filterTopmostParent(objs: Object3D[]) {
|
210
210
|
if (objs.length <= 0) return;
|
211
211
|
for (let index = 0; index < objs.length; index++) {
|
212
212
|
let obj = objs[index];
|
@@ -9,11 +9,11 @@
|
|
9
9
|
@serializable()
|
10
10
|
public isGizmo: boolean = false;
|
11
11
|
@serializable(Color)
|
12
|
-
private color0!:
|
12
|
+
private color0!: Color;
|
13
13
|
@serializable(Color)
|
14
|
-
private color1!:
|
14
|
+
private color1!: Color;
|
15
15
|
|
16
|
-
private gridHelper!:
|
16
|
+
private gridHelper!: _GridHelper | null;
|
17
17
|
private size!: number;
|
18
18
|
private divisions!: number;
|
19
19
|
private offset!: number;
|
@@ -25,8 +25,7 @@
|
|
25
25
|
@serializable()
|
26
26
|
set radius(val: number) {
|
27
27
|
this._radius = val;
|
28
|
-
|
29
|
-
this.env.radius = val;
|
28
|
+
this.updateProjection();
|
30
29
|
}
|
31
30
|
get radius(): number { return this._radius; }
|
32
31
|
private _radius: number = 100;
|
@@ -34,13 +33,14 @@
|
|
34
33
|
@serializable()
|
35
34
|
set height(val: number) {
|
36
35
|
this._height = val;
|
37
|
-
|
38
|
-
this.env.height = val;
|
36
|
+
this.updateProjection();
|
39
37
|
}
|
40
38
|
get height(): number { return this._height; }
|
41
39
|
private _height: number = 100;
|
42
40
|
|
43
41
|
private _lastEnvironment?: Texture;
|
42
|
+
private _lastRadius?: number;
|
43
|
+
private _lastHeight?: number;
|
44
44
|
private env?: GroundProjection;
|
45
45
|
private _watcher?: Watch;
|
46
46
|
|
@@ -86,13 +86,16 @@
|
|
86
86
|
this.env?.removeFromParent();
|
87
87
|
return;
|
88
88
|
}
|
89
|
-
if (!this.env || this.context.scene.environment !== this._lastEnvironment) {
|
89
|
+
if (!this.env || this.context.scene.environment !== this._lastEnvironment || this._height !== this._lastHeight || this._radius !== this._lastRadius) {
|
90
90
|
if (debug)
|
91
91
|
console.log("Create/Update Ground Projection", this.context.scene.environment.name);
|
92
|
+
this.env?.removeFromParent();
|
92
93
|
this.env = new GroundProjection(this.context.scene.environment, this._height, this.radius, 32);
|
93
94
|
this.env.position.y = this._height;
|
94
95
|
}
|
95
96
|
this._lastEnvironment = this.context.scene.environment;
|
97
|
+
this._lastHeight = this._height;
|
98
|
+
this._lastRadius = this._radius;
|
96
99
|
if (!this.env.parent)
|
97
100
|
this.gameObject.add(this.env);
|
98
101
|
|
@@ -8,6 +8,9 @@
|
|
8
8
|
*/
|
9
9
|
export function getIconElement(str: string): HTMLElement {
|
10
10
|
const span = document.createElement("span");
|
11
|
+
span.style.maxWidth = "48px";
|
12
|
+
span.style.maxHeight = "48px";
|
13
|
+
span.style.overflow = "hidden";
|
11
14
|
span.classList.add("material-symbols-outlined");
|
12
15
|
span.innerText = str;
|
13
16
|
return span;
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Color, DirectionalLight, OrthographicCamera } from "three";
|
1
|
+
import { CameraHelper, Color, DirectionalLight, DirectionalLightHelper, Light as ThreeLight, OrthographicCamera, PointLight, SpotLight, Vector3 } from "three";
|
3
2
|
|
4
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
4
|
import { FrameEvent } from "../engine/engine_setup.js";
|
@@ -104,14 +103,14 @@
|
|
104
103
|
if (this.light) return this.light.color;
|
105
104
|
return this._color;
|
106
105
|
}
|
107
|
-
public _color:
|
106
|
+
public _color: Color = new Color(0xffffff);
|
108
107
|
|
109
108
|
@serializable()
|
110
109
|
set shadowNearPlane(val: number) {
|
111
110
|
if (val === this._shadowNearPlane) return;
|
112
111
|
this._shadowNearPlane = val;
|
113
112
|
if (this.light?.shadow?.camera !== undefined) {
|
114
|
-
const cam = this.light.shadow.camera as
|
113
|
+
const cam = this.light.shadow.camera as OrthographicCamera;
|
115
114
|
cam.near = val;
|
116
115
|
}
|
117
116
|
}
|
@@ -234,9 +233,9 @@
|
|
234
233
|
return false;
|
235
234
|
}
|
236
235
|
|
237
|
-
private light:
|
236
|
+
private light: ThreeLight | undefined = undefined;
|
238
237
|
|
239
|
-
public getWorldPosition(vec:
|
238
|
+
public getWorldPosition(vec: Vector3): Vector3 {
|
240
239
|
if (this.light) {
|
241
240
|
if (this.type === LightType.Directional) {
|
242
241
|
return this.light.getWorldPosition(vec).multiplyScalar(1);
|
@@ -251,7 +250,7 @@
|
|
251
250
|
// }
|
252
251
|
|
253
252
|
awake() {
|
254
|
-
this.color = new
|
253
|
+
this.color = new Color(this.color ?? 0xffffff);
|
255
254
|
if (debug) console.log(this.name, this);
|
256
255
|
}
|
257
256
|
|
@@ -308,7 +307,7 @@
|
|
308
307
|
const lightAlreadyCreated = this.selfIsLight;
|
309
308
|
|
310
309
|
if (lightAlreadyCreated && !this.light) {
|
311
|
-
this.light = this.gameObject as unknown as
|
310
|
+
this.light = this.gameObject as unknown as ThreeLight;
|
312
311
|
this.light.name = this.name;
|
313
312
|
this._intensity = this.light.intensity;
|
314
313
|
|
@@ -322,7 +321,7 @@
|
|
322
321
|
switch (this.type) {
|
323
322
|
case LightType.Directional:
|
324
323
|
// console.log(this);
|
325
|
-
const dirLight = new
|
324
|
+
const dirLight = new DirectionalLight(this.color, this.intensity * Math.PI);
|
326
325
|
// directional light target is at 0 0 0 by default
|
327
326
|
dirLight.position.set(0, 0, -shadowMaxDistance * .5).applyQuaternion(this.gameObject.quaternion);
|
328
327
|
this.gameObject.add(dirLight.target);
|
@@ -332,15 +331,15 @@
|
|
332
331
|
this.gameObject.rotation.set(0, 0, 0);
|
333
332
|
|
334
333
|
if (debug) {
|
335
|
-
const spotLightHelper = new
|
334
|
+
const spotLightHelper = new DirectionalLightHelper(this.light as DirectionalLight, .2, this.color);
|
336
335
|
this.context.scene.add(spotLightHelper);
|
337
|
-
// const bh = new
|
336
|
+
// const bh = new BoxHelper(this.context.scene, 0xfff0000);
|
338
337
|
// this.context.scene.add(bh);
|
339
338
|
}
|
340
339
|
break;
|
341
340
|
|
342
341
|
case LightType.Spot:
|
343
|
-
const spotLight = new
|
342
|
+
const spotLight = new SpotLight(this.color, this.intensity * Math.PI, this.range, toRadians(this.spotAngle / 2), 1 - toRadians(this.innerSpotAngle / 2) / toRadians(this.spotAngle / 2), 2);
|
344
343
|
spotLight.position.set(0, 0, 0);
|
345
344
|
spotLight.rotation.set(0, 0, 0);
|
346
345
|
|
@@ -352,10 +351,10 @@
|
|
352
351
|
break;
|
353
352
|
|
354
353
|
case LightType.Point:
|
355
|
-
const pointLight = new
|
354
|
+
const pointLight = new PointLight(this.color, this.intensity * Math.PI, this.range);
|
356
355
|
this.light = pointLight;
|
357
356
|
|
358
|
-
// const pointHelper = new
|
357
|
+
// const pointHelper = new PointLightHelper(pointLight, this.range, this.color);
|
359
358
|
// scene.add(pointHelper);
|
360
359
|
break;
|
361
360
|
}
|
@@ -395,7 +394,7 @@
|
|
395
394
|
|
396
395
|
this.updateShadowSoftHard();
|
397
396
|
|
398
|
-
const cam = this.light.shadow.camera as
|
397
|
+
const cam = this.light.shadow.camera as OrthographicCamera;
|
399
398
|
cam.near = this.shadowNearPlane;
|
400
399
|
// use shadow distance that was set explictly (if any)
|
401
400
|
if (this._shadowDistance !== undefined && typeof this._shadowDistance === "number")
|
@@ -426,7 +425,7 @@
|
|
426
425
|
this.light.shadow.needsUpdate = true;
|
427
426
|
|
428
427
|
if (debug)
|
429
|
-
this.context.scene.add(new
|
428
|
+
this.context.scene.add(new CameraHelper(cam));
|
430
429
|
}
|
431
430
|
|
432
431
|
|
@@ -467,9 +466,9 @@
|
|
467
466
|
// this.light.shadow.blurSamples = Math.floor(this.light.shadow.blurSamples * .5);
|
468
467
|
// }
|
469
468
|
// if (Light.allowChangingRendererShadowMapType) {
|
470
|
-
// if(this.context.renderer.shadowMap.type !==
|
469
|
+
// if(this.context.renderer.shadowMap.type !== VSMShadowMap){
|
471
470
|
// if(isLocalNetwork()) console.warn("Changing renderer shadow map type to VSMShadowMap because a light with soft shadows enabled was found (this will cause all shadow receivers to also cast shadows). If you don't want this behaviour either set the shadow type to hard or set Light.allowChangingRendererShadowMapType to false.", this);
|
472
|
-
// this.context.renderer.shadowMap.type =
|
471
|
+
// this.context.renderer.shadowMap.type = VSMShadowMap;
|
473
472
|
// }
|
474
473
|
// }
|
475
474
|
}
|
@@ -486,5 +485,5 @@
|
|
486
485
|
}
|
487
486
|
}
|
488
487
|
|
489
|
-
const vec = new
|
488
|
+
const vec = new Vector3(0, 0, 0);
|
490
489
|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Vector3 } from "three";
|
1
|
+
import { LOD as ThreeLOD, Object3D, Vector3 } from "three";
|
3
2
|
|
4
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
4
|
import { getParam } from "../engine/engine_utils.js";
|
@@ -34,7 +33,7 @@
|
|
34
33
|
}
|
35
34
|
|
36
35
|
declare class LODSetting {
|
37
|
-
lod:
|
36
|
+
lod: ThreeLOD;
|
38
37
|
levelIndex: number;
|
39
38
|
distance: number;
|
40
39
|
}
|
@@ -43,7 +42,7 @@
|
|
43
42
|
|
44
43
|
fadeMode: LODFadeMode = LODFadeMode.None;
|
45
44
|
@serializable(Vector3)
|
46
|
-
localReferencePoint:
|
45
|
+
localReferencePoint: Vector3 | undefined = undefined;
|
47
46
|
lodCount: number = 0;
|
48
47
|
size: number = 0;
|
49
48
|
animateCrossFading: boolean = false;
|
@@ -55,7 +54,7 @@
|
|
55
54
|
private _settings: LODSetting[] = [];
|
56
55
|
|
57
56
|
// https://threejs.org/docs/#api/en/objects/LOD
|
58
|
-
private _lodsHandler?: Array<
|
57
|
+
private _lodsHandler?: Array<ThreeLOD>;
|
59
58
|
|
60
59
|
start(): void {
|
61
60
|
if (debug)
|
@@ -74,13 +73,13 @@
|
|
74
73
|
renderers.push(rend);
|
75
74
|
}
|
76
75
|
}
|
77
|
-
this._lodsHandler = new Array<
|
76
|
+
this._lodsHandler = new Array<ThreeLOD>();
|
78
77
|
for (let i = 0; i < renderers.length; i++) {
|
79
|
-
const handler = new
|
78
|
+
const handler = new ThreeLOD();
|
80
79
|
this._lodsHandler.push(handler);
|
81
80
|
this.gameObject.add(handler);
|
82
81
|
}
|
83
|
-
const empty = new
|
82
|
+
const empty = new Object3D();
|
84
83
|
empty.name = "Cull " + this.name;
|
85
84
|
for (let i = 0; i < renderers.length; i++) {
|
86
85
|
const rend = renderers[i];
|
@@ -92,7 +91,7 @@
|
|
92
91
|
const dist = lod.model.distance;
|
93
92
|
|
94
93
|
// get object to be lodded, it can be empty
|
95
|
-
let object:
|
94
|
+
let object: Object3D | null = null;
|
96
95
|
if (lod.renderers.includes(rend)) {
|
97
96
|
object = obj;
|
98
97
|
}
|
@@ -121,10 +120,13 @@
|
|
121
120
|
|
122
121
|
for (const h of this._lodsHandler) {
|
123
122
|
h.update(cam);
|
123
|
+
const levelIndex = h.getCurrentLevel();
|
124
|
+
const level = h.levels[levelIndex];
|
125
|
+
h.layers.mask = level.object.layers.mask;
|
124
126
|
}
|
125
127
|
}
|
126
128
|
|
127
|
-
private onAddLodLevel(lod:
|
129
|
+
private onAddLodLevel(lod: ThreeLOD, obj: Object3D, dist: number) {
|
128
130
|
if(obj === this.gameObject) {
|
129
131
|
console.warn("LODGroup component must be on parent object and not mesh directly at the moment", obj.name, obj)
|
130
132
|
return;
|
@@ -1,13 +1,12 @@
|
|
1
|
-
import * as THREE from "three";
|
2
1
|
import { Object3D } from "three";
|
3
2
|
|
4
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
|
-
import { Behaviour
|
4
|
+
import { Behaviour } from "./Component.js";
|
6
5
|
|
7
6
|
export class LookAtConstraint extends Behaviour {
|
8
7
|
|
9
8
|
constraintActive: boolean = true;
|
10
9
|
locked: boolean = false;
|
11
10
|
@serializable(Object3D)
|
12
|
-
sources:
|
11
|
+
sources: Object3D[] = [];
|
13
12
|
}
|
@@ -112,19 +112,7 @@
|
|
112
112
|
if (!this.registry)
|
113
113
|
console.log(LightmapType[entry.type], entry.pointer, tex);
|
114
114
|
else {
|
115
|
-
|
116
|
-
if (entry.type !== LightmapType.Lightmap)
|
117
|
-
tex.colorSpace = SRGBColorSpace;
|
118
|
-
else
|
119
|
-
tex.colorSpace = LinearSRGBColorSpace;
|
120
|
-
|
121
|
-
|
122
|
-
// Dont flip skybox textures anymore - previously we exported them flipped when baking in Unity but now we allow to pass through export without re-baking exisitng skybox textures if they use default values. So we expect textures to be NOT flipped anymore
|
123
|
-
// if (entry.type === LightmapType.Skybox) {
|
124
|
-
// if (tex.type == FloatType || tex.type == HalfFloatType)
|
125
|
-
// tex.flipY = true;
|
126
|
-
// }
|
127
|
-
|
115
|
+
tex.colorSpace = LinearSRGBColorSpace;
|
128
116
|
this.registry.registerTexture(this.source, entry.type, tex, entry.index);
|
129
117
|
}
|
130
118
|
}
|
@@ -2,6 +2,7 @@
|
|
2
2
|
import type { Context } from "../../engine_context.js";
|
3
3
|
import { hasProLicense, onLicenseCheckResultChanged } from "../../engine_license.js";
|
4
4
|
import { getParam } from "../../engine_utils.js";
|
5
|
+
import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
|
5
6
|
import { ButtonsFactory } from "../buttons.js";
|
6
7
|
import { ensureFonts, loadFont } from "../fonts.js";
|
7
8
|
import { getIconElement } from "../icons.js";
|
@@ -36,6 +37,7 @@
|
|
36
37
|
this._context = context;
|
37
38
|
this._spatialMenu = new NeedleSpatialMenu(context, this._menu);
|
38
39
|
window.addEventListener("message", this.onPostMessage);
|
40
|
+
onXRSessionStart(this.onStartXR);
|
39
41
|
}
|
40
42
|
|
41
43
|
/** @ignore internal method */
|
@@ -83,6 +85,21 @@
|
|
83
85
|
}
|
84
86
|
};
|
85
87
|
|
88
|
+
private onStartXR = (args: XRSessionEventArgs) => {
|
89
|
+
if (args.session.isScreenBasedAR) {
|
90
|
+
this._menu["previousParent"] = this._menu.parentNode;
|
91
|
+
this._context.arOverlayElement.appendChild(this._menu);
|
92
|
+
args.session.session.addEventListener("end", this.onExitXR);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
private onExitXR = () => {
|
97
|
+
if (this._menu["previousParent"]) {
|
98
|
+
this._menu["previousParent"].appendChild(this._menu);
|
99
|
+
delete this._menu["previousParent"];
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
86
103
|
/** Experimental: Change the menu position to be at the top or the bottom of the needle engine webcomponent
|
87
104
|
* @param position "top" or "bottom"
|
88
105
|
*/
|
@@ -184,6 +201,7 @@
|
|
184
201
|
overflow: clip;
|
185
202
|
box-shadow: 0px 7px 0.5rem 0px rgb(0 0 0 / 6%), inset 0px 0px 1.3rem rgba(0,0,0,.05);
|
186
203
|
backdrop-filter: blur(16px);
|
204
|
+
pointer-events: all;
|
187
205
|
}
|
188
206
|
|
189
207
|
/** using a div here because then we can change the class for placement **/
|
@@ -352,8 +370,8 @@
|
|
352
370
|
// https://github.com/google/material-design-icons/issues/1165
|
353
371
|
ensureFonts();
|
354
372
|
// add to document head AND shadow dom to work
|
355
|
-
loadFont("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200", { loadedCallback: () => { this.handleSizeChange() } });
|
356
|
-
loadFont("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200", { element: shadow });
|
373
|
+
loadFont("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&display=block", { loadedCallback: () => { this.handleSizeChange() } });
|
374
|
+
loadFont("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&display=block", { element: shadow });
|
357
375
|
|
358
376
|
const content = template.content.cloneNode(true) as DocumentFragment;
|
359
377
|
shadow?.appendChild(content);
|
@@ -549,7 +567,7 @@
|
|
549
567
|
private _lastAvailableWidthChange = 0;
|
550
568
|
private _timeoutHandle: number = 0;
|
551
569
|
|
552
|
-
private handleSizeChange = (_evt?:Event, forceOrEvent?: boolean) => {
|
570
|
+
private handleSizeChange = (_evt?: Event, forceOrEvent?: boolean) => {
|
553
571
|
if (!this._domElement) return;
|
554
572
|
|
555
573
|
const width = this._domElement.clientWidth;
|
@@ -558,8 +576,8 @@
|
|
558
576
|
return;
|
559
577
|
}
|
560
578
|
|
561
|
-
const padding =
|
562
|
-
const availableWidth = width - padding
|
579
|
+
const padding = 20 * 2;
|
580
|
+
const availableWidth = width - padding;
|
563
581
|
|
564
582
|
// if the available width has not changed significantly then we can skip the rest
|
565
583
|
if (!forceOrEvent && Math.abs(availableWidth - this._lastAvailableWidthChange) < 1) return;
|
@@ -568,16 +586,25 @@
|
|
568
586
|
clearTimeout(this._timeoutHandle!);
|
569
587
|
|
570
588
|
this._timeoutHandle = setTimeout(() => {
|
571
|
-
const
|
572
|
-
|
573
|
-
if (spaceLeft <= 0) {
|
589
|
+
const spaceLeft = getSpaceLeft();
|
590
|
+
if (spaceLeft < 0) {
|
574
591
|
this.root.classList.add("compact")
|
575
592
|
}
|
576
|
-
else if (spaceLeft >
|
593
|
+
else if (spaceLeft > 0) {
|
577
594
|
this.root.classList.remove("compact")
|
595
|
+
if (getSpaceLeft() < 0) {
|
596
|
+
// ensure we still have enough space left
|
597
|
+
this.root.classList.add("compact")
|
598
|
+
}
|
578
599
|
}
|
579
600
|
}, 200) as unknown as number;
|
580
601
|
|
602
|
+
const getCurrentWidth = () => {
|
603
|
+
return this.options.clientWidth + this.logoContainer.clientWidth;
|
604
|
+
}
|
605
|
+
const getSpaceLeft = () => {
|
606
|
+
return availableWidth - getCurrentWidth();
|
607
|
+
}
|
581
608
|
}
|
582
609
|
|
583
610
|
|
@@ -73,8 +73,32 @@
|
|
73
73
|
|
74
74
|
|
75
75
|
|
76
|
-
|
77
|
-
function
|
76
|
+
handleSessionGranted();
|
77
|
+
async function handleSessionGranted() {
|
78
|
+
|
79
|
+
// TODO: asap session granted doesnt handle the pre-room yet
|
80
|
+
if (getParam("debugasap")) {
|
81
|
+
let asapSession = globalThis["needle:XRSession"] as XRSession | undefined;
|
82
|
+
if (asapSession instanceof Promise<XRSession>) {
|
83
|
+
delete globalThis["needle:XRSession"];
|
84
|
+
ContextRegistry.addContextCreatedCallback(async cb => {
|
85
|
+
if (!asapSession) return;
|
86
|
+
// TODO: add support to pass this to the temporary room
|
87
|
+
enableSpatialConsole(true);
|
88
|
+
const session = await asapSession;
|
89
|
+
if (session) {
|
90
|
+
const sessionInit = NeedleXRSession.getDefaultSessionInit("immersive-vr");
|
91
|
+
NeedleXRSession.setSession("immersive-vr", session, sessionInit, cb.context);
|
92
|
+
}
|
93
|
+
else {
|
94
|
+
console.error("NeedleXRSession: ASAP session was rejected");
|
95
|
+
}
|
96
|
+
asapSession = undefined;
|
97
|
+
});
|
98
|
+
return;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
78
102
|
if ('xr' in navigator) {
|
79
103
|
// WebXRViewer (based on Firefox) has a bug where addEventListener
|
80
104
|
// throws a silent exception and aborts execution entirely.
|
@@ -84,6 +108,8 @@
|
|
84
108
|
}
|
85
109
|
|
86
110
|
navigator.xr?.addEventListener('sessiongranted', async () => {
|
111
|
+
enableSpatialConsole(true);
|
112
|
+
|
87
113
|
console.log("Received Session Granted...")
|
88
114
|
await delay(100);
|
89
115
|
|
@@ -109,7 +135,9 @@
|
|
109
135
|
// if no session was found we start VR by default
|
110
136
|
NeedleXRSession.start("immersive-vr").catch(e => console.warn("Session Granted failed:", e));
|
111
137
|
}
|
112
|
-
|
138
|
+
// make sure we only subscribe to the event once
|
139
|
+
}, { once: true });
|
140
|
+
|
113
141
|
}
|
114
142
|
}
|
115
143
|
function saveSessionInfo(mode: XRSessionMode, init: XRSessionInit) {
|
@@ -520,7 +548,7 @@
|
|
520
548
|
/** @returns the given controller if it is connected */
|
521
549
|
getController(side: XRHandedness) { return this.controllers.find(c => c.side === side) || null; }
|
522
550
|
|
523
|
-
/** Returns true if running in pass through mode in immersive AR */
|
551
|
+
/** Returns true if running in pass through mode in immersive AR (e.g. user is wearing a headset while in AR) */
|
524
552
|
get isPassThrough() {
|
525
553
|
if (this.environmentBlendMode !== 'opaque' && this.interactionMode === "world-space") return true;
|
526
554
|
// since we can not rely on interactionMode check we check the controllers too
|
@@ -538,6 +566,8 @@
|
|
538
566
|
}
|
539
567
|
get isAR() { return this.mode === 'immersive-ar'; }
|
540
568
|
get isVR() { return this.mode === 'immersive-vr'; }
|
569
|
+
/** If the AR mode is not immersive (meaning the user is e.g. holding a phone instead of wearing a AR passthrough headset) */
|
570
|
+
get isScreenBasedAR() { return this.isAR && !this.isPassThrough; }
|
541
571
|
|
542
572
|
get posePosition() { return this._transformPosition; }
|
543
573
|
get poseOrientation() { return this._transformOrientation; }
|
@@ -1,10 +1,8 @@
|
|
1
|
-
import
|
2
|
-
import {
|
3
|
-
import
|
4
|
-
import { BatchedParticleRenderer, ConstantColor, ConstantValue, ParticleSystem as _ParticleSystem, RenderMode, TrailBatch, TrailParticle } from "three.quarks";
|
1
|
+
import { AxesHelper, BackSide, Blending, BufferGeometry, FrontSide, LinearSRGBColorSpace, Material, Matrix4, Mesh, NormalBlending, Object3D, PlaneGeometry, Quaternion, SpriteMaterial, Texture, Vector3, Vector4 } from "three";
|
2
|
+
import type { BatchedRenderer, Behavior, BurstParameters, EmissionState, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, Particle, ParticleSystemParameters, RecordState, TrailSettings, ValueGenerator } from "three.quarks";
|
3
|
+
import { BatchedParticleRenderer, ConstantColor, ConstantValue, ParticleSystem as _ParticleSystem, RenderMode, TrailParticle } from "three.quarks";
|
5
4
|
|
6
5
|
import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
|
7
|
-
import { Gizmos } from "../engine/engine_gizmos.js";
|
8
6
|
import { Mathf } from "../engine/engine_math.js";
|
9
7
|
// https://github.dev/creativelifeform/three-nebula
|
10
8
|
// import System, { Emitter, Position, Life, SpriteRenderer, Particle, Body, MeshRenderer, } from 'three-nebula.js';
|
@@ -12,12 +10,12 @@
|
|
12
10
|
import { assign } from "../engine/engine_serialization_core.js";
|
13
11
|
import { Context } from "../engine/engine_setup.js";
|
14
12
|
import { createFlatTexture } from "../engine/engine_shaders.js";
|
15
|
-
import { getWorldPosition, getWorldQuaternion, getWorldScale
|
13
|
+
import { getWorldPosition, getWorldQuaternion, getWorldScale } from "../engine/engine_three_utils.js";
|
16
14
|
import { getParam } from "../engine/engine_utils.js";
|
17
15
|
import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js";
|
18
16
|
import { Behaviour, GameObject } from "./Component.js";
|
19
17
|
import { RGBAColor } from "./js-extensions/RGBAColor.js";
|
20
|
-
import { ColorBySpeedModule, ColorOverLifetimeModule, EmissionModule, InheritVelocityModule, type IParticleSystem, LimitVelocityOverLifetimeModule, MainModule, MinMaxCurve, MinMaxGradient, NoiseModule, ParticleBurst, ParticleSystemRenderMode, ParticleSystemScalingMode,
|
18
|
+
import { ColorBySpeedModule, ColorOverLifetimeModule, EmissionModule, InheritVelocityModule, type IParticleSystem, LimitVelocityOverLifetimeModule, MainModule, MinMaxCurve, MinMaxGradient, NoiseModule, ParticleBurst, ParticleSystemRenderMode, ParticleSystemScalingMode, ParticleSystemSimulationSpace, RotationBySpeedModule, RotationOverLifetimeModule, ShapeModule, SizeBySpeedModule, SizeOverLifetimeModule, TextureSheetAnimationModule, TrailModule, VelocityOverLifetimeModule } from "./ParticleSystemModules.js"
|
21
19
|
import { ParticleSubEmitter } from "./ParticleSystemSubEmitter.js";
|
22
20
|
|
23
21
|
const debug = getParam("debugparticles");
|
@@ -161,7 +159,7 @@
|
|
161
159
|
|
162
160
|
type: "function" = "function";
|
163
161
|
|
164
|
-
genColor(color:
|
162
|
+
genColor(color: Vector4, t: number): Vector4 {
|
165
163
|
const col = this._curve.evaluate(t, Math.random());
|
166
164
|
// TODO: incoming color should probably be blended?
|
167
165
|
color.set(col.r, col.g, col.b, col.alpha);
|
@@ -606,17 +604,17 @@
|
|
606
604
|
}
|
607
605
|
return factor;
|
608
606
|
}
|
609
|
-
private flatWhiteTexture?:
|
610
|
-
private clonedTexture: { original?:
|
611
|
-
get texture():
|
607
|
+
private flatWhiteTexture?: Texture;
|
608
|
+
private clonedTexture: { original?: Texture, clone?: Texture } = { original: undefined, clone: undefined };
|
609
|
+
get texture(): Texture {
|
612
610
|
const mat = this.material;
|
613
611
|
if (mat && mat["map"]) {
|
614
|
-
const original = mat["map"]! as
|
612
|
+
const original = mat["map"]! as Texture;
|
615
613
|
// cache the last original one so we're not creating tons of clones
|
616
614
|
if (this.clonedTexture.original !== original || !this.clonedTexture.clone) {
|
617
615
|
const tex = original.clone();
|
618
616
|
tex.premultiplyAlpha = false;
|
619
|
-
tex.colorSpace =
|
617
|
+
tex.colorSpace = LinearSRGBColorSpace;
|
620
618
|
this.clonedTexture.original = original;
|
621
619
|
this.clonedTexture.clone = tex;
|
622
620
|
}
|
@@ -630,7 +628,7 @@
|
|
630
628
|
get uTileCount() { return this.anim.enabled ? this.anim?.numTilesX : undefined }
|
631
629
|
get vTileCount() { return this.anim.enabled ? this.anim?.numTilesY : undefined }
|
632
630
|
get renderOrder() { return 1; }
|
633
|
-
get blending():
|
631
|
+
get blending(): Blending { return this.system.renderer.particleMaterial?.blending ?? NormalBlending; }
|
634
632
|
get transparent() { return this.system.renderer.transparent; }
|
635
633
|
get worldSpace() { return this.system.main.simulationSpace === ParticleSystemSimulationSpace.World; }
|
636
634
|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Object3D, Quaternion, Vector3 } from 'three';
|
1
|
+
import { AnimationMixer, Object3D, Quaternion, Vector3 } from 'three';
|
3
2
|
|
4
3
|
import { FrameEvent } from '../../engine/engine_context.js';
|
5
4
|
import { isLocalNetwork } from '../../engine/engine_networking_utils.js';
|
@@ -348,7 +347,7 @@
|
|
348
347
|
}
|
349
348
|
}
|
350
349
|
}
|
351
|
-
const obj = binding as
|
350
|
+
const obj = binding as Object3D;
|
352
351
|
if (obj.visible !== undefined) {
|
353
352
|
if (obj.visible !== isActive) {
|
354
353
|
obj.visible = isActive;
|
@@ -452,7 +451,7 @@
|
|
452
451
|
}
|
453
452
|
}
|
454
453
|
|
455
|
-
private findRoot(current:
|
454
|
+
private findRoot(current: Object3D): Object3D {
|
456
455
|
if (current.parent)
|
457
456
|
return this.findRoot(current.parent);
|
458
457
|
return current;
|
@@ -541,14 +540,14 @@
|
|
541
540
|
}
|
542
541
|
// If we can not get the mixer from the animator then create a new one
|
543
542
|
if (!handler.mixer)
|
544
|
-
handler.mixer = new
|
543
|
+
handler.mixer = new AnimationMixer(binding.gameObject);
|
545
544
|
handler.clips.push(clip);
|
546
545
|
// uncache because we want to create a new action
|
547
546
|
// this is needed because if a clip is used multiple times in a track (or even multiple tracks)
|
548
547
|
// we want to avoid setting weights on the same instance for clips/objects that are not active
|
549
548
|
handler.mixer.uncacheAction(clip);
|
550
549
|
handler.createHooks(clipModel.asset as Models.AnimationClipModel, clip);
|
551
|
-
const clipAction = handler.mixer.clipAction(clip); // new
|
550
|
+
const clipAction = handler.mixer.clipAction(clip); // new AnimationAction(handler.mixer, clip, null, null);
|
552
551
|
handler.actions.push(clipAction);
|
553
552
|
handler.models.push(clipModel);
|
554
553
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import { Color, Material, Mesh } from "three";
|
2
2
|
|
3
3
|
import { WaitForSeconds } from "../engine/engine_coroutine.js";
|
4
4
|
import { RoomEvents } from "../engine/engine_networking.js";
|
@@ -50,7 +50,7 @@
|
|
50
50
|
const hash = PlayerColor.hashCode(id);
|
51
51
|
const color = PlayerColor.colorFromHashCode(hash);
|
52
52
|
if (this.gameObject.type === "Mesh") {
|
53
|
-
const mesh:
|
53
|
+
const mesh: Mesh = this.gameObject as any;
|
54
54
|
this.assignColor(color, id, mesh);
|
55
55
|
}
|
56
56
|
else if (this.gameObject.children) {
|
@@ -63,8 +63,8 @@
|
|
63
63
|
}
|
64
64
|
}
|
65
65
|
|
66
|
-
private assignColor(col:
|
67
|
-
let mat = mesh.material as
|
66
|
+
private assignColor(col: Color, id: string, mesh: Mesh) {
|
67
|
+
let mat = mesh.material as Material;
|
68
68
|
if (!mat) return;
|
69
69
|
if (mat["_playerMaterial"] !== id) {
|
70
70
|
// console.log("ORIG", mat);
|
@@ -92,7 +92,7 @@
|
|
92
92
|
const r = (hash & 0xFF0000) >> 16;
|
93
93
|
const g = (hash & 0x00FF00) >> 8;
|
94
94
|
const b = hash & 0x0000FF;
|
95
|
-
return new
|
95
|
+
return new Color(r / 255, g / 255, b / 255);
|
96
96
|
}
|
97
97
|
}
|
98
98
|
|
@@ -91,7 +91,7 @@
|
|
91
91
|
get mode(): XRTargetRayMode { return this.event.mode; }
|
92
92
|
|
93
93
|
/** The object this event hit or interacted with */
|
94
|
-
object!:
|
94
|
+
object!: Object3D;
|
95
95
|
/** The world position of this event */
|
96
96
|
point?: Vector3;
|
97
97
|
/** The object-space normal of this event */
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { SkinnedMesh } from "three";
|
1
|
+
import { Intersection, Object3D, SkinnedMesh } from "three";
|
2
2
|
|
3
3
|
import { type IRaycastOptions, RaycastOptions } from "../../engine/engine_physics.js";
|
4
4
|
import { serializable } from "../../engine/engine_serialization.js";
|
@@ -24,13 +24,13 @@
|
|
24
24
|
EventSystem.get(this.context)?.unregister(this);
|
25
25
|
}
|
26
26
|
|
27
|
-
abstract performRaycast(_opts?: IRaycastOptions | RaycastOptions | null):
|
27
|
+
abstract performRaycast(_opts?: IRaycastOptions | RaycastOptions | null): Intersection[] | null;
|
28
28
|
}
|
29
29
|
|
30
30
|
|
31
31
|
export class ObjectRaycaster extends Raycaster {
|
32
|
-
private targets:
|
33
|
-
private raycastHits:
|
32
|
+
private targets: Object3D[] | null = null;
|
33
|
+
private raycastHits: Intersection[] = [];
|
34
34
|
|
35
35
|
@serializable()
|
36
36
|
ignoreSkinnedMeshes = false;
|
@@ -39,7 +39,7 @@
|
|
39
39
|
this.targets = [this.gameObject];
|
40
40
|
}
|
41
41
|
|
42
|
-
performRaycast(opts: IRaycastOptions | RaycastOptions | null = null):
|
42
|
+
performRaycast(opts: IRaycastOptions | RaycastOptions | null = null): Intersection[] | null {
|
43
43
|
if (!this.targets) return null;
|
44
44
|
opts ??= new RaycastOptions();
|
45
45
|
opts.targets = this.targets;
|
@@ -75,7 +75,7 @@
|
|
75
75
|
}
|
76
76
|
|
77
77
|
export class SpatialGrabRaycaster extends Raycaster {
|
78
|
-
performRaycast(_opts?: IRaycastOptions | RaycastOptions | null):
|
78
|
+
performRaycast(_opts?: IRaycastOptions | RaycastOptions | null): Intersection[] | null {
|
79
79
|
// ensure we're in XR, otherwise return
|
80
80
|
if (!NeedleXRSession.active) return null;
|
81
81
|
if (!_opts?.ray) return null;
|
@@ -19,7 +19,7 @@
|
|
19
19
|
return obj;
|
20
20
|
};
|
21
21
|
|
22
|
-
static isInteractable(obj:
|
22
|
+
static isInteractable(obj: Object3D, out?: { canvasGroup?: ICanvasGroup, graphic?: IGraphic }): boolean {
|
23
23
|
// reset state
|
24
24
|
if (out) {
|
25
25
|
out.canvasGroup = undefined;
|
@@ -54,7 +54,7 @@
|
|
54
54
|
}
|
55
55
|
|
56
56
|
|
57
|
-
private static tryFindCanvasGroup(obj:
|
57
|
+
private static tryFindCanvasGroup(obj: Object3D | null): ICanvasGroup | null {
|
58
58
|
if (!obj) return null;
|
59
59
|
// test for canvas groups
|
60
60
|
const res = foreachComponent(obj, c => {
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { AxesHelper, Material, Matrix4, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
|
1
|
+
import { AxesHelper, BufferGeometry, Color, InstancedMesh, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D, RawShaderMaterial, SkinnedMesh, Texture, Vector4 } from "three";
|
3
2
|
|
4
3
|
import { showBalloonWarning } from "../engine/debug/index.js";
|
5
4
|
import { Gizmos } from "../engine/engine_gizmos.js";
|
@@ -54,10 +53,10 @@
|
|
54
53
|
// support sharedMaterials[index] assigning materials directly to the objects
|
55
54
|
class SharedMaterialArray implements ISharedMaterials {
|
56
55
|
|
57
|
-
[num: number]:
|
56
|
+
[num: number]: Material;
|
58
57
|
|
59
58
|
private _renderer: Renderer;
|
60
|
-
private _targets:
|
59
|
+
private _targets: Object3D[] = [];
|
61
60
|
|
62
61
|
private _indexMapMaxIndex?: number;
|
63
62
|
private _indexMap?: Map<number, number>;
|
@@ -215,7 +214,7 @@
|
|
215
214
|
@serializable()
|
216
215
|
lightmapIndex: number = -1;
|
217
216
|
@serializable(Vector4)
|
218
|
-
lightmapScaleOffset:
|
217
|
+
lightmapScaleOffset: Vector4 = new Vector4(1, 1, 0, 0);
|
219
218
|
@serializable()
|
220
219
|
enableInstancing: boolean[] | undefined = undefined;
|
221
220
|
@serializable()
|
@@ -711,7 +710,7 @@
|
|
711
710
|
}
|
712
711
|
}
|
713
712
|
|
714
|
-
loadProgressiveTextures(material:
|
713
|
+
loadProgressiveTextures(material: Material) {
|
715
714
|
// progressive load before rendering so we only load textures for visible materials
|
716
715
|
if (!suppressProgressiveLoading && material) {
|
717
716
|
|
@@ -727,7 +726,7 @@
|
|
727
726
|
return Promise.resolve(true);
|
728
727
|
}
|
729
728
|
|
730
|
-
applySettings(go:
|
729
|
+
applySettings(go: Object3D) {
|
731
730
|
go.receiveShadow = this.receiveShadows;
|
732
731
|
if (this.shadowCastingMode == ShadowCastingMode.On) {
|
733
732
|
go.castShadow = true;
|
@@ -783,7 +782,7 @@
|
|
783
782
|
export class SkinnedMeshRenderer extends MeshRenderer {
|
784
783
|
awake() {
|
785
784
|
super.awake();
|
786
|
-
// disable skinned mesh occlusion because of https://github.com/mrdoob/
|
785
|
+
// disable skinned mesh occlusion because of https://github.com/mrdoob/js/issues/14499
|
787
786
|
this.allowOcclusionWhenDynamic = false;
|
788
787
|
// If we don't do that here the bounding sphere matrix used for raycasts will be wrong. Not sure *why* this is necessary
|
789
788
|
this.gameObject.parent?.updateWorldMatrix(false, true);
|
@@ -829,7 +828,7 @@
|
|
829
828
|
|
830
829
|
public objs: InstancedMeshRenderer[] = [];
|
831
830
|
|
832
|
-
public setup(renderer: Renderer, obj:
|
831
|
+
public setup(renderer: Renderer, obj: Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
|
833
832
|
: InstanceHandle[] | null {
|
834
833
|
|
835
834
|
// make sure setting casting settings are applied so when we add the mesh to the InstancedMesh we can ask for the correct cast shadow setting
|
@@ -858,7 +857,7 @@
|
|
858
857
|
return handlesArray;
|
859
858
|
}
|
860
859
|
|
861
|
-
private tryCreateOrAddInstance(obj:
|
860
|
+
private tryCreateOrAddInstance(obj: Object3D, context: Context, args: InstancingSetupArgs): InstanceHandle | null {
|
862
861
|
if (obj.type === "Mesh") {
|
863
862
|
const index = args.foundMeshes;
|
864
863
|
args.foundMeshes += 1;
|
@@ -873,9 +872,9 @@
|
|
873
872
|
return null;
|
874
873
|
}
|
875
874
|
// instancing is enabled:
|
876
|
-
const mesh = obj as
|
877
|
-
const geo = mesh.geometry as
|
878
|
-
const mat = mesh.material as
|
875
|
+
const mesh = obj as Mesh;
|
876
|
+
const geo = mesh.geometry as BufferGeometry;
|
877
|
+
const mat = mesh.material as Material;
|
879
878
|
|
880
879
|
for (const i of this.objs) {
|
881
880
|
if (i.isFull()) continue;
|
@@ -895,7 +894,7 @@
|
|
895
894
|
|
896
895
|
private autoUpdateInstanceMatrix(obj: Object3D) {
|
897
896
|
const original = obj.matrixWorld["multiplyMatrices"].bind(obj.matrixWorld);
|
898
|
-
const previousMatrix:
|
897
|
+
const previousMatrix: Matrix4 = obj.matrixWorld.clone();
|
899
898
|
const matrixChangeWrapper = (a: Matrix4, b: Matrix4) => {
|
900
899
|
const newMatrixWorld = original(a, b);
|
901
900
|
if (obj[NEED_UPDATE_INSTANCE_KEY] || previousMatrix.equals(newMatrixWorld) === false) {
|
@@ -927,10 +926,10 @@
|
|
927
926
|
}
|
928
927
|
|
929
928
|
instanceIndex: number = -1;
|
930
|
-
object:
|
929
|
+
object: Mesh;
|
931
930
|
instancer: InstancedMeshRenderer;
|
932
931
|
|
933
|
-
constructor(instanceIndex: number, originalObject:
|
932
|
+
constructor(instanceIndex: number, originalObject: Mesh, instancer: InstancedMeshRenderer) {
|
934
933
|
this.instanceIndex = instanceIndex;
|
935
934
|
this.object = originalObject;
|
936
935
|
this.instancer = instancer;
|
@@ -944,7 +943,7 @@
|
|
944
943
|
this.instancer.updateInstance(this.object.matrixWorld, this.instanceIndex);
|
945
944
|
}
|
946
945
|
|
947
|
-
setMatrix(matrix:
|
946
|
+
setMatrix(matrix: Matrix4) {
|
948
947
|
if (this.instanceIndex < 0) return;
|
949
948
|
this.instancer.updateInstance(matrix, this.instanceIndex);
|
950
949
|
}
|
@@ -992,16 +991,16 @@
|
|
992
991
|
}
|
993
992
|
|
994
993
|
public name: string = "";
|
995
|
-
public geo:
|
996
|
-
public material:
|
994
|
+
public geo: BufferGeometry;
|
995
|
+
public material: Material;
|
997
996
|
get currentCount(): number { return this.inst.count; }
|
998
997
|
|
999
998
|
private context: Context;
|
1000
|
-
private inst:
|
999
|
+
private inst: InstancedMesh;
|
1001
1000
|
private handles: (InstanceHandle | null)[] = [];
|
1002
1001
|
private maxCount: number;
|
1003
1002
|
|
1004
|
-
private static nullMatrix:
|
1003
|
+
private static nullMatrix: Matrix4 = new Matrix4();
|
1005
1004
|
|
1006
1005
|
isFull(): boolean {
|
1007
1006
|
return this.currentCount >= this.maxCount;
|
@@ -1009,16 +1008,16 @@
|
|
1009
1008
|
|
1010
1009
|
private _needUpdateBounds: boolean = false;
|
1011
1010
|
|
1012
|
-
constructor(name: string, geo:
|
1011
|
+
constructor(name: string, geo: BufferGeometry, material: Material, count: number, context: Context) {
|
1013
1012
|
this.name = name;
|
1014
1013
|
this.geo = geo;
|
1015
1014
|
this.material = material;
|
1016
1015
|
this.context = context;
|
1017
1016
|
this.maxCount = count;
|
1018
1017
|
if (debugInstancing) {
|
1019
|
-
material = new
|
1018
|
+
material = new MeshBasicMaterial({ color: this.randomColor() });
|
1020
1019
|
}
|
1021
|
-
this.inst = new
|
1020
|
+
this.inst = new InstancedMesh(geo, material, count);
|
1022
1021
|
this.inst[$instancingAutoUpdateBounds] = true;
|
1023
1022
|
this.inst.count = 0;
|
1024
1023
|
this.inst.visible = true;
|
@@ -1032,7 +1031,7 @@
|
|
1032
1031
|
// Same would apply if we support skinning -
|
1033
1032
|
// there we would have to split instanced batches so that the ones using skinning
|
1034
1033
|
// are all in the same batch.
|
1035
|
-
if (material instanceof
|
1034
|
+
if (material instanceof RawShaderMaterial) {
|
1036
1035
|
material.defines["USE_INSTANCING"] = true;
|
1037
1036
|
material.needsUpdate = true;
|
1038
1037
|
}
|
@@ -1057,10 +1056,10 @@
|
|
1057
1056
|
}
|
1058
1057
|
|
1059
1058
|
private randomColor() {
|
1060
|
-
return new
|
1059
|
+
return new Color(Math.random(), Math.random(), Math.random());
|
1061
1060
|
}
|
1062
1061
|
|
1063
|
-
addInstance(obj:
|
1062
|
+
addInstance(obj: Mesh): InstanceHandle | null {
|
1064
1063
|
if (this.currentCount >= this.maxCount) {
|
1065
1064
|
console.error("TOO MANY INSTANCES - resize is not yet implemented!", this.inst.count); // todo: make it resize
|
1066
1065
|
return null;
|
@@ -1140,7 +1139,7 @@
|
|
1140
1139
|
if (debugInstancing) console.log("Removed", this.name, this.inst.count);
|
1141
1140
|
}
|
1142
1141
|
|
1143
|
-
updateInstance(mat:
|
1142
|
+
updateInstance(mat: Matrix4, index: number) {
|
1144
1143
|
this.inst.setMatrixAt(index, mat);
|
1145
1144
|
this.inst.instanceMatrix.needsUpdate = true;
|
1146
1145
|
this._needUpdateBounds = true;
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import { Material, Mesh, ShaderMaterial, Texture, Vector4,
|
1
|
+
import { Material, Mesh, ShaderMaterial, Texture, Vector4, WebGLProgramParametersWithUniforms } from "three";
|
2
2
|
|
3
|
-
import type { Context
|
3
|
+
import type { Context } from "../engine/engine_setup.js";
|
4
4
|
import { getParam } from "../engine/engine_utils.js";
|
5
5
|
|
6
6
|
const debug = getParam("debuglightmaps");
|
@@ -21,7 +21,7 @@
|
|
21
21
|
}
|
22
22
|
|
23
23
|
private lightmapIndex: number = -1;
|
24
|
-
private lightmapScaleOffset:
|
24
|
+
private lightmapScaleOffset: Vector4 = new Vector4(1, 1, 0, 0);
|
25
25
|
|
26
26
|
private context: Context;
|
27
27
|
private gameObject: Mesh;
|
@@ -95,6 +95,8 @@
|
|
95
95
|
const mat = this.gameObject.material as any;
|
96
96
|
if (mat) {
|
97
97
|
mat.lightMap = this.lightmapTexture;
|
98
|
+
// always on channel 1 for now. We could optimize this by passing the correct lightmap index along
|
99
|
+
this.lightmapTexture.channel = 1;
|
98
100
|
mat.needsUpdate = true;
|
99
101
|
}
|
100
102
|
}
|
@@ -102,11 +104,8 @@
|
|
102
104
|
|
103
105
|
private onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, _) => {
|
104
106
|
if (debug) console.log("Lightmaps, before compile", shader)
|
105
|
-
//@ts-ignore
|
106
|
-
shader.lightMapUv = "uv1";
|
107
107
|
this.lightmapScaleOffsetUniform.value = this.lightmapScaleOffset;
|
108
108
|
this.lightmapUniform.value = this.lightmapTexture;
|
109
|
-
// shader.uniforms.lightmap = this.lightmapUniform;
|
110
109
|
shader.uniforms.lightmapScaleOffset = this.lightmapScaleOffsetUniform;
|
111
110
|
}
|
112
111
|
|
@@ -21,8 +21,11 @@
|
|
21
21
|
return cloned;
|
22
22
|
}
|
23
23
|
|
24
|
-
copy(col : RGBAColor | Color){
|
25
|
-
|
24
|
+
copy(col : RGBAColor | Color) {
|
25
|
+
this.r = col.r;
|
26
|
+
this.g = col.g;
|
27
|
+
this.b = col.b;
|
28
|
+
|
26
29
|
if("alpha" in col && typeof col.alpha === "number") {
|
27
30
|
this.alpha = col.alpha;
|
28
31
|
}
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import * as THREE from 'three'
|
2
1
|
import { Matrix4, Object3D, Vector3 } from "three";
|
3
2
|
|
4
3
|
import { CollisionDetectionMode, RigidbodyConstraints } from "../engine/engine_physics.types.js";
|
@@ -271,12 +270,12 @@
|
|
271
270
|
@validate()
|
272
271
|
dominanceGroup: number = 0;
|
273
272
|
|
274
|
-
private static tempPosition:
|
273
|
+
private static tempPosition: Vector3 = new Vector3();
|
275
274
|
private _propertiesChanged: boolean = false;
|
276
|
-
private _currentVelocity:
|
277
|
-
private _smoothedVelocity:
|
278
|
-
private _smoothedVelocityGetter:
|
279
|
-
private _lastPosition:
|
275
|
+
private _currentVelocity: Vector3 = new Vector3();
|
276
|
+
private _smoothedVelocity: Vector3 = new Vector3();
|
277
|
+
private _smoothedVelocityGetter: Vector3 = new Vector3();
|
278
|
+
private _lastPosition: Vector3 = new Vector3();
|
280
279
|
|
281
280
|
private _watch?: TransformWatch;
|
282
281
|
|
@@ -371,7 +370,7 @@
|
|
371
370
|
/** Forces affect the rigid-body's acceleration whereas impulses affect the rigid-body's velocity
|
372
371
|
* the acceleration change is equal to the force divided by the mass:
|
373
372
|
* @link see https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses */
|
374
|
-
public applyForce(vec: Vector3 | Vec3, _rel?:
|
373
|
+
public applyForce(vec: Vector3 | Vec3, _rel?: Vector3, wakeup: boolean = true) {
|
375
374
|
if (this._propertiesChanged) this.updateProperties();
|
376
375
|
this.context.physics.engine?.addForce(this, vec, wakeup);
|
377
376
|
}
|
@@ -454,7 +453,7 @@
|
|
454
453
|
/**d
|
455
454
|
* @deprecated not used anymore and will be removed in a future update
|
456
455
|
*/
|
457
|
-
public setBodyFromGameObject(_velocity:
|
456
|
+
public setBodyFromGameObject(_velocity: Vector3 | null | { x: number, y: number, z: number } = null) { }
|
458
457
|
|
459
458
|
|
460
459
|
private captureVelocity() {
|
@@ -106,7 +106,7 @@
|
|
106
106
|
}
|
107
107
|
}
|
108
108
|
|
109
|
-
//
|
109
|
+
// ShadowMaterial: only does a mask; shadowed areas are fully black.
|
110
110
|
// doesn't take light attenuation into account.
|
111
111
|
// works great for Directional Lights.
|
112
112
|
applyShadowMaterial() {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { EquirectangularRefractionMapping,
|
1
|
+
import { EquirectangularRefractionMapping, SRGBColorSpace, Texture, TextureLoader } from "three"
|
2
2
|
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
|
3
3
|
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
|
4
4
|
|
@@ -1,17 +1,15 @@
|
|
1
|
-
import
|
2
|
-
import { Object3D } from "three";
|
1
|
+
import { Object3D, Quaternion, Vector3 } from "three";
|
3
2
|
|
4
3
|
import { Mathf } from "../engine/engine_math.js";
|
5
4
|
import { Axes } from "../engine/engine_physics.types.js";
|
6
5
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
7
6
|
import { getWorldPosition, getWorldQuaternion } from "../engine/engine_three_utils.js";
|
8
|
-
import {
|
9
|
-
import { Behaviour, GameObject } from "./Component.js";
|
7
|
+
import { Behaviour } from "./Component.js";
|
10
8
|
|
11
9
|
export class SmoothFollow extends Behaviour {
|
12
10
|
|
13
11
|
@serializable(Object3D)
|
14
|
-
target:
|
12
|
+
target: Object3D | null = null;
|
15
13
|
|
16
14
|
@serializable()
|
17
15
|
followFactor = .1;
|
@@ -23,7 +21,7 @@
|
|
23
21
|
|
24
22
|
flipForward: boolean = false;
|
25
23
|
|
26
|
-
private static _invertForward:
|
24
|
+
private static _invertForward: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
27
25
|
private _firstUpdate = true;
|
28
26
|
|
29
27
|
onBeforeRender(): void {
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import
|
1
|
+
import { Box3, MeshBasicMaterial } from 'three';
|
2
2
|
import { HTMLMesh } from 'three/examples/jsm/interactive/HTMLMesh.js';
|
3
3
|
import { InteractiveGroup } from 'three/examples/jsm/interactive/InteractiveGroup.js';
|
4
4
|
|
5
5
|
import { serializable } from '../../engine/engine_serialization.js';
|
6
|
-
import {
|
6
|
+
import { getWorldRotation, setWorldRotationXYZ } from '../../engine/engine_three_utils.js';
|
7
7
|
import { Behaviour } from '../Component.js';
|
8
8
|
|
9
9
|
export class SpatialHtml extends Behaviour {
|
@@ -30,14 +30,16 @@
|
|
30
30
|
div.style.display = "block";
|
31
31
|
div.style.visibility = "hidden";
|
32
32
|
|
33
|
-
const group = new InteractiveGroup(
|
33
|
+
const group = new InteractiveGroup();
|
34
|
+
group.listenToPointerEvents(this.context.renderer, this.context.mainCamera!);
|
35
|
+
// TODO listen to controller events?
|
34
36
|
this.gameObject.add(group);
|
35
37
|
|
36
38
|
const mesh = new HTMLMesh(div);
|
37
39
|
group.add(mesh);
|
38
40
|
mesh.visible = false;
|
39
41
|
|
40
|
-
const mat = mesh.material as
|
42
|
+
const mat = mesh.material as MeshBasicMaterial;
|
41
43
|
mat.transparent = true;
|
42
44
|
|
43
45
|
// need to wait one frame for it to render to get bounds
|
@@ -47,7 +49,7 @@
|
|
47
49
|
const rot = getWorldRotation(this.gameObject).clone();
|
48
50
|
setWorldRotationXYZ(this.gameObject, 0, 0, 0);
|
49
51
|
this.gameObject.updateMatrixWorld();
|
50
|
-
const aabb = new
|
52
|
+
const aabb = new Box3();
|
51
53
|
aabb.setFromObject(group);
|
52
54
|
this.setWorldRotation(rot.x, rot.y, rot.z);
|
53
55
|
// apply bounds
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { Layers, Object3D } from "three";
|
2
2
|
|
3
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
4
4
|
import { getParam } from "../engine/engine_utils.js";
|
@@ -103,7 +103,7 @@
|
|
103
103
|
SpatialTrigger.triggers.splice(SpatialTrigger.triggers.indexOf(this), 1);
|
104
104
|
}
|
105
105
|
|
106
|
-
test(obj:
|
106
|
+
test(obj: Object3D): boolean {
|
107
107
|
if (!this.boxHelper) return false;
|
108
108
|
return this.boxHelper.isInBox(obj) ?? false;
|
109
109
|
}
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Object3D } from "three";
|
1
|
+
import { Color, Object3D, PerspectiveCamera, Quaternion, Vector3, WebGLState } from "three";
|
3
2
|
|
4
3
|
import { InputEvents } from "../engine/engine_input.js";
|
5
4
|
import { RoomEvents } from "../engine/engine_networking.js";
|
@@ -115,8 +114,8 @@
|
|
115
114
|
return this.isSpectating && this.target?.currentObject === this.context.players.getPlayerView(this.localId)?.currentObject;
|
116
115
|
}
|
117
116
|
|
118
|
-
// private currentViewport :
|
119
|
-
// private currentScissor :
|
117
|
+
// private currentViewport : Vector4 = new Vector4();
|
118
|
+
// private currentScissor : Vector4 = new Vector4();
|
120
119
|
// private currentScissorTest : boolean = false;
|
121
120
|
|
122
121
|
private orbit: OrbitControls | null = null;
|
@@ -212,7 +211,7 @@
|
|
212
211
|
const previousRenderTarget = renderer.getRenderTarget();
|
213
212
|
let oldFramebuffer: WebGLFramebuffer | null = null;
|
214
213
|
|
215
|
-
const webglState = renderer.state as
|
214
|
+
const webglState = renderer.state as WebGLState & { bindXRFramebuffer?: Function };
|
216
215
|
|
217
216
|
// seems that in some cases, renderer.getRenderTarget returns null
|
218
217
|
// even when we're rendering to a headset.
|
@@ -250,11 +249,11 @@
|
|
250
249
|
this.cam.farClipPlane = mainCam.farClipPlane;
|
251
250
|
}
|
252
251
|
else
|
253
|
-
renderer.setClearColor(new
|
252
|
+
renderer.setClearColor(new Color(1, 1, 1));
|
254
253
|
renderer.setRenderTarget(null); // null: direct to Canvas
|
255
254
|
renderer.xr.enabled = false;
|
256
255
|
const cam = this.cam?.cam;
|
257
|
-
this.context.updateAspect(cam as
|
256
|
+
this.context.updateAspect(cam as PerspectiveCamera);
|
258
257
|
const wasPresenting = renderer.xr.isPresenting;
|
259
258
|
renderer.xr.isPresenting = false;
|
260
259
|
renderer.setSize(this.context.domWidth, this.context.domHeight);
|
@@ -326,7 +325,7 @@
|
|
326
325
|
readonly spectator: SpectatorCamera;
|
327
326
|
|
328
327
|
private follow?: SmoothFollow;
|
329
|
-
private target?:
|
328
|
+
private target?: Object3D;
|
330
329
|
private view?: PlayerView;
|
331
330
|
private currentObject: Object3D | undefined;
|
332
331
|
|
@@ -352,7 +351,7 @@
|
|
352
351
|
if (!this.follow)
|
353
352
|
this.follow = GameObject.addNewComponent(this.cam.gameObject, SmoothFollow);
|
354
353
|
if (!this.target)
|
355
|
-
this.target = new
|
354
|
+
this.target = new Object3D();
|
356
355
|
followObject.add(this.target);
|
357
356
|
|
358
357
|
this.follow.enabled = true;
|
@@ -390,7 +389,7 @@
|
|
390
389
|
if (debug) console.log("Target changed", this.currentObject, "to", this.currentTarget.currentObject);
|
391
390
|
this.set(this.currentTarget);
|
392
391
|
}
|
393
|
-
const perspectiveCamera = this.context.mainCamera as
|
392
|
+
const perspectiveCamera = this.context.mainCamera as PerspectiveCamera;
|
394
393
|
if (perspectiveCamera) {
|
395
394
|
if (this.cam.cam.near !== perspectiveCamera.near || this.cam.cam.far !== perspectiveCamera.far) {
|
396
395
|
this.cam.cam.near = perspectiveCamera.near;
|
@@ -431,7 +430,7 @@
|
|
431
430
|
|
432
431
|
}
|
433
432
|
|
434
|
-
const _inverseYQuat = new
|
433
|
+
const _inverseYQuat = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
435
434
|
|
436
435
|
|
437
436
|
class SpectatorSelectionController {
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import { Material, NearestFilter, Texture } from "three";
|
1
|
+
import { BufferAttribute, BufferGeometry, Color, DoubleSide, Material, Mesh, MeshBasicMaterial, NearestFilter, SRGBColorSpace, Texture } from "three";
|
3
2
|
|
4
3
|
import { Context } from "../engine/engine_context.js";
|
5
4
|
import { serializable, serializeable } from "../engine/engine_serialization_decorator.js";
|
@@ -13,9 +12,9 @@
|
|
13
12
|
|
14
13
|
class SpriteUtils {
|
15
14
|
|
16
|
-
static cache: { [key: string]:
|
15
|
+
static cache: { [key: string]: BufferGeometry } = {};
|
17
16
|
|
18
|
-
static getOrCreateGeometry(sprite: Sprite):
|
17
|
+
static getOrCreateGeometry(sprite: Sprite): BufferGeometry {
|
19
18
|
if (sprite._geometry) return sprite._geometry;
|
20
19
|
if (sprite.guid) {
|
21
20
|
if (SpriteUtils.cache[sprite.guid]) {
|
@@ -23,7 +22,7 @@
|
|
23
22
|
return SpriteUtils.cache[sprite.guid];
|
24
23
|
}
|
25
24
|
}
|
26
|
-
const geo = new
|
25
|
+
const geo = new BufferGeometry();
|
27
26
|
sprite._geometry = geo;
|
28
27
|
const vertices = new Float32Array(sprite.triangles.length * 3);
|
29
28
|
const uvs = new Float32Array(sprite.triangles.length * 2);
|
@@ -38,8 +37,8 @@
|
|
38
37
|
uvs[i * 2] = uv.x;
|
39
38
|
uvs[i * 2 + 1] = 1 - uv.y;
|
40
39
|
}
|
41
|
-
geo.setAttribute("position", new
|
42
|
-
geo.setAttribute("uv", new
|
40
|
+
geo.setAttribute("position", new BufferAttribute(vertices, 3));
|
41
|
+
geo.setAttribute("uv", new BufferAttribute(uvs, 2));
|
43
42
|
if (sprite.guid)
|
44
43
|
this.cache[sprite.guid] = geo;
|
45
44
|
if (debug)
|
@@ -75,7 +74,7 @@
|
|
75
74
|
@serializable()
|
76
75
|
guid?: string;
|
77
76
|
@serializable(Texture)
|
78
|
-
texture?:
|
77
|
+
texture?: Texture;
|
79
78
|
@serializeable()
|
80
79
|
triangles!: Array<number>;
|
81
80
|
@serializeable()
|
@@ -83,7 +82,7 @@
|
|
83
82
|
@serializeable()
|
84
83
|
vertices!: Array<Vec2>;
|
85
84
|
|
86
|
-
_geometry?:
|
85
|
+
_geometry?: BufferGeometry;
|
87
86
|
_hasLoadedProgressive: boolean = false;
|
88
87
|
}
|
89
88
|
|
@@ -111,7 +110,7 @@
|
|
111
110
|
const slice = this.spriteSheet.sprites[index];
|
112
111
|
const tex = slice?.texture;
|
113
112
|
if (!tex) return;
|
114
|
-
tex.colorSpace =
|
113
|
+
tex.colorSpace = SRGBColorSpace;
|
115
114
|
if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter)
|
116
115
|
tex.anisotropy = 1;
|
117
116
|
tex.needsUpdate = true;
|
@@ -144,7 +143,7 @@
|
|
144
143
|
color?: RGBAColor;
|
145
144
|
|
146
145
|
@serializable(Material)
|
147
|
-
sharedMaterial?:
|
146
|
+
sharedMaterial?: Material;
|
148
147
|
|
149
148
|
@serializable(SpriteData)
|
150
149
|
get sprite(): SpriteData | undefined {
|
@@ -180,7 +179,7 @@
|
|
180
179
|
}
|
181
180
|
|
182
181
|
private _spriteSheet?: SpriteData;
|
183
|
-
private _currentSprite?:
|
182
|
+
private _currentSprite?: Mesh;
|
184
183
|
|
185
184
|
// additional data
|
186
185
|
@serializable()
|
@@ -214,12 +213,12 @@
|
|
214
213
|
return;
|
215
214
|
}
|
216
215
|
if (!this._currentSprite) {
|
217
|
-
const mat = new
|
216
|
+
const mat = new MeshBasicMaterial({ color: 0xffffff, side: DoubleSide });
|
218
217
|
if (!mat) return;
|
219
218
|
if (showWireframe)
|
220
219
|
mat.wireframe = true;
|
221
220
|
if (this.color) {
|
222
|
-
if (!mat["color"]) mat["color"] = new
|
221
|
+
if (!mat["color"]) mat["color"] = new Color();
|
223
222
|
mat["color"].copy(this.color);
|
224
223
|
mat["opacity"] = this.color.alpha;
|
225
224
|
}
|
@@ -237,7 +236,7 @@
|
|
237
236
|
mat["map"] = tex;
|
238
237
|
}
|
239
238
|
this.sharedMaterial = mat;
|
240
|
-
this._currentSprite = new
|
239
|
+
this._currentSprite = new Mesh(SpriteUtils.getOrCreateGeometry(sprite), mat);
|
241
240
|
NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, mat, 0);
|
242
241
|
}
|
243
242
|
else {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Builder } from "flatbuffers";
|
2
|
-
import { Object3D } from "three";
|
2
|
+
import { Camera as ThreeCamera, Object3D, Quaternion, Vector3 } from "three";
|
3
3
|
|
4
4
|
import { isDevEnvironment } from "../engine/debug/index.js";
|
5
5
|
import { AssetReference } from "../engine/engine_addressables.js";
|
@@ -12,7 +12,6 @@
|
|
12
12
|
import { registerBinaryType } from "../engine-schemes/schemes.js";
|
13
13
|
import { SyncedCameraModel } from "../engine-schemes/synced-camera-model.js";
|
14
14
|
import { Vec3 } from "../engine-schemes/vec3.js";
|
15
|
-
import { Camera } from "./Camera.js";
|
16
15
|
import { Behaviour, GameObject } from "./Component.js";
|
17
16
|
import { AvatarMarker } from "./webxr/WebXRAvatar.js";
|
18
17
|
|
@@ -36,7 +35,7 @@
|
|
36
35
|
this.userId = connectionId;
|
37
36
|
}
|
38
37
|
|
39
|
-
send(cam:
|
38
|
+
send(cam: ThreeCamera | null | undefined, con: NetworkConnection) {
|
40
39
|
if (cam) {
|
41
40
|
builder.clear();
|
42
41
|
const guid = builder.createString(this.guid);
|
@@ -56,7 +55,7 @@
|
|
56
55
|
}
|
57
56
|
|
58
57
|
declare type UserCamInfo = {
|
59
|
-
obj:
|
58
|
+
obj: Object3D,
|
60
59
|
lastUpdate: number;
|
61
60
|
userId: string;
|
62
61
|
};
|
@@ -65,17 +64,17 @@
|
|
65
64
|
|
66
65
|
static instances: UserCamInfo[] = [];
|
67
66
|
|
68
|
-
getCameraObject(userId: string):
|
67
|
+
getCameraObject(userId: string): Object3D | null {
|
69
68
|
const guid = this.userToCamMap[userId];
|
70
69
|
if (!guid) return null;
|
71
70
|
return this.remoteCams[guid].obj;
|
72
71
|
}
|
73
72
|
|
74
73
|
@serializable([Object3D, AssetReference])
|
75
|
-
public cameraPrefab:
|
74
|
+
public cameraPrefab: Object3D | null | AssetReference = null;
|
76
75
|
|
77
|
-
private _lastWorldPosition!:
|
78
|
-
private _lastWorldQuaternion!:
|
76
|
+
private _lastWorldPosition!: Vector3;
|
77
|
+
private _lastWorldQuaternion!: Quaternion;
|
79
78
|
private _model: CameraModel | null = null;
|