@needle-tools/engine 2.37.0-pre → 2.38.0-pre.1

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 (101) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/needle-engine.d.ts +118 -19
  3. package/dist/needle-engine.js +352 -352
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +25 -25
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine_addressables.d.ts +3 -1
  8. package/lib/engine/engine_addressables.js +12 -5
  9. package/lib/engine/engine_addressables.js.map +1 -1
  10. package/lib/engine/engine_element.js +3 -2
  11. package/lib/engine/engine_element.js.map +1 -1
  12. package/lib/engine/engine_element_overlay.js +4 -3
  13. package/lib/engine/engine_element_overlay.js.map +1 -1
  14. package/lib/engine/engine_input.d.ts +2 -0
  15. package/lib/engine/engine_input.js +14 -3
  16. package/lib/engine/engine_input.js.map +1 -1
  17. package/lib/engine/engine_physics.d.ts +11 -4
  18. package/lib/engine/engine_physics.js +107 -42
  19. package/lib/engine/engine_physics.js.map +1 -1
  20. package/lib/engine/engine_physics.types.d.ts +7 -0
  21. package/lib/engine/engine_physics.types.js +8 -0
  22. package/lib/engine/engine_physics.types.js.map +1 -1
  23. package/lib/engine/engine_setup.d.ts +12 -6
  24. package/lib/engine/engine_setup.js +30 -22
  25. package/lib/engine/engine_setup.js.map +1 -1
  26. package/lib/engine/engine_types.d.ts +3 -1
  27. package/lib/engine/engine_types.js.map +1 -1
  28. package/lib/engine-components/Animation.d.ts +1 -0
  29. package/lib/engine-components/Animation.js +7 -0
  30. package/lib/engine-components/Animation.js.map +1 -1
  31. package/lib/engine-components/AnimatorController.js +14 -7
  32. package/lib/engine-components/AnimatorController.js.map +1 -1
  33. package/lib/engine-components/Camera.d.ts +2 -0
  34. package/lib/engine-components/Camera.js +24 -6
  35. package/lib/engine-components/Camera.js.map +1 -1
  36. package/lib/engine-components/CharacterController.d.ts +34 -0
  37. package/lib/engine-components/CharacterController.js +179 -0
  38. package/lib/engine-components/CharacterController.js.map +1 -0
  39. package/lib/engine-components/Collider.d.ts +10 -5
  40. package/lib/engine-components/Collider.js +18 -11
  41. package/lib/engine-components/Collider.js.map +1 -1
  42. package/lib/engine-components/Joints.d.ts +15 -0
  43. package/lib/engine-components/Joints.js +42 -0
  44. package/lib/engine-components/Joints.js.map +1 -0
  45. package/lib/engine-components/Light.d.ts +2 -0
  46. package/lib/engine-components/Light.js +13 -2
  47. package/lib/engine-components/Light.js.map +1 -1
  48. package/lib/engine-components/OrbitControls.js +1 -1
  49. package/lib/engine-components/OrbitControls.js.map +1 -1
  50. package/lib/engine-components/Renderer.js +4 -0
  51. package/lib/engine-components/Renderer.js.map +1 -1
  52. package/lib/engine-components/RigidBody.d.ts +6 -1
  53. package/lib/engine-components/RigidBody.js +62 -25
  54. package/lib/engine-components/RigidBody.js.map +1 -1
  55. package/lib/engine-components/SmoothFollow.d.ts +2 -1
  56. package/lib/engine-components/SmoothFollow.js +25 -17
  57. package/lib/engine-components/SmoothFollow.js.map +1 -1
  58. package/lib/engine-components/WebXR.js +3 -4
  59. package/lib/engine-components/WebXR.js.map +1 -1
  60. package/lib/engine-components/codegen/components.d.ts +4 -0
  61. package/lib/engine-components/codegen/components.js +4 -0
  62. package/lib/engine-components/codegen/components.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/engine/codegen/register_types.js +16 -0
  65. package/src/engine/dist/engine_physics.js +739 -0
  66. package/src/engine/dist/engine_setup.js +777 -0
  67. package/src/engine/engine_addressables.ts +18 -8
  68. package/src/engine/engine_element.ts +3 -2
  69. package/src/engine/engine_element_overlay.ts +4 -3
  70. package/src/engine/engine_input.ts +12 -3
  71. package/src/engine/engine_physics.ts +119 -57
  72. package/src/engine/engine_physics.types.ts +9 -0
  73. package/src/engine/engine_setup.ts +53 -45
  74. package/src/engine/engine_types.ts +4 -1
  75. package/src/engine-components/Animation.ts +8 -0
  76. package/src/engine-components/AnimatorController.ts +16 -11
  77. package/src/engine-components/Camera.ts +25 -5
  78. package/src/engine-components/CharacterController.ts +185 -0
  79. package/src/engine-components/Collider.ts +21 -15
  80. package/src/engine-components/Joints.ts +40 -0
  81. package/src/engine-components/Light.ts +17 -3
  82. package/src/engine-components/OrbitControls.ts +1 -1
  83. package/src/engine-components/Renderer.ts +5 -1
  84. package/src/engine-components/RigidBody.ts +63 -29
  85. package/src/engine-components/SmoothFollow.ts +21 -18
  86. package/src/engine-components/WebXR.ts +3 -4
  87. package/src/engine-components/codegen/components.ts +4 -0
  88. package/src/engine-components/dist/CharacterController.js +123 -0
  89. package/src/engine-components/dist/RigidBody.js +458 -0
  90. package/src/include/console/ConsoleReroute.js +79 -0
  91. package/src/include/draco/draco_decoder.js +48 -0
  92. package/src/include/draco/draco_decoder.wasm +0 -0
  93. package/src/include/ktx2/basis_transcoder.js +21 -0
  94. package/src/include/ktx2/basis_transcoder.wasm +0 -0
  95. package/src/include/three/ARButton.js +208 -0
  96. package/src/include/three/DragControls.js +232 -0
  97. package/src/include/three/EXT_mesh_gpu_instancing_exporter.js +67 -0
  98. package/src/include/three/VRButton.js +196 -0
  99. package/src/include/three-mesh-ui-assets/backspace.png +0 -0
  100. package/src/include/three-mesh-ui-assets/enter.png +0 -0
  101. package/src/include/three-mesh-ui-assets/shift.png +0 -0
