Needle Engine

Changes between version 3.9.0-alpha.1 and 3.9.1-alpha
Files changed (6) hide show
  1. src/engine-components/Camera.ts +6 -3
  2. src/engine/engine_element_overlay.ts +9 -2
  3. src/engine/engine_loaders.ts +1 -1
  4. src/engine-components/ParticleSystem.ts +16 -13
  5. src/engine-components/SceneSwitcher.ts +24 -2
  6. src/engine-components/webxr/WebXR.ts +19 -14
src/engine-components/Camera.ts CHANGED
@@ -40,7 +40,7 @@
40
40
  }
41
41
  @serializable()
42
42
  set fieldOfView(val: number | undefined) {
43
- const changed = this._fov != val;
43
+ const changed = this.fieldOfView != val;
44
44
  this._fov = val;
45
45
  if (changed && this._cam) {
46
46
  if (this._cam instanceof PerspectiveCamera) {
@@ -300,8 +300,11 @@
300
300
  if (debug) console.log("Camera does not exist (apply clear flags)")
301
301
  return;
302
302
  }
303
- if (debug)
304
- showBalloonMessage("apply Camera clear flags: " + this._clearFlags);
303
+
304
+ // restore previous fov (e.g. when user was in VR or AR and the camera's fov has changed)
305
+ this.fieldOfView = this._fov;
306
+
307
+ if (debug) showBalloonMessage("apply Camera clear flags: " + this._clearFlags);
305
308
  switch (this._clearFlags) {
306
309
  case ClearFlags.Skybox:
307
310
  if (Camera.backgroundShouldBeTransparent(this.context)) {
src/engine/engine_element_overlay.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context } from "./engine_setup";
2
2
  import { getParam, isMobileDevice, isMozillaXR } from "./engine_utils";
3
3
 
4
- const debug = getParam("debugaroverlay");
4
+ const debug = getParam("debugoverlay");
5
5
  export const arContainerClassName = "ar";
6
6
  export const quitARClassName = "quit-ar";
7
7
 
@@ -148,12 +148,19 @@
148
148
  svg.classList.add("quit-ar-button");
149
149
  svg.setAttribute('width', "38px");
150
150
  svg.setAttribute('height', "38px");
151
+ svg.style.cssText = `
152
+ background: radial-gradient(circle, rgba(0,0,0,.05) 70%, rgba(0,0,0,0) 100%);
153
+ border-radius: 50%;
154
+ `;
151
155
  fixedButtonContainer.appendChild(svg);
152
156
 
153
157
  var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
154
158
  path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
155
- path.setAttribute('stroke', '#aaa');
159
+ path.setAttribute('stroke', '#fff');
156
160
  path.setAttribute('stroke-width', "3px");
161
+ path.style.cssText = `
162
+ filter: drop-shadow(0 0px 1.2px rgba(0,0,0,.7));
163
+ `
157
164
  svg.appendChild(path);
158
165
  if (debug) console.log("Created fallback close button", svg, element);
159
166
  }
src/engine/engine_loaders.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  const DEFAULT_KTX2_TRANSCODER_LOCATION ='https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
14
14
 
15
15
  let dracoLoader: DRACOLoader;
16
- let meshoptDecoder: MeshoptDecoder;
16
+ let meshoptDecoder: typeof MeshoptDecoder;
17
17
  let ktx2Loader: KTX2Loader;
18
18
 
19
19
  export function setDracoDecoderPath(path: string | undefined) {
src/engine-components/ParticleSystem.ts CHANGED
@@ -75,7 +75,7 @@
75
75
 
76
76
  if (!ParticleSystemRenderer._havePatchedQuarkShaders) {
77
77
  ParticleSystemRenderer._havePatchedQuarkShaders = true;
78
-
78
+
79
79
  // HACK patch three.quarks fo three152+, see https://github.com/Alchemist0823/three.quarks/issues/56#issuecomment-1560825038
80
80
  const _rebuild = TrailBatch.prototype.rebuildMaterial;
81
81
  TrailBatch.prototype.rebuildMaterial = function () {
@@ -130,13 +130,17 @@
130
130
  class MinMaxCurveFunction implements FunctionValueGenerator {
131
131
 
132
132
  private _curve: MinMaxCurve;
133
+ private _factor: number;
133
134
 
134
- constructor(curve: MinMaxCurve) { this._curve = curve; }
135
+ constructor(curve: MinMaxCurve, factor: number = 1) {
136
+ this._curve = curve;
137
+ this._factor = factor;
138
+ }
135
139
 
136
140
  type: "function" = "function";
137
141
 
138
142
  genValue(t: number): number {
139
- return this._curve.evaluate(t, Math.random());
143
+ return this._curve.evaluate(t, Math.random()) * this._factor;
140
144
  }
141
145
  toJSON(): FunctionJSON {
142
146
  throw new Error("Method not implemented.");
@@ -416,7 +420,7 @@
416
420
  initialize(particle: Particle): void {
417
421
  const simulationSpeed = this.system.main.simulationSpeed;
418
422
 
419
- const factor = 1 * simulationSpeed;
423
+ const factor = 1;
420
424
  particle.startSpeed = this.system.main.startSpeed.evaluate(Math.random(), Math.random()) * factor;
421
425
  particle.velocity.copy(this.system.shape.getDirection(particle.position)).multiplyScalar(particle.startSpeed);
422
426
  if (this.system.inheritVelocity?.enabled) {
@@ -425,16 +429,16 @@
425
429
  if (!particle[$startVelocity]) particle[$startVelocity] = particle.velocity.clone();
426
430
  else particle[$startVelocity].copy(particle.velocity);
427
431
 
428
- const gravityFactor = this.system.main.gravityModifier.evaluate(Math.random(), Math.random()) / (9.81 * 2);
432
+ const gravityFactor = this.system.main.gravityModifier.evaluate(Math.random(), Math.random());
429
433
  particle[$gravityFactor] = gravityFactor * simulationSpeed;
430
- particle[$gravitySpeed] = 1;
434
+ particle[$gravitySpeed] = gravityFactor * simulationSpeed * .5
431
435
 
432
436
  particle[$velocityLerpFactor] = Math.random();
433
437
  this.system.velocityOverLifetime?.init(particle);
434
438
 
435
439
  this._gravityDirection.set(0, -1, 0);
436
440
  if (this.system.main.simulationSpace === ParticleSystemSimulationSpace.Local)
437
- this._gravityDirection.applyQuaternion(this.system.worldQuaternionInverted);
441
+ this._gravityDirection.applyQuaternion(this.system.worldQuaternionInverted).normalize();
438
442
  }
439
443
 
440
444
  update(particle: Particle, delta: number): void {
@@ -444,10 +448,9 @@
444
448
  const baseVelocity = particle[$startVelocity];
445
449
  let gravityFactor = particle[$gravityFactor];
446
450
  if (gravityFactor !== 0) {
447
- // gravityFactor *= -1;
448
- temp3.copy(this._gravityDirection).multiplyScalar(gravityFactor * particle[$gravitySpeed]);
449
- particle[$gravitySpeed] += delta;
450
- if (debug) Gizmos.DrawDirection(particle.position, temp3, 0x0000ff, 0, false, 10);
451
+ const factor = gravityFactor * particle[$gravitySpeed];
452
+ temp3.copy(this._gravityDirection).multiplyScalar(factor);
453
+ particle[$gravitySpeed] += delta * .05;
451
454
  baseVelocity.add(temp3);
452
455
  }
453
456
  particle.velocity.copy(baseVelocity);
@@ -931,10 +934,10 @@
931
934
  const duration = this.main.duration;
932
935
  const lifetime = this.main.startLifetime.getMax();
933
936
  const maxDurationToPrewarm = 1000;
934
- const timeToSimulate = Math.min(duration, lifetime, maxDurationToPrewarm);
937
+ const timeToSimulate = Math.min(Math.max(duration, lifetime) / Math.max(.01, this.main.simulationSpeed), maxDurationToPrewarm);
935
938
  const framesToSimulate = Math.ceil(timeToSimulate / dt);
936
939
  const startTime = Date.now();
937
- if (debug)
940
+ if (debug || this.name === "Snow")
938
941
  console.log(`Particles ${this.name} - Prewarm for ${framesToSimulate} frames (${timeToSimulate} sec). Duration: ${duration}, Lifetime: ${lifetime}`);
939
942
  for (let i = 0; i < framesToSimulate; i++) {
940
943
  if (this.currentParticles >= this.maxParticles) break;
src/engine-components/SceneSwitcher.ts CHANGED
@@ -38,7 +38,14 @@
38
38
  @serializable()
39
39
  queryParameterName: string = "scene";
40
40
 
41
+ /**
42
+ * when enabled the scene name will be used as the query parameter (otherwise the scene index will be used)
43
+ * Needs `queryParameterName` set
44
+ * */
41
45
  @serializable()
46
+ useSceneName: boolean = true;
47
+
48
+ @serializable()
42
49
  clamp: boolean = true;
43
50
 
44
51
  /** when enabled the new scene is pushed to the browser navigation history, only works with a valid query parameter set */
@@ -281,9 +288,15 @@
281
288
  if (this.useSceneLighting)
282
289
  this.context.sceneLighting.enable(scene)
283
290
  if (this.useHistory && index >= 0) {
291
+ // take the index as the query parameter value
292
+ let queryParameterValue = index.toString();
293
+ // unless the user defines that he wants to use the scene name
294
+ if (this.useSceneName) {
295
+ queryParameterValue = sceneUriToName(scene.uri);
296
+ }
284
297
  // save the loaded scene as an url parameter
285
298
  if (this.queryParameterName?.length)
286
- setParamWithoutReload(this.queryParameterName, index.toString(), this.useHistory);
299
+ setParamWithoutReload(this.queryParameterName, queryParameterValue, this.useHistory);
287
300
  // or set the history state without updating the url parameter
288
301
  else {
289
302
  const lastState = history.state;
@@ -328,9 +341,11 @@
328
341
  }
329
342
  else {
330
343
  // Try to find a scene with a matching name
344
+ // we don't care about casing. e.g. Scene1 and scene1 should both match
345
+ const lowerCaseValue = value.toLowerCase();
331
346
  for (let i = 0; i < this.scenes.length; i++) {
332
347
  const scene = this.scenes[i];
333
- if (scene.uri.toLowerCase().includes(value)) {
348
+ if (sceneUriToName(scene.uri).toLowerCase().includes(lowerCaseValue)) {
334
349
  return this.select(i);;
335
350
  }
336
351
  }
@@ -349,8 +364,15 @@
349
364
  }
350
365
 
351
366
 
367
+ function sceneUriToName(uri: string): string {
368
+ const name = uri.split("/").pop();
369
+ let value = name?.split(".").shift();
370
+ if (value?.length) return value;
371
+ return uri;
372
+ }
352
373
 
353
374
 
375
+
354
376
  class PreLoadScheduler {
355
377
  maxLoadAhead: number;
356
378
  maxLoadBehind: number;
src/engine-components/webxr/WebXR.ts CHANGED
@@ -21,7 +21,7 @@
21
21
  const debugWebXR = getParam("debugwebxr");
22
22
 
23
23
  export async function detectARSupport() {
24
- if(isMozillaXR()) return true;
24
+ if (isMozillaXR()) return true;
25
25
  if ("xr" in navigator) {
26
26
  //@ts-ignore
27
27
  return (await navigator["xr"].isSessionSupported('immersive-ar')) === true;
@@ -136,7 +136,7 @@
136
136
  return arButton;
137
137
  }
138
138
 
139
- private static onModifyAROptions(options){
139
+ private static onModifyAROptions(options) {
140
140
  WebXR.dispatchEvent(WebXREvent.ModifyAROptions, options);
141
141
  }
142
142
 
@@ -255,11 +255,10 @@
255
255
  this.context.appendHTMLElement(buttonsContainer);
256
256
 
257
257
  const forceButtons = debugWebXR;
258
- if(debugWebXR) console.log("ARSupported?", arSupported, "VRSupported?", vrSupported);
258
+ if (debugWebXR) console.log("ARSupported?", arSupported, "VRSupported?", vrSupported);
259
259
 
260
260
  // AR support
261
- if (forceButtons || (this.createARButton && this.enableAR && arSupported))
262
- {
261
+ if (forceButtons || (this.createARButton && this.enableAR && arSupported)) {
263
262
  arButton = WebXR.createARButton(this);
264
263
  this._arButton = arButton;
265
264
  buttonsContainer.appendChild(arButton);
@@ -271,7 +270,7 @@
271
270
  this._vrButton = vrButton;
272
271
  buttonsContainer.appendChild(vrButton);
273
272
  }
274
-
273
+
275
274
  setTimeout(() => {
276
275
  WebXR.resetButtonStyles(vrButton);
277
276
  WebXR.resetButtonStyles(arButton);
@@ -289,11 +288,11 @@
289
288
  // TODO: figure out why screen is black if we enable the code written here
290
289
  // const referenceSpace = renderer.xr.getReferenceSpace();
291
290
  const session = this.context.renderer.xr.getSession();
292
-
293
291
 
292
+
294
293
  if (session) {
295
294
  const pose = frame.getViewerPose(this.context.renderer.xr.getReferenceSpace());
296
- if(!pose) return;
295
+ if (!pose) return;
297
296
  this._currentHeadPose = pose;
298
297
  const transform: XRRigidTransform = pose?.transform;
299
298
  if (transform) {
@@ -356,7 +355,7 @@
356
355
  this._originalCameraRotation.copy(getWorldQuaternion(this.context.mainCamera));
357
356
  this._originalCameraParent = this.context.mainCamera.parent;
358
357
  }
359
- if(this.Rig){
358
+ if (this.Rig) {
360
359
  this._originalXRRigParent = this.Rig.parent;
361
360
  this._originalXRRigPosition.copy(this.Rig.position);
362
361
  this._originalXRRigRotation.copy(this.Rig.quaternion);
@@ -415,9 +414,9 @@
415
414
  const xr = this.context.renderer.xr;
416
415
  if (this.context.mainCamera) {
417
416
  const cam = xr.getCamera() as WebXRArrayCamera;
418
- if(debugWebXR) console.log("WebXRCamera", cam);
417
+ if (debugWebXR) console.log("WebXRCamera", cam);
419
418
  const cull = this.context.mainCameraComponent?.cullingMask;
420
- if(cam && cull !== undefined){
419
+ if (cam && cull !== undefined) {
421
420
  for (const c of cam.cameras) {
422
421
  c.layers.mask = cull;
423
422
  }
@@ -467,8 +466,14 @@
467
466
 
468
467
  const wasInAR = this._isInAR;
469
468
 
470
- if (this._isInAR && session) {
471
- this.webAR?.onEnd(session);
469
+ if (session) {
470
+ if (this._isInAR) {
471
+ this.webAR?.onEnd(session);
472
+ }
473
+ else {
474
+ // if in VR we want to restore the FOV
475
+ this.context.mainCameraComponent?.applyClearFlagsIfIsActiveCamera();
476
+ }
472
477
  }
473
478
 
474
479
  this._isInAR = false;
@@ -491,7 +496,7 @@
491
496
  this.context.mainCamera.scale.set(1, 1, 1);
492
497
  }
493
498
 
494
- if(wasInAR){
499
+ if (wasInAR) {
495
500
  this._originalXRRigParent?.add(this.rig);
496
501
  this.rig.position.copy(this._originalXRRigPosition);
497
502
  this.rig.quaternion.copy(this._originalXRRigRotation);