@needle-tools/engine 2.55.0-pre → 2.55.2-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 (65) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/needle-engine.d.ts +143 -133
  3. package/dist/needle-engine.js +358 -358
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +20 -20
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/dist/needle-engine.tsbuildinfo +1 -1
  8. package/lib/engine/engine_gizmos.d.ts +2 -0
  9. package/lib/engine/engine_gizmos.js +24 -1
  10. package/lib/engine/engine_gizmos.js.map +1 -1
  11. package/lib/engine/engine_physics.d.ts +1 -0
  12. package/lib/engine/engine_physics.js +5 -2
  13. package/lib/engine/engine_physics.js.map +1 -1
  14. package/lib/engine/engine_serialization_core.js +20 -10
  15. package/lib/engine/engine_serialization_core.js.map +1 -1
  16. package/lib/engine/engine_setup.js +7 -1
  17. package/lib/engine/engine_setup.js.map +1 -1
  18. package/lib/engine/engine_time.js +3 -3
  19. package/lib/engine/engine_time.js.map +1 -1
  20. package/lib/engine/engine_types.d.ts +1 -0
  21. package/lib/engine/engine_types.js.map +1 -1
  22. package/lib/engine/extensions/NEEDLE_lightmaps.js +3 -1
  23. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  24. package/lib/engine-components/Camera.d.ts +1 -2
  25. package/lib/engine-components/Camera.js +10 -10
  26. package/lib/engine-components/Camera.js.map +1 -1
  27. package/lib/engine-components/CameraUtils.d.ts +2 -2
  28. package/lib/engine-components/CameraUtils.js +13 -4
  29. package/lib/engine-components/CameraUtils.js.map +1 -1
  30. package/lib/engine-components/LODGroup.js +9 -0
  31. package/lib/engine-components/LODGroup.js.map +1 -1
  32. package/lib/engine-components/OrbitControls.d.ts +8 -7
  33. package/lib/engine-components/OrbitControls.js +38 -7
  34. package/lib/engine-components/OrbitControls.js.map +1 -1
  35. package/lib/engine-components/ParticleSystem.js +7 -1
  36. package/lib/engine-components/ParticleSystem.js.map +1 -1
  37. package/lib/engine-components/ParticleSystemModules.d.ts +1 -1
  38. package/lib/engine-components/ParticleSystemModules.js +2 -2
  39. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  40. package/lib/engine-components/Renderer.js +16 -2
  41. package/lib/engine-components/Renderer.js.map +1 -1
  42. package/lib/engine-components/RigidBody.d.ts +4 -0
  43. package/lib/engine-components/RigidBody.js +16 -0
  44. package/lib/engine-components/RigidBody.js.map +1 -1
  45. package/lib/engine-components/js-extensions/RGBAColor.d.ts +2 -0
  46. package/lib/engine-components/js-extensions/RGBAColor.js +2 -0
  47. package/lib/engine-components/js-extensions/RGBAColor.js.map +1 -1
  48. package/lib/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +1 -1
  50. package/src/engine/engine_gizmos.ts +27 -2
  51. package/src/engine/engine_physics.ts +5 -4
  52. package/src/engine/engine_serialization_core.ts +25 -13
  53. package/src/engine/engine_setup.ts +9 -4
  54. package/src/engine/engine_time.ts +2 -2
  55. package/src/engine/engine_types.ts +1 -0
  56. package/src/engine/extensions/NEEDLE_lightmaps.ts +3 -1
  57. package/src/engine-components/Camera.ts +12 -15
  58. package/src/engine-components/CameraUtils.ts +15 -5
  59. package/src/engine-components/LODGroup.ts +9 -0
  60. package/src/engine-components/OrbitControls.ts +58 -19
  61. package/src/engine-components/ParticleSystem.ts +9 -2
  62. package/src/engine-components/ParticleSystemModules.ts +2 -2
  63. package/src/engine-components/Renderer.ts +19 -2
  64. package/src/engine-components/RigidBody.ts +22 -5
  65. package/src/engine-components/js-extensions/RGBAColor.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.55.0-pre",
3
+ "version": "2.55.2-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",
@@ -1,10 +1,11 @@
1
1
  import * as THREE from 'three';
2
- import { BufferAttribute, Line, BoxGeometry, EdgesGeometry, Color, LineSegments, LineBasicMaterial, Object3D, Mesh, SphereGeometry, ColorRepresentation, Vector3, Box3, Quaternion } from 'three';
2
+ import { BufferAttribute, Line, BoxGeometry, EdgesGeometry, Color, LineSegments, LineBasicMaterial, Object3D, Mesh, SphereGeometry, ColorRepresentation, Vector3, Box3, Quaternion, CylinderGeometry } from 'three';
3
3
  import { Context } from './engine_setup';
