Needle Engine

Changes between version 3.33.0-beta and 3.34.0-alpha
Files changed (75) hide show
  1. plugins/vite/alias.js +62 -3
  2. plugins/vite/build-pipeline.js +3 -0
  3. plugins/common/buildinfo.js +8 -2
  4. plugins/vite/buildinfo.js +15 -3
  5. plugins/vite/index.js +4 -0
  6. plugins/vite/meta.js +3 -2
  7. src/engine-components/avatar/Avatar_Brain_LookAt.ts +9 -9
  8. src/engine-components/avatar/Avatar_MouthShapes.ts +4 -4
  9. src/engine-components/avatar/Avatar_MustacheShake.ts +3 -1
  10. src/engine-components/avatar/AvatarBlink_Simple.ts +2 -2
  11. src/engine-components/avatar/AvatarEyeLook_Rotation.ts +5 -11
  12. src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +1 -1
  13. src/engine/webcomponents/buttons.ts +2 -2
  14. src/engine-components/Camera.ts +0 -1
  15. src/engine-components/postprocessing/Effects/ColorAdjustments.ts +13 -13
  16. src/engine-components/DeleteBox.ts +2 -2
  17. src/engine-components/Duplicatable.ts +3 -3
  18. src/engine/engine_audio.ts +13 -10
  19. src/engine/engine_element_loading.ts +3 -3
  20. src/engine/engine_input.ts +46 -9
  21. src/engine/engine_lightdata.ts +1 -1
  22. src/engine/engine_math.ts +3 -1
  23. src/engine/engine_networking_files_default_components.ts +2 -1
  24. src/engine/engine_networking_instantiate.ts +7 -7
  25. src/engine/engine_physics_rapier.ts +1 -1
  26. src/engine/engine_scenelighting.ts +9 -11
  27. src/engine/engine_serialization_builtin_serializer.ts +6 -6
  28. src/engine/engine_serialization_core.ts +5 -5
  29. src/engine/engine_shaders.ts +4 -4
  30. src/engine/engine_time.ts +24 -6
  31. src/engine/engine_utils.ts +3 -1
  32. src/engine/xr/events.ts +4 -2
  33. src/engine-components/ui/EventSystem.ts +12 -12
  34. src/engine/extensions/extension_utils.ts +1 -1
  35. src/engine-components/Gizmos.ts +4 -5
  36. src/engine-components/export/gltf/GltfExport.ts +5 -5
  37. src/engine-components/GridHelper.ts +3 -3
  38. src/engine-components/GroundProjection.ts +8 -5
  39. src/engine/webcomponents/icons.ts +3 -0
  40. src/engine-components/Light.ts +18 -19
  41. src/engine-components/LODGroup.ts +12 -10
  42. src/engine-components/LookAtConstraint.ts +2 -3
  43. src/engine/extensions/NEEDLE_lightmaps.ts +1 -13
  44. src/engine/webcomponents/needle menu/needle-menu.ts +36 -9
  45. src/engine/xr/NeedleXRSession.ts +34 -4
  46. src/engine-components/ParticleSystem.ts +12 -14
  47. src/engine-components/timeline/PlayableDirector.ts +5 -6
  48. src/engine-components/PlayerColor.ts +5 -5
  49. src/engine-components/ui/PointerEvents.ts +1 -1
  50. src/engine-components/ui/Raycaster.ts +6 -6
  51. src/engine-components/ui/RaycastUtils.ts +2 -2
  52. src/engine-components/Renderer.ts +27 -28
  53. src/engine-components/RendererLightmap.ts +5 -6
  54. src/engine-components/js-extensions/RGBAColor.ts +5 -2
  55. src/engine-components/RigidBody.ts +7 -8
  56. src/engine-components/ShadowCatcher.ts +1 -1
  57. src/engine-components/Skybox.ts +1 -1
  58. src/engine-components/SmoothFollow.ts +4 -6
  59. src/engine-components/ui/SpatialHtml.ts +7 -5
  60. src/engine-components/SpatialTrigger.ts +2 -2
  61. src/engine-components/SpectatorCamera.ts +10 -11
  62. src/engine-components/SpriteRenderer.ts +14 -15
  63. src/engine-components/SyncedCamera.ts +7 -8
  64. src/engine-components/SyncedTransform.ts +13 -13
  65. src/engine-components/TestRunner.ts +2 -2
  66. src/engine-components/export/usdz/ThreeUSDZExporter.ts +3 -3
  67. src/engine-components/timeline/TimelineModels.ts +7 -8
  68. src/engine-components/postprocessing/Effects/Tonemapping.ts +3 -3
  69. src/engine-components/TransformGizmo.ts +2 -2
  70. plugins/types/userconfig.d.ts +4 -0
  71. src/engine-components/VideoPlayer.ts +1 -1
  72. src/engine-components/postprocessing/Volume.ts +4 -3
  73. src/engine-components/webxr/WebXRButtons.ts +1 -1
  74. src/engine-components/webxr/controllers/XRControllerMovement.ts +1 -1
  75. plugins/vite/asap.js +53 -0
