@needle-tools/engine 2.54.2-pre → 2.55.1-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 (94) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/needle-engine.d.ts +39 -14
  3. package/dist/needle-engine.js +355 -355
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +35 -35
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/dist/needle-engine.tsbuildinfo +1 -1
  8. package/lib/engine/engine_lightdata.js +1 -1
  9. package/lib/engine/engine_lightdata.js.map +1 -1
  10. package/lib/engine/engine_mainloop_utils.js +8 -0
  11. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  12. package/lib/engine/engine_physics.d.ts +1 -1
  13. package/lib/engine/engine_physics.js +44 -3
  14. package/lib/engine/engine_physics.js.map +1 -1
  15. package/lib/engine/engine_physics.types.d.ts +13 -0
  16. package/lib/engine/engine_physics.types.js +7 -0
  17. package/lib/engine/engine_physics.types.js.map +1 -1
  18. package/lib/engine/engine_serialization_core.js +20 -10
  19. package/lib/engine/engine_serialization_core.js.map +1 -1
  20. package/lib/engine/engine_setup.js +2 -2
  21. package/lib/engine/engine_setup.js.map +1 -1
  22. package/lib/engine/engine_time.d.ts +1 -0
  23. package/lib/engine/engine_time.js +7 -0
  24. package/lib/engine/engine_time.js.map +1 -1
  25. package/lib/engine/engine_types.d.ts +4 -1
  26. package/lib/engine/engine_types.js.map +1 -1
  27. package/lib/engine/engine_utils.d.ts +1 -0
  28. package/lib/engine/engine_utils.js +3 -0
  29. package/lib/engine/engine_utils.js.map +1 -1
  30. package/lib/engine/extensions/NEEDLE_lightmaps.js +3 -1
  31. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  32. package/lib/engine-components/Camera.d.ts +1 -2
  33. package/lib/engine-components/Camera.js +8 -10
  34. package/lib/engine-components/Camera.js.map +1 -1
  35. package/lib/engine-components/Collider.d.ts +2 -0
  36. package/lib/engine-components/Collider.js +4 -0
  37. package/lib/engine-components/Collider.js.map +1 -1
  38. package/lib/engine-components/LODGroup.js +9 -0
  39. package/lib/engine-components/LODGroup.js.map +1 -1
  40. package/lib/engine-components/OrbitControls.d.ts +8 -7
  41. package/lib/engine-components/OrbitControls.js +38 -7
  42. package/lib/engine-components/OrbitControls.js.map +1 -1
  43. package/lib/engine-components/ParticleSystem.d.ts +1 -0
  44. package/lib/engine-components/ParticleSystem.js +17 -6
  45. package/lib/engine-components/ParticleSystem.js.map +1 -1
  46. package/lib/engine-components/ParticleSystemModules.d.ts +2 -2
  47. package/lib/engine-components/ParticleSystemModules.js +27 -21
  48. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  49. package/lib/engine-components/Renderer.d.ts +1 -0
  50. package/lib/engine-components/Renderer.js +21 -2
  51. package/lib/engine-components/Renderer.js.map +1 -1
  52. package/lib/engine-components/RigidBody.js +1 -19
  53. package/lib/engine-components/RigidBody.js.map +1 -1
  54. package/lib/engine-components/SyncedTransform.js +1 -3
  55. package/lib/engine-components/SyncedTransform.js.map +1 -1
  56. package/lib/engine-components/VideoPlayer.d.ts +2 -1
  57. package/lib/engine-components/VideoPlayer.js +54 -51
  58. package/lib/engine-components/VideoPlayer.js.map +1 -1
  59. package/lib/engine-components/WebARSessionRoot.js +5 -0
  60. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  61. package/lib/engine-components/WebXR.js +13 -13
  62. package/lib/engine-components/WebXR.js.map +1 -1
  63. package/lib/engine-components/WebXRController.js +1 -2
  64. package/lib/engine-components/WebXRController.js.map +1 -1
  65. package/lib/engine-components/js-extensions/RGBAColor.d.ts +2 -0
  66. package/lib/engine-components/js-extensions/RGBAColor.js +2 -0
  67. package/lib/engine-components/js-extensions/RGBAColor.js.map +1 -1
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +1 -1
  70. package/src/engine/codegen/register_types.js +2 -2
  71. package/src/engine/engine_lightdata.ts +1 -1
  72. package/src/engine/engine_mainloop_utils.ts +6 -0
  73. package/src/engine/engine_physics.ts +47 -5
  74. package/src/engine/engine_physics.types.ts +17 -0
  75. package/src/engine/engine_serialization_core.ts +25 -13
  76. package/src/engine/engine_setup.ts +2 -2
  77. package/src/engine/engine_time.ts +7 -1
  78. package/src/engine/engine_types.ts +5 -2
  79. package/src/engine/engine_utils.ts +4 -0
  80. package/src/engine/extensions/NEEDLE_lightmaps.ts +3 -1
  81. package/src/engine-components/Camera.ts +10 -15
  82. package/src/engine-components/Collider.ts +3 -0
  83. package/src/engine-components/LODGroup.ts +9 -0
  84. package/src/engine-components/OrbitControls.ts +58 -19
  85. package/src/engine-components/ParticleSystem.ts +18 -6
  86. package/src/engine-components/ParticleSystemModules.ts +29 -22
  87. package/src/engine-components/Renderer.ts +25 -2
  88. package/src/engine-components/RigidBody.ts +1 -20
  89. package/src/engine-components/SyncedTransform.ts +1 -3
  90. package/src/engine-components/VideoPlayer.ts +55 -51
  91. package/src/engine-components/WebARSessionRoot.ts +5 -0
  92. package/src/engine-components/WebXR.ts +15 -13
  93. package/src/engine-components/WebXRController.ts +1 -2
  94. 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.54.2-pre",