4
4
  import { setWorldPosition, setWorldPositionXYZ } from './engine_three_utils';
5
5
  import { Vec3, Vec4 } from './engine_types';
6
6
 
7
7
  const _tmp = new Vector3();
8
+ const _tmp2 = new Vector3();
8
9
  const _quat = new Quaternion();
9
10
 
10
11
  const defaultColor: ColorRepresentation = 0x888888;
@@ -76,7 +77,7 @@ export class Gizmos {
76
77
  obj.material["wireframe"] = true;
77
78
  }
78
79
 
79
- static DrawBox3(box:Box3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true) {
80
+ static DrawBox3(box: Box3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true) {
80
81
  const obj = Internal.getBox(duration);
81
82
  obj.position.copy(box.getCenter(_tmp));
82
83
  obj.scale.copy(box.getSize(_tmp));
@@ -84,6 +85,20 @@ export class Gizmos {
84
85
  obj.material["depthTest"] = depthTest;
85
86
  obj.material["wireframe"] = true;
86
87
  }
88
+
89
+ private static _up = new Vector3(0, 1, 0);
90
+ static DrawArrow(pt0: Vec3, pt1: Vec3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true, wireframe: boolean = false) {
91
+ const obj = Internal.getArrowHead(duration);
92
+ obj.position.set(pt1.x, pt1.y, pt1.z);
93
+ obj.quaternion.setFromUnitVectors(this._up.set(0, 1, 0), _tmp.set(pt1.x, pt1.y, pt1.z).sub(_tmp2.set(pt0.x, pt0.y, pt0.z)).normalize());
94
+ const dist = _tmp.set(pt1.x, pt1.y, pt1.z).sub(_tmp2.set(pt0.x, pt0.y, pt0.z)).length();
95
+ const scale = dist * 0.1;
96
+ obj.scale.set(scale, scale, scale);
97
+ obj.material["color"].set(color);
98
+ obj.material["depthTest"] = depthTest;
99
+ obj.material["wireframe"] = wireframe;
100
+ this.DrawLine(pt0, pt1, color, duration, depthTest);
101
+ }
87
102
  }
88
103
 
89
104
  const box: BoxGeometry = new BoxGeometry(1, 1, 1);
@@ -141,9 +156,19 @@ class Internal {
141
156
  return sphere;
142
157
  }
143
158
 
159
+ static getArrowHead(duration: number): Mesh {
160
+ let arrowHead = this.arrowHeadsCache.pop();
161
+ if (!arrowHead) {
162
+ arrowHead = new Mesh(new CylinderGeometry(0, .5, 1, 8));
163
+ }
164
+ this.registerTimedObject(Context.Current, arrowHead, duration, this.arrowHeadsCache);
165
+ return arrowHead;
166
+ }
167
+
144
168
  private static linesCache: Array<Line> = [];
145
169
  private static spheresCache: Mesh[] = [];
146
170
  private static boxesCache: Mesh[] = [];
171
+ private static arrowHeadsCache: Mesh[] = [];
147
172
 
148
173
  private static registerTimedObject(context: Context, object: Object3D, duration: number, cache: Array<Object3D>) {
149
174
  if (!this.contextPostRenderCallbacks.get(context)) {
@@ -386,10 +386,11 @@ export class Physics {
386
386
  this._meshCache.set(key, scaledPositions);
387
387
  }
388
388
  }
389
-
390
389
  const desc = convex ? ColliderDesc.convexMesh(positions) : ColliderDesc.trimesh(positions, indices);
391
390
  if (desc) {
392
- this.createCollider(collider, desc);
391
+ const col = this.createCollider(collider, desc);
392
+ col.setMassProperties(1, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 });
393
+ // rb?.setTranslation({ x: 0, y: 2, z: 0 });
393
394
  // col.setTranslationWrtParent(new Vector3(0,2,0));
394
395
 
395
396
  }
@@ -593,7 +594,7 @@ export class Physics {
593
594
  rigidbody.enableCcd(rb.collisionDetectionMode !== CollisionDetectionMode.Discrete);
594
595
  rigidbody.setLinearDamping(rb.drag);
595
596
  rigidbody.setAngularDamping(rb.angularDrag);
596
- rigidbody.setGravityScale(rb.useGravity ? 1 : 0, true);
597
+ rigidbody.setGravityScale(rb.useGravity ? rb.gravityScale : 0, true);
597
598
 
598
599
  // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
599
600
  // rigidbody.setAdditionalMass(rb.mass, true);
@@ -807,7 +808,7 @@ export class Physics {
807
808
  }
808
809
 
809
810
 
810
-
811
+ /** The joint prevents any relative movement between two rigid-bodies, except for relative rotations along one axis. This is typically used to simulate wheels, fans, etc. They are characterized by one local anchor as well as one local axis on each rigid-body. */
811
812
  addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: { x: number, y: number, z: number }, axis: { x: number, y: number, z: number }) {
812
813
  if (!this.world) {
813
814
  console.error("Physics world not initialized");
@@ -5,7 +5,7 @@ import { Context } from "./engine_setup";
5
5
  import { isPersistentAsset } from "./extensions/NEEDLE_persistent_assets";
6
6
  import { IComponent, SourceIdentifier } from "./engine_types";
7
7
  import { debugExtension } from "../engine/engine_default_parameters";
8
- import { LogType, showBalloonMessage } from "./debug/debug";
8
+ import { LogType, showBalloonMessage, showBalloonWarning } from "./debug/debug";
9
9
  import { isLocalNetwork } from "./engine_networking_utils";
10
10
  import { $BuiltInTypeFlag } from "./engine_typestore";
11
11
 
@@ -357,7 +357,7 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
357
357
  const blockChecks = getParam("noerrors");
358
358
  function checkObjectAssignments(obj: any, serializedData: any, implementationInformation?: ImplementationInformation) {
359
359
  if (blockChecks) return;
360
- if(!serializedData) return;
360
+ if (!serializedData) return;
361
361
  if (isLocalNetwork() === false) return;
362
362
  if (!obj) return;
363
363
 
@@ -371,7 +371,17 @@ function checkObjectAssignments(obj: any, serializedData: any, implementationInf
371
371
  if (key === "sourceId") continue;
372
372
  const value = obj[key];
373
373
  const serialized = serializedData[key];
374
+ // check if the field is defined in the class
374
375
  if (implementationInformation?.getDefinedKey(typeName, key) === false) {
376
+
377
+ // if the field is defined but the defined key is uppercase we need to show a warning
378
+ // because all fields are serialized in lowercase
379
+ const firstCharUppercase = key.charAt(0).toUpperCase() + key.slice(1);
380
+ if (implementationInformation.getDefinedKey(typeName, firstCharUppercase)) {
381
+ showBalloonWarning("<strong>Please rename</strong> \"" + firstCharUppercase + "\" to \"" + key + "\" in " + typeName);
382
+ console.warn("Please use lowercase for field: \"" + firstCharUppercase + "\" in " + typeName, serialized, obj);
383
+ }
384
+
375
385
  continue;
376
386
  }
377
387
  if (serialized === undefined || serialized === null) continue;
@@ -385,14 +395,16 @@ function checkObjectAssignments(obj: any, serializedData: any, implementationInf
385
395
  if (!hasOtherKeys) {
386
396
  showBalloonMessage(`<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(Object3D)\n${key}? : Object3D;\n\nin script ${typeName}.ts\n<a href="https://docs.needle.tools/serializable" target="_blank">documentation</a>`, LogType.Warn);
387
397
  console.warn(typeName, key, obj[key], obj);
398
+ continue;
388
399
  }
389
400
  }
390
401
  }
391
402
  }
392
- else if (typeof value === "string") {
403
+ if (typeof value === "string") {
393
404
  if (serialized.endsWith(".gltf") || serialized.endsWith(".glb")) {
394
405
  showBalloonMessage(`<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(AssetReference)\n${key}? : AssetReference;\n\nin script ${typeName}.ts\n<a href="https://docs.needle.tools/serializable" target="_blank">documentation</a>`, LogType.Warn);
395
406
  console.warn(typeName, key, obj[key], obj);
407
+ continue;
396
408
  }
397
409
  }
398
410
  }
@@ -421,16 +433,16 @@ function implictlyAssignPrimitiveTypes(obj: any, serializedData: any) {
421
433
  }
422
434
  }
423
435
  }
436
+ }
424
437
 
