Needle Engine

Changes between version 3.6.10 and 3.6.11
Files changed (8) hide show
  1. src/engine/debug/debug_console.ts +5 -3
  2. src/engine/engine_constants.ts +22 -17
  3. src/engine/engine_gltf_builtin_components.ts +14 -5
  4. src/engine/engine_networking.ts +15 -3
  5. src/engine-components/ui/InputField.ts +31 -5
  6. src/engine-components/LODGroup.ts +8 -32
  7. src/engine-components/ParticleSystem.ts +4 -4
  8. src/engine-components/ParticleSystemSubEmitter.ts +2 -1
src/engine/debug/debug_console.ts CHANGED
@@ -113,15 +113,17 @@
113
113
  consoleHtmlElement = getConsoleElement();
114
114
  if (consoleHtmlElement) {
115
115
  consoleHtmlElement[$defaultConsoleParent] = consoleHtmlElement.parentElement;
116
+ consoleHtmlElement.style.position = "absolute";
117
+ consoleHtmlElement.style.zIndex = Number.MAX_SAFE_INTEGER.toString();
116
118
  }
117
119
  consoleInstance.setSwitchPosition(20, 10);
118
120
  consoleSwitchButton = getConsoleSwitchButton();
119
121
  if (consoleSwitchButton) {
120
122
  consoleSwitchButton.innerText = defaultButtonIcon;
121
123
  consoleSwitchButton.addEventListener("click", onConsoleSwitchButtonClicked);
122
- const buttonStyles = document.createElement("style");
124
+ const styles = document.createElement("style");
123
125
  const size = 40;
124
- buttonStyles.innerHTML = `
126
+ styles.innerHTML = `
125
127
  #__vconsole .vc-switch {
126
128
  border: 1px solid rgba(255,255,255,.2);
127
129
  border-radius: 50%;
@@ -161,7 +163,7 @@
161
163
  }
162
164
  }