plugins/vite/alias.js CHANGED
@@ -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
- return {
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
plugins/vite/build-pipeline.js CHANGED
@@ -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
plugins/common/buildinfo.js CHANGED
@@ -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
- info.files.push(relpath);
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);
plugins/vite/buildinfo.js CHANGED
@@ -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
- const buildDirectory = getOutputDirectory();
20
- createBuildInfoFile(buildDirectory);
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
  }
plugins/vite/index.js CHANGED
@@ -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),
plugins/vite/meta.js CHANGED
@@ -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
src/engine-components/avatar/Avatar_Brain_LookAt.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as THREE from "three";
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: THREE.Object3D, avatar: AvatarMarker | null }[] = [];
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: THREE.Object3D, ignoredBy: AvatarMarker | null = null) {
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: THREE.Object3D | null) {
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: THREE.Vector3 = new THREE.Vector3();
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: THREE.Object3D) {
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 THREE.AxesHelper(.1));
62
+ // this.target.add(new AxesHelper(.1));
63
63
  }
64
64
 
65
65
  // that target to copy positions into
66
- private target: THREE.Object3D | null = null;
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: THREE.Object3D | null = null;
71
+ private _currentTargetObject: Object3D | null = null;
72
72
  private _lastUpdateTime: number = 0;
73
73
  private _lookDuration: number = 0;
74
74
  private _lastPoiChangedTime: number = 0;
src/engine-components/avatar/Avatar_MouthShapes.ts CHANGED
@@ -10,9 +10,9 @@
10
10
 
11
11
  export class Avatar_MouthShapes extends Behaviour {
12
12
  @serializable(Object3D)
13
- public idle: THREE.Object3D[] = [];
13
+ public idle: Object3D[] = [];
14
14
  @serializable(Object3D)
15
- public talking: THREE.Object3D[] = [];
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: THREE.Object3D[], index: number) {
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 THREE.Mesh);
78
+ // this.mouthShapes.push(o as Mesh);
79
79
  // }
80
80
  // }
81
81
  // });
src/engine-components/avatar/Avatar_MustacheShake.ts CHANGED
@@ -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 : THREE.Vector3 | null = null;
11
+ private _startPosition : Vector3 | null = null;
10
12
 
11
13
  awake() {
12
14
  this.voip = GameObject.findObjectOfType(Voip, this.context);
src/engine-components/avatar/AvatarBlink_Simple.ts CHANGED
@@ -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, XRState } from "../webxr/XRFlag.js";
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: THREE.Object3D[] = [];
11
+ private eyes: Object3D[] = [];
12
12
  @serializable()
13
13
  private lastBlinkTime: number = 0;
14
14
  @serializable()
src/engine-components/avatar/AvatarEyeLook_Rotation.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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: THREE.Object3D | null = null;
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: THREE.Vector3 = new THREE.Vector3();
40
- private static forward: THREE.Vector3 = new THREE.Vector3(0, 0, 1);
41
- private currentTargetPoint: THREE.Vector3 = new THREE.Vector3();
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;
src/engine-components/export/usdz/extensions/behavior/Behaviour.ts CHANGED
@@ -168,7 +168,7 @@
168
168
  // emptyParent.add(model);
