Needle Engine

Changes between version 3.5.0-alpha and 3.5.1-alpha
Files changed (8) hide show
  1. src/engine-components/ui/Canvas.ts +29 -16
  2. src/engine/engine_context.ts +43 -19
  3. src/engine-components/ui/Layout.ts +10 -5
  4. src/engine-components/ui/RectTransform.ts +10 -6
  5. src/engine-components/ReflectionProbe.ts +17 -7
  6. src/engine-components/Renderer.ts +5 -5
  7. src/engine-components/RendererLightmap.ts +1 -1
  8. src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -5
src/engine-components/ui/Canvas.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  import * as ThreeMeshUI from 'three-mesh-ui'
12
12
  import { getParam } from "../../engine/engine_utils";
13
13
  import { LayoutGroup } from "./Layout";
14
+ import { Mathf } from "../../engine/engine_math";
14
15
 
15
16
  export enum RenderMode {
16
17
  ScreenSpaceOverlay = 0,
@@ -127,6 +128,9 @@
127
128
  awake() {
128
129
  //@ts-ignore
129
130
  this.shadowComponent = this.gameObject;
131
+ this.previousParent = this.gameObject.parent;
132
+ if (debugLayout)
133
+ console.log("Canvas.Awake()", this.previousParent?.name + "/" + this.gameObject.name)
130
134
  super.awake();
131
135
  }
132
136
 
@@ -137,7 +141,8 @@
137
141
  onEnable() {
138
142
  super.onEnable();
139
143
  this._updateRenderSettingsRoutine = undefined;
140
- this.onRenderSettingsChanged();
144
+ this._lastMatrixWorld = new Matrix4();
145
+ this.onUpdateRenderMode();
141
146
  document.addEventListener("resize", this._boundRenderSettingsChanged);
142
147
  // We want to run AFTER all regular onBeforeRender callbacks
143
148
  this.context.pre_render_callbacks.push(this.onBeforeRenderRoutine);
@@ -185,9 +190,11 @@
185
190
  }
186
191
 
187
192
  onBeforeRenderRoutine = () => {
193
+ this.previousParent = this.gameObject.parent;
194
+ // console.log(this.previousParent?.name + "/" + this.gameObject.name);
195
+
188
196
  if (this.renderOnTop) {
189
197
  // This is just a test but in reality it should be combined with all world canvases with render on top in one render pass
190
- this.previousParent = this.gameObject.parent;
191
198
  this.gameObject.removeFromParent();
192
199
  }
193
200
  else {
@@ -201,8 +208,13 @@
201
208
  }
202
209
 
203
210
  onAfterRenderRoutine = () => {
204
- if (this.renderOnTop && this.previousParent && this.context.mainCamera) {
205
- this.previousParent.add(this.gameObject);
211
+ if ((this.screenspace || this.renderMode) && this.previousParent && this.context.mainCamera) {
212
+ if (this.screenspace) {
213
+ const camObj = this.context.mainCamera;
214
+ camObj?.add(this.gameObject);
215
+ } else {
216
+ this.previousParent.add(this.gameObject);
217
+ }
206
218
  this.context.renderer.autoClear = false;
207
219
  this.context.renderer.clearDepth();
208
220
  this.onUpdateRenderMode(true);
@@ -212,6 +224,7 @@
212
224
  EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
213
225
  this.context.renderer.render(this.gameObject, this.context.mainCamera);
214
226
  this.context.renderer.autoClear = true;
227
+ this.previousParent.add(this.gameObject);
215
228
  }
216
229
  this._lastMatrixWorld?.copy(this.gameObject.matrixWorld);
217
230
  }
@@ -278,7 +291,10 @@
278
291
  }
279
292
  this._activeRenderMode = this._renderMode;
280
293
  let camera = this.context.mainCameraComponent;
281
- let planeDistance: number = camera?.farClipPlane ?? 100;
294
+ let planeDistance: number = 10;
295
+ if (camera && camera.nearClipPlane > 0 && camera.farClipPlane > 0) {
296
+ planeDistance = Mathf.lerp(camera.nearClipPlane, camera.farClipPlane, .5);
297
+ }
282
298
  if (this._renderMode === RenderMode.ScreenSpaceCamera) {
283
299
  if (this.worldCamera)
284
300
  camera = this.worldCamera as Camera;
@@ -295,15 +311,12 @@
295
311
  // showBalloonWarning("Screenspace Canvas is not supported yet. Please use worldspace");
296
312
  if (!camera) return;
297
313
 
298
- const canvas = this.gameObject;
299
- const camObj = camera.gameObject;
300
- camObj?.add(canvas);
301
314
  // we move the plane SLIGHTLY closer to be sure not to cull the canvas
302
- const plane = planeDistance - .1;
303
- canvas.position.x = 0;
304
- canvas.position.y = 0;
305
- canvas.position.z = -plane;
306
- canvas.quaternion.identity();
315
+ const plane = planeDistance + .01;
316
+ this.gameObject.position.x = 0;
317
+ this.gameObject.position.y = 0;
318
+ this.gameObject.position.z = -plane;
319
+ this.gameObject.quaternion.identity();
307
320
 
308
321
  const rect = this.gameObject.getComponent(RectTransform)!;
309
322
  let hasChanged = false;
@@ -316,10 +329,10 @@
316
329
 
317
330
  const vFOV = camera.fieldOfView! * Math.PI / 180;
318
331
  const h = 2 * Math.tan(vFOV / 2) * Math.abs(plane);
319
- canvas.scale.x = h / this.context.domHeight;
320
- canvas.scale.y = h / this.context.domHeight;
332
+ this.gameObject.scale.x = h / this.context.domHeight;
333
+ this.gameObject.scale.y = h / this.context.domHeight;
321
334
  // Set scale.z, otherwise small offsets in screenspace mode have different visual results based on export scale and other settings
322
- canvas.scale.z = .01;
335
+ this.gameObject.scale.z = .01;
323
336
 
324
337
  if (hasChanged) {
325
338
  rect.sizeDelta.x = this.context.domWidth;
src/engine/engine_context.ts CHANGED
@@ -80,7 +80,7 @@
80
80
  ImmersiveAR = "immersive-ar",
81
81
  }
82
82
 
83
- export declare type OnBeforeRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
83
+ export declare type OnRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
84
84
 
85
85
 
86
86
  export function registerComponent(script: IComponent, context?: Context) {
@@ -440,33 +440,57 @@
440
440
  }
441
441
  }
442
442
 
443
- private _onBeforeRenderListeners: { [key: string]: OnBeforeRenderCallback[] } = {};
444
443
 
444
+
445
+ private _onBeforeRenderListeners = new Map<string, OnRenderCallback[]>();
446
+ private _onAfterRenderListeners = new Map<string, OnRenderCallback[]>();
447
+
445
448
  /** use this to subscribe to onBeforeRender events on threejs objects */
446
- addBeforeRenderListener(target: Object3D, callback: OnBeforeRenderCallback) {
447
- if (!this._onBeforeRenderListeners[target.uuid]) {
448
- this._onBeforeRenderListeners[target.uuid] = [];
449
- const onBeforeRenderCallback = (renderer, scene, camera, geometry, material, group) => {
450
- const arr = this._onBeforeRenderListeners[target.uuid];
451
- if (!arr) return;
452
- for (let i = 0; i < arr.length; i++) {
453
- const fn = arr[i];
454
- fn(renderer, scene, camera, geometry, material, group);
455
- }
456
- }
457
- target.onBeforeRender = onBeforeRenderCallback as any;
449
+ addBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
450
+ if (!this._onBeforeRenderListeners.has(target.uuid)) {
451
+ this._onBeforeRenderListeners.set(target.uuid, []);
452
+ target.onBeforeRender = this._createRenderCallbackWrapper(target, this._onBeforeRenderListeners);
458
453
  }
459
- this._onBeforeRenderListeners[target.uuid].push(callback);
454
+ this._onBeforeRenderListeners.get(target.uuid)?.push(callback);
460
455
  }
456
+ removeBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
457
+ if (this._onBeforeRenderListeners.has(target.uuid)) {
458
+ const arr = this._onBeforeRenderListeners.get(target.uuid)!;
459
+ const idx = arr.indexOf(callback);
460
+ if (idx >= 0) arr.splice(idx, 1);
461
+ }
462
+ }
461
463
 
462
- removeBeforeRenderListener(target: Object3D, callback: OnBeforeRenderCallback) {
463
- if (this._onBeforeRenderListeners[target.uuid]) {
464
- const arr = this._onBeforeRenderListeners[target.uuid];
464
+ /** use this to subscribe to onAfterRender events on threejs objects */
465
+ addAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
466
+ if (!this._onAfterRenderListeners.has(target.uuid)) {
467
+ this._onAfterRenderListeners.set(target.uuid, []);
468
+ target.onAfterRender = this._createRenderCallbackWrapper(target, this._onAfterRenderListeners);
469
+ }
470
+ this._onAfterRenderListeners.get(target.uuid)?.push(callback);
471
+ }
472
+ removeAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
473
+ if (this._onAfterRenderListeners.has(target.uuid)) {
474
+ const arr = this._onAfterRenderListeners.get(target.uuid)!;
465
475
  const idx = arr.indexOf(callback);
466
476
  if (idx >= 0) arr.splice(idx, 1);
467
477
  }
468
478
  }
469
479
 
480
+
481
+ private _createRenderCallbackWrapper(target: Object3D, array: Map<string, OnRenderCallback[]>): OnRenderCallback {
482
+ return (renderer, scene, camera, geometry, material, group) => {
483
+ const arr = array.get(target.uuid);
484
+ if (!arr) return;
485
+ for (let i = 0; i < arr.length; i++) {
486
+ const fn = arr[i];
487
+ fn(renderer, scene, camera, geometry, material, group);
488
+ }
489
+ }
490
+ }
491
+
492
+
493
+
470
494
  private _requireDepthTexture: boolean = false;
471
495
  private _requireColorTexture: boolean = false;
472
496
  private _renderTarget?: WebGLRenderTarget;
@@ -584,7 +608,7 @@
584
608
  else {
585
609
  ContextRegistry.dispatchCallback(ContextEvent.MissingCamera, this);
586
610
  if (!this.mainCamera && !this.isManagedExternally)
587
- console.error("MISSING camera", this);
611
+ console.warn("Missing camera in main scene", this);
588
612
  }
589
613
  }
590
614
 
src/engine-components/ui/Layout.ts CHANGED
@@ -164,7 +164,8 @@
164
164
  actualWidth -= this.padding.horizontal;
165
165
  actualHeight -= this.padding.vertical;
166
166
 
167
- // console.log(rt.name, "width=" + totalWidth + ", height=" + totalHeight)
167
+ // if (rect.name === "Title")
168
+ // console.log(rect.name, "width=" + totalWidth + ", height=" + totalHeight, rect.anchoredPosition.x)
168
169
 
169
170
  const paddingAxis = axis === Axis.Horizontal ? this.padding.horizontal : this.padding.vertical;
170
171
  const isHorizontal = axis === Axis.Horizontal;
@@ -243,7 +244,7 @@
243
244
  }