163
165
  `;
164
- document.head.appendChild(buttonStyles);
166
+ document.head.appendChild(styles);
165
167
  if (startHidden === true)
166
168
  hideDebugConsole();
167
169
  }
src/engine/engine_constants.ts CHANGED
@@ -1,27 +1,32 @@
1
+ import { getParam } from "../engine/engine_utils";
2
+ const debug = getParam("debugdefines");
3
+
1
4
  declare const NEEDLE_ENGINE_VERSION: string;
2
5
  declare const NEEDLE_ENGINE_GENERATOR: string;
3
6
 
4
- try {
5
- // https://esbuild.github.io/content-types/#direct-eval
6
- (0, eval)(`
7
- if(!globalThis["NEEDLE_ENGINE_VERSION"])
8
- globalThis["NEEDLE_ENGINE_VERSION"] = "0.0.0";
9
- if(!globalThis["NEEDLE_ENGINE_GENERATOR"])
10
- globalThis["NEEDLE_ENGINE_GENERATOR"] = "unknown";
11
- `);
12
- }
13
- catch
14
- {
15
- // ignore errors
16
- // when built with vite the strings above might be replaced with the actual variables
17
- // and that would then fail (e.g. NEEDLE_ENGINE_VERSION becomes 1.0.0 for example)
18
- }
7
+ tryEval(`if(!globalThis["NEEDLE_ENGINE_VERSION"]) globalThis["NEEDLE_ENGINE_VERSION"] = "0.0.0";`)
8
+ tryEval(`if(!globalThis["NEEDLE_ENGINE_GENERATOR"]) globalThis["NEEDLE_ENGINE_GENERATOR"] = "unknown";`)
19
9
 
20
10
  export const VERSION = NEEDLE_ENGINE_VERSION;
21
11
  export const GENERATOR = NEEDLE_ENGINE_GENERATOR;
12
+ if (debug) console.log(`Engine version: ${VERSION} (generator: ${GENERATOR})`);
22
13
 
23
-
24
14
  export const activeInHierarchyFieldName = "needle_isActiveInHierarchy";
25
15
  export const builtinComponentKeyName = "builtin_components";
26
16
  // It's easier to use a string than a symbol here because the symbol might not be the same when imported in other packages
27
- export const editorGuidKeyName = "needle_editor_guid";
17
+ export const editorGuidKeyName = "needle_editor_guid";
18
+
19
+
20
+ function tryEval(str: string) {
21
+ try {
22
+ // https://esbuild.github.io/content-types/#direct-eval
23
+ (0, eval)(str);
24
+ }
25
+ catch (err) {
26
+ // ignore errors
27
+ // when built with vite the strings above might be replaced with the actual variables
28
+ // and that would then fail (e.g. NEEDLE_ENGINE_VERSION becomes 1.0.0 for example)
29
+ if (debug)
30
+ console.error(err);
31
+ }
32
+ }
src/engine/engine_gltf_builtin_components.ts CHANGED
@@ -95,9 +95,14 @@
95
95
  if (idProvider) {
96
96
  // TODO: should we do this after setup callbacks now?
97
97
  const guidsMap: GuidsMap = {};
98
- recursiveCreateGuids(gltf, idProvider, guidsMap);
98
+ const resolveGuids: IHasResolveGuids[] = [];
99
+ recursiveCreateGuids(gltf, idProvider, guidsMap, resolveGuids);
99
100
  for (const scene of gltf.scenes)
100
- recursiveCreateGuids(scene, idProvider, guidsMap);
101
+ recursiveCreateGuids(scene, idProvider, guidsMap, resolveGuids);
102
+ // make sure to resolve all guids AFTER new guids have been assigned
103
+ for (const res of resolveGuids) {
104
+ res.resolveGuids(guidsMap);
105
+ }
101
106
  }
102
107
  });
103
108
 
@@ -106,7 +111,11 @@
106
111
  // console.log("finished creating builtin components", gltf.scene?.name, gltf);
107
112
  }
108
113
 
109
- function recursiveCreateGuids(obj: IGameObject, idProvider: UIDProvider | null, guidsMap: GuidsMap) {
114
+ declare type IHasResolveGuids = {
115
+ resolveGuids: (guidsMap: GuidsMap) => void;
116
+ }
117
+
118
+ function recursiveCreateGuids(obj: IGameObject, idProvider: UIDProvider | null, guidsMap: GuidsMap, resolveGuids: IHasResolveGuids[]) {
110
119
  if (idProvider === null) return;
111
120
  if (!obj) return;
112
121
  const prev = obj.guid;
@@ -122,13 +131,13 @@
122
131
  if (prev && prev !== "invalid")
123
132
  guidsMap[prev] = comp.guid;
124
133
  if (comp.resolveGuids)
125
- comp.resolveGuids(guidsMap);
134
+ resolveGuids.push(comp);
126
135
  }
127
136
 
128
137
  }
129
138
  if (obj.children) {
130
139
  for (const child of obj.children) {
131
- recursiveCreateGuids(child as IGameObject, idProvider, guidsMap);
140
+ recursiveCreateGuids(child as IGameObject, idProvider, guidsMap, resolveGuids);
132
141
  }
133
142
  }
134
143
  }
src/engine/engine_networking.ts CHANGED
@@ -291,21 +291,33 @@
291
291
  return target;
292
292
  }
293
293
 
294
- public joinRoom(room: string, viewOnly: boolean = false) {
294
+ public joinRoom(room: string, viewOnly: boolean = false): boolean {
295
+ if (!room) {
296
+ console.error("Missing room name, can not join: \"" + room + "\"");
297
+ return false;
298
+ }
299
+ // There's not really a reason to limit the room name length
300
+ if (room.length > 1024) {
301
+ console.error("Room name too long, can not join: \"" + room + "\". Max length is 1024 characters.");
302
+ return false;
303
+ }
304
+
295
305
  this.connect();
296
306
 
297
307
  if (debugNet)
298
308
  console.log("join: " + room);
299
309
  this.send(RoomEvents.Join, { room: room, viewOnly: viewOnly }, SendQueue.OnConnection);
310
+ return true;
300
311
  }
301
312
 
302
313
  public leaveRoom(room: string | null = null) {
303
314
  if (!room) room = this.currentRoomName;
304
315
  if (!room) {
305
- console.error("Can not leave unknown room");
306
- return;
316
+ console.error("Missing room name, can not join: \"" + room + "\"");
317
+ return false;
307
318
  }
308
319
  this.send(RoomEvents.Leave, { room: room });
320
+ return true;
309
321
  }
310
322
 
311
323
  public send<T extends WebsocketSendType>(key: string | OwnershipEvent, data: T | null = null, queue: SendQueue = SendQueue.Queued) {
src/engine-components/ui/InputField.ts CHANGED
@@ -49,9 +49,17 @@
49
49
  onEnable() {
50
50
  if (!InputField.htmlField) {
51
51
  InputField.htmlField = document.createElement("input");
52
+ // we can not use visibility or display because it won't receive input then
53
+ InputField.htmlField.style.width = "0px";
54
+ InputField.htmlField.style.height = "0px";
55
+ InputField.htmlField.style.padding = "0px";
56
+ InputField.htmlField.style.border = "none";
57
+ InputField.htmlField.style.overflow = "hidden";
52
58
  InputField.htmlField.style.caretColor = "transparent";
59
+ InputField.htmlField.style.outline = "none";
53
60
  InputField.htmlField.classList.add("ar");
54
- document.body.appendChild(InputField.htmlField);
61
+ // TODO: instead of this we should add it to the shadowdom?
62
+ document.body.append(InputField.htmlField);
55
63
  }
56
64
  if (!this.inputEventFn) {
57
65
  this.inputEventFn = this.onInput.bind(this);
@@ -78,6 +86,28 @@
78
86
  }
79
87
  }
80
88
 
89
+ /** Clear the input field if it's currently active */
90
+ clear() {
91
+ if (InputField.active === this && InputField.htmlField) {
92
+ InputField.htmlField.value = "";
93
+ this.setTextFromInputField();
94
+ }
95
+ else {
96
+ if(this.textComponent) this.textComponent.text = "";
97
+ if(this.placeholder) GameObject.setActive(this.placeholder.gameObject, true);
98
+ }
99
+ }
100
+
101
+ /** Select the input field, set it active to receive keyboard input */
102
+ select() {
103
+ this.onSelected();
104
+ }
105
+
106
+ /** Deselect the input field, stop receiving keyboard input */
107
+ deselect() {
108
+ this.onDeselected();
109
+ }
110
+
81
111
  onPointerClick(_args) {
82
112
  if (debug) console.log("CLICK", _args, InputField.active);
83
113
  InputField.activeTime = this.context.time.time;
@@ -120,8 +150,6 @@
120
150
  if (this.context.isInXR) {
121
151
  const overlay = this.context.arOverlayElement;
122
152
  if (overlay) {
123
- InputField.htmlField.style.width = "0px";
124
- InputField.htmlField.style.height = "0px";
125
153
  overlay.append(InputField.htmlField)
126
154
  }
127
155
  }
@@ -138,8 +166,6 @@
138
166
  if (InputField.htmlField) {
139
167
  InputField.htmlField.blur();
140
168
  document.body.append(InputField.htmlField);
141
- InputField.htmlField.style.width = "";
142
- InputField.htmlField.style.height = "";
143
169
  }
144
170
  if (this.placeholder && (!this.textComponent || this.textComponent.text.length <= 0))
145
171
  GameObject.setActive(this.placeholder.gameObject, true);
src/engine-components/LODGroup.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  import { getParam } from "../engine/engine_utils";
5
5
  import { serializable } from "../engine/engine_serialization_decorator";
6
6
  import { Vector3 } from "three";
7
+ import { isDevEnvironment } from "../engine/debug";
8
+ import { GuidsMap } from "../engine/engine_types";
7
9
 
8
10
  const debug = getParam("debugLODs");
9
11
  const noLods = getParam("noLODs");
@@ -19,43 +21,17 @@
19
21
  screenRelativeTransitionHeight!: number;
20
22
  @serializable()
21
23
  distance!: number;
22
- @serializable()
23
- renderers!: string[];
24
+ @serializable(Renderer)
25
+ renderers!: Renderer[];
24
26
  }
25
27
 
26
28
  class LOD {
27
- model: LODModel;
28
- renderers: Renderer[];
29
+ readonly model: LODModel;
30
+ get renderers(): Renderer[] { return this.model.renderers; }
29
31
 
30
- constructor(group: LODGroup, model: LODModel) {
32
+ constructor(model: LODModel) {
31
33
  this.model = model;
32
- this.renderers = [];
33
- for (const guid of model.renderers) {
34
- const rend = this.findRenderer(guid, group.gameObject as GameObject);// GameObject.findByGuid(guid, group.gameObject) as Renderer;
35
- if (rend && rend.gameObject) {
36
- this.renderers.push(rend);
37
- }
38
- else if(debug){
39
- console.warn("Renderer not found: " + guid, group.gameObject);
40
- }
41
- }
42
34
  }
43
-
44
- findRenderer(guid: string, go : GameObject) : Renderer | null {
45
- const res = GameObject.foreachComponent(go, comp => {
46
- if(comp.guid === guid) return comp;
47
- // explanation: https://github.com/needle-tools/needle-tiny-playground/issues/218#issuecomment-1150234346
48
- const prototypeGuid = Object.getPrototypeOf(comp)?.guid;
49
- if(prototypeGuid === guid) return comp;
50
- return null;
51
- });
52
- if(res) return res;
53
- for(const ch of go.children){
54
- const rend = this.findRenderer(guid, ch as GameObject);
55
- if(rend) return rend;
56
- }
57
- return null;
58
- }
59
35
  }
60
36
 
61
37
  declare class LODSetting {
@@ -94,7 +70,7 @@
94
70
  let renderers: Renderer[] = [];
95
71
  for (const model of this.lodModels) {
96
72
  maxDistance = Math.max(model.distance, maxDistance);
97
- const lod = new LOD(this, model);
73
+ const lod = new LOD(model);
98
74
  this._lods.push(lod);
99
75
  for (const rend of lod.renderers) {
100
76
  if (!renderers.includes(rend))
src/engine-components/ParticleSystem.ts CHANGED
@@ -233,9 +233,9 @@
233
233
  abstract class ParticleSystemBaseBehaviour implements Behavior {
234
234
  readonly system: ParticleSystem;
235
235
 
236
- get scaleFactorDiff(): number {
237
- return this.system.worldScale.x - this.system.scale;
238
- }
236
+ // get scaleFactorDiff(): number {
237
+ // return this.system.scale - this.system.worldScale.x;
238
+ // }
239
239
 
240
240
  constructor(ps: ParticleSystem) {
241
241
  this.system = ps;
@@ -402,7 +402,7 @@
402
402
  initialize(particle: Particle): void {
403
403
  const simulationSpeed = this.system.main.simulationSpeed;
404
404
 
405
- const factor = 1 + this.scaleFactorDiff * simulationSpeed;
405
+ const factor = 1 * simulationSpeed;
406
406
  particle.startSpeed = this.system.main.startSpeed.evaluate(Math.random(), Math.random()) * factor;
407
407
  particle.velocity.copy(this.system.shape.getDirection(particle.position)).multiplyScalar(particle.startSpeed);
408
408
  if (this.system.inheritVelocity?.enabled) {
src/engine-components/ParticleSystemSubEmitter.ts CHANGED
@@ -50,7 +50,8 @@
50
50
  // matrix: new Matrix4(),
51
51
  } as EmissionState;
52
52
  // particle[$emitterMatrix] = new Matrix4();
53
- this._emitterMatrix.copy(this.subSystem.matrixWorld).invert().premultiply(this.system.matrixWorld)
53
+ this._emitterMatrix.copy(this.subSystem.matrixWorld).invert().premultiply(this.system.matrixWorld);
54
+ this._emitterMatrix.setPosition(0, 0, 0);
54
55
 
55
56
  if (this.emitterType === SubEmitterType.Birth) {
56
57
  this.run(particle);