169
169
 
170
170
 
171
- // const geometry = new THREE.SphereGeometry(.6, 32, 16);
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
 
src/engine/webcomponents/buttons.ts CHANGED
@@ -53,10 +53,10 @@
53
53
  }
54
54
  });
55
55
  // xr session started?
56
- document.addEventListener("needle-xrsession-start", () => {
56
+ globalThis.addEventListener("needle-xrsession-start", () => {
57
57
  button.style.display = "none";
58
58
  });
59
- document.addEventListener("needle-xrsession-end", () => {
59
+ globalThis.addEventListener("needle-xrsession-end", () => {
60
60
  button.style.display = "";
61
61
  });
62
62
  return button;
src/engine-components/Camera.ts CHANGED
@@ -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
  }
src/engine-components/postprocessing/Effects/ColorAdjustments.ts CHANGED
@@ -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.ACES_FILMIC; // TODO, no Linear mode in postprocessing, see https://github.com/pmndrs/postprocessing/issues/605
68
+ case LinearToneMapping: return ToneMappingMode.LINEAR;
69
69
  case ACESFilmicToneMapping: return ToneMappingMode.ACES_FILMIC;
70
70
  case AgXToneMapping: return ToneMappingMode.AGX;
71
- default: return ToneMappingMode.ACES_FILMIC; // TODO, no Linear mode in postprocessing, see https://github.com/pmndrs/postprocessing/issues/605
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
- const apply = (v) => {
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
- tonemapping.mode = this.threeToneMappingToEffectMode(this.toneMappingEffect?.getThreeToneMapping(currentMode));
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
 
src/engine-components/DeleteBox.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import * as THREE from "three";
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 THREE.Mesh;
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);
src/engine-components/Duplicatable.ts CHANGED
@@ -26,8 +26,8 @@
26
26
  limitInterval = 60;
27
27
 
28
28
  private _currentCount = 0;
29
- private _startPosition: THREE.Vector3 | null = null;
30
- private _startQuaternion: THREE.Quaternion | null = null;
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(): THREE.Object3D | null {
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;
src/engine/engine_audio.ts CHANGED
@@ -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
- // this is a fix for https://github.com/mrdoob/three.js/issues/27779 & https://linear.app/needle/issue/NE-4257
7
- const ctx = AudioContext.getContext();
8
- ctx.addEventListener("statechange", () => {
9
- // 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
10
- const state = ctx.state as AudioContextState | "interrupted";
11
- if (state === "suspended" || state === "interrupted") {
12
- ctx.resume()
13
- .then(() => { console.log("AudioContext resumed successfully"); })
14
- .catch((e) => { console.log("Failed to resume AudioContext: " + e); });
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);
src/engine/engine_element_loading.ts CHANGED
@@ -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 USE REQUIRES A LICENSE.\nCLICK HERE TO GET ONE.";
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();
src/engine/engine_input.ts CHANGED
@@ -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.indexOf(callback);
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.indexOf(callback);
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 (const l of queue.listeners)
249
- (l as KeyboardEventListener)(evt);
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 (const l of queue.listeners) {
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
- (l as PointerEventListener)(evt);
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
  }
src/engine/engine_lightdata.ts CHANGED
@@ -76,7 +76,7 @@
76
76
 
77
77
 
78
78
  // all the chunks we can patch
79
- // console.log(THREE.ShaderChunk);
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)));
src/engine/engine_math.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { Vector } from "three";
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 {
src/engine/engine_networking_files_default_components.ts CHANGED
@@ -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: THREE.Object3D, _idProv: UIDProvider, _gltf?: GLTF) {
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
 
src/engine/engine_networking_instantiate.ts CHANGED
@@ -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 THREE.Vector3(model.position.x, model.position.y, model.position.z);
264
+ options.position = new Vector3(model.position.x, model.position.y, model.position.z);
265
265
  if (model.rotation)
266
- options.rotation = new THREE.Quaternion(model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w);
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 THREE.Vector3(model.scale.x, model.scale.y, model.scale.z);
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: THREE.Object3D): Promise<THREE.Object3D | null> {
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 THREE.Object3D;
316
+ return tryFindObjectByGuid(guid, obj) as Object3D;
317
317
  }
318
318
 
319
- function tryFindObjectByGuid(guid: string, obj: THREE.Object3D): THREE.Object3D | null {
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) {
src/engine/engine_physics_rapier.ts CHANGED
@@ -1021,7 +1021,7 @@
1021
1021
  const material = new LineBasicMaterial({
1022
1022
  color: 0x77dd77,
1023
1023
  fog: false,
1024
- // vertexColors: THREE.VertexColors
1024
+ // vertexColors: VertexColors
1025
1025
  });
1026
1026
  const geometry = new BufferGeometry();
1027
1027
  this.lines = new LineSegments(geometry, material);
src/engine/engine_scenelighting.ts CHANGED
@@ -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 _sphericalHarmonics: SphericalHarmonics3 | null = null;
235
- private _sphericalHarmonicsArray?: number[];
236
- private _ambientScale: number = 1;
237
- private _lightProbe?: LightProbe;
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(context: Context, tex: Texture, ambientScale: number = 1) {
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
src/engine/engine_serialization_builtin_serializer.ts CHANGED
@@ -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): THREE.Color | RGBAColor | void {
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 THREE.Color(data.r, data.g, data.b);
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: THREE.Object3D): any {
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: THREE.LinearSRGBColorSpace,
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 = THREE.RGBAFormat;
402
+ tex.format = RGBAFormat;
403
403
  }
404
404
 
405
405
  return rt;
src/engine/engine_serialization_core.ts CHANGED
@@ -152,12 +152,12 @@
152
152
 
153
153
  // passed to serializers
154
154
  export class SerializationContext {
155
- root: THREE.Object3D;
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!: THREE.Object3D;
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: THREE.Object3D) {
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: THREE.Color },
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 THREE.Vector3 in mind but SHOULD actually not be used/called anymore
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) {
src/engine/engine_shaders.ts CHANGED
@@ -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: THREE.Matrix4, buffer?: Array<THREE.Vector4>): Array<THREE.Vector4> {
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 THREE.Vector3(1,0,0);
140
- // 24 is too bright with probe.sh.coefficients[8] = new THREE.Vector3(1,0,0);
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]) };
src/engine/engine_time.ts CHANGED
@@ -9,24 +9,41 @@
9
9
 
10
10
  export class Time implements ITime {
11
11
 
12
- deltaTime = 0;
13
- time = 0;
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 = 0;
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
- get frameCount() { return this.frame; }
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
 
src/engine/engine_utils.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // use for typesafe interface method calls
2
- import { Object3D,Quaternion, type Vector, Vector2, Vector3, Vector4 } from "three";
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";
src/engine/xr/events.ts CHANGED
@@ -15,13 +15,15 @@
15
15
  onXRSessionStartListeners.splice(index, 1);
16
16
  }
17
17
  }
