@needle-tools/engine 2.67.6-pre → 2.67.8-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 (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/needle-engine.js +5787 -5768
  3. package/dist/needle-engine.umd.cjs +83 -83
  4. package/lib/engine/engine_context.d.ts +170 -0
  5. package/lib/engine/engine_context.js +855 -0
  6. package/lib/engine/engine_context.js.map +1 -0
  7. package/lib/engine/engine_mainloop_utils.js +4 -1
  8. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  9. package/lib/engine/engine_setup.d.ts +1 -166
  10. package/lib/engine/engine_setup.js +2 -839
  11. package/lib/engine/engine_setup.js.map +1 -1
  12. package/lib/engine/engine_time.d.ts +2 -0
  13. package/lib/engine/engine_time.js +4 -1
  14. package/lib/engine/engine_time.js.map +1 -1
  15. package/lib/engine-components/AnimatorController.js.map +1 -1
  16. package/lib/engine-components/ui/Canvas.d.ts +4 -1
  17. package/lib/engine-components/ui/Canvas.js +17 -1
  18. package/lib/engine-components/ui/Canvas.js.map +1 -1
  19. package/lib/engine-components/ui/EventSystem.js +1 -2
  20. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  21. package/lib/engine-components/ui/Text.js +4 -0
  22. package/lib/engine-components/ui/Text.js.map +1 -1
  23. package/lib/engine-components/ui/Utils.js +6 -4
  24. package/lib/engine-components/ui/Utils.js.map +1 -1
  25. package/package.json +1 -1
  26. package/plugins/vite/drop.js +1 -0
  27. package/plugins/vite/editor-connection.js +1 -1
  28. package/plugins/vite/poster.js +1 -1
  29. package/plugins/vite/reload.js +1 -1
  30. package/src/engine/engine_context.ts +957 -0
  31. package/src/engine/engine_mainloop_utils.ts +4 -2
  32. package/src/engine/engine_setup.ts +2 -941
  33. package/src/engine/engine_time.ts +4 -1
  34. package/src/engine-components/AnimatorController.ts +2 -2
  35. package/src/engine-components/postprocessing/Volume.ts +213 -213
  36. package/src/engine-components/ui/Canvas.ts +19 -1
  37. package/src/engine-components/ui/EventSystem.ts +1 -2
  38. package/src/engine-components/ui/Text.ts +3 -0
  39. package/src/engine-components/ui/Utils.ts +6 -4
@@ -20,11 +20,13 @@ export class Time {
20
20
 
21
21
  get frameCount() { return this.frame; }
22
22
  get smoothedFps() { return this._smoothedFps; }
23
+ get smoothedDeltaTime() { return 1 / this._smoothedFps; }
23
24
 
24
25
 
25
26
  private clock = new Clock();
26
27
 
27
28
  private _smoothedFps: number = 0;
29
+ private _smoothedDeltaTime: number = 0;
28
30
  private _fpsSamples: number[] = [];
29
31
  private _fpsSampleIndex: number = 0;
30
32
 
@@ -47,6 +49,7 @@ export class Time {
47
49
  let sum = 0;
48
50
  for (let i = 0; i < this._fpsSamples.length; i++)
49
51
  sum += this._fpsSamples[i];
50
- this._smoothedFps = 1 / (sum / this._fpsSamples.length);
52
+ this._smoothedDeltaTime = sum / this._fpsSamples.length;
53
+ this._smoothedFps = 1 / this._smoothedDeltaTime;
51
54
  }
52
55
  }
@@ -432,9 +432,9 @@ export class AnimatorController {
432
432
  case AnimatorConditionMode.IfNot:
433
433
  return param.value === false;
434
434
  case AnimatorConditionMode.Greater:
435
- return param.value > cond.threshold;
435
+ return param.value as number > cond.threshold;
436
436
  case AnimatorConditionMode.Less:
437
- return param.value < cond.threshold;
437
+ return param.value as number < cond.threshold;
438
438
  case AnimatorConditionMode.Equals:
439
439
  return param.value === cond.threshold;
440
440
  case AnimatorConditionMode.NotEqual:
@@ -1,213 +1,213 @@
1
- import { Behaviour, GameObject } from "../Component";
2
- import { serializeable } from "../../engine/engine_serialization_decorator";
3
- import { getParam } from "../../engine/engine_utils";
4
- import { VolumeProfile } from "./VolumeProfile";
5
- import { EditorModification, IEditorModification as IEditorModificationReceiver } from "../../engine/engine_editor-sync";
6
- import { PostProcessingHandler } from "./PostProcessingHandler";
7
- import { PostProcessingEffect } from "./PostProcessingEffect";
8
- import { VolumeParameter } from "./VolumeParameter";
9
- import { getEditorModificationCache } from "../../engine/engine_editor-sync";
10
- import { isDevEnvironment } from "../../engine/debug";
11
- import { EffectComposer } from "postprocessing";
12
-
13
- const debug = getParam("debugpost");
14
-
15
- export class Volume extends Behaviour implements IEditorModificationReceiver {
16
-
17
- @serializeable(VolumeProfile)
18
- sharedProfile?: VolumeProfile;
19
-
20
- private _postprocessing?: PostProcessingHandler;
21
- private _effects: PostProcessingEffect[] = [];
22
-
23
- awake() {
24
- // ensure the profile is initialized
25
- this.sharedProfile?.init();
26
-
27
- if (debug) {
28
- console.log(this);
29
- console.log("Press P to toggle post processing");
30
- window.addEventListener("keydown", (e) => {
31
- if (e.key === "p") {
32
- console.log("Toggle volume: " + this.name, !this.enabled);
33
- this.enabled = !this.enabled;
34
- }
35
- });
36
- }
37
- }
38
-
39
- onDisable() {
40
- this._postprocessing?.unapply();
41
- }
42
-
43
- onBeforeRender(): void {
44
- if (!this.context.isInXR) {
45
-
46
- if (this.context.composer && (this.context.composer instanceof EffectComposer) === false) {
47
- return;
48
- }
49
-
50
- // Wait for the first frame to be rendered before creating because then we know we have a camera (issue 135)
51
- if (this.context.mainCamera) {
52
- if (!this._postprocessing || !this._postprocessing.isActive) {
53
- this.apply();
54
- // TODO: remove this workaround for postprocessing rendering not working correctly when applied for the first time
55
- this.apply();
56
- }
57
- }
58
-
59
- if (this.context.composer) {
60
- this.context.composer.setRenderer(this.context.renderer);
61
- this.context.composer.setMainScene(this.context.scene);
62
- if (this.context.mainCamera)
63
- this.context.composer.setMainCamera(this.context.mainCamera);
64
- }
65
- }
66
- }
67
-
68
- onDestroy(): void {
69
- this._postprocessing?.dispose();
70
- }
71
-
72
- private _lastApplyTime?: number;
73
- private _rapidApplyCount = 0;
74
-
75
- private apply() {
76
- if (debug) console.log("Apply PostProcessing", this, this.context.mainCamera?.name);
77
-
78
- if (isDevEnvironment()) {
79
- if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
80
- this._rapidApplyCount++;
81
- if (this._rapidApplyCount === 5)
82
- console.warn("Detected rapid post processing modifications - this might be a bug", this);
83
- }
84
- this._lastApplyTime = Date.now();
85
- }
86
-
87
-
88
- this.unapply();
89
-
90
- this._effects.length = 0;
91
- // get from profile
92
- if (this.sharedProfile?.components) {
93
- this._effects.push(...this.sharedProfile.components);
94
- }
95
- // get additional effects
96
- const additionalComponents = this.gameObject.getComponentsInChildren(PostProcessingEffect);
97
- if (debug && additionalComponents?.length)
98
- console.log("Additional", additionalComponents);
99
- if (additionalComponents) {
100
- for (const comp of additionalComponents) {
101
- if (comp.active) this._effects.push(comp);
102
- }
103
- }
104
-
105
- if (this._effects.length > 0) {
106
- if (!this._postprocessing)
107
- this._postprocessing = new PostProcessingHandler(this.context);
108
- this._postprocessing.apply(this._effects);
109
- this._applyPostQueue();
110
- }
111
-
112
- }
113
-
114
- private unapply() {
115
- this._postprocessing?.unapply();
116
- }
117
-
118
-
119
- private _applyPostQueue() {
120
- if (this._modificationQueue) {
121
- for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
122
- this._modificationQueue.clear();
123
- }
124
- }
125
-
126
- /** called from needle editor sync package if its active */
127
- onEditorModification(modification: EditorModification): void | boolean | undefined {
128
-
129
- if (modification.propertyName.startsWith("postprocessing.")) {
130
-
131
- if (!this._postprocessing) {
132
- if (!this._modificationQueue) this._modificationQueue = new Map<string, EditorModification>();
133
- this._modificationQueue.set(modification.propertyName, modification);
134
- return true;
135
- }
136
-
137
- if (!this._effects?.length) return;
138
- const path = modification.propertyName.split(".");
139
- if (path.length === 3 || path.length === 4) {
140
- const componentName = path[1];
141
- const propertyName = path[2];
142
- for (const comp of this._effects) {
143
- if (comp.typeName?.toLowerCase() === componentName.toLowerCase()) {
144
-
145
- if (propertyName === "active") {
146
- comp.active = modification.value;
147
- this.scheduleRecreate();
148
- return;
149
- }
150
-
151
- // cache the volume parameters
152
- if (!effectVolumeProperties.has(componentName)) {
153
- const volumeParameterKeys = new Array<string>();
154
- effectVolumeProperties.set(componentName, volumeParameterKeys);
155
- const keys = Object.keys(comp);
156
- for (const key of keys) {
157
- const prop = comp[key];
158
- if (prop instanceof VolumeParameter) {
159
- volumeParameterKeys.push(key);
160
- }
161
- }
162
- }
163
-
164
- if (effectVolumeProperties.has(componentName)) {
165
- const paramName = propertyName.toLowerCase();
166
- const volumeParameterKeys = effectVolumeProperties.get(componentName)!;
167
- for (const key of volumeParameterKeys) {
168
- if (key.toLowerCase() === paramName) {
169
- const prop = comp[key] as VolumeParameter;
170
- if (prop instanceof VolumeParameter) {
171
- const isActiveStateChange = path.length === 4 && path[3] === "active";
172
- if (isActiveStateChange) {
173
- prop.overrideState = modification.value;
174
- this.scheduleRecreate();
175
- }
176
- else if (prop && prop.value !== undefined) {
177
- prop.value = modification.value;
178
- }
179
- }
180
- return;
181
- }
182
- }
183
- }
184
-
185
- console.warn("Unknown modification", propertyName);
186
- return;
187
- }
188
- }
189
- }
190
- return true;
191
- }
192
-
193
- return false;
194
- }
195
-
196
- private _modificationQueue?: Map<string, EditorModification>;
197
-
198
- private _recreateId: number = -1;
199
- private scheduleRecreate() {
200
- // When the editor modifications come in with changed active effects we want/need to re-create the effects
201
- // We defer it slightly because multiple active changes could be made and we dont want to recreate the full effect stack multiple times
202
- const id = ++this._recreateId;
203
- setTimeout(() => {
204
- if (id !== this._recreateId) return;
205
- this.onDisable();
206
- this.onEnable();
207
- }, 200);
208
- }
209
-
210
- }
211
-
212
- /** cached VolumeParameter keys per object */
213
- const effectVolumeProperties: Map<string, string[]> = new Map<string, string[]>();
1
+ import { Behaviour, GameObject } from "../Component";
2
+ import { serializeable } from "../../engine/engine_serialization_decorator";
3
+ import { getParam } from "../../engine/engine_utils";
4
+ import { VolumeProfile } from "./VolumeProfile";
5
+ import { EditorModification, IEditorModification as IEditorModificationReceiver } from "../../engine/engine_editor-sync";
6
+ import { PostProcessingHandler } from "./PostProcessingHandler";
7
+ import { PostProcessingEffect } from "./PostProcessingEffect";
8
+ import { VolumeParameter } from "./VolumeParameter";
9
+ import { getEditorModificationCache } from "../../engine/engine_editor-sync";
10
+ import { isDevEnvironment } from "../../engine/debug";
11
+ import { EffectComposer } from "postprocessing";
12
+
13
+ const debug = getParam("debugpost");
14
+
15
+ export class Volume extends Behaviour implements IEditorModificationReceiver {
16
+
17
+ @serializeable(VolumeProfile)
18
+ sharedProfile?: VolumeProfile;
19
+
20
+ private _postprocessing?: PostProcessingHandler;
21
+ private _effects: PostProcessingEffect[] = [];
22
+
23
+ awake() {
24
+ // ensure the profile is initialized
25
+ this.sharedProfile?.init();
26
+
27
+ if (debug) {
28
+ console.log(this);
29
+ console.log("Press P to toggle post processing");
30
+ window.addEventListener("keydown", (e) => {
31
+ if (e.key === "p") {
32
+ console.log("Toggle volume: " + this.name, !this.enabled);
33
+ this.enabled = !this.enabled;
34
+ }
35
+ });
36
+ }
37
+ }
38
+
39
+ onDisable() {
40
+ this._postprocessing?.unapply();
41
+ }
42
+
43
+ onBeforeRender(): void {
44
+ if (!this.context.isInXR) {
45
+
46
+ if (this.context.composer && (this.context.composer instanceof EffectComposer) === false) {
47
+ return;
48
+ }
49
+
50
+ // Wait for the first frame to be rendered before creating because then we know we have a camera (issue 135)
51
+ if (this.context.mainCamera) {
52
+ if (!this._postprocessing || !this._postprocessing.isActive) {
53
+ this.apply();
54
+ // TODO: remove this workaround for postprocessing rendering not working correctly when applied for the first time
55
+ this.apply();
56
+ }
57
+ }
58
+
59
+ if (this.context.composer) {
60
+ this.context.composer.setRenderer(this.context.renderer);
61
+ this.context.composer.setMainScene(this.context.scene);
62
+ if (this.context.mainCamera)
63
+ this.context.composer.setMainCamera(this.context.mainCamera);
64
+ }
65
+ }
66
+ }
67
+
68
+ onDestroy(): void {
69
+ this._postprocessing?.dispose();
70
+ }
71
+
72
+ private _lastApplyTime?: number;
73
+ private _rapidApplyCount = 0;
74
+
75
+ private apply() {
76
+ if (debug) console.log("Apply PostProcessing", this, this.context.mainCamera?.name);
77
+
78
+ if (isDevEnvironment()) {
79
+ if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
80
+ this._rapidApplyCount++;
81
+ if (this._rapidApplyCount === 5)
82
+ console.warn("Detected rapid post processing modifications - this might be a bug", this);
83
+ }
84
+ this._lastApplyTime = Date.now();
85
+ }
86
+
87
+
88
+ this.unapply();
89
+
90
+ this._effects.length = 0;
91
+ // get from profile
92
+ if (this.sharedProfile?.components) {
93
+ this._effects.push(...this.sharedProfile.components);
94
+ }
95
+ // get additional effects
96
+ const additionalComponents = this.gameObject.getComponentsInChildren(PostProcessingEffect);
97
+ if (debug && additionalComponents?.length)
98
+ console.log("Additional", additionalComponents);
99
+ if (additionalComponents) {
100
+ for (const comp of additionalComponents) {
101
+ if (comp.active) this._effects.push(comp);
102
+ }
103
+ }
104
+
105
+ if (this._effects.length > 0) {
106
+ if (!this._postprocessing)
107
+ this._postprocessing = new PostProcessingHandler(this.context);
108
+ this._postprocessing.apply(this._effects);
109
+ this._applyPostQueue();
110
+ }
111
+
112
+ }
113
+
114
+ private unapply() {
115
+ this._postprocessing?.unapply();
116
+ }
117
+
118
+
119
+ private _applyPostQueue() {
120
+ if (this._modificationQueue) {
121
+ for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
122
+ this._modificationQueue.clear();
123
+ }
124
+ }
125
+
126
+ /** called from needle editor sync package if its active */
127
+ onEditorModification(modification: EditorModification): void | boolean | undefined {
128
+
129
+ if (modification.propertyName.startsWith("postprocessing.")) {
130
+
131
+ if (!this._postprocessing) {
132
+ if (!this._modificationQueue) this._modificationQueue = new Map<string, EditorModification>();
133
+ this._modificationQueue.set(modification.propertyName, modification);
134
+ return true;
135
+ }
136
+
137
+ if (!this._effects?.length) return;
138
+ const path = modification.propertyName.split(".");
139
+ if (path.length === 3 || path.length === 4) {
140
+ const componentName = path[1];
141
+ const propertyName = path[2];
142
+ for (const comp of this._effects) {
143
+ if (comp.typeName?.toLowerCase() === componentName.toLowerCase()) {
144
+
145
+ if (propertyName === "active") {
146
+ comp.active = modification.value;
147
+ this.scheduleRecreate();
148
+ return;
149
+ }
150
+
151
+ // cache the volume parameters
152
+ if (!effectVolumeProperties.has(componentName)) {
153
+ const volumeParameterKeys = new Array<string>();
154
+ effectVolumeProperties.set(componentName, volumeParameterKeys);
155
+ const keys = Object.keys(comp);
156
+ for (const key of keys) {
157
+ const prop = comp[key];
158
+ if (prop instanceof VolumeParameter) {
159
+ volumeParameterKeys.push(key);
160
+ }
161
+ }
162
+ }
163
+
164
+ if (effectVolumeProperties.has(componentName)) {
165
+ const paramName = propertyName.toLowerCase();
166
+ const volumeParameterKeys = effectVolumeProperties.get(componentName)!;
167
+ for (const key of volumeParameterKeys) {
168
+ if (key.toLowerCase() === paramName) {
169
+ const prop = comp[key] as VolumeParameter;
170
+ if (prop instanceof VolumeParameter) {
171
+ const isActiveStateChange = path.length === 4 && path[3] === "active";
172
+ if (isActiveStateChange) {
173
+ prop.overrideState = modification.value;
174
+ this.scheduleRecreate();
175
+ }
176
+ else if (prop && prop.value !== undefined) {
177
+ prop.value = modification.value;
178
+ }
179
+ }
180
+ return;
181
+ }
182
+ }
183
+ }
184
+
185
+ console.warn("Unknown modification", propertyName);
186
+ return;
187
+ }
188
+ }
189
+ }
190
+ return true;
191
+ }
192
+
193
+ return false;
194
+ }
195
+
196
+ private _modificationQueue?: Map<string, EditorModification>;
197
+
198
+ private _recreateId: number = -1;
199
+ private scheduleRecreate() {
200
+ // When the editor modifications come in with changed active effects we want/need to re-create the effects
201
+ // We defer it slightly because multiple active changes could be made and we dont want to recreate the full effect stack multiple times
202
+ const id = ++this._recreateId;
203
+ setTimeout(() => {
204
+ if (id !== this._recreateId) return;
205
+ this.onDisable();
206
+ this.onEnable();
207
+ }, 200);
208
+ }
209
+
210
+ }
211
+
212
+ /** cached VolumeParameter keys per object */
213
+ const effectVolumeProperties: Map<string, string[]> = new Map<string, string[]>();
@@ -8,11 +8,13 @@ import { getComponentsInChildren } from "../../engine/engine_components";
8
8
  import { IComponent } from "../../engine/engine_types";
