@needle-tools/engine 2.57.0-pre → 2.58.0-pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/needle-engine.d.ts +33 -2
  3. package/dist/needle-engine.js +200 -200
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +18 -18
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/dist/needle-engine.tsbuildinfo +1 -1
  8. package/lib/engine/codegen/register_types.js +2 -0
  9. package/lib/engine/codegen/register_types.js.map +1 -1
  10. package/lib/engine/engine.d.ts +1 -0
  11. package/lib/engine/engine.js +1 -0
  12. package/lib/engine/engine.js.map +1 -1
  13. package/lib/engine/engine_hot_reload.d.ts +3 -0
  14. package/lib/engine/engine_hot_reload.js +168 -0
  15. package/lib/engine/engine_hot_reload.js.map +1 -0
  16. package/lib/engine/engine_input.js +1 -1
  17. package/lib/engine/engine_setup.js +41 -36
  18. package/lib/engine/engine_setup.js.map +1 -1
  19. package/lib/engine-components/Fog.d.ts +20 -0
  20. package/lib/engine-components/Fog.js +61 -0
  21. package/lib/engine-components/Fog.js.map +1 -0
  22. package/lib/engine-components/codegen/components.d.ts +1 -0
  23. package/lib/engine-components/codegen/components.js +1 -0
  24. package/lib/engine-components/codegen/components.js.map +1 -1
  25. package/lib/engine-components/ui/EventSystem.d.ts +1 -1
  26. package/lib/engine-components/ui/EventSystem.js +25 -10
  27. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  28. package/lib/engine-components/ui/PointerEvents.d.ts +3 -1
  29. package/lib/engine-components/ui/PointerEvents.js +1 -0
  30. package/lib/engine-components/ui/PointerEvents.js.map +1 -1
  31. package/lib/engine-components/ui/Raycaster.js.map +1 -1
  32. package/lib/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +1 -1
  34. package/src/engine/codegen/register_types.js +2 -0
  35. package/src/engine/engine.ts +2 -0
  36. package/src/engine/engine_hot_reload.ts +186 -0
  37. package/src/engine/engine_input.ts +1 -1
  38. package/src/engine/engine_setup.ts +39 -36
  39. package/src/engine-components/Fog.ts +60 -0
  40. package/src/engine-components/codegen/components.ts +1 -0
  41. package/src/engine-components/ui/EventSystem.ts +30 -15
  42. package/src/engine-components/ui/PointerEvents.ts +7 -4
  43. package/src/engine-components/ui/Raycaster.ts +6 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.57.0-pre",
3
+ "version": "2.58.0-pre",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.js",
6
6
  "module": "src/needle-engine.ts",
@@ -55,6 +55,7 @@ import { EventTrigger } from "../../engine-components/EventTrigger.ts";
55
55
  import { FieldWithDefault } from "../../engine-components/Renderer.ts";
56
56
  import { FixedJoint } from "../../engine-components/Joints.ts";
57
57
  import { FlyControls } from "../../engine-components/FlyControls.ts";
58
+ import { Fog } from "../../engine-components/Fog.ts";
58
59
  import { GltfExport } from "../../engine-components/export/gltf/GltfExport.ts";
59
60
  import { GltfExportBox } from "../../engine-components/export/gltf/GltfExport.ts";
60
61
  import { Gradient } from "../../engine-components/ParticleSystemModules.ts";
@@ -225,6 +226,7 @@ TypeStore.add("EventTrigger", EventTrigger);
225
226
  TypeStore.add("FieldWithDefault", FieldWithDefault);
226
227
  TypeStore.add("FixedJoint", FixedJoint);
227
228
  TypeStore.add("FlyControls", FlyControls);
229
+ TypeStore.add("Fog", Fog);
228
230
  TypeStore.add("GltfExport", GltfExport);
229
231
  TypeStore.add("GltfExportBox", GltfExportBox);
230
232
  TypeStore.add("Gradient", Gradient);
@@ -1,3 +1,5 @@
1
+ import "./engine_hot_reload"
2
+
1
3
  import * as layers from "./js-extensions/Layers";