18
+
19
+
18
20
  export function invokeXRSessionStart(evt: XRSessionEventArgs) {
19
- document.dispatchEvent(new CustomEvent("needle-xrsession-start", { detail: evt }));
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
- document.dispatchEvent(new CustomEvent("needle-xrsession-end", { detail: evt }));
28
+ globalThis.dispatchEvent(new CustomEvent("needle-xrsession-end", { detail: evt }));
27
29
  }
src/engine-components/ui/EventSystem.ts CHANGED
@@ -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: THREE.Intersection[] = [];
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): THREE.Intersection[] | 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: THREE.Intersection[] = [];
359
- private _noDepthTestingResults: THREE.Intersection[] = [];
358
+ private _sortingBuffer: Intersection[] = [];
359
+ private _noDepthTestingResults: Intersection[] = [];
360
360
 
361
- private sortCandidates(hits: THREE.Intersection[]): THREE.Intersection[] {
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 THREE.Mesh;
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: THREE.Object3D, args: PointerEventData): boolean {
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: THREE.Object3D, pressed: boolean): boolean {
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: THREE.Object3D | null): boolean {
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: THREE.Object3D | null = null;
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: THREE.Object3D, _selectState: boolean): Object3D | null {
808
+ static updateState(intersect: Object3D, _selectState: boolean): Object3D | null {
809
809
  let foundBlock: Object3D | null = null;
810
810
 
811
811
  if (intersect) {
src/engine/extensions/extension_utils.ts CHANGED
@@ -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") {
src/engine-components/Gizmos.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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?: THREE.Color;
14
+ color?: Color;
16
15
  @serializable()
17
16
  isGizmo : boolean = true;
18
17
 
19
- private _gizmoObject: THREE.Object3D | null | BoxHelper = null;
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 THREE.BoxHelper(this.gameObject, this.color ?? 0xffff00);
25
+ this._gizmoObject = new BoxHelper(this.gameObject, this.color ?? 0xffff00);
27
26
  }
28
27
  else {
29
28
  this.objectBounds = false;
src/engine-components/export/gltf/GltfExport.ts CHANGED
@@ -16,14 +16,14 @@
16
16
  const debugExport = getParam("debuggltfexport");
17
17
 
18
18
  declare type ExportOptions = GLTFExporterOptions & {
19
- pivot?: THREE.Vector3
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?: THREE.Object3D;
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: THREE.Object3D[], target?: Array<AnimationClip>): Array<AnimationClip> {
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: THREE.Object3D[], target?: Vector3): Vector3 {
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: THREE.Object3D[]) {
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];
src/engine-components/GridHelper.ts CHANGED
@@ -9,11 +9,11 @@
9
9
  @serializable()
10
10
  public isGizmo: boolean = false;
11
11
  @serializable(Color)
12
- private color0!: THREE.Color;
12
+ private color0!: Color;
13
13
  @serializable(Color)
14
- private color1!: THREE.Color;
14
+ private color1!: Color;
15
15
 
16
- private gridHelper!: THREE.GridHelper | null;
16
+ private gridHelper!: _GridHelper | null;
17
17
  private size!: number;
18
18
  private divisions!: number;
19
19
  private offset!: number;
src/engine-components/GroundProjection.ts CHANGED
@@ -25,8 +25,7 @@
25
25
  @serializable()
26
26
  set radius(val: number) {
27
27
  this._radius = val;
28
- if (this.env)
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
- if (this.env)
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
 
src/engine/webcomponents/icons.ts CHANGED
@@ -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;
src/engine-components/Light.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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: THREE.Color = new THREE.Color(0xffffff);
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 THREE.OrthographicCamera;
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: THREE.Light | undefined = undefined;
236
+ private light: ThreeLight | undefined = undefined;
238
237
 
239
- public getWorldPosition(vec: THREE.Vector3): THREE.Vector3 {
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 THREE.Color(this.color ?? 0xffffff);
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 THREE.Light;
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 THREE.DirectionalLight(this.color, this.intensity * Math.PI);
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 THREE.DirectionalLightHelper(this.light as THREE.DirectionalLight, .2, this.color);
334
+ const spotLightHelper = new DirectionalLightHelper(this.light as DirectionalLight, .2, this.color);
336
335
  this.context.scene.add(spotLightHelper);
337
- // const bh = new THREE.BoxHelper(this.context.scene, 0xfff0000);
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 THREE.SpotLight(this.color, this.intensity * Math.PI, this.range, toRadians(this.spotAngle / 2), 1 - toRadians(this.innerSpotAngle / 2) / toRadians(this.spotAngle / 2), 2);
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 THREE.PointLight(this.color, this.intensity * Math.PI, this.range);
354
+ const pointLight = new PointLight(this.color, this.intensity * Math.PI, this.range);
356
355
  this.light = pointLight;
357
356
 
358
- // const pointHelper = new THREE.PointLightHelper(pointLight, this.range, this.color);
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 THREE.OrthographicCamera;
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 THREE.CameraHelper(cam));
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 !== THREE.VSMShadowMap){
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 = THREE.VSMShadowMap;
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 THREE.Vector3(0, 0, 0);
488
+ const vec = new Vector3(0, 0, 0);
490
489
 
src/engine-components/LODGroup.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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: THREE.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: THREE.Vector3 | undefined = undefined;
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<THREE.LOD>;
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<THREE.LOD>();
76
+ this._lodsHandler = new Array<ThreeLOD>();
78
77
  for (let i = 0; i < renderers.length; i++) {
79
- const handler = new THREE.LOD();
78
+ const handler = new ThreeLOD();
80
79
  this._lodsHandler.push(handler);
81
80
  this.gameObject.add(handler);
82
81
  }
83
- const empty = new THREE.Object3D();
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: THREE.Object3D | null = null;
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: THREE.LOD, obj: THREE.Object3D, dist: number) {
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;
src/engine-components/LookAtConstraint.ts CHANGED
@@ -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, GameObject } from "./Component.js";
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: THREE.Object3D[] = [];
11
+ sources: Object3D[] = [];
13
12
  }
src/engine/extensions/NEEDLE_lightmaps.ts CHANGED
@@ -112,19 +112,7 @@
112
112
  if (!this.registry)
113
113
  console.log(LightmapType[entry.type], entry.pointer, tex);
114
114
  else {
115
- // TODO this is most likely wrong for floating point textures
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
  }
src/engine/webcomponents/needle menu/needle-menu.ts CHANGED
@@ -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 = 40;
562
- const availableWidth = width - padding * 2;
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 currentWidth = this.options.clientWidth + this.logoContainer.clientWidth;
572
- const spaceLeft = availableWidth - currentWidth;
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 > 5) {
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
 
src/engine/xr/NeedleXRSession.ts CHANGED
@@ -73,8 +73,32 @@
73
73
 
74
74
 
75
75
 
76
- registerSessionGranted();
77
- function registerSessionGranted() {
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; }
src/engine-components/ParticleSystem.ts CHANGED
@@ -1,10 +1,8 @@
1
- import * as THREE from "three";
2
- import { AxesHelper, BackSide, BufferGeometry, Color, FrontSide, Material, Matrix4, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, PlaneGeometry, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
3
- import type { BatchedRenderer, Behavior, BehaviorPlugin, BillBoardSettings, BurstParameters, ColorGenerator, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystemParameters, PointEmitter, RecordState, RotationGenerator, SizeOverLife, TrailSettings, ValueGenerator, VFXBatchSettings } from "three.quarks";
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, setWorldScale } from "../engine/engine_three_utils.js";
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, ParticleSystemShapeType, ParticleSystemSimulationSpace, RotationBySpeedModule, RotationOverLifetimeModule, ShapeModule, SizeBySpeedModule, SizeOverLifetimeModule, TextureSheetAnimationModule, TrailModule, VelocityOverLifetimeModule } from "./ParticleSystemModules.js"
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: THREE.Vector4, t: number): THREE.Vector4 {
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?: THREE.Texture;
610
- private clonedTexture: { original?: THREE.Texture, clone?: THREE.Texture } = { original: undefined, clone: undefined };
611
- get texture(): THREE.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 THREE.Texture;
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 = THREE.LinearSRGBColorSpace;
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(): THREE.Blending { return this.system.renderer.particleMaterial?.blending ?? THREE.NormalBlending; }
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
 
src/engine-components/timeline/PlayableDirector.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from 'three';
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 THREE.Object3D;
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: THREE.Object3D): THREE.Object3D {
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 THREE.AnimationMixer(binding.gameObject);
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 THREE.AnimationAction(handler.mixer, clip, null, null);
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
  }