425
- function isPrimitiveType(val): boolean {
426
- switch (typeof val) {
427
- case "number":
428
- case "string":
429
- case "boolean":
430
- return true;
431
- }
432
- return false;
438
+ function isPrimitiveType(val): boolean {
439
+ switch (typeof val) {
440
+ case "number":
441
+ case "string":
442
+ case "boolean":
443
+ return true;
433
444
  }
445
+ return false;
434
446
  }
435
447
 
436
448
  // this is a wrapper for the cached serializer
@@ -459,7 +471,7 @@ function deserializeObjectWithType(data: any, typeOrConstructor: Constructor<any
459
471
  }
460
472
  }
461
473
  context.type = type;
462
-
474
+
463
475
  // e.g. when @serializable(Texture) and the texture is already resolved via json pointer from gltf
464
476
  // then we dont need to do anything else
465
477
  if (!typeIsFunction && currentValue instanceof type) return currentValue;
@@ -531,7 +543,7 @@ function deserializeObjectWithType(data: any, typeOrConstructor: Constructor<any
531
543
  }
532
544
  else {
533
545
  // happens when exporting e.g. Animation component with only clip assigned (clips array is marked as serialized but it might be undefined if no clips are assigned in e.g. blender)
534
- if(data === undefined) return undefined;
546
+ if (data === undefined) return undefined;
535
547
  // the fallback - this assumes that the type has a constructor that accepts the serialized arguments
536
548
  // made originally with THREE.Vector3 in mind but SHOULD actually not be used/called anymore
537
549
  instance = new type(...setBuffer(data));
@@ -26,6 +26,7 @@ import { PlayerViewManager } from './engine_playerview';
26
26
 
27
27
  import { ICamera, IComponent, ILight } from "./engine_types"
28
28
  import { destroy, foreachComponent } from './engine_gameobject';
29
+ import { createCameraWithOrbitControl } from '../engine-components/CameraUtils';
29
30
 
30
31
 
31
32
  const debug = utils.getParam("debugSetup");
@@ -147,12 +148,12 @@ export class Context {
147
148
 
148
149
  get domWidth(): number {
149
150
  // for mozilla XR
150
- if(this.isInAR) return window.innerWidth;
151
+ if (this.isInAR) return window.innerWidth;
151
152
  return this.domElement.clientWidth;
152
153
  }
153
154
  get domHeight(): number {
154
155
  // for mozilla XR
155
- if(this.isInAR) return window.innerHeight;
156
+ if (this.isInAR) return window.innerHeight;
156
157
  return this.domElement.clientHeight;
157
158
  }
158
159
  get domX(): number {
@@ -543,6 +544,8 @@ export class Context {
543
544
  foreachComponent(this.scene, comp => {
544
545
  const cam = comp as ICamera;
545
546
  if (cam?.isCamera) {
547
+ looputils.updateActiveInHierarchyWithoutEventCall(cam.gameObject);
548
+ if (!cam.activeAndEnabled) return undefined;
546
549
  if (cam.tag === "MainCamera") {
547
550
  camera = cam;
548
551
  return true;
@@ -554,8 +557,10 @@ export class Context {
554
557
  if (camera) {
555
558
  this.setCurrentCamera(camera);
556
559
  }
557
- else
560
+ else {
558
561
  console.error("MISSING camera", this);
562
+ createCameraWithOrbitControl(this.scene, "unknown");
563
+ }
559
564
  }
560
565
 
561
566
  Context._current = this;
@@ -738,7 +743,7 @@ export class Context {
738
743
  private onHandlePaused(): boolean {
739
744
  const paused = this.evaluatePaused();
740
745
  if (this._wasPaused !== paused) {
741
- if(debugActive) console.log("Paused?", paused, "context:" + this.alias);
746
+ if (debugActive) console.log("Paused?", paused, "context:" + this.alias);
742
747
  for (let i = 0; i < this.scripts_pausedChanged.length; i++) {
743
748
  const script = this.scripts_pausedChanged[i];
744
749
  if (!script.activeAndEnabled) continue;
@@ -1,9 +1,9 @@
1
1
  import { Clock } from 'three'
2
2
  import { getParam } from './engine_utils';
3
3
 
4
- const debug = getParam("debugtime");
4
+ const timescaleUrl = getParam("timescale");
5
5
  let timeScale = 1;
6
- if(typeof debug === "number") timeScale = debug;
6
+ if(typeof timescaleUrl === "number") timeScale = timescaleUrl;
7
7
 
8
8
  export class Time {
9
9
 
@@ -150,6 +150,7 @@ export declare interface IRigidbody extends IComponent {
150
150
  drag: number;
151
151
  angularDrag: number;
152
152
  useGravity: boolean;
153
+ gravityScale : number;
153
154
  collisionDetectionMode: CollisionDetectionMode;
154
155
 
155
156
  lockPositionX: boolean;
@@ -1,5 +1,5 @@
1
1
  import { ILightDataRegistry } from "../engine_lightdata";
2
- import { FloatType, HalfFloatType, sRGBEncoding, Texture } from "three";
2
+ import { FloatType, HalfFloatType, LinearEncoding, sRGBEncoding, Texture } from "three";
3
3
  import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader";
4
4
  import { SourceIdentifier } from "../engine_types";
5
5
  import { resolveReferences } from "./extension_utils";
@@ -70,6 +70,8 @@ export class NEEDLE_lightmaps implements GLTFLoaderPlugin {
70
70
  // TODO this is most likely wrong for floating point textures
71
71
  if (entry.type !== LightmapType.Lightmap)
72
72
  tex.encoding = sRGBEncoding;
73
+ else
74
+ tex.encoding = LinearEncoding;
73
75
 
74
76
  // not sure why, but seems EXR-loaded float textures need to be flipped
75
77
  if (entry.type === LightmapType.Skybox) {
@@ -1,16 +1,15 @@
1
- import { Behaviour, GameObject } from "./Component";
2
- import * as THREE from "three";
3
- // import { OrbitControls } from "./OrbitControls";
1
+ import { Behaviour } from "./Component";
4
2
  import { getParam } from "../engine/engine_utils";
5
3
  import { serializable } from "../engine/engine_serialization_decorator";
6
4
  import { RGBAColor } from "./js-extensions/RGBAColor";
7
- import { PerspectiveCamera, Ray } from "three";
8
5
  import { XRSessionMode } from "../engine/engine_setup";
9
6
  import { ICamera } from "../engine/engine_types"
10
7
  import { showBalloonMessage } from "../engine/debug/debug";
11
8
  import { getWorldPosition } from "../engine/engine_three_utils";
12
9
  import { Gizmos } from "../engine/engine_gizmos";
13
10
 
11
+ import { EquirectangularReflectionMapping, OrthographicCamera, PerspectiveCamera, Ray, sRGBEncoding, Vector3 } from "three";
12
+
14
13
  export enum ClearFlags {
15
14
  Skybox = 1,
16
15
  SolidColor = 2,
@@ -37,7 +36,7 @@ export class Camera extends Behaviour implements ICamera {
37
36
  const changed = this._fov != val;
38
37
  this._fov = val;
39
38
  if (changed && this._cam) {
40
- if (this._cam instanceof THREE.PerspectiveCamera) {
39
+ if (this._cam instanceof PerspectiveCamera) {
41
40
  this._cam.fov = this._fov;
42
41
  this._cam.updateProjectionMatrix();
43
42
  }
@@ -148,16 +147,14 @@ export class Camera extends Behaviour implements ICamera {
148
147
  private _clearFlags: ClearFlags = ClearFlags.SolidColor;
149
148
  private _skybox?: CameraSkybox;
150
149
 
151
-
152
150
  public get cam(): THREE.PerspectiveCamera | THREE.OrthographicCamera {
153
151
  if (this.activeAndEnabled)
154
152
  this.buildCamera();
155
153
  return this._cam!;
156
154
  }
157
155
 
158
-
159
- private static _origin: THREE.Vector3 = new THREE.Vector3();
160
- private static _direction: THREE.Vector3 = new THREE.Vector3();
156
+ private static _origin: THREE.Vector3 = new Vector3();
157
+ private static _direction: THREE.Vector3 = new Vector3();
161
158
  public screenPointToRay(x: number, y: number, ray?: Ray): Ray {
162
159
  let cam = this.cam;
163
160
  const origin = Camera._origin;
@@ -231,12 +228,14 @@ export class Camera extends Behaviour implements ICamera {
231
228
  }
232
229
  }
233
230
  else if (!this.orthographic) {
234
- cam = new THREE.PerspectiveCamera(this.fieldOfView, window.innerWidth / window.innerHeight, this._nearClipPlane, this._farClipPlane);
231
+ cam = new PerspectiveCamera(this.fieldOfView, window.innerWidth / window.innerHeight, this._nearClipPlane, this._farClipPlane);
235
232
  cam.fov = this.fieldOfView;
233
+ this.gameObject.add(cam);
236
234
  }
237
235
  else {
238
236
  const factor = this.orthographicSize * 100;
239
- cam = new THREE.OrthographicCamera(window.innerWidth / -factor, window.innerWidth / factor, window.innerHeight / factor, window.innerHeight / -factor, this._nearClipPlane, this._farClipPlane);
237
+ cam = new OrthographicCamera(window.innerWidth / -factor, window.innerWidth / factor, window.innerHeight / factor, window.innerHeight / -factor, this._nearClipPlane, this._farClipPlane);
238
+ this.gameObject.add(cam);
240
239
  }
241
240
  this._cam = cam;
242
241
 
@@ -312,7 +311,6 @@ export class Camera extends Behaviour implements ICamera {
312
311
  return transparent;
313
312
  }
314
313
 
315
-
316
314
  private enableSkybox() {
317
315
  if (!this._skybox)
318
316
  this._skybox = new CameraSkybox(this);
@@ -340,10 +338,9 @@ class CameraSkybox {
340
338
  else if(this.context.scene.background !== this._skybox) {
341
339
  if (debug)
342
340
  console.log("Set skybox", this._camera, this._skybox);
343
- this._skybox.encoding = THREE.sRGBEncoding;
344
- this._skybox.mapping = THREE.EquirectangularReflectionMapping;
341
+ this._skybox.encoding = sRGBEncoding;
342
+ this._skybox.mapping = EquirectangularReflectionMapping;
345
343
  this.context.scene.background = this._skybox;
346
344
  }
347
345
  }
348
-
349
346
  }
@@ -1,15 +1,25 @@
1
1
  import { OrbitControls } from "./OrbitControls";
2
2
  import { Camera } from "./Camera";
3
3
  import { addNewComponent } from "../engine/engine_components";
4
- import { Object3D, Scene } from "three";
4
+ import { Color, Object3D, Scene, Vector3 } from "three";
5
+ import { ICamera, SourceIdentifier } from "../engine/engine_types";
6
+ import { lookAtInverse } from "../engine/engine_three_utils";
7
+ import { RGBAColor } from "./js-extensions/RGBAColor";
5
8
 
6
- export function createCameraWithOrbitControl(scene: Scene): Camera {
7
- const srcId = "created/implictly";
9
+ export function createCameraWithOrbitControl(scene: Scene, source: SourceIdentifier): ICamera {
10
+ const srcId = source;
8
11
  const go = new Object3D();
9
12
  scene.add(go);
10
- const cam = addNewComponent(go, new Camera(), false);
13
+ const camInstance = new Camera();
14
+ const cam = addNewComponent(go, camInstance, true) as ICamera
11
15
  cam.sourceId = srcId;
12
- const orbit = addNewComponent(go, new OrbitControls(), false);
16
+ cam.clearFlags = 2;
17
+ cam.backgroundColor = new RGBAColor(0.5, 0.5, 0.5, 1);
18
+ const orbit = addNewComponent(go, new OrbitControls(), false) as OrbitControls;
13
19
  orbit.sourceId = srcId;
20
+ go.position.x = -2;
21
+ go.position.y = 2;
22
+ go.position.z = 2;
23
+ lookAtInverse(go, new Vector3(0, 0, 0));
14
24
  return cam as Camera;
15
25
  }
@@ -108,6 +108,7 @@ export class LODGroup extends Behaviour {
108
108
  this.gameObject.add(handler);
109
109
  }
110
110
  const empty = new THREE.Object3D();
111
+ empty.name = "Cull " + this.name;
111
112
  if (debug)
112
113
  console.log(renderers);
113
114
  for (let i = 0; i < renderers.length; i++) {
@@ -132,6 +133,10 @@ export class LODGroup extends Behaviour {
132
133
  const dist = lod.model.distance;
133
134
  lodDistanceDiff = dist - maxDistance;
134
135
  maxDistance = Math.max(dist, maxDistance);
136
+ if (object.type === "Group") {
137
+ console.warn("LODGroup: Group is not supported as LOD object", obj.name, object);
138
+ continue;
139
+ }
135
140
  this.onAddLodLevel(handler, object, dist);
136
141
  }
137
142
  const cullDistance = maxDistance + lodDistanceDiff;
@@ -155,6 +160,10 @@ export class LODGroup extends Behaviour {
155
160
  }
156
161
 
157
162
  private onAddLodLevel(lod: THREE.LOD, obj: THREE.Object3D, dist: number) {
163
+ if(obj === this.gameObject) {
164
+ console.warn("LODGroup component must be on parent object and not mesh directly at the moment", obj.name, obj)
165
+ return;
166
+ }
158
167
  lod.addLevel(obj, dist * this._distanceFactor);
159
168
  const setting = { lod: lod, levelIndex: lod.levels.length - 1, distance: dist };
160
169
  this._settings.push(setting)
@@ -1,14 +1,14 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
- // import { Camera } from "./Camera";
3
- import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls";
2
+ import { Camera } from "./Camera";
4
3
  import { LookAtConstraint } from "./LookAtConstraint";
5
- import * as THREE from "three";
6
- import { getWorldPosition, setWorldPosition, slerp } from "../engine/engine_three_utils";
4
+ import { getWorldPosition, slerp } from "../engine/engine_three_utils";
7
5
  import { RaycastOptions } from "../engine/engine_physics";
8
6
  import { serializable } from "../engine/engine_serialization_decorator";
9
- import { Camera } from "./Camera";
10
7
  import { getParam, isMobileDevice } from "../engine/engine_utils";
11
8
 
9
+ import { Box3, Object3D, PerspectiveCamera, Vector2, Vector3 } from "three";
10
+ import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls";
11
+
12
12
  const freeCam = getParam("freecam");
13
13
 
14
14
  const disabledKeys = { LEFT: "", UP: "", RIGHT: "", BOTTOM: "" };
@@ -19,7 +19,7 @@ export class OrbitControls extends Behaviour {
19
19
  return this._controls;
20
20
  }
21
21
 
22
- public get controllerObject(): THREE.Object3D | null {
22
+ public get controllerObject(): Object3D | null {
23
23
  return this._cameraObject;
24
24
  }
25
25
 
@@ -45,29 +45,27 @@ export class OrbitControls extends Behaviour {
45
45
  // remove once slerp works correctly
46
46
  useSlerp: boolean = true;
47
47
 
48
-
49
48
  debugLog: boolean = false;
50
49
  targetLerpSpeed = 5;
51
50
 
52
- private _lookTargetPosition!: THREE.Vector3;
51
+ private _lookTargetPosition!: Vector3;
53
52
  private _controls: ThreeOrbitControls | null = null;
54
- private _cameraObject: THREE.Object3D | null = null;
53
+ private _cameraObject: Object3D | null = null;
55
54
 
56
55
  private _lerpToTargetPosition: boolean = false;
57
56
  private _lerpCameraToTarget: boolean = false;
58
- private _cameraTargetPosition: THREE.Vector3 | null = null;
57
+ private _cameraTargetPosition: Vector3 | null = null;
59
58
 
60
59
  private _inputs: number = 0;
61
60
  private _enableTime: number = 0; // use to disable double click when double clicking on UI
62
61
  private _startedListeningToKeyEvents: boolean = false;
63
62
 
64
63
  awake(): void {
65
- this._lookTargetPosition = new THREE.Vector3();
64
+ this._lookTargetPosition = new Vector3();
66
65
  this._startedListeningToKeyEvents = false;
67
66
  }
68
67
 
69
68
  onEnable() {
70
-
71
69
  this._enableTime = this.context.time.time;
72
70
  const camGo = GameObject.getComponent(this.gameObject, Camera);
73
71
  const cam = camGo?.cam;
@@ -119,7 +117,6 @@ export class OrbitControls extends Behaviour {
119
117
  }
120
118
  }
121
119
 
122
-
123
120
  onDisable() {
124
121
  if (this._controls) {
125
122
  this._controls.enabled = false;
@@ -138,7 +135,7 @@ export class OrbitControls extends Behaviour {
138
135
  if (camGo && !this.setFromTargetPosition()) {
139
136
  if (this.debugLog)
140
137
  console.log("NO TARGET");
141
- const forward = new THREE.Vector3(0, 0, -1).applyMatrix4(camGo.cam.matrixWorld);
138
+ const forward = new Vector3(0, 0, -1).applyMatrix4(camGo.cam.matrixWorld);
142
139
  this.setTarget(forward, true);
143
140
  }
144
141
  }
@@ -151,7 +148,7 @@ export class OrbitControls extends Behaviour {
151
148
  if (!this.setFromTargetPosition()) {
152
149
  const opts = new RaycastOptions();
153
150
  // center of the screen:
154
- opts.screenPoint = new THREE.Vector2(0, 0);
151
+ opts.screenPoint = new Vector2(0, 0);
155
152
  opts.lineThreshold = 0.1;
156
153
  const hits = this.context.physics.raycast(opts);
157
154
  if (hits.length > 0) {
@@ -229,7 +226,7 @@ export class OrbitControls extends Behaviour {
229
226
  }
230
227
  }
231
228
 
232
- public setCameraTarget(position?: THREE.Vector3 | null, immediate: boolean = false) {
229
+ public setCameraTarget(position?: Vector3 | null, immediate: boolean = false) {
233
230
  if (!position) this._lerpCameraToTarget = false;
234
231
  else {
235
232
  this._lerpCameraToTarget = true;
@@ -254,7 +251,7 @@ export class OrbitControls extends Behaviour {
254
251
  return false;
255
252
  }
256
253
 
257
- public setTarget(position: THREE.Vector3 | null = null, immediate: boolean = false) {
254
+ public setTarget(position: Vector3 | null = null, immediate: boolean = false) {
258
255
  if (!this._controls) return;
259
256
  if (position !== null) this._lookTargetPosition.copy(position);
260
257
  if (immediate)
@@ -262,12 +259,12 @@ export class OrbitControls extends Behaviour {
262
259
  else this._lerpToTargetPosition = true;
263
260
  }
264
261
 
265
- public lerpTarget(position: THREE.Vector3, delta: number) {
262
+ public lerpTarget(position: Vector3, delta: number) {
266
263
  if (!this._controls) return;
267
264
  this._controls.target.lerp(position, delta);
268
265
  }
269
266
 
270
- public distanceToTarget(position: THREE.Vector3): number {
267
+ public distanceToTarget(position: Vector3): number {
271
268
  if (!this._controls) return -1;
272
269
  return this._controls.target.distanceTo(position);
273
270
  }
@@ -296,6 +293,48 @@ export class OrbitControls extends Behaviour {
296
293
  }
297
294
  }
298
295
 
296
+ // Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
297
+ // Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
298
+ fitCameraToObjects(objects: Array<Object3D>, fitOffset: number = 1.5) {
299
+ const camera = this._cameraObject as PerspectiveCamera;
300
+ const controls = this._controls as ThreeOrbitControls | null;
301
+
302
+ if (!camera || !controls) return;
303
+
304
+ const size = new Vector3();
305
+ const center = new Vector3();
306
+ const box = new Box3();
307
+
308
+ box.makeEmpty();
309
+ for (const object of objects)
310
+ box.expandByObject(object);
311
+
312
+ box.getSize( size );
313
+ box.getCenter( center );
314
+
315
+ const maxSize = Math.max(size.x, size.y, size.z);
316
+ const fitHeightDistance = maxSize / (2 * Math.atan( Math.PI * camera.fov / 360 ));
317
+ const fitWidthDistance = fitHeightDistance / camera.aspect;
318
+ const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
319
+
320
+ const direction = controls.target.clone()
321
+ .sub(camera.position)
322
+ .normalize()
323
+ .multiplyScalar(distance);
324
+
325
+ controls.maxDistance = distance * 10;
326
+ controls.minDistance = distance * 0.01;
327
+ controls.target.copy(center);
328
+
329
+ camera.near = distance / 100;
330
+ camera.far = distance * 100;
331
+ camera.updateProjectionMatrix();
332
+
333
+ camera.position.copy(controls.target).sub(direction);
334
+
335
+ controls.update();
336
+ }
337
+
299
338
  // private onPositionDrag(){
300
339
 
301
340
  // }
@@ -153,6 +153,7 @@ class ParticleSystemEmissionOverTime extends BaseValueGenerator {
153
153
  }
154
154
 
155
155
  genValue(): number {
156
+ if (!this.system.emission.enabled) return 0;
156
157
  if (this.system.currentParticles >= this.system.maxParticles) return 0;
157
158
  // emission over time
158
159
  let emission = this.system.emission.rateOverTime.evaluate(this.system.time / this.system.duration, Math.random());
@@ -225,9 +226,15 @@ class TextureSheetAnimationBehaviour extends ParticleSystemBaseBehaviour {
225
226
 
226
227
  }
227
228
 
229
+ const $particleRotation = Symbol("particleRotation")
230
+
228
231
  class RotationBehaviour extends ParticleSystemBaseBehaviour {
229
232
  type: string = "NeedleRotation"
230
233
 
234
+ initialize(particle: Particle) {
235
+ particle[$particleRotation] = Math.random();
236
+ }
237
+
231
238
  update(particle: Particle, delta: number) {
232
239
  if (particle.rotation === undefined) return;
233
240
 
@@ -235,7 +242,7 @@ class RotationBehaviour extends ParticleSystemBaseBehaviour {
235
242
 
236
243
  if (typeof particle.rotation === "number") {
237
244
  if (this.system.rotationOverLifetime.enabled) {
238
- particle.rotation += this.system.rotationOverLifetime.evaluate(t) * delta;
245
+ particle.rotation += this.system.rotationOverLifetime.evaluate(t, particle[$particleRotation]) * delta;
239
246
  }
240
247
  else {
241
248
  if (this.system.renderer.renderMode === ParticleSystemRenderMode.Billboard)
@@ -777,7 +784,7 @@ export class ParticleSystem extends Behaviour implements IParticleSystem {
777
784
  if (this._time > this.duration) this._time = 0;
778
785
  }
779
786
 
780
- private onUpdate(){
787
+ private onUpdate() {
781
788
  if (this._bursts) {
782
789
  this.emission.bursts = this._bursts;
783
790
  delete this._bursts;
@@ -1117,10 +1117,10 @@ export class RotationOverLifetimeModule {
1117
1117
  @serializable()
1118
1118
  zMultiplier!: number;
1119
1119
 
1120
- evaluate(t01: number): number {
1120
+ evaluate(t01: number, t:number): number {
1121
1121
  if (!this.enabled) return 0;
1122
1122
  if (!this.separateAxes) {
1123
- const rot = this.z.evaluate(t01) * -1;
1123
+ const rot = this.z.evaluate(t01, t) * -1;
1124
1124
  return rot;
1125
1125
  }
1126
1126
  return 0;