2
4
  layers.patchLayers();
3
5
 
@@ -0,0 +1,186 @@
1
+ import { IComponent } from "./engine_types";
2
+ import { TypeStore } from "./engine_typestore";
3
+ import { addScriptToArrays, removeScriptFromContext } from "./engine_mainloop_utils"
4
+ import { showBalloonWarning } from "./debug/debug";
5
+ import { getParam } from "./engine_utils";
6
+
7
+ const debug = getParam("debughotreload");
8
+
9
+ declare type BeforeUpdateArgs = {
10
+ type: string,
11
+ updates: Array<{ path: string, timestamp: number, acceptedPath: string, explicitImportRequired: boolean, type: string }>,
12
+ }
13
+
14
+ //@ts-ignore
15
+ if (import.meta.hot) {
16
+ //@ts-ignore
17
+ import.meta.hot.on('vite:beforeUpdate', (cb: BeforeUpdateArgs) => {
18
+ if (debug) console.log(cb);
19
+ for (const update of cb.updates) {
20
+ console.log("[Needle Engine] Hot reloading " + update.path);
21
+ }
22
+ });
23
+ }
24
+
25
+
26
+ let isApplyingChanges = false;
27
+
28
+ const instances: Map<string, object[]> = new Map();
29
+
30
+ export function register(instance: object) {
31
+ if (isApplyingChanges) return;
32
+ const type = instance.constructor;
33
+ const name = type.name;
34
+ if (!instances.has(name)) {
35
+ instances.set(name, [instance]);
36
+ }
37
+ else {
38
+ instances.get(name)?.push(instance);
39
+ }
40
+ }
41
+
42
+ export function unregister(instance: object) {
43
+ if (isApplyingChanges) return;
44
+ const type = instance.constructor;
45
+ const name = type.name;
46
+ const instancesOfType = instances.get(name);
47
+ if (!instancesOfType) return;
48
+ const idx = instancesOfType.indexOf(instance);
49
+ if (idx === -1) return;
50
+ instancesOfType.splice(idx, 1);
51
+ }
52
+
53
+
54
+ let didRegisterUnhandledExceptionListener = false;
55
+ function reloadPageOnHotReloadError() {
56
+ if (debug) return;
57
+ if (didRegisterUnhandledExceptionListener) return;
58
+ didRegisterUnhandledExceptionListener = true;
59
+
60
+ const error = console.error;
61
+ console.error = (...args: any[]) => {
62
+ if (args.length) {
63
+ const arg: string = args[0];
64
+ // When making changes in e.g. the engine package and then making changes in project scripts again that import the engine package: hot reload fails and reports redefinitions of types, we just reload the page in those cases for now
65
+ // editing a script in one package seems to work for now so it should be good enough for a start
66
+ if (typeof arg === "string" && arg.includes("[hmr] Failed to reload ")) {
67
+ console.log("[Needle Engine] Hot reloading failed")
68
+ window.location.reload();
69
+ return;
70
+ }
71
+
72
+ }
73
+ error.apply(console, args);
74
+ };
75
+ }
76
+
77
+
78
+ export function applyChanges(newModule): boolean {
79
+
80
+ reloadPageOnHotReloadError();
81
+
82
+ // showBalloonMessage("Hot reloading");
83
+
84
+ // console.dir(newModule);
85
+
86
+ for (const key of Object.keys(newModule)) {
87
+ try {
88
+ isApplyingChanges = true;
89
+
90
+ const typeToUpdate = TypeStore.get(key);
91
+ if (!typeToUpdate)
92
+ {
93
+ continue;
94
+ }
95
+ const newType = newModule[key];
96
+
97
+ console.log("[Needle Engine] Updating type: " + key);
98
+
99
+ // Update prototype (methods and properties)
100
+ const previousMethods = Object.getOwnPropertyNames(typeToUpdate.prototype);
101
+ const methodsAndProperties = Object.getOwnPropertyDescriptors(newType.prototype);
102
+ for (const typeKey in methodsAndProperties) {
103
+ const desc = methodsAndProperties[typeKey];
104
+ if (!desc.writable) continue;
105
+ typeToUpdate.prototype[typeKey] = newModule[key].prototype[typeKey];
106
+ }
107
+ // Remove methods that are no longer present
108
+ for (const typeKey of previousMethods) {
109
+ if (!methodsAndProperties[typeKey]) {
110
+ delete typeToUpdate.prototype[typeKey];
111
+ }
112
+ }
113
+
114
+ // Update fields (we only add new fields if they are undefined)
115
+ // we create a instance to get access to the fields
116
+ const instancesOfType = instances.get(newType.name);
117
+ if (instancesOfType) {
118
+ const newTypeInstance = new newType();
119
+ const keys = Object.getOwnPropertyDescriptors(newTypeInstance);
120
+ for (const inst of instancesOfType) {
121
+ const componentInstance = inst as unknown as IComponent;
122
+ const isComponent = componentInstance.isComponent === true;
123
+ const active = isComponent ? componentInstance.activeAndEnabled : true;
124
+ const context = isComponent ? componentInstance.context : undefined;
125
+ try {
126
+ if (isComponent) {
127
+ removeScriptFromContext(componentInstance, context);
128
+ }
129
+ if (isComponent && active) {
130
+ componentInstance.enabled = false;
131
+ }
132
+
133
+ if (inst["onBeforeHotReloadFields"]) {
134
+ const res = inst["onBeforeHotReloadFields"]();
135
+ if (res === false) continue;
136
+ }
137
+ for (const key in keys) {
138
+ const desc = keys[key];
139
+ if (!desc.writable) continue;
140
+ if (inst[key] === undefined) {
141
+ inst[key] = newTypeInstance[key];
142
+ }
143
+ // if its a function but not on the prototype
144
+ // then its a bound method that needs to be rebound
145
+ else if (typeof inst[key] === "function" && !inst[key].prototype) {
146
+ const boundMethod = inst[key];
147
+ // try to get the target method name
148
+ const targetMethodName = boundMethod.name;
149
+ const prefix = "bound "; // < magic prefix
150
+ if (targetMethodName === prefix) continue;
151
+ const name = boundMethod.name.substring(prefix.length);
152
+ // if the target method name still exists on the new prototype
153
+ // we want to rebind it and assign it to the field
154
+ // Beware that this will not work if the method is added to some event listener etc
155
+ const newTarget = newType.prototype[name];
156
+ if (newTarget)
157
+ inst[key] = newTarget.bind(inst);
158
+ }
159
+ }
160
+ if (inst["onAfterHotReloadFields"]) inst["onAfterHotReloadFields"]();
161
+ }
162
+ finally {
163
+ if (isComponent) {
164
+ addScriptToArrays(componentInstance, context);
165
+ }
166
+ if (isComponent && active) {
167
+ componentInstance.enabled = true;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ catch (err) {
174
+ if (debug) console.error(err);
175
+ // we only want to invalidate changes if we debug hot reload
176
+ else return false;
177
+ }
178
+ finally {
179
+ isApplyingChanges = false;
180
+ }
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+
@@ -170,7 +170,7 @@ export class Input extends EventTarget {
170
170
  *foreachPointerId(pointerType?: string | PointerType | string[] | PointerType[]): Generator<number> {
171
171
  for (let i = 0; i < this._pointerTypes.length; i++) {
172
172
  // check if the pointer is active
173
- if (this._pointerIsActive[i]) {
173
+ if (this._pointerIsActive(i)) {
174
174
  // if specific pointer types are requested
175
175
  if (pointerType !== undefined) {
176
176
  const type = this._pointerTypes[i];
@@ -809,47 +809,50 @@ export class Context {
809
809
  if (this.coroutines[evt]) {
810
810
  const evts = this.coroutines[evt];
811
811
  for (let i = 0; i < evts.length; i++) {
812
- const evt = evts[i];
813
- // TODO we might want to keep coroutines playing even if the component is disabled or inactive
814
- const remove = !evt.comp || evt.comp.destroyed || !evt.main || evt.comp["enabled"] === false;
815
- if (remove) {
816
- evts.splice(i, 1);
817
- --i;
818
- continue;
819
- }
820
- const iter = evt.chained;
821
- if (iter && iter.length > 0) {
822
- const last: Generator = iter[iter.length - 1];
823
- const res = last.next();
824
- if (res.done) {
825
- iter.pop();
812
+ try {
813
+ const evt = evts[i];
814
+ // TODO we might want to keep coroutines playing even if the component is disabled or inactive
815
+ const remove = !evt.comp || evt.comp.destroyed || !evt.main || evt.comp["enabled"] === false;
816
+ if (remove) {
817
+ evts.splice(i, 1);
818
+ --i;
819
+ continue;
826
820
  }
827
- if (isGenerator(res)) {
828
- if (!evt.chained) evt.chained = [];
829
- evt.chained.push(res.value);
821
+ const iter = evt.chained;
822
+ if (iter && iter.length > 0) {
823
+ const last: Generator = iter[iter.length - 1];
824
+ const res = last.next();
825
+ if (res.done) {
826
+ iter.pop();
827
+ }
828
+ if (isGenerator(res)) {
829
+ if (!evt.chained) evt.chained = [];
830
+ evt.chained.push(res.value);
831
+ }
832
+ if (!res.done) continue;
830
833
  }
831
- if (!res.done) continue;
832
- }
833
834
 
834
- const res = evt.main.next();
835
- if (res.done === true) {
836
- evts.splice(i, 1);
837
- --i;
838
- continue;
835
+ const res = evt.main.next();
836
+ if (res.done === true) {
837
+ evts.splice(i, 1);
838
+ --i;
839
+ continue;
840
+ }
841
+ const val = res.value;
842
+ if (isGenerator(val)) {
843
+ // invoke once if its a generator
844
+ // this means e.g. WaitForFrame(1) works and will capture
845
+ // the frame it was created
846
+ const gen = val as Generator;
847
+ const res = gen.next();
848
+ if (res.done) continue;
849
+ if (!evt.chained) evt.chained = [];
850
+ evt.chained.push(val as Generator);
851
+ }
839
852
  }
840
- const val = res.value;
841
- if (isGenerator(val)) {
842
- // invoke once if its a generator
843
- // this means e.g. WaitForFrame(1) works and will capture
844
- // the frame it was created
845
- const gen = val as Generator;
846
- const res = gen.next();
847
- if (res.done) continue;
848
- if (!evt.chained) evt.chained = [];
849
- evt.chained.push(val as Generator);
853
+ catch (e) {
854
+ console.error(e);
850
855
  }
851
-
852
-
853
856
  }
854
857
  }
855
858
 
@@ -0,0 +1,60 @@
1
+ import { Behaviour } from "./Component";
2
+ import { Color, Fog as Fog3 } from "three";
3
+ import { serializable } from "../engine/engine_serialization";
4
+
5
+
6
+ export enum FogMode {
7
+
8
+ Linear = 1,
9
+ Exponential = 2,
10
+ ExponentialSquared = 3,
11
+ }
12
+
13
+ export class Fog extends Behaviour {
14
+
15
+ get fog() {
16
+ if (!this._fog) this._fog = new Fog3(0x000000, 0, 50);
17
+ return this._fog;
18
+ }
19
+
20
+ get mode() {
21
+ return FogMode.Linear;
22
+ }
23
+
24
+ @serializable()
25
+ set near(value: number) {
26
+ this.fog.near = value;
27
+ }
28
+ get near() {
29
+ return this.fog.near;
30
+ }
31
+
32
+ @serializable()
33
+ set far(value: number) {
34
+ this.fog.far = value;
35
+ }
36
+ get far() {
37
+ return this.fog.far;
38
+ }
39
+
40
+ @serializable(Color)
41
+ set color(value: Color) {
42
+ this.fog.color.copy(value);
43
+ }
44
+ get color() {
45
+ return this.fog.color;
46
+ }
47
+
48
+ private _fog?: Fog3;
49
+
50
+ onEnable() {
51
+ this.scene.fog = this.fog;
52
+ }
53
+
54
+ onDisable() {
55
+ if (this.scene.fog === this._fog)
56
+ this.scene.fog = null;
57
+ }
58
+
59
+
60
+ }
@@ -53,6 +53,7 @@ export { EventTrigger } from "../EventTrigger";
53
53
  export { FieldWithDefault } from "../Renderer";
54
54
  export { FixedJoint } from "../Joints";
55
55
  export { FlyControls } from "../FlyControls";
56
+ export { Fog } from "../Fog";
56
57
  export { GltfExport } from "../export/gltf/GltfExport";
57
58
  export { GltfExportBox } from "../export/gltf/GltfExport";
58
59
  export { Gradient } from "../ParticleSystemModules";
@@ -36,13 +36,12 @@ export class EventSystem extends Behaviour {
36
36
  GameObject.addNewComponent(go, EventSystem);
37
37
  context.scene.add(go);
38
38
  }
39
-
40
- static get systems(): EventSystem[]
41
- {
39
+
40
+ static get systems(): EventSystem[] {
42
41
  const context = Context.Current;
43
42
  if (!this._eventSystemMap.has(context)) {
44
43
  this._eventSystemMap.set(context, []);
45
- }
44
+ }
46
45
  return this._eventSystemMap.get(context)!;
47
46
  }
48
47
 
@@ -91,6 +90,7 @@ export class EventSystem extends Behaviour {
91
90
  private _selectStartFn?: any;
92
91
  private _selectEndFn?: any;
93
92
  private _selectUpdateFn?: any;
93
+ private _onBeforeUpdateFn?: any;
94
94
 
95
95
  onEnable(): void {
96
96
 
@@ -118,7 +118,7 @@ export class EventSystem extends Behaviour {
118
118
  opts.isPressed = ctrl.selectionPressed;
119
119
  opts.isClicked = ctrl.selectionClick;
120
120
  this.handleEvents(args.grab, opts);
121
-
121
+
122
122
  const prevGrabbed = grabbed.get(ctrl);
123
123
  grabbed.set(ctrl, null);
124
124
  if (prevGrabbed) {
@@ -149,20 +149,22 @@ export class EventSystem extends Behaviour {
149
149
  WebXRController.addEventListener(ControllerEvents.SelectEnd, this._selectEndFn);
150
150
  WebXRController.addEventListener(ControllerEvents.Update, this._selectUpdateFn);
151
151
 
152
- this.context.pre_update_callbacks.push(this.onBeforeUpdate.bind(this));
153
- this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerDown.bind(this));
152
+ // TODO: unregister
153
+ this._onBeforeUpdateFn ??= this.onBeforeUpdate.bind(this);
154
+ this.context.pre_update_callbacks.push(this._onBeforeUpdateFn);
155
+ this.context.input.addEventListener(InputEvents.PointerDown, this._onBeforeUpdateFn);
154
156
  }
155
157
 
156
158
  onDisable(): void {
157
159
  WebXRController.removeEventListener(ControllerEvents.SelectStart, this._selectStartFn);
158
160
  WebXRController.removeEventListener(ControllerEvents.SelectEnd, this._selectEndFn);
159
161
  WebXRController.removeEventListener(ControllerEvents.Update, this._selectUpdateFn);
160
- }
161
162
 
162
- onPointerDown() {
163
- this.onBeforeUpdate();
163
+ this.context.pre_update_callbacks.splice(this.context.pre_update_callbacks.indexOf(this._onBeforeUpdateFn), 1);
164
+ this.context.input.removeEventListener(InputEvents.PointerDown, this._onBeforeUpdateFn);
164
165
  }
165
166
 
167
+
166
168
  // doesnt work in dist
167
169
  // onBeforeRender() {
168
170
  // MeshUIHelper.update(this.context);
@@ -195,12 +197,25 @@ export class EventSystem extends Behaviour {
195
197
  }
196
198
 
197
199
  const hits = this.performRaycast(null);
198
- const args: PointerEventData = new PointerEventData(this.context.input.getPointerEvent(0));
200
+ let pointerId = 0;
201
+ for (const i of this.context.input.foreachPointerId()) {
202
+ const isDown = this.context.input.getPointerDown(i);
203
+ const isUp = this.context.input.getPointerUp(i);
204
+ if (isDown || isUp) {
205
+ pointerId = i;
206
+ break;
207
+ }
208
+ }
209
+ const ptr = this.context.input.getPointerEvent(pointerId);
210
+ // console.log(ptr);
211
+ const args: PointerEventData = new PointerEventData(ptr);
199
212
  args.inputSource = this.context.input;
200
- args.isClicked = this.context.input.mouseClick;
201
- args.isDown = this.context.input.mouseDown;
202
- args.isUp = this.context.input.mouseUp;
203
- args.isPressed = this.context.input.mousePressed;
213
+ args.pointerId = pointerId;
214
+ args.isClicked = this.context.input.getPointerClicked(pointerId)
215
+ args.isDown = this.context.input.getPointerDown(pointerId);
216
+ args.isUp = this.context.input.getPointerUp(pointerId);
217
+ args.isPressed = this.context.input.getPointerPressed(pointerId);
218
+ // if(args.isClicked || args.isUp) console.log("clicked", args);
204
219
  this.lastPointerEvent = args;
205
220
  if (!hits) return;
206
221
 
@@ -1,3 +1,5 @@
1
+ import { Input } from "../../engine/engine_input";
2
+
1
3
  export interface IInputEventArgs {
2
4
  get used(): boolean;
3
5
  Use(): void;
@@ -5,7 +7,7 @@ export interface IInputEventArgs {
5
7
  }
6
8
 
7
9
  export class PointerEventData implements IInputEventArgs {
8
- used: boolean = false;
10
+ used: boolean = false;
9
11
 
10
12
  Use() {
11
13
  this.used = true;
@@ -15,17 +17,18 @@ export class PointerEventData implements IInputEventArgs {
15
17
  this.event?.stopImmediatePropagation();
16
18
  }
17
19
 
18
- inputSource: any;
20
+ inputSource: Input | any;
19
21
  object!: THREE.Object3D;
20
22
 
23
+ pointerId: number | undefined;
21
24
  isDown: boolean | undefined;
22
25
  isUp: boolean | undefined;
23
26
  isPressed: boolean | undefined;
24
27
  isClicked: boolean | undefined;
25
28
 
26
- private event?:Event;
29
+ private event?: Event;
27
30
 
28
- constructor(events?:Event){
31
+ constructor(events?: Event) {
29
32
  this.event = events;
30
33
  }
31
34
  }
@@ -1,8 +1,8 @@
1
- import { Camera } from "three";
2
1
  import { RaycastOptions } from "../../engine/engine_physics";
3
- import { Behaviour } from "../Component";
2
+ import { Behaviour, Component } from "../Component";
4
3
  import { EventSystem } from "./EventSystem";
5
4
 
5
+
6
6
  export class Raycaster extends Behaviour {
7
7
  awake(): void {
8
8
  EventSystem.createIfNoneExists(this.context);
@@ -21,6 +21,7 @@ export class Raycaster extends Behaviour {
21
21
  }
22
22
  }
23
23
 
24
+
24
25
  export class ObjectRaycaster extends Raycaster {
25
26
  private targets: THREE.Object3D[] | null = null;
26
27
  private raycastHits: THREE.Intersection[] = [];
@@ -44,4 +45,6 @@ export class GraphicRaycaster extends ObjectRaycaster {
44
45
  // eventCamera: Camera | null = null;
45
46
  // ignoreReversedGraphics: boolean = false;
46
47
  // rootRaycaster: GraphicRaycaster | null = null;
47
- }
48
+ }
49
+
50
+