src/engine-components/PlayerColor.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as THREE from "three";
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: THREE.Mesh = this.gameObject as any;
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: THREE.Color, id: string, mesh: THREE.Mesh) {
67
- let mat = mesh.material as THREE.Material;
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 THREE.Color(r / 255, g / 255, b / 255);
95
+ return new Color(r / 255, g / 255, b / 255);
96
96
  }
97
97
  }
98
98
 
src/engine-components/ui/PointerEvents.ts CHANGED
@@ -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!: THREE.Object3D;
94
+ object!: Object3D;
95
95
  /** The world position of this event */
96
96
  point?: Vector3;
97
97
  /** The object-space normal of this event */
src/engine-components/ui/Raycaster.ts CHANGED
@@ -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): THREE.Intersection[] | 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: THREE.Object3D[] | null = null;
33
- private raycastHits: THREE.Intersection[] = [];
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): THREE.Intersection[] | 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): THREE.Intersection[] | 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;
src/engine-components/ui/RaycastUtils.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  return obj;
20
20
  };
21
21
 
22
- static isInteractable(obj: THREE.Object3D, out?: { canvasGroup?: ICanvasGroup, graphic?: IGraphic }): boolean {
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: THREE.Object3D | null): ICanvasGroup | null {
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 => {
src/engine-components/Renderer.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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]: THREE.Material;
56
+ [num: number]: Material;
58
57
 
59
58
  private _renderer: Renderer;
60
- private _targets: THREE.Object3D[] = [];
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: THREE.Vector4 = new THREE.Vector4(1, 1, 0, 0);
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: THREE.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: THREE.Object3D) {
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/three.js/issues/14499
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: THREE.Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
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: THREE.Object3D, context: Context, args: InstancingSetupArgs): InstanceHandle | null {
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 THREE.Mesh;
877
- const geo = mesh.geometry as THREE.BufferGeometry;
878
- const mat = mesh.material as THREE.Material;
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: THREE.Matrix4 = obj.matrixWorld.clone();
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: THREE.Mesh;
929
+ object: Mesh;
931
930
  instancer: InstancedMeshRenderer;
932
931
 
933
- constructor(instanceIndex: number, originalObject: THREE.Mesh, instancer: InstancedMeshRenderer) {
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: THREE.Matrix4) {
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: THREE.BufferGeometry;
996
- public material: THREE.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: THREE.InstancedMesh;
999
+ private inst: InstancedMesh;
1001
1000
  private handles: (InstanceHandle | null)[] = [];
1002
1001
  private maxCount: number;
1003
1002
 
1004
- private static nullMatrix: THREE.Matrix4 = new THREE.Matrix4();
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: THREE.BufferGeometry, material: THREE.Material, count: number, context: Context) {
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 THREE.MeshBasicMaterial({ color: this.randomColor() });
1018
+ material = new MeshBasicMaterial({ color: this.randomColor() });
1020
1019
  }
1021
- this.inst = new THREE.InstancedMesh(geo, material, count);
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 THREE.RawShaderMaterial) {
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 THREE.Color(Math.random(), Math.random(), Math.random());
1059
+ return new Color(Math.random(), Math.random(), Math.random());
1061
1060
  }