3
+ "version": "2.55.1-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,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components.ts";
5
5
  import { AlignmentConstraint } from "../../engine-components/AlignmentConstraint.ts";
@@ -172,7 +172,7 @@ import { XRGrabModel } from "../../engine-components/WebXRGrabRendering.ts";
172
172
  import { XRGrabRendering } from "../../engine-components/WebXRGrabRendering.ts";
173
173
  import { XRRig } from "../../engine-components/WebXRRig.ts";
174
174
  import { XRState } from "../../engine-components/XRFlag.ts";
175
-
175
+
176
176
  // Register types
177
177
  TypeStore.add("__Ignore", __Ignore);
178
178
  TypeStore.add("AlignmentConstraint", AlignmentConstraint);
@@ -33,7 +33,7 @@ export class LightDataRegistry implements ILightDataRegistry {
33
33
  }
34
34
 
35
35
  registerTexture(sourceId: SourceIdentifier, type: LightmapType, tex: Texture, index: number) {
36
- if (debugLightmap) console.log("Registering lightmap", sourceId, LightmapType[type], tex);
36
+ if (debugLightmap) console.log("Registering ", LightmapType[type], tex, sourceId);
37
37
  if (!this._lightmaps.has(sourceId))
38
38
  this._lightmaps.set(sourceId, new Map());
39
39
  const map = this._lightmaps.get(sourceId);
@@ -68,6 +68,12 @@ export function processNewScripts(context: Context) {
68
68
  new_scripts_buffer.splice(i, 1);
69
69
  i--; continue;
70
70
  }
71
+ if (script.registering) {
72
+ try {
73
+ script.registering();
74
+ }
75
+ catch (err) { console.error(err); }
76
+ }
71
77
  // console.log(script, script.gameObject)
72
78
  // TODO: we should not call awake on components with inactive gameobjects
73
79
  if (script.__internalAwake !== undefined) {
@@ -15,9 +15,10 @@ import {
15
15
  import { InstancingUtil } from './engine_instancing';
16
16
  import { foreachComponent } from './engine_gameobject';
17
17
 
18
- import RAPIER, { ActiveEvents, Collider, ColliderDesc, EventQueue, JointData, RigidBody, RigidBodyType, World } from '@dimforge/rapier3d-compat';
19
- import { CollisionDetectionMode } from '../engine/engine_physics.types';
18
+ import RAPIER, { ActiveEvents, CoefficientCombineRule, Collider, ColliderDesc, EventQueue, JointData, RigidBody, RigidBodyType, World } from '@dimforge/rapier3d-compat';
19
+ import { CollisionDetectionMode, PhysicsMaterialCombine } from '../engine/engine_physics.types';
20
20
  import { Gizmos } from './engine_gizmos';
21
+ import { Mathf } from './engine_math';
21
22
  export type Rapier = typeof RAPIER;
22
23
 
23
24
 
@@ -354,7 +355,7 @@ export class Physics {
354
355
  this.createCollider(collider, desc, center);
355
356
  }
356
357
 
357
- addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale:Vector3) {
358
+ addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3) {
358
359
  const geo = mesh.geometry;
359
360
  if (!geo) {
360
361
  if (debugPhysics) console.warn("Missing mesh geometry", mesh.name);
@@ -405,7 +406,7 @@ export class Physics {
405
406
  matrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
406
407
  getWorldScale(collider.gameObject, this._tempScale);
407
408
  if (center) {
408
- center.multiply( this._tempScale);
409
+ center.multiply(this._tempScale);
409
410
  this._tempPosition.x -= center.x;
410
411
  this._tempPosition.y += center.y;
411
412
  this._tempPosition.z += center.z;
@@ -414,6 +415,42 @@ export class Physics {
414
415
  desc.setRotation(this._tempQuaternion);
415
416
  desc.setSensor(collider.isTrigger);
416
417
 
418
+ // TODO: we might want to update this if the material changes
419
+ const physicsMaterial = collider.sharedMaterial;
420
+ if (physicsMaterial) {
421
+ CoefficientCombineRule
422
+ desc.setRestitution(physicsMaterial.bounciness);
423
+ switch (physicsMaterial.bounceCombine) {
424
+ case PhysicsMaterialCombine.Average:
425
+ desc.setRestitutionCombineRule(CoefficientCombineRule.Average);
426
+ break;
427
+ case PhysicsMaterialCombine.Maximum:
428
+ desc.setRestitutionCombineRule(CoefficientCombineRule.Max);
429
+ break;
430
+ case PhysicsMaterialCombine.Minimum:
431
+ desc.setRestitutionCombineRule(CoefficientCombineRule.Min);
432
+ break;
433
+ case PhysicsMaterialCombine.Multiply:
434
+ desc.setRestitutionCombineRule(CoefficientCombineRule.Multiply);
435
+ break;
436
+ }
437
+ desc.setFriction(physicsMaterial.dynamicFriction);
438
+ switch (physicsMaterial.frictionCombine) {
439
+ case PhysicsMaterialCombine.Average:
440
+ desc.setFrictionCombineRule(CoefficientCombineRule.Average);
441
+ break;
442
+ case PhysicsMaterialCombine.Maximum:
443
+ desc.setFrictionCombineRule(CoefficientCombineRule.Max);
444
+ break;
445
+ case PhysicsMaterialCombine.Minimum:
446
+ desc.setFrictionCombineRule(CoefficientCombineRule.Min);
447
+ break;
448
+ case PhysicsMaterialCombine.Multiply:
449
+ desc.setFrictionCombineRule(CoefficientCombineRule.Multiply);
450
+ break;
451
+ }
452
+ }
453
+
417
454
  // if we want to use explicit mass properties, we need to set the collider density to 0
418
455
  // otherwise rapier will compute the mass properties based on the collider shape and density
419
456
  // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
@@ -583,12 +620,17 @@ export class Physics {
583
620
  // private _lastStepTime: number | undefined = 0;
584
621
  private lines?: LineSegments;
585
622
 
586
- public step(_deltaTime?: number) {
623
+ public step(dt?: number) {
587
624
  if (!this.world) return;
588
625
  this._isUpdatingPhysicsWorld = true;
589
626
  if (!this.eventQueue) {
590
627
  this.eventQueue = new EventQueue(false);
591
628
  }
629
+ if (dt) {
630
+ // if we make to sudden changes to the timestep the physics can get unstable
631
+ // https://rapier.rs/docs/user_guides/javascript/integration_parameters/#dt
632
+ this.world.timestep = Mathf.lerp(this.world.timestep, dt, 0.8);
633
+ }
592
634
  this.world.step(this.eventQueue);
593
635
  this._isUpdatingPhysicsWorld = false;
594
636
  this.updateDebugRendering(this.world);
@@ -1,5 +1,22 @@
1
1
 
2
2
 
3
+
4
+ export enum PhysicsMaterialCombine
5
+ {
6
+ Average = 0,
7
+ Multiply = 1,
8
+ Minimum = 2,
9
+ Maximum = 3,
10
+ }
11
+
12
+ export type PhysicsMaterial = {
13
+ bounceCombine: PhysicsMaterialCombine;
14
+ bounciness: number;
15
+ frictionCombine: PhysicsMaterialCombine;
16
+ dynamicFriction: number;
17
+ staticFriction: number;
18
+ }
19
+
3
20
  export enum CollisionDetectionMode {
4
21
  Discrete = 0,
5
22
  Continuous = 1,
@@ -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));
@@ -315,8 +315,8 @@ export class Context {
315
315
  this.renderer.domElement.style.width = "100%";
316
316
  this.renderer.domElement.style.height = "100%";
317
317
  if (this.composer) {
318
- this.composer.setSize(width, height);
319
- this.composer.setPixelRatio(window.devicePixelRatio);
318
+ this.composer.setSize?.call(this.composer, width, height);
319
+ this.composer.setPixelRatio?.call(this.composer, window.devicePixelRatio);
320
320
  }
321
321
  }
322
322
  }
@@ -1,10 +1,15 @@
1
-
2
1
  import { Clock } from 'three'
2
+ import { getParam } from './engine_utils';
3
+
4
+ const debug = getParam("debugtime");
5
+ let timeScale = 1;
6
+ if(typeof debug === "number") timeScale = debug;
3
7
 
4
8
  export class Time {
5
9
 
6
10
  deltaTime = 0;
7
11
  time = 0;
12
+ timeScale = 1;
8
13
 
9
14
  /** same as frameCount */
10
15
  frame = 0;
@@ -27,6 +32,7 @@ export class Time {
27
32
  this.deltaTime = this.clock.getDelta();
28
33
  // clamp delta time because if tab is not active clock.getDelta can get pretty big
29
34
  this.deltaTime = Math.min(.1, this.deltaTime);
35
+ this.deltaTime *= timeScale * this.timeScale;
30
36
  if(this.deltaTime <= 0) this.deltaTime = 0.000000000001;
31
37
  this.frame += 1;
32
38
  this.time += this.deltaTime;
@@ -1,6 +1,6 @@
1
1
  import { Camera, Color, Material, Object3D, Vector3, Quaternion, Ray } from "three";
2
2
  import { RGBAColor } from "../engine-components/js-extensions/RGBAColor";
3
- import { CollisionDetectionMode, RigidbodyConstraints } from "./engine_physics.types";
3
+ import { CollisionDetectionMode, PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types";
4
4
  import { getWorldPosition } from "./engine_three_utils";
5
5
  import { CircularBuffer } from "./engine_utils";
6
6
 
@@ -67,6 +67,8 @@ export interface IComponent {
67
67
  __internalDestroy();
68
68
  resolveGuids?(guidsMap: GuidsMap): void;
69
69
 
70
+ /** experimental, called when the script is registered for the first time, this is called even if the component is not enabled. */
71
+ registering?();
70
72
  awake();
71
73
  onEnable();
72
74
  onDisable();
@@ -78,7 +80,7 @@ export interface IComponent {
78
80
  /** called when this.context.isPaused changes or when rendering loop changes due to changing DOM element visibility
79
81
  * e.g. when the DOM element becomes hidden or out ot view
80
82
  */
81
- onPausedChanged?(isPaused:boolean, wasPaused:boolean);
83
+ onPausedChanged?(isPaused: boolean, wasPaused: boolean);
82
84
 
83
85
  start?(): void;
84
86
  earlyUpdate?(): void;
@@ -138,6 +140,7 @@ export declare interface ICollider extends IComponent {
138
140
  get isCollider();
139
141
  attachedRigidbody: IRigidbody | null;
140
142
  isTrigger: boolean;
143
+ sharedMaterial?: PhysicsMaterial;
141
144
  }
142
145
 
143
146
  export declare interface IRigidbody extends IComponent {
@@ -385,4 +385,8 @@ export function isiOS() {
385
385
 
386
386
  export function isSafari() {
387
387
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
388
+ }
389
+
390
+ export function isQuest() {
391
+ return navigator.userAgent.includes("OculusBrowser");
388
392
  }
@@ -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,12 @@ 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;
236
233
  }
237
234
  else {
238
235
  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);
236
+ cam = new OrthographicCamera(window.innerWidth / -factor, window.innerWidth / factor, window.innerHeight / factor, window.innerHeight / -factor, this._nearClipPlane, this._farClipPlane);
240
237
  }
241
238
  this._cam = cam;
242
239
 
@@ -312,7 +309,6 @@ export class Camera extends Behaviour implements ICamera {
312
309
  return transparent;
313
310
  }
314
311
 
315
-
316
312
  private enableSkybox() {
317
313
  if (!this._skybox)
318
314
  this._skybox = new CameraSkybox(this);
@@ -340,10 +336,9 @@ class CameraSkybox {
340
336
  else if(this.context.scene.background !== this._skybox) {
341
337
  if (debug)
342
338
  console.log("Set skybox", this._camera, this._skybox);
343
- this._skybox.encoding = THREE.sRGBEncoding;
344
- this._skybox.mapping = THREE.EquirectangularReflectionMapping;
339
+ this._skybox.encoding = sRGBEncoding;
340
+ this._skybox.mapping = EquirectangularReflectionMapping;
345
341
  this.context.scene.background = this._skybox;
346
342
  }
347
343
  }
348
-
349
344
  }
@@ -5,6 +5,7 @@ import { Event, Mesh, Object3D, Vector3 } from "three"
5
5
  // import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics";
6
6
  import { ICollider } from "../engine/engine_types";
7
7
  import { getWorldScale } from "../engine/engine_three_utils";
8
+ import { PhysicsMaterial } from "../engine/engine_physics.types";
8
9
 
9
10
 
10
11
  export class Collider extends Behaviour implements ICollider {
@@ -18,6 +19,8 @@ export class Collider extends Behaviour implements ICollider {
18
19
  @serializable()
19
20
  isTrigger: boolean = false;
20
21
 
22
+ @serializable()
23
+ sharedMaterial?: PhysicsMaterial;
21
24
 
22
25
  awake() {
23
26
  super.awake();
@@ -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
  // }