@@ -24,8 +24,8 @@ import { Application } from './engine_application';
24
24
  import { LightDataRegistry, ILightDataRegistry } from './engine_lightdata';
25
25
  import { PlayerViewManager } from './engine_playerview';
26
26
 
27
- import { ICamera as Camera, IComponent, ILight as Light } from "./engine_types"
28
- import { destroy } from './engine_gameobject';
27
+ import { ICamera, IComponent, ILight } from "./engine_types"
28
+ import { destroy, foreachComponent } from './engine_gameobject';
29
29
 
30
30
 
31
31
  const debug = utils.getParam("debugSetup");
@@ -58,6 +58,7 @@ export class ContextArgs {
58
58
  alias: string | undefined | null = undefined;
59
59
  domElement: HTMLElement | null;
60
60
  renderer?: THREE.WebGLRenderer = undefined;
61
+ hash?: string;
61
62
 
62
63
  constructor(domElement: HTMLElement | null) {
63
64
  this.domElement = domElement ?? document.body;
@@ -70,7 +71,8 @@ export enum FrameEvent {
70
71
  LateUpdate = 2,
71
72
  OnBeforeRender = 3,
72
73
  OnAfterRender = 4,
73
- PhysicsStep = 10,
74
+ PrePhysicsStep = 9,
75
+ PostPhysicsStep = 10,
74
76
  }
75
77
 
76
78
  export enum XRSessionMode {
@@ -81,12 +83,12 @@ export enum XRSessionMode {
81
83
  export declare type OnBeforeRenderCallback = (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera, geometry: THREE.BufferGeometry, material: THREE.Material, group: THREE.Group) => void
82
84
 
83
85
 
84
- export function registerComponent(script : IComponent, context?: Context) {
85
- if (!script) return;
86
- const new_scripts = context?.new_scripts ?? Context.Current.new_scripts;
87
- if (!new_scripts.includes(script)) {
88
- new_scripts.push(script);
89
- }
86
+ export function registerComponent(script: IComponent, context?: Context) {
87
+ if (!script) return;
88
+ const new_scripts = context?.new_scripts ?? Context.Current.new_scripts;
89
+ if (!new_scripts.includes(script)) {
90
+ new_scripts.push(script);
91
+ }
90
92
  }
91
93
 
92
94
  export class Context {
@@ -105,13 +107,16 @@ export class Context {
105
107
  alias: string | undefined | null;
106
108
  isManagedExternally: boolean = false;
107
109
 
110
+ /** used to append to loaded assets */
111
+ hash?: string;
112
+
108
113
  domElement: HTMLElement;
109
114
  get resolutionScaleFactor() { return this._resolutionScaleFactor; }
110
115
  /** use to scale the resolution up or down of the renderer. default is 1 */
111
- set resolutionScaleFactor(val: number) {
112
- if(val === this._resolutionScaleFactor) return;
113
- if(typeof val !== "number") return;
114
- if(val <= 0) {
116
+ set resolutionScaleFactor(val: number) {
117
+ if (val === this._resolutionScaleFactor) return;
118
+ if (typeof val !== "number") return;
119
+ if (val <= 0) {
115
120
  console.error("Invalid resolution scale factor", val);
116
121
  return;
117
122
  }
@@ -124,7 +129,9 @@ export class Context {
124
129
  get domX(): number { return this.domElement.offsetLeft; }
125
130
  get domY(): number { return this.domElement.offsetTop; }
126
131
  get isInXR() { return this.renderer.xr?.isPresenting || false; }
127
- xrSessionMode : XRSessionMode | undefined = undefined;
132
+ xrSessionMode: XRSessionMode | undefined = undefined;
133
+ get isInVR() { return this.xrSessionMode === XRSessionMode.ImmersiveVR; }
134
+ get isInAR() { return this.xrSessionMode === XRSessionMode.ImmersiveAR; }
128
135
  get xrSession() { return this.renderer.xr?.getSession(); }
129
136
  get arOverlayElement(): HTMLElement {
130
137
  const el = this.domElement as any;
@@ -150,14 +157,14 @@ export class Context {
150
157
 
151
158
  get mainCamera(): THREE.Camera | null {
152
159
  if (this.mainCameraComponent) {
153
- const cam = this.mainCameraComponent as Camera;
160
+ const cam = this.mainCameraComponent as ICamera;
154
161
  if (!cam.cam)
155
162
  cam.buildCamera();
156
163
  return cam.cam;
157
164
  }
158
165
  return null;
159
166
  }
160
- mainCameraComponent: Camera | undefined;
167
+ mainCameraComponent: ICamera | undefined;
161
168
 
162
169
  post_setup_callbacks: Function[] = [];
163
170
  pre_update_callbacks: Function[] = [];
@@ -178,11 +185,11 @@ export class Context {
178
185
  * @deprecated AssetDataBase is deprecated
179
186
  */
180
187
  assets: AssetDatabase;
181
- mainLight: Light | null = null;
188
+ mainLight: ILight | null = null;
182
189
  rendererData: RendererData;
183
190
  addressables: Addressables;
184
191
  lightmaps: ILightDataRegistry;
185
- players : PlayerViewManager;
192
+ players: PlayerViewManager;
186
193
 
187
194
  private _sizeChanged: boolean = false;
188
195
  private _isCreated: boolean = false;
@@ -193,6 +200,7 @@ export class Context {
193
200
  this.name = args?.name || "";
194
201
  this.alias = args?.alias;
195
202
  this.domElement = args?.domElement || document.body;
203
+ this.hash = args?.hash;
196
204
  if (args?.renderer) {
197
205
  this.renderer = args.renderer;
198
206
  this.isManagedExternally = true;
@@ -232,7 +240,7 @@ export class Context {
232
240
  this.lightmaps = new LightDataRegistry(this);
233
241
  this.players = new PlayerViewManager(this);
234
242
 
235
- window.addEventListener('resize', () => this._sizeChanged = true );
243
+ window.addEventListener('resize', () => this._sizeChanged = true);
236
244
  const ro = new ResizeObserver(_ => this._sizeChanged = true);
237
245
  ro.observe(this.domElement);
238
246
  }
@@ -315,9 +323,9 @@ export class Context {
315
323
  }
316
324
  }
317
325
 
318
- private _cameraStack: Camera[] = [];
326
+ private _cameraStack: ICamera[] = [];
319
327
 
320
- setCurrentCamera(cam: Camera) {
328
+ setCurrentCamera(cam: ICamera) {
321
329
  if (!cam) return;
322
330
  if (!cam.cam) cam.buildCamera(); // < to build camera
323
331
  if (!cam.cam) {
@@ -331,17 +339,17 @@ export class Context {
331
339
  const camera = cam.cam as THREE.PerspectiveCamera;
332
340
  if (camera.isPerspectiveCamera)
333
341
  this.updateAspect(camera);
334
- (this.mainCameraComponent as Camera)?.applyClearFlagsIfIsActiveCamera();
342
+ (this.mainCameraComponent as ICamera)?.applyClearFlagsIfIsActiveCamera();
335
343
  }
336
344
 
337
- removeCamera(cam?: Camera | null) {
338
- if(!cam) return;
345
+ removeCamera(cam?: ICamera | null) {
346
+ if (!cam) return;
339
347
  const index = this._cameraStack.indexOf(cam);
340
348
  if (index >= 0) this._cameraStack.splice(index, 1);
341
349
 
342
350
  if (this.mainCameraComponent === cam) {
343
351
  this.mainCameraComponent = undefined;
344
-
352
+
345
353
  if (this._cameraStack.length > 0) {
346
354
  const last = this._cameraStack[this._cameraStack.length - 1];
347
355
  this.setCurrentCamera(last);
@@ -460,25 +468,24 @@ export class Context {
460
468
  }
461
469
 
462
470
  if (!this.mainCamera) {
463
- // console.error("MISSING camera", this);
464
- // return;
465
- // Context._current = this;
466
- // //@ts-ignore
467
- // const cams = findObjectsOfType(Camera, this);
468
- // console.log(cams);
469
- // // get main camera, fallback to first camera
470
- // const mc: Camera = cams.find(c => c.tag == "MainCamera") ?? cams.find(_ => true) as Camera;
471
- // if (mc) {
472
- // if (mc.tag !== "MainCamera")
473
- // console.warn("No mainCamera configured. Some components rely on a mainCamera object being present. " +
474
- // "When exporting your scene, make sure to set the mainCamera tag on your camera. Using \"" + mc.name + "\" as mainCamera now.");
475
- // this.setCurrentCamera(mc);
476
- // }
477
- // else {
478
- // console.error("No camera found");
479
- // const cam = createCameraWithOrbitControl(this.scene);
480
- // this.setCurrentCamera(cam);
481
- // }
471
+ Context._current = this;
472
+ let camera: ICamera | null = null;
473
+ foreachComponent(this.scene, comp => {
474
+ const cam = comp as ICamera;
475
+ if (cam?.isCamera) {
476
+ if (cam.tag === "MainCamera") {
477
+ camera = cam;
478
+ return true;
479
+ }
480
+ else camera = cam;
481
+ }
482
+ return undefined;
483
+ });
484
+ if (camera) {
485
+ this.setCurrentCamera(camera);
486
+ }
487
+ else
488
+ console.error("MISSING camera", this);
482
489
  }
483
490
 
484
491
  Context._current = this;
@@ -569,8 +576,9 @@ export class Context {
569
576
  const physicsSteps = 1;
570
577
  const dt = this.time.deltaTime / physicsSteps;
571
578
  for (let i = 0; i < physicsSteps; i++) {
579
+ this.executeCoroutines(FrameEvent.PrePhysicsStep);
572
580
  this.physics.step(dt);
573
- this.executeCoroutines(FrameEvent.PhysicsStep);
581
+ this.executeCoroutines(FrameEvent.PostPhysicsStep);
574
582
  }
575
583
  }
576
584
  catch (err) {
@@ -40,6 +40,7 @@ export interface IComponent {
40
40
  get name(): string;
41
41
  get layer(): number;
42
42
  get destroyed(): boolean;
43
+ get tag() : string;
43
44
 
44
45
  context: any;
45
46
 
@@ -83,6 +84,7 @@ export interface IComponent {
83
84
 
84
85
 
85
86
  export declare interface ICamera extends IComponent {
87
+ get isCamera() : boolean;
86
88
  applyClearFlagsIfIsActiveCamera(): unknown;
87
89
  buildCamera();
88
90
  get cam(): Camera;
@@ -173,7 +175,7 @@ export class ContactPoint {
173
175
  /// all info in here must be readonly because the object is only created once per started collision
174
176
  export class Collision {
175
177
 
176
- private readonly contacts: ContactPoint[];
178
+ readonly contacts: ContactPoint[];
177
179
 
178
180
  constructor(obj: Object3D, otherCollider: ICollider, contacts: ContactPoint[]) {
179
181
  this.me = obj;
@@ -201,6 +203,7 @@ export class Collision {
201
203
  return this.collider?.attachedRigidbody;
202
204
  }
203
205
 
206
+
204
207
 
205
208
  // private _normal?: Vector3;
206
209
  // get normal(): Vector3 {
@@ -112,6 +112,14 @@ export class Animation extends Behaviour {
112
112
  return this.actions?.find(a => a.getClip().name === name);
113
113
  }
114
114
 
115
+ get isPlaying() {
116
+ for (let i = 0; i < this._currentActions.length; i++) {
117
+ if (this._currentActions[i].isRunning())
118
+ return true;
119
+ }
120
+ return false;
121
+ }
122
+
115
123
  play(clipOrNumber: AnimationClip | number | string, options?: PlayOptions): Promise<AnimationAction> | void {
116
124
  this.init();
117
125
  if (!this.mixer) return;
@@ -218,7 +218,7 @@ export class AnimatorController {
218
218
  if (!allConditionsAreMet) continue;
219
219
 
220
220
  if (debug && allConditionsAreMet) {
221
- console.log("All conditions are met", transition);
221
+ // console.log("All conditions are met", transition);
222
222
  }
223
223
 
224
224
  // disable triggers
@@ -232,16 +232,16 @@ export class AnimatorController {
232
232
  if (action) {
233
233
  const dur = state.motion.clip!.duration;
234
234
  const normalizedTime = dur <= 0 ? 1 : action.time / dur;
235
- if (!transition.hasExitTime
236
- || (normalizedTime >= transition.exitTime && action.time >= action.getClip().duration
237
- )
238
- ) {
235
+ const makeTransition = transition.hasExitTime ? normalizedTime >= transition.exitTime : true;
236
+ // console.log(state.name, makeTransition, transition.hasExitTime, normalizedTime, transition.exitTime)
237
+ if (makeTransition) {
239
238
  // if (transition.hasExitTime && transition.exitTime >= .9999)
240
239
  action.clampWhenFinished = true;
241
240
  // else action.clampWhenFinished = false;
242
- if (debug) {
243
- console.log("transition to " + transition.destinationState, transition);
244
- console.log(action.time, transition.exitTime);
241
+ if (debug)
242
+ {
243
+ console.log("transition to " + transition.destinationState, transition, normalizedTime, transition.exitTime, transition.hasExitTime);
244
+ // console.log(action.time, transition.exitTime);
245
245
  }
246
246
  this.transitionTo(transition.destinationState as State, transition.duration, transition.offset);
247
247
  // use the first transition that matches all conditions and make the transition as soon as in range
@@ -252,7 +252,7 @@ export class AnimatorController {
252
252
  this.transitionTo(transition.destinationState as State, transition.duration, transition.offset);
253
253
  return;
254
254
  }
255
- break;
255
+ // if none of the transitions can be made continue searching for another transition meeting the conditions
256
256
  }
257
257
 
258
258
  let didTriggerLooping = false;
@@ -376,7 +376,13 @@ export class AnimatorController {
376
376
  }
377
377
 
378
378
  private createAction(clip: AnimationClip) {
379
- this._mixer.uncacheClip(clip);
379
+
380
+ // uncache clip causes issues when multiple states use the same clip
381
+ // this._mixer.uncacheClip(clip);
382
+ // instead only uncache the action when one already exists to make sure
383
+ // we get unique actions per state
384
+ const existing = this._mixer.existingAction(clip);
385
+ if (existing) this._mixer.uncacheAction(clip, this.animator?.gameObject);
380
386
 
381
387
  if (this.animator?.applyRootMotion) {
382
388
  if (!this.rootMotionHandler) {
@@ -387,7 +393,6 @@ export class AnimatorController {
387
393
  return this.rootMotionHandler.createClip(this._mixer, root, clip);
388
394
  }
389
395
  else {
390
-
391
396
  const action = this._mixer.clipAction(clip);
392
397
  return action;
393
398
  }
@@ -7,6 +7,7 @@ import { RGBAColor } from "./js-extensions/RGBAColor";
7
7
  import { PerspectiveCamera } from "three";
8
8
  import { XRSessionMode } from "../engine/engine_setup";
9
9
  import { ICamera } from "../engine/engine_types"
10
+ import { showBalloonMessage } from "../engine/debug/debug";
10
11
 
11
12
  export enum ClearFlags {
12
13
  Skybox = 1,
@@ -18,6 +19,9 @@ const debug = getParam("debugcam");
18
19
 
19
20
  export class Camera extends Behaviour implements ICamera {
20
21
 
22
+ get isCamera() {
23
+ return true;
24
+ }
21
25
 
22
26
  get aspect(): number {
23
27
  if (this._cam instanceof PerspectiveCamera) return this._cam.aspect;
@@ -121,12 +125,16 @@ export class Camera extends Behaviour implements ICamera {
121
125
  onEnable(): void {
122
126
  if (debug) console.log(this);
123
127
  this.buildCamera();
124
- if (this.tag == "MainCamera") {
128
+ if (this.tag == "MainCamera" || !this.context.mainCameraComponent) {
125
129
  this.context.setCurrentCamera(this);
126
130
  }
127
131
  this.applyClearFlagsIfIsActiveCamera();
128
132
  }
129
133
 
134
+ onDisable() {
135
+ this.context.removeCamera(this);
136
+ }
137
+
130
138
  buildCamera() {
131
139
  if (this._cam) return;
132
140
 
@@ -168,6 +176,8 @@ export class Camera extends Behaviour implements ICamera {
168
176
  }
169
177
 
170
178
  applyClearFlagsIfIsActiveCamera() {
179
+ if (debug)
180
+ showBalloonMessage("apply Camera clear flags");
171
181
  if (this._cam && this.context.mainCameraComponent === this) {
172
182
  switch (this._clearFlags) {
173
183
  case ClearFlags.Skybox:
@@ -203,12 +213,22 @@ export class Camera extends Behaviour implements ICamera {
203
213
  const session = this.context.renderer.xr?.getSession();
204
214
  if (!session) return false;
205
215
  const environmentBlendMode = session.environmentBlendMode;
216
+ if (debug)
217
+ showBalloonMessage("Environment blend mode: " + environmentBlendMode + " on " + navigator.userAgent);
206
218
  const transparent = environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend';
207
- // workaround for Quest 2 returning opaque when it should be alpha-blend
208
- // check user agent if this is the Quest browser and return true if so
209
219
 
210
- if (environmentBlendMode === "opaque" && navigator.userAgent?.includes("OculusBrowser")) {
211
- if (this.context.xrSessionMode === XRSessionMode.ImmersiveAR) return true;
220
+ if (this.context.xrSessionMode === XRSessionMode.ImmersiveAR) {
221
+ if (environmentBlendMode === "opaque") {
222
+ // workaround for Quest 2 returning opaque when it should be alpha-blend
223
+ // check user agent if this is the Quest browser and return true if so
224
+ if (navigator.userAgent?.includes("OculusBrowser")) {
225
+ return true;
226
+ }
227
+ // Mozilla WebXR Viewer
228
+ else if (navigator.userAgent?.includes("Mozilla") && navigator.userAgent?.includes("Mobile WebXRViewer/v2")) {
229
+ return true;
230
+ }
231
+ }
212
232
  }
213
233
  return transparent;
214
234
  }
@@ -0,0 +1,185 @@
1
+ import { Quaternion, Ray, Vector3 } from "three";
2
+ import { Mathf } from "../engine/engine_math";
3
+ import { serializeable } from "../engine/engine_serialization";
4
+ import { Collision } from "../engine/engine_types";
5
+ import { CapsuleCollider } from "./Collider";
6
+ import { Behaviour, GameObject } from "./Component";
7
+ import { Rigidbody } from "./RigidBody";
8
+ import { Animator } from "./Animator"
9
+ import { RaycastOptions } from "../engine/engine_physics";
10
+ import { getWorldPosition } from "../engine/engine_three_utils";
11
+
12
+ export class CharacterController extends Behaviour {
13
+
14
+ @serializeable(Vector3)
15
+ center: Vector3 = new Vector3(0, 0, 0);
16
+ @serializeable()
17
+ radius: number = .5;
18
+ @serializeable()
19
+ height: number = 2;
20
+
21
+ private _rigidbody: Rigidbody | null = null;
22
+ get rigidbody(): Rigidbody {
23
+ if (this._rigidbody) return this._rigidbody;
24
+ this._rigidbody = this.gameObject.getComponent(Rigidbody);
25
+ if (!this._rigidbody)
26
+ this._rigidbody = this.gameObject.addNewComponent(Rigidbody) as Rigidbody;
27
+ return this.rigidbody;
28
+ }
29
+
30
+ onEnable() {
31
+ let rb = this.rigidbody;
32
+ let collider = this.gameObject.getComponent(CapsuleCollider);
33
+ if (!collider)
34
+ collider = this.gameObject.addNewComponent(CapsuleCollider) as CapsuleCollider;
35
+ // rb.isKinematic = true;
36
+ collider.center.copy(this.center);
37
+ collider.radius = this.radius;
38
+ collider.height = this.height;
39
+ this.gameObject.rotation.x = 0;
40
+ this.gameObject.rotation.z = 0;
41
+ rb.lockRotationX = true;
42
+ rb.lockRotationY = true;
43
+ rb.lockRotationZ = true;
44
+
45
+ // TODO: this doesnt work yet
46
+ // setInterval(()=>{
47
+ // this.rigidbody.isKinematic = !this.rigidbody.isKinematic;
48
+ // console.log(this.rigidbody.isKinematic);
49
+ // }, 1000)
50
+ }
51
+
52
+ move(vec: Vector3) {
53
+ this.gameObject.position.add(vec);
54
+ }
55
+
56
+ private _activeGroundCollisions: Set<Collision> = new Set();
57
+
58
+ onCollisionEnter(col: Collision) {
59
+ for (const contact of col.contacts) {
60
+ // console.log(contact.normal);
61
+ if (contact.normal.y > .1) {
62
+ this._activeGroundCollisions.add(col);
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ onCollisionExit(col: Collision) {
69
+ this._activeGroundCollisions.delete(col);
70
+ }
71
+
72
+ get isGrounded(): boolean { return this._activeGroundCollisions.size > 0; }
73
+ }
74
+
75
+ export class CharacterControllerInput extends Behaviour {
76
+
77
+ @serializeable(CharacterController)
78
+ controller?: CharacterController;
79
+
80
+ @serializeable()
81
+ movementSpeed: number = 2;
82
+
83
+ @serializeable()
84
+ rotationSpeed: number = 2;
85
+
86
+ @serializeable()
87
+ jumpForce: number = 1;
88
+
89
+ @serializeable(Animator)
90
+ animator?: Animator;
91
+
92
+ lookForward: boolean = true;
93
+
94
+ private _currentSpeed: Vector3 = new Vector3(0, 0, 0);
95
+ private _currentAngularSpeed: Vector3 = new Vector3(0, 0, 0);
96
+
97
+ private _temp: Vector3 = new Vector3(0, 0, 0);
98
+ private _jumpCount: number = 0;
99
+ private _currentRotation!: Quaternion;
100
+
101
+ awake(){
102
+ this._currentRotation = new Quaternion();
103
+ }
104
+
105
+ update() {
106
+
107
+ if (this.controller?.isGrounded) {
108
+ this._jumpCount = 0;
109
+ this.animator?.SetBool("doubleJump", false);
110
+ }
111
+
112
+ const forward = this.context.input.isKeyPressed("w");
113
+ const backward = this.context.input.isKeyPressed("s");
114
+ const rotateLeft = this.context.input.isKeyPressed("a");
115
+ const rotateRight = this.context.input.isKeyPressed("d");
116
+ const jump = this.context.input.isKeyDown(" ");
117
+ // if (jumpDown) this._jumpDownTime = this.context.time.time;
118
+ // const jumpUp = this.context.input.isKeyUp(" ");
119
+
120
+ const step = forward ? 1 : 0 + backward ? -1 : 0;
121
+ this._currentSpeed.z += step * this.movementSpeed * this.context.time.deltaTime;
122
+
123
+ // if (!this.controller || this.controller.isGrounded)
124
+ this.animator?.SetBool("running", step != 0);
125
+ this.animator?.SetBool("jumping", this.controller?.isGrounded === true && jump);
126
+
127
+ this._temp.copy(this._currentSpeed);
128
+ this._temp.applyQuaternion(this.gameObject.quaternion);
129
+ if (this.controller) this.controller.move(this._temp);
130
+ else this.gameObject.position.add(this._temp);
131
+
132
+ const rotation = rotateLeft ? 1 : 0 + rotateRight ? -1 : 0;
133
+ this._currentAngularSpeed.y += Mathf.toRadians(rotation * this.rotationSpeed) * this.context.time.deltaTime;
134
+ if (this.lookForward && Math.abs(this._currentAngularSpeed.y) < .01) {
135
+ const forwardVector = this.context.mainCameraComponent!.forward;
136
+ forwardVector.y = 0;
137
+ forwardVector.normalize();
138
+ this._currentRotation.setFromUnitVectors(new Vector3(0, 0, 1), forwardVector);
139
+ this.gameObject.quaternion.slerp(this._currentRotation, this.context.time.deltaTime * 10);
140
+ }
141
+ this.gameObject.rotateY(this._currentAngularSpeed.y);
142
+
143
+
144
+ this._currentSpeed.multiplyScalar(1 - this.context.time.deltaTime * 10);
145
+ this._currentAngularSpeed.y *= 1 - this.context.time.deltaTime * 10;
146
+
147
+ if (this.controller && jump && this.jumpForce > 0) {
148
+ let canJump = this.controller?.isGrounded;
149
+ if (!this.controller?.isGrounded && this._jumpCount === 1) {
150
+ canJump = true;
151
+ this.animator?.SetBool("doubleJump", true);
152
+ }
153
+
154
+ if (canJump) {
155
+ this._jumpCount += 1;
156
+ // TODO: factor in mass
157
+ const rb = this.controller.rigidbody;
158
+ // const fullJumpHoldLength = .1;
159
+ const factor = this._jumpCount === 2 ? 2 : 1;// Mathf.clamp((this.context.time.time - this._jumpDownTime), 0, fullJumpHoldLength) / fullJumpHoldLength;
160
+ rb.applyImpulse(new Vector3(0, 1, 0).multiplyScalar(this.jumpForce * factor));
161
+ }
162
+ }
163
+
164
+ if (this.controller) {
165
+ // TODO: should probably raycast to the ground or check if we're still in the jump animation
166
+ const verticalSpeed = this.controller?.rigidbody.getVelocity().y;
167
+ if (verticalSpeed < -1) {
168
+ if (!this._raycastOptions.ray) this._raycastOptions.ray = new Ray();
169
+ this._raycastOptions.ray.origin.copy(getWorldPosition(this.gameObject));
170
+ this._raycastOptions.ray.direction.set(0, -1, 0);
171
+ const currentLayer = this.layer;
172
+ this.gameObject.layers.disableAll();
173
+ this.gameObject.layers.set(2);
174
+ const hits = this.context.physics.raycast(this._raycastOptions);
175
+ this.gameObject.layers.set(currentLayer);
176
+ if ((hits.length && hits[0].distance > 2 || verticalSpeed < -10)) {
177
+ this.animator?.SetBool("falling", true);
178
+ }
179
+ }
180
+ else this.animator?.SetBool("falling", false);
181
+ }
182
+ }
183
+
184
+ private _raycastOptions = new RaycastOptions();
185
+ }
@@ -2,16 +2,9 @@ import { Behaviour } from "./Component";
2
2
  import { Rigidbody } from "./RigidBody";
3
3
  import { serializeable } from "../engine/engine_serialization_decorator";
4
4
  import { Event, Mesh, Object3D, Vector3 } from "three"
5
- import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics";
5
+ // import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics";
6
6
  import { ICollider } from "../engine/engine_types";
7
- import { getComponentInChildren } from "../engine/engine_components";
8
7
 
9
- class ColliderProvider implements IColliderProvider {
10
- getCollider(obj: Object3D): ICollider {
11
- return getComponentInChildren<Collider>(obj, Collider);
12
- }
13
- }
14
- registerColliderProvider(new ColliderProvider());
15
8
 
16
9
  export class Collider extends Behaviour implements ICollider {
17
10
 
@@ -54,7 +47,7 @@ export class SphereCollider extends Collider {
54
47
  @serializeable()
55
48
  radius: number = .5;
56
49
  @serializeable(Vector3)
57
- center: THREE.Vector3 = new Vector3(0, 0, 0);
50
+ center: Vector3 = new Vector3(0, 0, 0);
58
51
 
59
52
  onEnable() {
60
53
  super.onEnable();
@@ -65,9 +58,9 @@ export class SphereCollider extends Collider {
65
58
  export class BoxCollider extends Collider {
66
59
 
67
60
  @serializeable(Vector3)
68
- size: THREE.Vector3 = new Vector3(1, 1, 1);
61
+ size: Vector3 = new Vector3(1, 1, 1);
69
62
  @serializeable(Vector3)
70
- center: THREE.Vector3 = new Vector3(0, 0, 0);
63
+ center: Vector3 = new Vector3(0, 0, 0);
71
64
 
72
65
  onEnable() {
73
66
  super.onEnable();
@@ -83,10 +76,6 @@ export class MeshCollider extends Collider {
83
76
  @serializeable()
84
77
  convex: boolean = false;
85
78
 
86
- awake(){
87
- console.log(this);
88
- }
89
-
90
79
  onEnable() {
91
80
  super.onEnable();
92
81
  if (!this.sharedMesh?.isMesh) {
@@ -98,4 +87,21 @@ export class MeshCollider extends Collider {
98
87
  if (this.sharedMesh?.isMesh)
99
88
  this.context.physics.addMeshCollider(this, this.sharedMesh, this.convex);
100
89
  }
90
+ }
91
+
92
+
93
+ export class CapsuleCollider extends Collider {
94
+ @serializeable(Vector3)
95
+ center: Vector3 = new Vector3(0, 0, 0);
96
+
97
+ @serializeable()
98
+ radius: number = .5;
99
+ @serializeable()
100
+ height: number = 2;
101
+
102
+ onEnable() {
103
+ super.onEnable();
104
+ this.context.physics.addCapsuleCollider(this, this.center, this.height, this.radius);
105
+ }
106
+
101
107
  }