1062
1061
 
1063
- addInstance(obj: THREE.Mesh): InstanceHandle | null {
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: THREE.Matrix4, index: number) {
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;
src/engine-components/RendererLightmap.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Material, Mesh, ShaderMaterial, Texture, Vector4,type WebGLProgramParametersWithUniforms } from "three";
1
+ import { Material, Mesh, ShaderMaterial, Texture, Vector4, WebGLProgramParametersWithUniforms } from "three";
2
2
 
3
- import type { Context, OnRenderCallback } from "../engine/engine_setup.js";
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: THREE.Vector4 = new Vector4(1, 1, 0, 0);
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
 
src/engine-components/js-extensions/RGBAColor.ts CHANGED
@@ -21,8 +21,11 @@
21
21
  return cloned;
22
22
  }
23
23
 
24
- copy(col : RGBAColor | Color){
25
- super.copy(col);
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
  }
src/engine-components/RigidBody.ts CHANGED
@@ -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: THREE.Vector3 = new THREE.Vector3();
273
+ private static tempPosition: Vector3 = new Vector3();
275
274
  private _propertiesChanged: boolean = false;
276
- private _currentVelocity: THREE.Vector3 = new THREE.Vector3();
277
- private _smoothedVelocity: THREE.Vector3 = new THREE.Vector3();
278
- private _smoothedVelocityGetter: THREE.Vector3 = new THREE.Vector3();
279
- private _lastPosition: THREE.Vector3 = new THREE.Vector3();
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?: THREE.Vector3, wakeup: boolean = true) {
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: THREE.Vector3 | null | { x: number, y: number, z: number } = null) { }
456
+ public setBodyFromGameObject(_velocity: Vector3 | null | { x: number, y: number, z: number } = null) { }
458
457
 