244
245
 
245
246
  // Apply layout
246
- let k = 0;
247
+ let k = 1;
247
248
  for (let i = 0; i < this.gameObject.children.length; i++) {
248
249
  const ch = this.gameObject.children[i];
249
250
  const rt = GameObject.getComponent(ch, RectTransform);
@@ -269,10 +270,14 @@
269
270
  let halfSize = size * .5;
270
271
  start += halfSize;
271
272
 
273
+ // TODO: this isnt correct yet!
272
274
  if (forceExpandSize) {
273
- let preferredStart = sizePerChild * (k + 1) - sizePerChild * .5;
274
- if (preferredStart > start)
275
- start = preferredStart;
275
+ // this is the center of the cell
276
+ let preferredStart = sizePerChild * k - sizePerChild * .5;
277
+ if (preferredStart > start) {
278
+ start = preferredStart - sizePerChild * .5 + size + this.padding.left;
279
+ start -= halfSize;
280
+ }
276
281
  }
277
282
 
278
283
  let value = start;
src/engine-components/ui/RectTransform.ts CHANGED
@@ -31,7 +31,7 @@
31
31
 
32
32
  export class RectTransform extends BaseUIComponent implements IRectTransform, IRectTransformChangedReceiver {
33
33
 
34
- offset: number = 0.1;
34
+ offset: number = .01;
35
35
 
36
36
  // @serializable(Object3D)
37
37
  // root? : Object3D;
@@ -103,7 +103,8 @@
103
103
  awake() {
104
104
  super.awake();
105
105
  this.lastMatrix = new Matrix4();
106
- this.rectBlock = new Object3D();;
106
+ this.rectBlock = new Object3D();
107
+ // Is this legacy? Not sure if this is still needed
107
108
  this.rectBlock.position.z = .1;
108
109
  this.rectBlock.name = this.name;
109
110
 
@@ -116,6 +117,8 @@
116
117
  onChange(this, "_anchoredPosition", () => { this.markDirty(); });
117
118
  onChange(this, "sizeDelta", () => { this.markDirty(); });
118
119
  onChange(this, "pivot", () => { this.markDirty(); });
120
+ onChange(this, "anchorMin", () => { this.markDirty(); });
121
+ onChange(this, "anchorMax", () => { this.markDirty(); });
119
122
 
120
123
  // When exported with an anchored position offset we remove it here
121
124
  // because it would otherwise be applied twice when the anchoring is animated
@@ -191,9 +194,10 @@
191
194
 
192
195
  const uiobject = this.shadowComponent;
193
196
  if (!uiobject) return;
194
- if (!this.gameObject.parent) return;
195
- this._parentRectTransform = GameObject.getComponentInParent(this.gameObject.parent, RectTransform) as RectTransform;
196
-
197
+ if (this.gameObject.parent)
198
+ this._parentRectTransform = GameObject.getComponentInParent(this.gameObject.parent, RectTransform) as RectTransform;
199
+ else
200
+ this._parentRectTransform = undefined;
197
201
  this._transformNeedsUpdate = false;
198
202
  this.lastMatrix.copy(this.gameObject.matrix);
199
203
 
@@ -229,7 +233,7 @@
229
233
  else {
230
234
  // We have to rotate the canvas when it's in worldspace
231
235
  const canvas = this.Root as any as ICanvas;
232
- if (canvas && !canvas.screenspace) uiobject.rotation.y = Math.PI;
236
+ if (!canvas.screenspace) uiobject.rotation.y = Math.PI;
233
237
  }
234
238
 
235
239
  // iterate other components on this object that might need to know about the transform change
src/engine-components/ReflectionProbe.ts CHANGED
@@ -129,17 +129,28 @@
129
129
  // otherwise some renderers outside of the probe will be affected or vice versa
130
130
  for (let i = 0; i < _rend.sharedMaterials.length; i++) {
131
131
  const material = _rend.sharedMaterials[i];
132
- if (!material) continue;
133
- if (material["envMap"] === undefined) continue;
134
- if (material["envMap"] === this.texture) {
132
+ if (!material) {
135
133
  continue;
136
134
  }
135
+ if (material["envMap"] === undefined) {
136
+ continue;
137
+ }
138
+
137
139
  let cached = rendererCache[i];
138
140
 
139
141
  // make sure we have the currently assigned material cached (and an up to date clone of that)
140
142
  // TODO: this is causing problems with progressive textures sometimes (depending on the order) when the version changes and we re-create materials over and over. We might want to just set the material envmap instead of making a clone
141
- if (!cached || cached.material !== material || cached.copy.version !== material.version) {
142
- if(debug) console.log("Cloning material", material.name, material.version);
143
+ let isCachedInstance = material === cached?.copy;
144
+ let hasChanged = !cached || cached.material.uuid !== material.uuid || cached.copy.version !== material.version;
145
+ if (!isCachedInstance && hasChanged) {
146
+ if (debug) {
147
+ let reason = "";
148
+ if (!cached) reason = "not cached";
149
+ else if (cached.material !== material) reason = "reference changed; cached instance?: " + isCachedInstance;
150
+ else if (cached.copy.version !== material.version) reason = "version changed";
151
+ console.warn("Cloning material", material.name, material.version, "Reason:", reason, "\n", material.uuid, "\n", cached?.copy.uuid, "\n", _rend.name);
152
+ }
153
+
143
154
  const clone = material.clone();
144
155
  clone.version = material.version;
145
156
 
@@ -158,8 +169,7 @@
158
169
  clone[$reflectionProbeKey] = this;
159
170
  clone[$originalMaterial] = material;
160
171
 
161
- if (debug)
162
- console.log("Set reflection", _rend.name, _rend.guid);
172
+ if (debug) console.log("Set reflection", _rend.name, _rend.guid);
163
173
  }
164
174
 
165
175
  /** this is the material that we copied and that has the reflection probe */
src/engine-components/Renderer.ts CHANGED
@@ -605,15 +605,15 @@
605
605
  }
606
606
  }
607
607
 
608
+
609
+ if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
610
+ this._reflectionProbe.onSet(this);
611
+ }
612
+
608
613
  }
609
-
610
614
  onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
611
615
 
612
616
  this.loadProgressiveTextures(material);
613
- // TODO: we might want to just set the material.envMap for one material during rendering instead of cloning the material
614
- if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
615
- this._reflectionProbe.onSet(this);
616
- }
617
617
 
618
618
  if (material.envMapIntensity !== undefined) {
619
619
  const factor = this.hasLightmap ? Math.PI : 1;
src/engine-components/RendererLightmap.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import * as THREE from "three";
3
3
  import { Texture } from "three";
4
- import { Context, OnBeforeRenderCallback } from "../engine/engine_setup";
4
+ import { Context, OnRenderCallback } from "../engine/engine_setup";
5
5
 
6
6
  // this component is automatically added by the Renderer if the object has lightmap uvs AND we have a lightmap
7
7
  // for multimaterial objects GLTF exports a "Group" with the renderer component
src/engine-components/export/usdz/ThreeUSDZExporter.ts CHANGED
@@ -17,7 +17,6 @@
17
17
  Camera,
18
18
  Color,
19
19
  MeshStandardMaterial,
20
- LinearEncoding,
21
20
  sRGBEncoding,
22
21
  MeshPhysicalMaterial,
23
22
  } from 'three';
@@ -1109,9 +1108,6 @@
1109
1108
  const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
1110
1109
  const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
1111
1110
 
1112
- const rawTextureExtra = `(
1113
- colorSpace = "Raw"
1114
- )`;
1115
1111
  const needsTextureScale = mapType !== 'normal' && (color && (color.r !== 1 || color.g !== 1 || color.b !== 1 || opacity !== 1)) || false;
1116
1112
  const needsNormalScaleAndBias = mapType === 'normal';
1117
1113
  const normalScaleValueString = (material.normalScale ? material.normalScale.x * 2 : 2).toFixed( PRECISION );
@@ -1133,7 +1129,8 @@
1133
1129
  def Shader "Texture_${texture.id}_${mapType}"
1134
1130
  {
1135
1131
  uniform token info:id = "UsdUVTexture"
1136
- asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@ ${mapType === 'normal' ? rawTextureExtra : ''}
1132
+ asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
1133
+ token inputs:sourceColorSpace = "${ texture.colorSpace === 'srgb' ? 'sRGB' : 'raw' }"
1137
1134
  float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
1138
1135
  ${needsTextureScale ? `
1139
1136
  float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})