9
9
  import { GameObject } from "../Component";
10
10
  import { showBalloonMessage, showBalloonWarning } from "../../engine/debug";
11
+ import { Object3D } from "three";
11
12
 
12
13
  export enum RenderMode {
13
14
  ScreenSpaceOverlay = 0,
14
15
  ScreenSpaceCamera = 1,
15
16
  WorldSpace = 2,
17
+ Undefined = -1,
16
18
  }
17
19
 
18
20
  export class Canvas extends UIRootComponent {
@@ -74,7 +76,7 @@ export class Canvas extends UIRootComponent {
74
76
  this._renderMode = val;
75
77
  this.onRenderSettingsChanged();
76
78
  }
77
- private _renderMode: RenderMode = -1;
79
+ private _renderMode: RenderMode = RenderMode.Undefined;
78
80
 
79
81
  private _rootCanvas!: Canvas;
80
82
 
@@ -109,12 +111,28 @@ export class Canvas extends UIRootComponent {
109
111
  }
110
112
 
111
113
  private previousAspect: number = -1;
114
+ private previousParent: Object3D | null = null;
112
115
 
113
116
  onBeforeRender() {
114
117
  if (this.isScreenSpace && this.context.mainCameraComponent && this.context.mainCameraComponent.aspect !== this.previousAspect) {
115
118
  this.previousAspect = this.context.mainCameraComponent.aspect;
116
119
  this.updateRenderMode();
117
120
  }
121
+ else if(this.renderOnTop){
122
+ // This is just a test but in reality it should be combined with all world canvases with render on top in one render pass
123
+ this.previousParent = this.gameObject.parent;
124
+ this.gameObject.removeFromParent();
125
+ }
126
+ }
127
+
128
+ onAfterRender(): void {
129
+ if (this.renderOnTop && this.previousParent && this.context.mainCamera) {
130
+ this.previousParent.add(this.gameObject);
131
+ this.context.renderer.autoClear = false;
132
+ this.context.renderer.clearDepth();
133
+ this.context.renderer.render(this.gameObject, this.context.mainCamera);
134
+ this.context.renderer.autoClear = true;
135
+ }
118
136
  }
119
137
 
120
138
  applyRenderSettings(){
@@ -382,8 +382,7 @@ export class EventSystem extends Behaviour {
382
382
  const actualGo = parent[$shadowDomOwner].gameObject;
383
383
  if (actualGo) {
384
384
  const res = UIRaycastUtils.isInteractable(actualGo, this.out);
385
- // console.log(actualGo, res);
386
- if (!res) return this.out.canvasGroup?.interactable ?? false;
385
+ if (!res) return false;
387
386
  canvasGroup = this.out.canvasGroup ?? null;
388
387
 
389
388
  const handled = this.handleMeshUIIntersection(object, pressedOrClicked);
@@ -214,6 +214,9 @@ export class Text extends Graphic {
214
214
 
215
215
  this.getAlignment(opts, isTextIntermediate);
216
216
  const block = rt.createNewBlock(opts);
217
+ // The text block should never write depth to avoid z-fighting
218
+ const mat = block["backgroundMaterial"];
219
+ if(mat) mat.depthWrite = false;
217
220
  if (content) {
218
221
  if (Array.isArray(content)) {
219
222
  block.add(...content);
@@ -34,11 +34,13 @@ export function updateRenderSettings(shadowComponent: THREE.Object3D, settings:
34
34
  // console.log(shadowComponent)
35
35
  const mat = shadowComponent["material"];
36
36
  if (mat?.isMaterial === true) {
37
- // console.log(shadowComponent, shadowComponent.name);
38
- // console.log(mat, component.renderOnTop, component.doubleSided, component.depthWrite);
39
- mat.depthTest = !settings.renderOnTop ?? true;
37
+ const parent = shadowComponent.parent;
38
+ if (parent && parent["isText"] === true) {
39
+ // console.log(shadowComponent, shadowComponent.name);
40
+ }
41
+ // mat.depthTest = !settings.renderOnTop ?? true;
42
+ // mat.depthWrite = settings.depthWrite ?? false;
40
43
  mat.side = (settings.doubleSided ?? true) ? DoubleSide : FrontSide;
41
- mat.depthWrite = settings.depthWrite ?? false;
42
44
  mat.shadowSide = settings.doubleSided ? DoubleSide : FrontSide;
43
45
  shadowComponent.castShadow = settings.castShadows ? settings.castShadows : false;
44
46
  shadowComponent.receiveShadow = settings.receiveShadows ? settings.receiveShadows : false;