459
458
 
460
459
  private captureVelocity() {
src/engine-components/ShadowCatcher.ts CHANGED
@@ -106,7 +106,7 @@
106
106
  }
107
107
  }
108
108
 
109
- // THREE.ShadowMaterial: only does a mask; shadowed areas are fully black.
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() {
src/engine-components/Skybox.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EquirectangularRefractionMapping, NeverDepth, SRGBColorSpace, sRGBEncoding, Texture, TextureLoader } from "three"
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
 
src/engine-components/SmoothFollow.ts CHANGED
@@ -1,17 +1,15 @@
1
- import * as THREE from "three";
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 { Camera } from "./Camera.js";
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: THREE.Object3D | null = null;
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: THREE.Quaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
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 {
src/engine-components/ui/SpatialHtml.ts CHANGED
@@ -1,9 +1,9 @@
1
- import * as THREE from 'three'
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 { getWorldEuler, getWorldRotation, setWorldRotationXYZ } from '../../engine/engine_three_utils.js';
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(this.context.renderer, this.context.mainCamera!);
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 THREE.MeshBasicMaterial;
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 THREE.Box3();
52
+ const aabb = new Box3();
51
53
  aabb.setFromObject(group);
52
54
  this.setWorldRotation(rot.x, rot.y, rot.z);
53
55
  // apply bounds
src/engine-components/SpatialTrigger.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BoxHelper, Layers } from "three";
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: THREE.Object3D): boolean {
106
+ test(obj: Object3D): boolean {
107
107
  if (!this.boxHelper) return false;
108
108
  return this.boxHelper.isInBox(obj) ?? false;
109
109
  }
src/engine-components/SpectatorCamera.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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 : THREE.Vector4 = new THREE.Vector4();
119
- // private currentScissor : THREE.Vector4 = new THREE.Vector4();
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 THREE.WebGLState & { bindXRFramebuffer?: Function };
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 THREE.Color(1, 1, 1));
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 THREE.PerspectiveCamera);
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?: THREE.Object3D;
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 THREE.Object3D();
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 THREE.PerspectiveCamera;
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 THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
433
+ const _inverseYQuat = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
435
434
 
436
435
 
437
436
  class SpectatorSelectionController {
src/engine-components/SpriteRenderer.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as THREE from "three";
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]: THREE.BufferGeometry } = {};
15
+ static cache: { [key: string]: BufferGeometry } = {};
17
16
 
18
- static getOrCreateGeometry(sprite: Sprite): THREE.BufferGeometry {
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 THREE.BufferGeometry();
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 THREE.BufferAttribute(vertices, 3));
42
- geo.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
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?: THREE.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?: THREE.BufferGeometry;
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 = THREE.SRGBColorSpace;
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?: THREE.Material;
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?: THREE.Mesh;
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 THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide });
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 THREE.Color();
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 THREE.Mesh(SpriteUtils.getOrCreateGeometry(sprite), mat);
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 {
src/engine-components/SyncedCamera.ts CHANGED
@@ -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: THREE.Camera | null | undefined, con: NetworkConnection) {
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: THREE.Object3D,
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): THREE.Object3D | null {
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: THREE.Object3D | null | AssetReference = null;
74
+ public cameraPrefab: Object3D | null | AssetReference = null;
76
75
 
77
- private _lastWorldPosition!: THREE.Vector3;
78
- private _lastWorldQuaternion!: THREE.Quaternion;
76
+ private _lastWorldPosition!: Vector3;
77
+ private _lastWorldQuaternion!: Quaternion;
79
78
  private _model: CameraModel | null = null;