@mml-io/3d-web-client-core 0.1.0 → 0.2.0

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.
package/build/index.js CHANGED
@@ -27,25 +27,53 @@ var round = (n, digits) => {
27
27
  var ease = (target, n, factor) => {
28
28
  return round((target - n) * factor, 5);
29
29
  };
30
+ function clamp(value, min, max) {
31
+ return Math.min(Math.max(value, min), max);
32
+ }
33
+ var remap = (value, minValue, maxValue, minScaledValue, maxScaledValue) => {
34
+ return minScaledValue + (maxScaledValue - minScaledValue) * (value - minValue) / (maxValue - minValue);
35
+ };
36
+
37
+ // src/rendering/tweakPaneActivity.ts
38
+ var isTweakpaneActive = false;
39
+ function setTweakpaneActive(status) {
40
+ isTweakpaneActive = status;
41
+ }
42
+ function getTweakpaneActive() {
43
+ return isTweakpaneActive;
44
+ }
30
45
 
31
46
  // src/camera/CameraManager.ts
32
47
  var CameraManager = class {
33
48
  constructor() {
34
49
  __publicField(this, "camera");
35
- __publicField(this, "dragging", false);
36
- __publicField(this, "target", new Vector32(0, 1.55, 0));
37
- __publicField(this, "targetDistance");
38
- __publicField(this, "maxTargetDistance", 20);
39
- __publicField(this, "distance");
50
+ __publicField(this, "initialDistance", 2.5);
51
+ __publicField(this, "minDistance", 0.1);
52
+ __publicField(this, "maxDistance", 6);
53
+ __publicField(this, "initialFOV", 80);
54
+ __publicField(this, "fov", this.initialFOV);
55
+ __publicField(this, "minFOV", 65);
56
+ __publicField(this, "maxFOV", 85);
57
+ __publicField(this, "targetFOV", this.initialFOV);
58
+ __publicField(this, "minPolarAngle", Math.PI * 0.25);
59
+ __publicField(this, "maxPolarAngle", Math.PI * 0.95);
60
+ __publicField(this, "dampingFactor", 0.091);
61
+ __publicField(this, "targetDistance", this.initialDistance);
62
+ __publicField(this, "distance", this.initialDistance);
40
63
  __publicField(this, "targetPhi", Math.PI / 2);
41
64
  __publicField(this, "phi", Math.PI / 2);
42
65
  __publicField(this, "targetTheta", -Math.PI / 2);
43
66
  __publicField(this, "theta", -Math.PI / 2);
67
+ __publicField(this, "dragging", false);
68
+ __publicField(this, "target", new Vector32(0, 1.55, 0));
44
69
  __publicField(this, "hadTarget", false);
45
- this.camera = new PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 2e3);
46
- this.camera.position.set(0, 1.4, 3);
47
- this.targetDistance = 2.5;
48
- this.distance = this.targetDistance;
70
+ this.camera = new PerspectiveCamera(
71
+ this.fov,
72
+ window.innerWidth / window.innerHeight,
73
+ 0.1,
74
+ 2e3
75
+ );
76
+ this.camera.position.set(0, 1.4, -this.initialDistance);
49
77
  document.addEventListener("mousedown", this.onMouseDown.bind(this));
50
78
  document.addEventListener("mouseup", this.onMouseUp.bind(this));
51
79
  document.addEventListener("mousemove", this.onMouseMove.bind(this));
@@ -65,22 +93,21 @@ var CameraManager = class {
65
93
  this.dragging = false;
66
94
  }
67
95
  onMouseMove(event) {
68
- if (!this.dragging) {
96
+ if (!this.dragging || getTweakpaneActive() === true)
69
97
  return;
70
- }
71
- if (this.targetTheta === null || this.targetPhi === null) {
98
+ if (this.targetTheta === null || this.targetPhi === null)
72
99
  return;
73
- }
74
100
  this.targetTheta += event.movementX * 0.01;
75
101
  this.targetPhi -= event.movementY * 0.01;
76
- this.targetPhi = Math.max(Math.PI * 0.1, Math.min(Math.PI - Math.PI * 0.1, this.targetPhi));
77
- this.targetPhi = Math.min(Math.PI * 0.7, this.targetPhi);
102
+ this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
78
103
  }
79
104
  onMouseWheel(event) {
80
- const scrollAmount = event.deltaY * 0.01;
105
+ const scrollAmount = event.deltaY * 1e-3;
81
106
  this.targetDistance += scrollAmount;
82
- this.targetDistance = Math.max(0, this.targetDistance);
83
- this.targetDistance = Math.min(this.targetDistance, this.maxTargetDistance);
107
+ this.targetDistance = Math.max(
108
+ this.minDistance,
109
+ Math.min(this.maxDistance, this.targetDistance)
110
+ );
84
111
  }
85
112
  setTarget(target) {
86
113
  this.target.copy(target);
@@ -103,18 +130,26 @@ var CameraManager = class {
103
130
  this.distance = this.targetDistance;
104
131
  }
105
132
  update() {
106
- if (this.target === null) {
133
+ if (this.target === null)
107
134
  return;
108
- }
109
135
  if (this.phi !== null && this.targetPhi !== null && this.theta !== null && this.targetTheta !== null) {
110
- this.distance += ease(this.targetDistance, this.distance, 0.02);
111
- this.distance = Math.min(this.distance, this.maxTargetDistance);
112
- this.phi += ease(this.targetPhi, this.phi, 0.07);
113
- this.theta += ease(this.targetTheta, this.theta, 0.07);
136
+ this.distance += (this.targetDistance - this.distance) * this.dampingFactor * 0.21;
137
+ this.phi += (this.targetPhi - this.phi) * this.dampingFactor;
138
+ this.theta += (this.targetTheta - this.theta) * this.dampingFactor;
114
139
  const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
115
140
  const y = this.target.y + this.distance * Math.cos(this.phi);
116
141
  const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
117
- this.camera.position.set(x, y, z);
142
+ this.targetFOV = remap(
143
+ this.targetDistance,
144
+ this.minDistance,
145
+ this.maxDistance,
146
+ this.minFOV,
147
+ this.maxFOV
148
+ );
149
+ this.fov += ease(this.targetFOV, this.fov, 0.07);
150
+ this.camera.fov = this.fov;
151
+ this.camera.updateProjectionMatrix();
152
+ this.camera.position.set(x, clamp(y, 0.1, Infinity), z);
118
153
  this.camera.lookAt(this.target);
119
154
  }
120
155
  }
@@ -233,15 +268,17 @@ var CharacterMaterial = class extends MeshPhysicalMaterial {
233
268
  __publicField(this, "colorsCube216", []);
234
269
  this.color = new Color(16777215);
235
270
  this.transmission = 0.5;
236
- this.metalness = 0.5;
237
- this.roughness = 0.3;
238
- this.ior = 2;
271
+ this.metalness = 0.9;
272
+ this.roughness = 0.1;
273
+ this.ior = 1.2;
239
274
  this.thickness = 0.1;
240
275
  this.specularColor = new Color(30719);
241
276
  this.specularIntensity = 0.1;
277
+ this.emissive = new Color(16777215);
278
+ this.emissiveIntensity = 0.1;
242
279
  this.envMapIntensity = 1.8;
243
- this.sheenColor = new Color(7798903);
244
- this.sheen = 0.35;
280
+ this.sheenColor = new Color(16777215);
281
+ this.sheen = 0.5;
245
282
  this.onBeforeCompile = (shader) => {
246
283
  this.uniforms = UniformsUtils.clone(shader.uniforms);
247
284
  this.uniforms.nearClip = { value: 0.01 };
@@ -257,6 +294,7 @@ var CharacterMaterial = class extends MeshPhysicalMaterial {
257
294
  shader.fragmentShader,
258
295
  /* glsl */
259
296
  `
297
+ //#define showPattern
260
298
  varying vec2 vUv;
261
299
  uniform float nearClip;
262
300
  uniform float farClip;
@@ -265,6 +303,25 @@ var CharacterMaterial = class extends MeshPhysicalMaterial {
265
303
  uniform float time;
266
304
  uniform vec3 diffuseRandomColor;
267
305
  ${bayerDither}
306
+
307
+ #ifdef showPattern
308
+ vec2 rand2(vec2 p) {
309
+ return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077)));
310
+ }
311
+ float voronoi(in vec2 x) {
312
+ vec2 p = floor(x);
313
+ vec2 f = fract(x);
314
+ float minDistance = 1.0;
315
+ for(int j = -1; j <= 1; j ++)
316
+ for(int i = -1; i <= 1; i ++) {
317
+ vec2 b = vec2(i, j);
318
+ vec2 rand = 0.5 + 0.5 * sin(time * 1.5 + 12.0 * rand2(p + b));
319
+ vec2 r = vec2(b) - f + rand;
320
+ minDistance = min(minDistance, length(r));
321
+ }
322
+ return minDistance;
323
+ }
324
+ #endif
268
325
  `
269
326
  );
270
327
  shader.fragmentShader = injectBefore(
@@ -286,10 +343,22 @@ var CharacterMaterial = class extends MeshPhysicalMaterial {
286
343
  d = bayerDither(bayerbr, p - ivec2(4, 4));
287
344
  }
288
345
  if (distance <= ditheringNear + d * ditheringRange) discard;
289
- vec2 suv = vUv;
290
- float s = clamp(0.35 + 0.35 * sin(5.0 * -time + suv.y * 500.0), 0.0, 1.0);
346
+
347
+ vec2 uv = vUv;
348
+ float s = clamp(0.35 + 0.35 * sin(5.0 * -time + vUv.y * 600.0), 0.0, 1.0);
291
349
  float scanLines = pow(s, 1.33);
350
+
292
351
  outgoingLight *= diffuseRandomColor;
352
+
353
+ #ifdef showPattern
354
+ float val = pow(voronoi(uv * 8.0) * 1.2, 0.5);
355
+ float thickness = 1.0 / 500.0;
356
+ vec2 g = step(mod(uv, 0.015), vec2(thickness));
357
+ float a = 1.0 - clamp(val * (g.x + g.y), 0.0, 1.0);
358
+ vec3 grid = vec3(smoothstep(0.01, 0.0, a) * 1.15) * diffuseRandomColor;
359
+ outgoingLight += grid;
360
+ #endif
361
+
293
362
  outgoingLight += smoothstep(0.1, 0.0, scanLines) * 0.1;
294
363
  `
295
364
  );
@@ -297,8 +366,8 @@ var CharacterMaterial = class extends MeshPhysicalMaterial {
297
366
  this.generateColorCube();
298
367
  }
299
368
  generateColorCube() {
300
- const saturation = 0.7;
301
- const lightness = 0.8;
369
+ const saturation = 0.4;
370
+ const lightness = 0.7;
302
371
  const goldenRatioConjugate = 0.618033988749895;
303
372
  let hue = 0;
304
373
  for (let i = 0; i < 216; i++) {
@@ -323,73 +392,130 @@ var AnimationState = /* @__PURE__ */ ((AnimationState2) => {
323
392
 
324
393
  // src/character/ModelLoader.ts
325
394
  import { LoadingManager } from "three";
326
- import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
327
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
328
- var ModelLoader = class {
329
- constructor() {
330
- __publicField(this, "debug", false);
395
+ import { GLTFLoader as ThreeGLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
396
+ var CachedGLTFLoader = class extends ThreeGLTFLoader {
397
+ constructor(manager) {
398
+ super(manager);
399
+ __publicField(this, "blobCache");
400
+ this.blobCache = /* @__PURE__ */ new Map();
401
+ }
402
+ setBlobUrl(originalUrl, blobUrl) {
403
+ this.blobCache.set(originalUrl, blobUrl);
404
+ }
405
+ getBlobUrl(originalUrl) {
406
+ return this.blobCache.get(originalUrl);
407
+ }
408
+ load(url, onLoad, onProgress, onError) {
409
+ const blobUrl = this.getBlobUrl(url);
410
+ if (blobUrl) {
411
+ console.log(`Loading cached ${url.split("/").pop()}`);
412
+ super.load(blobUrl, onLoad, onProgress, onError);
413
+ } else {
414
+ super.load(url, onLoad, onProgress, onError);
415
+ }
416
+ }
417
+ };
418
+ var LRUCache = class {
419
+ constructor(maxSize = 100) {
420
+ __publicField(this, "maxSize");
421
+ __publicField(this, "cache");
422
+ this.maxSize = maxSize;
423
+ this.cache = /* @__PURE__ */ new Map();
424
+ }
425
+ get(key) {
426
+ const item = this.cache.get(key);
427
+ if (item) {
428
+ this.cache.delete(key);
429
+ this.cache.set(key, item);
430
+ }
431
+ return item;
432
+ }
433
+ set(key, value) {
434
+ if (this.cache.size >= this.maxSize) {
435
+ const oldestKey = this.cache.keys().next().value;
436
+ this.cache.delete(oldestKey);
437
+ }
438
+ this.cache.set(key, value);
439
+ }
440
+ };
441
+ var _ModelLoader = class {
442
+ constructor(maxCacheSize = 100) {
331
443
  __publicField(this, "loadingManager");
332
- __publicField(this, "fbxLoader");
333
444
  __publicField(this, "gltfLoader");
445
+ __publicField(this, "modelCache");
446
+ __publicField(this, "ongoingLoads", /* @__PURE__ */ new Map());
334
447
  this.loadingManager = new LoadingManager();
335
- this.fbxLoader = new FBXLoader(this.loadingManager);
336
- this.gltfLoader = new GLTFLoader(this.loadingManager);
448
+ this.gltfLoader = new CachedGLTFLoader(this.loadingManager);
449
+ this.modelCache = new LRUCache(maxCacheSize);
337
450
  }
338
- async load(fileUrl, fileType) {
339
- const extension = fileUrl.split(".").pop();
340
- if (typeof extension === "undefined") {
341
- console.error(`Unable to identify model type from ${fileUrl}`);
342
- return;
451
+ /* TODO: decide between below lazy initialization or eager on this file's bottom export */
452
+ static getInstance() {
453
+ if (!_ModelLoader.instance) {
454
+ _ModelLoader.instance = new _ModelLoader();
343
455
  }
344
- const name = fileUrl.split("/").pop().replace(`.${extension}`, "");
345
- if (this.debug) {
346
- console.log(`Loading ${extension} model ${name} from ${fileUrl}`);
456
+ return _ModelLoader.instance;
457
+ }
458
+ async load(fileUrl, fileType) {
459
+ const cachedModel = this.modelCache.get(fileUrl);
460
+ if (cachedModel) {
461
+ const blobURL = URL.createObjectURL(cachedModel.blob);
462
+ this.gltfLoader.setBlobUrl(fileUrl, blobURL);
463
+ return this.loadFromUrl(fileUrl, fileType, cachedModel.originalExtension);
464
+ } else {
465
+ console.log(`Loading ${fileUrl} from server`);
466
+ const ongoingLoad = this.ongoingLoads.get(fileUrl);
467
+ if (ongoingLoad)
468
+ return ongoingLoad;
469
+ const loadPromise = fetch(fileUrl).then((response) => response.blob()).then((blob) => {
470
+ const originalExtension = fileUrl.split(".").pop() || "";
471
+ this.modelCache.set(fileUrl, { blob, originalExtension });
472
+ const blobURL = URL.createObjectURL(blob);
473
+ this.ongoingLoads.delete(fileUrl);
474
+ return this.loadFromUrl(blobURL, fileType, originalExtension);
475
+ });
476
+ this.ongoingLoads.set(fileUrl, loadPromise);
477
+ return loadPromise;
347
478
  }
479
+ }
480
+ async loadFromUrl(url, fileType, extension) {
348
481
  if (["gltf", "glb"].includes(extension)) {
349
482
  return new Promise((resolve, reject) => {
350
483
  this.gltfLoader.load(
351
- fileUrl,
484
+ url,
352
485
  (object) => {
353
486
  if (fileType === "model") {
354
487
  resolve(object.scene);
355
488
  } else if (fileType === "animation") {
356
489
  resolve(object.animations[0]);
357
490
  } else {
358
- const error = `Trying to load unknown ${fileType} type of element from file ${fileUrl}`;
491
+ const error = `Trying to load unknown ${fileType} type of element from file ${url}`;
359
492
  console.error(error);
360
493
  reject(error);
361
494
  }
362
495
  },
363
496
  void 0,
364
497
  (error) => {
365
- console.error(`Error loading GL(B|TF) from ${fileUrl}: ${error}`);
366
- reject(error);
367
- }
368
- );
369
- });
370
- } else if (extension === "fbx") {
371
- return new Promise((resolve, reject) => {
372
- this.fbxLoader.load(
373
- fileUrl,
374
- (object) => {
375
- resolve(object);
376
- },
377
- void 0,
378
- (error) => {
379
- console.error(`Error loading FBX from ${fileUrl}: ${error}`);
498
+ console.error(`Error loading GL(B|TF) from ${url}: ${error}`);
380
499
  reject(error);
381
500
  }
382
501
  );
383
502
  });
503
+ } else {
504
+ console.error(`Error: can't recognize ${url} extension: ${extension}`);
384
505
  }
385
506
  }
386
507
  };
508
+ var ModelLoader = _ModelLoader;
509
+ __publicField(ModelLoader, "instance", null);
510
+ var MODEL_LOADER = ModelLoader.getInstance();
511
+ var ModelLoader_default = MODEL_LOADER;
387
512
 
388
513
  // src/character/CharacterModel.ts
389
514
  var CharacterModel = class {
390
515
  constructor(characterDescription) {
391
516
  this.characterDescription = characterDescription;
392
- __publicField(this, "modelLoader", new ModelLoader());
517
+ /* TODO: pick between below eager instantiation or ModelLoader.getInstance() lazy one */
518
+ __publicField(this, "modelLoader", ModelLoader_default);
393
519
  __publicField(this, "mesh", null);
394
520
  __publicField(this, "material", new CharacterMaterial());
395
521
  __publicField(this, "animations", {});
@@ -410,6 +536,10 @@ var CharacterModel = class {
410
536
  this.characterDescription.sprintAnimationFileUrl,
411
537
  2 /* running */
412
538
  );
539
+ await this.setAnimationFromFile(
540
+ this.characterDescription.airAnimationFileUrl,
541
+ 4 /* air */
542
+ );
413
543
  this.applyMaterialToAllSkinnedMeshes(this.material);
414
544
  }
415
545
  updateAnimation(targetAnimation, deltaTime) {
@@ -462,7 +592,7 @@ var CharacterModel = class {
462
592
  if (typeof mainMesh !== "undefined") {
463
593
  this.mesh = new Object3D2();
464
594
  const model = mainMesh;
465
- model.position.set(0, -0.35, 0);
595
+ model.position.set(0, -0.4, 0);
466
596
  this.mesh.add(model);
467
597
  this.mesh.name = name;
468
598
  this.mesh.scale.set(scale, scale, scale);
@@ -485,7 +615,7 @@ var CharacterModel = class {
485
615
  }
486
616
  });
487
617
  }
488
- transitionToAnimation(targetAnimation, transitionDuration = 0.21) {
618
+ transitionToAnimation(targetAnimation, transitionDuration = 0.15) {
489
619
  if (!this.mesh || this.currentAnimation === null)
490
620
  return;
491
621
  const currentAction = this.animations[this.currentAnimation];
@@ -506,7 +636,7 @@ var CharacterModel = class {
506
636
  };
507
637
 
508
638
  // src/character/LocalController.ts
509
- import { Box3, Line3, Matrix4, Quaternion, Vector3 as Vector33 } from "three";
639
+ import { Box3, Line3, Matrix4, Quaternion, Raycaster, Vector3 as Vector33 } from "three";
510
640
  var LocalController = class {
511
641
  constructor(model, id, collisionsManager, keyInputManager, cameraManager, timeManager) {
512
642
  this.model = model;
@@ -520,10 +650,20 @@ var LocalController = class {
520
650
  radius: 0.4,
521
651
  segment: new Line3(new Vector33(), new Vector33(0, 1.05, 0))
522
652
  });
653
+ __publicField(this, "maxWalkSpeed", 6);
654
+ __publicField(this, "maxRunSpeed", 8.5);
655
+ __publicField(this, "gravity", -42);
656
+ __publicField(this, "jumpForce", 16);
657
+ __publicField(this, "coyoteTimeThreshold", 290);
658
+ __publicField(this, "coyoteTime", false);
659
+ __publicField(this, "canJump", true);
523
660
  __publicField(this, "characterOnGround", false);
661
+ __publicField(this, "characterWasOnGround", false);
662
+ __publicField(this, "characterAirborneSince", 0);
663
+ __publicField(this, "currentHeight", 0);
524
664
  __publicField(this, "characterVelocity", new Vector33());
525
- __publicField(this, "gravity", -20);
526
- __publicField(this, "upVector", new Vector33(0, 1, 0));
665
+ __publicField(this, "vectorUp", new Vector33(0, 1, 0));
666
+ __publicField(this, "vectorDown", new Vector33(0, -1, 0));
527
667
  __publicField(this, "rotationOffset", 0);
528
668
  __publicField(this, "azimuthalAngle", 0);
529
669
  __publicField(this, "tempBox", new Box3());
@@ -531,16 +671,15 @@ var LocalController = class {
531
671
  __publicField(this, "tempSegment", new Line3());
532
672
  __publicField(this, "tempVector", new Vector33());
533
673
  __publicField(this, "tempVector2", new Vector33());
534
- __publicField(this, "jumpInput", false);
535
- __publicField(this, "jumpForce", 10);
536
- __publicField(this, "canJump", true);
537
- __publicField(this, "inputDirections", {
538
- forward: false,
539
- backward: false,
540
- left: false,
541
- right: false
542
- });
543
- __publicField(this, "runInput", false);
674
+ __publicField(this, "rayCaster", new Raycaster());
675
+ __publicField(this, "forward");
676
+ __publicField(this, "backward");
677
+ __publicField(this, "left");
678
+ __publicField(this, "right");
679
+ __publicField(this, "run");
680
+ __publicField(this, "jump");
681
+ __publicField(this, "anyDirection");
682
+ __publicField(this, "conflictingDirections");
544
683
  __publicField(this, "thirdPersonCamera", null);
545
684
  __publicField(this, "speed", 0);
546
685
  __publicField(this, "targetSpeed", 0);
@@ -550,65 +689,72 @@ var LocalController = class {
550
689
  rotation: { quaternionY: 0, quaternionW: 0 },
551
690
  state: 0 /* idle */
552
691
  });
692
+ setInterval(() => this.update.bind(this), 3e3);
553
693
  }
554
694
  update() {
555
695
  if (!this.model?.mesh || !this.model?.animationMixer)
556
696
  return;
557
697
  if (!this.thirdPersonCamera)
558
698
  this.thirdPersonCamera = this.cameraManager.camera;
559
- const movementKeysPressed = this.keyInputManager.isMovementKeyPressed();
560
- const forward = this.keyInputManager.isKeyPressed("w");
561
- const backward = this.keyInputManager.isKeyPressed("s");
562
- const left = this.keyInputManager.isKeyPressed("a");
563
- const right = this.keyInputManager.isKeyPressed("d");
564
- this.inputDirections = { forward, backward, left, right };
565
- this.jumpInput = this.keyInputManager.isJumping();
566
- this.runInput = this.keyInputManager.isShiftPressed();
567
- if (movementKeysPressed) {
699
+ const { forward, backward, left, right, run, jump, anyDirection, conflictingDirection } = this.keyInputManager;
700
+ this.forward = forward;
701
+ this.backward = backward;
702
+ this.left = left;
703
+ this.right = right;
704
+ this.run = run;
705
+ this.jump = jump;
706
+ this.anyDirection = anyDirection;
707
+ this.conflictingDirections = conflictingDirection;
708
+ this.targetSpeed = this.run ? this.maxRunSpeed : this.maxWalkSpeed;
709
+ this.speed += ease(this.targetSpeed, this.speed, 0.07);
710
+ this.rayCaster.set(this.model.mesh.position, this.vectorDown);
711
+ const hit = this.rayCaster.intersectObjects([this.collisionsManager.colliders]);
712
+ if (hit.length > 0)
713
+ this.currentHeight = hit[0].distance;
714
+ if (anyDirection || !this.characterOnGround) {
568
715
  const targetAnimation = this.getTargetAnimation();
569
716
  this.model.updateAnimation(targetAnimation, this.timeManager.deltaTime);
570
717
  } else {
571
718
  this.model.updateAnimation(0 /* idle */, this.timeManager.deltaTime);
572
719
  }
573
- if (Object.values(this.inputDirections).some((v) => v)) {
720
+ if (this.anyDirection)
574
721
  this.updateRotation();
575
- }
576
722
  for (let i = 0; i < this.collisionDetectionSteps; i++) {
577
723
  this.updatePosition(this.timeManager.deltaTime / this.collisionDetectionSteps, i);
578
724
  }
579
- if (this.model.mesh.position.y < 0) {
725
+ if (this.model.mesh.position.y < 0)
580
726
  this.resetPosition();
581
- }
582
727
  this.updateNetworkState();
583
728
  }
584
729
  getTargetAnimation() {
585
- const { forward, backward, left, right } = this.inputDirections;
586
- const hasAnyDirection = forward || backward || left || right;
587
- const isRunning = this.runInput && hasAnyDirection;
588
- const conflictingDirections = forward && backward || left && right;
589
- if (conflictingDirections)
730
+ if (!this.model.mesh)
731
+ return 0 /* idle */;
732
+ if (this.conflictingDirections)
590
733
  return 0 /* idle */;
591
- return hasAnyDirection ? isRunning ? 2 /* running */ : 1 /* walking */ : 0 /* idle */;
734
+ const jumpHeight = this.characterVelocity.y > 0 ? 0.2 : 1.8;
735
+ if (this.currentHeight > jumpHeight && !this.characterOnGround) {
736
+ return 4 /* air */;
737
+ }
738
+ return this.run && this.anyDirection ? 2 /* running */ : this.anyDirection ? 1 /* walking */ : 0 /* idle */;
592
739
  }
593
740
  updateRotationOffset() {
594
- const { forward, backward, left, right } = this.inputDirections;
595
- if (left && right || forward && backward)
741
+ if (this.conflictingDirections)
596
742
  return;
597
- if (forward) {
743
+ if (this.forward) {
598
744
  this.rotationOffset = Math.PI;
599
- if (left)
745
+ if (this.left)
600
746
  this.rotationOffset = Math.PI + Math.PI / 4;
601
- if (right)
747
+ if (this.right)
602
748
  this.rotationOffset = Math.PI - Math.PI / 4;
603
- } else if (backward) {
749
+ } else if (this.backward) {
604
750
  this.rotationOffset = Math.PI * 2;
605
- if (left)
751
+ if (this.left)
606
752
  this.rotationOffset = -Math.PI * 2 - Math.PI / 4;
607
- if (right)
753
+ if (this.right)
608
754
  this.rotationOffset = Math.PI * 2 + Math.PI / 4;
609
- } else if (left) {
755
+ } else if (this.left) {
610
756
  this.rotationOffset = Math.PI * -0.5;
611
- } else if (right) {
757
+ } else if (this.right) {
612
758
  this.rotationOffset = Math.PI * 0.5;
613
759
  }
614
760
  }
@@ -626,7 +772,7 @@ var LocalController = class {
626
772
  this.updateRotationOffset();
627
773
  this.updateAzimuthalAngle();
628
774
  const rotationQuaternion = new Quaternion();
629
- rotationQuaternion.setFromAxisAngle(this.upVector, this.azimuthalAngle + this.rotationOffset);
775
+ rotationQuaternion.setFromAxisAngle(this.vectorUp, this.azimuthalAngle + this.rotationOffset);
630
776
  this.model.mesh.quaternion.rotateTowards(rotationQuaternion, 0.07);
631
777
  }
632
778
  addScaledVectorToCharacter(deltaTime) {
@@ -637,36 +783,43 @@ var LocalController = class {
637
783
  updatePosition(deltaTime, _iter) {
638
784
  if (!this.model?.mesh)
639
785
  return;
640
- const { forward, backward, left, right } = this.inputDirections;
641
- this.targetSpeed = this.runInput ? 14 : 8;
642
- this.speed += ease(this.targetSpeed, this.speed, 0.07);
643
786
  if (this.characterOnGround) {
644
- this.canJump = true;
645
- if (this.jumpInput && this.canJump) {
787
+ if (!this.jump)
788
+ this.canJump = true;
789
+ if (this.jump && this.canJump) {
646
790
  this.characterVelocity.y += this.jumpForce;
647
791
  this.canJump = false;
648
792
  } else {
649
793
  this.characterVelocity.y = deltaTime * this.gravity;
650
794
  }
795
+ } else if (this.jump && this.coyoteTime) {
796
+ console.log("coyoteJump");
797
+ this.characterVelocity.y = this.jumpForce;
798
+ this.canJump = false;
651
799
  } else {
652
800
  this.characterVelocity.y += deltaTime * this.gravity;
653
801
  this.canJump = false;
654
802
  }
655
803
  this.model.mesh.position.addScaledVector(this.characterVelocity, deltaTime);
656
- if (forward) {
657
- this.tempVector.set(0, 0, -1).applyAxisAngle(this.upVector, this.azimuthalAngle);
658
- this.addScaledVectorToCharacter(deltaTime);
804
+ this.tempVector.set(0, 0, 0);
805
+ if (this.forward) {
806
+ const forward = new Vector33(0, 0, -1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
807
+ this.tempVector.add(forward);
659
808
  }
660
- if (backward) {
661
- this.tempVector.set(0, 0, 1).applyAxisAngle(this.upVector, this.azimuthalAngle);
662
- this.addScaledVectorToCharacter(deltaTime);
809
+ if (this.backward) {
810
+ const backward = new Vector33(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
811
+ this.tempVector.add(backward);
663
812
  }
664
- if (left) {
665
- this.tempVector.set(-1, 0, 0).applyAxisAngle(this.upVector, this.azimuthalAngle);
666
- this.addScaledVectorToCharacter(deltaTime);
813
+ if (this.left) {
814
+ const left = new Vector33(-1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
815
+ this.tempVector.add(left);
816
+ }
817
+ if (this.right) {
818
+ const right = new Vector33(1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
819
+ this.tempVector.add(right);
667
820
  }
668
- if (right) {
669
- this.tempVector.set(1, 0, 0).applyAxisAngle(this.upVector, this.azimuthalAngle);
821
+ if (this.tempVector.length() > 0) {
822
+ this.tempVector.normalize();
670
823
  this.addScaledVectorToCharacter(deltaTime);
671
824
  }
672
825
  this.model.mesh.updateMatrixWorld();
@@ -687,6 +840,11 @@ var LocalController = class {
687
840
  deltaVector.normalize().multiplyScalar(offset);
688
841
  this.model.mesh.position.add(deltaVector);
689
842
  this.characterOnGround = deltaVector.y > Math.abs(deltaTime * this.characterVelocity.y * 0.25);
843
+ if (this.characterWasOnGround && !this.characterOnGround) {
844
+ this.characterAirborneSince = Date.now();
845
+ }
846
+ this.coyoteTime = this.characterVelocity.y < 0 && this.characterOnGround === false && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
847
+ this.characterWasOnGround = this.characterOnGround;
690
848
  if (this.characterOnGround) {
691
849
  this.characterVelocity.set(0, 0, 0);
692
850
  } else {
@@ -714,7 +872,7 @@ var LocalController = class {
714
872
  if (!this.model?.mesh)
715
873
  return;
716
874
  this.characterVelocity.y = 0;
717
- this.model.mesh.position.y = 5;
875
+ this.model.mesh.position.y = 3;
718
876
  this.characterOnGround = false;
719
877
  }
720
878
  };
@@ -770,24 +928,19 @@ import { Group, Vector3 as Vector36 } from "three";
770
928
  // src/character/RemoteController.ts
771
929
  import {
772
930
  AnimationMixer as AnimationMixer2,
773
- LoadingManager as LoadingManager2,
774
931
  Object3D as Object3D3,
775
932
  Quaternion as Quaternion2,
776
933
  Vector3 as Vector35
777
934
  } from "three";
778
- import { FBXLoader as FBXLoader2 } from "three/examples/jsm/loaders/FBXLoader.js";
779
- import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
780
935
  var RemoteController = class {
781
936
  constructor(character, id) {
782
937
  this.character = character;
783
938
  this.id = id;
939
+ __publicField(this, "modelLoader", ModelLoader_default);
784
940
  __publicField(this, "characterModel", null);
785
- __publicField(this, "loadManager", new LoadingManager2());
786
941
  __publicField(this, "animationMixer", new AnimationMixer2(new Object3D3()));
787
942
  __publicField(this, "animations", /* @__PURE__ */ new Map());
788
943
  __publicField(this, "currentAnimation", 0 /* idle */);
789
- __publicField(this, "fbxLoader", new FBXLoader2(this.loadManager));
790
- __publicField(this, "gltfLoader", new GLTFLoader2(this.loadManager));
791
944
  __publicField(this, "networkState", {
792
945
  id: 0,
793
946
  position: { x: 0, y: 0, z: 0 },
@@ -795,6 +948,7 @@ var RemoteController = class {
795
948
  state: this.currentAnimation
796
949
  });
797
950
  this.characterModel = this.character.model.mesh;
951
+ this.characterModel.updateMatrixWorld();
798
952
  this.animationMixer = new AnimationMixer2(this.characterModel);
799
953
  }
800
954
  update(clientUpdate, time, deltaTime) {
@@ -804,44 +958,15 @@ var RemoteController = class {
804
958
  this.updateFromNetwork(clientUpdate);
805
959
  this.animationMixer.update(deltaTime);
806
960
  }
807
- setAnimationFromFile(animationType, fileName) {
808
- const animationFile = `${fileName}`;
809
- const extension = fileName.split(".").pop();
810
- if (typeof extension !== "string") {
811
- console.error(`Error: could not recognize extension of animation: ${animationFile}`);
812
- return;
813
- }
814
- if (["gltf", "glb"].includes(extension)) {
815
- this.gltfLoader.load(
816
- animationFile,
817
- (anim) => {
818
- const animation = anim.animations[0];
819
- const animationAction = this.animationMixer.clipAction(animation);
820
- this.animations.set(animationType, animationAction);
821
- if (animationType === 0 /* idle */) {
822
- animationAction.play();
823
- }
824
- },
825
- void 0,
826
- (error) => console.error(`Error loading ${animationFile}: ${error}`)
827
- );
828
- } else if (["fbx"].includes(extension)) {
829
- this.fbxLoader.load(
830
- animationFile,
831
- (anim) => {
832
- const animation = anim.animations[0];
833
- const animationAction = this.animationMixer.clipAction(animation);
834
- this.animations.set(animationType, animationAction);
835
- if (animationType === 0 /* idle */) {
836
- animationAction.play();
837
- }
838
- },
839
- void 0,
840
- (error) => console.error(`Error loading ${animationFile}: ${error}`)
841
- );
961
+ async setAnimationFromFile(animationType, fileName) {
962
+ const animation = await this.modelLoader.load(fileName, "animation");
963
+ const animationAction = this.animationMixer.clipAction(animation);
964
+ this.animations.set(animationType, animationAction);
965
+ if (animationType === 0 /* idle */) {
966
+ animationAction.play();
842
967
  }
843
968
  }
844
- transitionToAnimation(targetAnimation, transitionDuration = 0.21) {
969
+ transitionToAnimation(targetAnimation, transitionDuration = 0.15) {
845
970
  if (this.currentAnimation === targetAnimation)
846
971
  return;
847
972
  const currentAction = this.animations.get(this.currentAnimation);
@@ -861,9 +986,9 @@ var RemoteController = class {
861
986
  if (!this.characterModel)
862
987
  return;
863
988
  const { position, rotation, state } = clientUpdate;
864
- this.characterModel.position.lerp(new Vector35(position.x, position.y, position.z), 0.2);
989
+ this.characterModel.position.lerp(new Vector35(position.x, position.y, position.z), 0.15);
865
990
  const rotationQuaternion = new Quaternion2(0, rotation.quaternionY, 0, rotation.quaternionW);
866
- this.characterModel.quaternion.slerp(rotationQuaternion, 0.2);
991
+ this.characterModel.quaternion.slerp(rotationQuaternion, 0.6);
867
992
  if (state !== this.currentAnimation) {
868
993
  this.transitionToAnimation(state);
869
994
  }
@@ -907,8 +1032,15 @@ var CharacterManager = class {
907
1032
  __publicField(this, "character", null);
908
1033
  __publicField(this, "group");
909
1034
  this.group = new Group();
910
- }
911
- spawnCharacter(characterDescription, id, isLocal = false) {
1035
+ setInterval(() => this.update.bind(this), 3e3);
1036
+ }
1037
+ /* TODO:
1038
+ 1) Separate this method into spawnLocalCharacter and spawnRemoteCharacter
1039
+ 2) Make this synchronous to avoid having loadingCharacters and instead manage
1040
+ the mesh loading async (would allow us to show a nameplate where a remote
1041
+ user is before the asset loads).
1042
+ */
1043
+ spawnCharacter(characterDescription, id, isLocal = false, spawnPosition = new Vector36()) {
912
1044
  this.characterDescription = characterDescription;
913
1045
  const characterLoadingPromise = new Promise((resolve) => {
914
1046
  const character = new Character(
@@ -923,15 +1055,26 @@ var CharacterManager = class {
923
1055
  this.cameraManager.camera
924
1056
  );
925
1057
  } else {
926
- const spawnPosition = getSpawnPositionInsideCircle(3, 30, id);
1058
+ spawnPosition = getSpawnPositionInsideCircle(3, 30, id, 0.4);
927
1059
  character.model.mesh.position.set(spawnPosition.x, spawnPosition.y, spawnPosition.z);
928
- this.cameraManager.camera.position.set(
929
- spawnPosition.x,
930
- spawnPosition.y + 1.5,
931
- spawnPosition.z + 3
932
- );
1060
+ character.model.mesh.updateMatrixWorld();
1061
+ this.sendUpdate({
1062
+ id,
1063
+ position: {
1064
+ x: spawnPosition.x,
1065
+ y: spawnPosition.y,
1066
+ z: spawnPosition.z
1067
+ },
1068
+ rotation: { quaternionY: 0, quaternionW: 0 },
1069
+ state: 0 /* idle */
1070
+ });
1071
+ }
1072
+ character.model.hideMaterialByMeshName("SK_Mannequin_2");
1073
+ if (!isLocal) {
1074
+ character.model?.mesh?.position.set(spawnPosition.x, spawnPosition.y, spawnPosition.z);
1075
+ character.model?.mesh?.updateMatrixWorld();
1076
+ character.position.set(spawnPosition.x, spawnPosition.y, spawnPosition.z);
933
1077
  }
934
- character.model.hideMaterialByMeshName("SK_UE5Mannequin_1");
935
1078
  this.group.add(character.model.mesh);
936
1079
  if (isLocal) {
937
1080
  this.character = character;
@@ -950,6 +1093,15 @@ var CharacterManager = class {
950
1093
  2 /* running */,
951
1094
  characterDescription.sprintAnimationFileUrl
952
1095
  );
1096
+ remoteController.setAnimationFromFile(
1097
+ 4 /* air */,
1098
+ characterDescription.airAnimationFileUrl
1099
+ );
1100
+ remoteController.characterModel?.position.set(
1101
+ spawnPosition.x,
1102
+ spawnPosition.y,
1103
+ spawnPosition.z
1104
+ );
953
1105
  this.remoteCharacterControllers.set(id, remoteController);
954
1106
  }
955
1107
  resolve(character);
@@ -998,8 +1150,14 @@ var CharacterManager = class {
998
1150
  }
999
1151
  }
1000
1152
  for (const [id, update] of this.clientStates) {
1153
+ const { position } = update;
1001
1154
  if (!this.remoteCharacters.has(id) && !this.loadingCharacters.has(id)) {
1002
- this.spawnCharacter(this.characterDescription, id).then(() => {
1155
+ this.spawnCharacter(
1156
+ this.characterDescription,
1157
+ id,
1158
+ false,
1159
+ new Vector36(position.x, position.y, position.z)
1160
+ ).then((_character) => {
1003
1161
  this.loadingCharacters.delete(id);
1004
1162
  });
1005
1163
  }
@@ -1031,6 +1189,10 @@ var KeyInputManager = class {
1031
1189
  __publicField(this, "keys", /* @__PURE__ */ new Map());
1032
1190
  document.addEventListener("keydown", this.onKeyDown.bind(this));
1033
1191
  document.addEventListener("keyup", this.onKeyUp.bind(this));
1192
+ window.addEventListener("blur", this.handleUnfocus.bind(this));
1193
+ }
1194
+ handleUnfocus(_event) {
1195
+ this.keys.clear();
1034
1196
  }
1035
1197
  onKeyDown(event) {
1036
1198
  this.keys.set(event.key.toLowerCase(), true);
@@ -1044,15 +1206,34 @@ var KeyInputManager = class {
1044
1206
  isMovementKeyPressed() {
1045
1207
  return ["w", "a", "s", "d"].some((key) => this.isKeyPressed(key));
1046
1208
  }
1047
- isShiftPressed() {
1209
+ get forward() {
1210
+ return this.isKeyPressed("w");
1211
+ }
1212
+ get backward() {
1213
+ return this.isKeyPressed("s");
1214
+ }
1215
+ get left() {
1216
+ return this.isKeyPressed("a");
1217
+ }
1218
+ get right() {
1219
+ return this.isKeyPressed("d");
1220
+ }
1221
+ get run() {
1048
1222
  return this.isKeyPressed("shift");
1049
1223
  }
1050
- isJumping() {
1224
+ get jump() {
1051
1225
  return this.isKeyPressed(" ");
1052
1226
  }
1227
+ get anyDirection() {
1228
+ return this.isMovementKeyPressed();
1229
+ }
1230
+ get conflictingDirection() {
1231
+ return this.isKeyPressed("w") && this.isKeyPressed("s") || this.isKeyPressed("a") && this.isKeyPressed("d");
1232
+ }
1053
1233
  dispose() {
1054
1234
  document.removeEventListener("keydown", this.onKeyDown.bind(this));
1055
1235
  document.removeEventListener("keyup", this.onKeyDown.bind(this));
1236
+ window.removeEventListener("blur", this.handleUnfocus.bind(this));
1056
1237
  }
1057
1238
  };
1058
1239
 
@@ -1133,14 +1314,125 @@ import {
1133
1314
  EffectPass,
1134
1315
  FXAAEffect,
1135
1316
  ShaderPass,
1136
- BloomEffect
1317
+ BloomEffect,
1318
+ SSAOEffect,
1319
+ NormalPass,
1320
+ BlendFunction as BlendFunction2,
1321
+ TextureEffect
1137
1322
  } from "postprocessing";
1138
1323
  import {
1139
- ACESFilmicToneMapping,
1140
- PCFSoftShadowMap,
1324
+ Color as Color4,
1325
+ LinearSRGBColorSpace,
1326
+ LoadingManager as LoadingManager2,
1327
+ PMREMGenerator,
1141
1328
  Vector2 as Vector22,
1142
1329
  WebGLRenderer as WebGLRenderer2
1143
1330
  } from "three";
1331
+ import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
1332
+ import { Pane } from "tweakpane";
1333
+
1334
+ // src/rendering/composerSettings.ts
1335
+ import { BlendFunction } from "postprocessing";
1336
+ import { Color as Color3 } from "three";
1337
+ var composerValues = {
1338
+ renderer: {
1339
+ shadowMap: 2,
1340
+ toneMapping: 1,
1341
+ exposure: 0.75,
1342
+ bgIntensity: 0.6,
1343
+ bgBlurriness: 0
1344
+ },
1345
+ ssao: {
1346
+ blendFunction: BlendFunction.MULTIPLY,
1347
+ distanceScaling: true,
1348
+ depthAwareUpsampling: true,
1349
+ samples: 50,
1350
+ rings: 11,
1351
+ luminanceInfluence: 0.3,
1352
+ radius: 0.07,
1353
+ intensity: 3,
1354
+ bias: 0.03,
1355
+ fade: 0.03,
1356
+ resolutionScale: 1,
1357
+ color: new Color3(0),
1358
+ worldDistanceThreshold: 200,
1359
+ worldDistanceFalloff: 2,
1360
+ worldProximityThreshold: 100,
1361
+ worldProximityFalloff: 2
1362
+ },
1363
+ grain: 0.04,
1364
+ bloom: 0.7
1365
+ };
1366
+ var composerOptions = {
1367
+ renderer: {
1368
+ shadowMap: { min: 0, max: 2, step: 1 },
1369
+ toneMapping: { min: 0, max: 5, step: 1 },
1370
+ exposure: { min: 0, max: 1, step: 0.01 },
1371
+ bgIntensity: { min: 0, max: 1, step: 0.01 },
1372
+ bgBlurriness: { min: 0, max: 0.1, step: 1e-3 }
1373
+ },
1374
+ ssao: {
1375
+ samples: { min: 1, max: 50, step: 1 },
1376
+ rings: { min: 1, max: 50, step: 1 },
1377
+ luminanceInfluence: { min: 0, max: 1, step: 0.01 },
1378
+ radius: { min: 0, max: 0.1, step: 1e-3 },
1379
+ intensity: { min: 0, max: 5, step: 0.1 },
1380
+ bias: { min: 0, max: 0.1, step: 1e-3 },
1381
+ fade: { min: 0, max: 0.1, step: 1e-3 },
1382
+ resolutionScale: { min: 0.25, max: 2, step: 0.25 },
1383
+ worldDistanceThreshold: { min: 0, max: 200, step: 1 },
1384
+ worldDistanceFalloff: { min: 0, max: 200, step: 1 },
1385
+ worldProximityThreshold: { min: 0, max: 2, step: 0.01 },
1386
+ worldProximityFalloff: { min: 0, max: 2, step: 0.01 }
1387
+ },
1388
+ grain: {
1389
+ amount: { min: 0, max: 0.2, step: 2e-3 }
1390
+ },
1391
+ bloom: {
1392
+ amount: { min: 0, max: 4, step: 0.1 }
1393
+ }
1394
+ };
1395
+ var shadowMapTypes = {
1396
+ 0: "BasicShadowMap",
1397
+ 1: "PCFShadowMap",
1398
+ 2: "PCFSoftShadowMap"
1399
+ };
1400
+ var rendererToneMappingTypes = {
1401
+ 0: "NoToneMapping",
1402
+ 1: "LinearToneMapping",
1403
+ 2: "ReinhardToneMapping",
1404
+ 3: "CineonToneMapping",
1405
+ 4: "ACESFilmicToneMapping",
1406
+ 5: "CustomToneMapping"
1407
+ };
1408
+ var rendererBlades = {
1409
+ shadowMapType: shadowMapTypes[composerValues.renderer.shadowMap],
1410
+ toneMappingType: rendererToneMappingTypes[composerValues.renderer.toneMapping]
1411
+ };
1412
+ var setShadowMapType = (value) => {
1413
+ rendererBlades.shadowMapType = shadowMapTypes[value];
1414
+ };
1415
+ var setToneMappingType = (value) => {
1416
+ rendererBlades.toneMappingType = rendererToneMappingTypes[value];
1417
+ };
1418
+ var ssaoMaterialParams = [
1419
+ "fade",
1420
+ "bias",
1421
+ "minRadiusScale",
1422
+ "worldDistanceThreshold",
1423
+ "worldDistanceFalloff",
1424
+ "worldProximityThreshold",
1425
+ "worldProximityFalloff"
1426
+ ];
1427
+ var statsData = {
1428
+ triangles: "0",
1429
+ geometries: "0",
1430
+ textures: "0",
1431
+ shaders: "0",
1432
+ postPasses: "0",
1433
+ drawCalls: "0",
1434
+ FPS: "0"
1435
+ };
1144
1436
 
1145
1437
  // src/rendering/post-effects/gauss-grain.ts
1146
1438
  import { ShaderMaterial, Uniform, Vector2 } from "three";
@@ -1219,6 +1511,7 @@ var Composer = class {
1219
1511
  __publicField(this, "width", window.innerWidth);
1220
1512
  __publicField(this, "height", window.innerHeight);
1221
1513
  __publicField(this, "resolution", new Vector22(this.width, this.height));
1514
+ __publicField(this, "isEnvHDRI", false);
1222
1515
  __publicField(this, "scene");
1223
1516
  __publicField(this, "camera");
1224
1517
  __publicField(this, "renderer");
@@ -1228,8 +1521,19 @@ var Composer = class {
1228
1521
  __publicField(this, "fxaaPass");
1229
1522
  __publicField(this, "bloomEffect");
1230
1523
  __publicField(this, "bloomPass");
1524
+ __publicField(this, "normalPass");
1525
+ __publicField(this, "normalTextureEffect");
1526
+ __publicField(this, "ssaoEffect");
1527
+ __publicField(this, "ssaoPass");
1231
1528
  __publicField(this, "gaussGrainEffect", GaussGrainEffect);
1232
1529
  __publicField(this, "gaussGrainPass");
1530
+ __publicField(this, "gui", new Pane());
1531
+ __publicField(this, "guiVisible", false);
1532
+ __publicField(this, "stats", this.gui.addFolder({ title: "stats", expanded: true }));
1533
+ __publicField(this, "renderOptions", this.gui.addFolder({ title: "renderOptions", expanded: false }));
1534
+ __publicField(this, "ssao", this.gui.addFolder({ title: "ambientOcclusion", expanded: false }));
1535
+ __publicField(this, "post", this.gui.addFolder({ title: "post", expanded: false }));
1536
+ __publicField(this, "export", this.gui.addFolder({ title: "import/export", expanded: false }));
1233
1537
  this.scene = scene;
1234
1538
  this.camera = camera;
1235
1539
  this.renderer = new WebGLRenderer2({
@@ -1238,27 +1542,161 @@ var Composer = class {
1238
1542
  stencil: false,
1239
1543
  depth: false
1240
1544
  });
1545
+ this.renderer.info.autoReset = false;
1241
1546
  this.renderer.setSize(this.width, this.height);
1242
1547
  this.renderer.shadowMap.enabled = true;
1243
- this.renderer.shadowMap.type = PCFSoftShadowMap;
1244
- this.renderer.toneMapping = ACESFilmicToneMapping;
1245
- this.renderer.toneMappingExposure = 0.5;
1548
+ this.renderer.shadowMap.type = composerValues.renderer.shadowMap;
1549
+ this.renderer.toneMapping = composerValues.renderer.toneMapping;
1550
+ this.renderer.toneMappingExposure = 0.7;
1246
1551
  document.body.appendChild(this.renderer.domElement);
1247
1552
  this.composer = new EffectComposer(this.renderer);
1248
1553
  this.renderPass = new RenderPass(this.scene, this.camera);
1554
+ this.normalPass = new NormalPass(this.scene, this.camera);
1555
+ this.normalTextureEffect = new TextureEffect({
1556
+ blendFunction: BlendFunction2.SKIP,
1557
+ texture: this.normalPass.texture
1558
+ });
1249
1559
  this.fxaaEffect = new FXAAEffect();
1560
+ this.bloomEffect = new BloomEffect({
1561
+ intensity: composerValues.bloom
1562
+ });
1563
+ this.ssaoEffect = new SSAOEffect(this.camera, this.normalPass.texture, {
1564
+ ...composerValues.ssao
1565
+ });
1250
1566
  this.fxaaPass = new EffectPass(this.camera, this.fxaaEffect);
1251
- this.bloomEffect = new BloomEffect();
1252
1567
  this.bloomPass = new EffectPass(this.camera, this.bloomEffect);
1568
+ this.ssaoPass = new EffectPass(this.camera, this.ssaoEffect, this.normalTextureEffect);
1253
1569
  this.gaussGrainPass = new ShaderPass(this.gaussGrainEffect, "tDiffuse");
1254
1570
  this.composer.addPass(this.renderPass);
1571
+ this.composer.addPass(this.normalPass);
1572
+ this.composer.addPass(this.ssaoPass);
1255
1573
  this.composer.addPass(this.fxaaPass);
1256
1574
  this.composer.addPass(this.bloomPass);
1257
1575
  this.composer.addPass(this.gaussGrainPass);
1258
- window.addEventListener("resize", () => {
1259
- this.updateProjection();
1260
- });
1576
+ window.addEventListener("resize", () => this.updateProjection());
1577
+ window.addEventListener("keydown", this.processKey.bind(this));
1578
+ this.setupGUIListeners.bind(this)();
1261
1579
  this.updateProjection();
1580
+ this.setupTweakPane();
1581
+ }
1582
+ setupGUIListeners() {
1583
+ const gui = this.gui;
1584
+ const paneElement = gui.containerElem_;
1585
+ paneElement.style.display = this.guiVisible ? "unset" : "none";
1586
+ this.gui.element.addEventListener("mousedown", () => setTweakpaneActive(true));
1587
+ this.gui.element.addEventListener("mouseup", () => setTweakpaneActive(false));
1588
+ this.gui.element.addEventListener("mouseleave", () => setTweakpaneActive(false));
1589
+ }
1590
+ setupTweakPane() {
1591
+ this.stats.addMonitor(statsData, "triangles");
1592
+ this.stats.addMonitor(statsData, "geometries");
1593
+ this.stats.addMonitor(statsData, "textures");
1594
+ this.stats.addMonitor(statsData, "shaders");
1595
+ this.stats.addMonitor(statsData, "postPasses");
1596
+ this.stats.addMonitor(statsData, "drawCalls");
1597
+ this.stats.addMonitor(statsData, "FPS");
1598
+ this.renderOptions.addInput(composerValues.renderer, "shadowMap", composerOptions.renderer.shadowMap);
1599
+ this.renderOptions.addMonitor(rendererBlades, "shadowMapType");
1600
+ this.renderOptions.addInput(composerValues.renderer, "toneMapping", composerOptions.renderer.toneMapping);
1601
+ this.renderOptions.addMonitor(rendererBlades, "toneMappingType");
1602
+ this.renderOptions.addInput(composerValues.renderer, "exposure", composerOptions.renderer.exposure);
1603
+ this.renderOptions.addInput(composerValues.renderer, "bgIntensity", composerOptions.renderer.bgIntensity);
1604
+ this.renderOptions.addInput(composerValues.renderer, "bgBlurriness", composerOptions.renderer.bgBlurriness);
1605
+ this.renderOptions.on("change", (e) => {
1606
+ const target = e.target;
1607
+ switch (target.label) {
1608
+ case "shadowMap":
1609
+ this.renderer.shadowMap.type = e.value;
1610
+ setShadowMapType(e.value);
1611
+ break;
1612
+ case "toneMapping":
1613
+ this.renderer.toneMapping = e.value;
1614
+ setToneMappingType(e.value);
1615
+ break;
1616
+ case "exposure":
1617
+ this.renderer.toneMappingExposure = e.value;
1618
+ break;
1619
+ case "bgIntensity":
1620
+ this.scene.backgroundIntensity = e.value;
1621
+ break;
1622
+ case "bgBlurriness":
1623
+ this.scene.backgroundBlurriness = e.value;
1624
+ break;
1625
+ default:
1626
+ break;
1627
+ }
1628
+ });
1629
+ this.ssao.addInput({ showEffectOnly: false }, "showEffectOnly");
1630
+ this.ssao.addInput(composerValues.ssao, "samples", composerOptions.ssao.samples);
1631
+ this.ssao.addInput(composerValues.ssao, "rings", composerOptions.ssao.rings);
1632
+ this.ssao.addInput(composerValues.ssao, "luminanceInfluence", composerOptions.ssao.luminanceInfluence);
1633
+ this.ssao.addInput(composerValues.ssao, "radius", composerOptions.ssao.radius);
1634
+ this.ssao.addInput(composerValues.ssao, "intensity", composerOptions.ssao.intensity);
1635
+ this.ssao.addInput(composerValues.ssao, "bias", composerOptions.ssao.bias);
1636
+ this.ssao.addInput(composerValues.ssao, "fade", composerOptions.ssao.fade);
1637
+ this.ssao.addInput(composerValues.ssao, "resolutionScale", composerOptions.ssao.resolutionScale);
1638
+ this.ssao.addInput(composerValues.ssao, "worldDistanceThreshold", composerOptions.ssao.worldDistanceThreshold);
1639
+ this.ssao.addInput(composerValues.ssao, "worldDistanceFalloff", composerOptions.ssao.worldDistanceFalloff);
1640
+ this.ssao.addInput(composerValues.ssao, "worldProximityThreshold", composerOptions.ssao.worldProximityThreshold);
1641
+ this.ssao.addInput(composerValues.ssao, "worldProximityFalloff", composerOptions.ssao.worldProximityFalloff);
1642
+ this.ssao.addInput(composerValues.ssao, "color");
1643
+ this.ssao.on("change", (e) => {
1644
+ if (!e.presetKey) {
1645
+ return;
1646
+ }
1647
+ const preset = e.presetKey;
1648
+ if (preset === "showEffectOnly") {
1649
+ this.ssaoEffect.blendMode.blendFunction = e.value === true ? BlendFunction2.NORMAL : BlendFunction2.MULTIPLY;
1650
+ return;
1651
+ }
1652
+ if (preset === "resolutionScale") {
1653
+ this.ssaoEffect.resolution.scale = e.value;
1654
+ return;
1655
+ }
1656
+ if (ssaoMaterialParams.includes(e.presetKey)) {
1657
+ this.ssaoEffect.ssaoMaterial[preset] = e.value;
1658
+ return;
1659
+ }
1660
+ if (e.presetKey === "color") {
1661
+ this.ssaoEffect.color = new Color4().setRGB(
1662
+ e.value.r / 255,
1663
+ e.value.g / 255,
1664
+ e.value.b / 255
1665
+ );
1666
+ return;
1667
+ }
1668
+ this.ssaoEffect[preset] = e.value;
1669
+ });
1670
+ this.post.addInput(composerValues, "bloom", composerOptions.bloom.amount);
1671
+ this.post.addInput(composerValues, "grain", composerOptions.grain.amount);
1672
+ this.post.on("change", (e) => {
1673
+ const target = e.presetKey;
1674
+ console.log(target);
1675
+ switch (target) {
1676
+ case "bloom":
1677
+ this.bloomEffect.intensity = e.value;
1678
+ break;
1679
+ case "grain":
1680
+ this.gaussGrainEffect.uniforms.amount.value = e.value;
1681
+ break;
1682
+ default:
1683
+ break;
1684
+ }
1685
+ });
1686
+ const button = this.export.addButton({ title: "export" });
1687
+ button.on("click", () => {
1688
+ console.log(this.gui.exportPreset());
1689
+ });
1690
+ }
1691
+ toggleGUI() {
1692
+ const gui = this.gui;
1693
+ const paneElement = gui.containerElem_;
1694
+ paneElement.style.display = this.guiVisible ? "none" : "unset";
1695
+ this.guiVisible = !this.guiVisible;
1696
+ }
1697
+ processKey(e) {
1698
+ if (e.key === "p")
1699
+ this.toggleGUI();
1262
1700
  }
1263
1701
  updateProjection() {
1264
1702
  this.width = window.innerWidth;
@@ -1270,15 +1708,59 @@ var Composer = class {
1270
1708
  this.fxaaPass.setSize(this.width, this.height);
1271
1709
  if (this.renderPass)
1272
1710
  this.renderPass.setSize(this.width, this.height);
1711
+ if (this.bloomPass)
1712
+ this.bloomPass.setSize(this.width, this.height);
1713
+ if (this.ssaoPass)
1714
+ this.ssaoPass.setSize(this.width, this.height);
1715
+ if (this.normalPass)
1716
+ this.normalPass.setSize(this.width, this.height);
1273
1717
  this.renderer.setSize(this.width, this.height);
1274
1718
  }
1275
- render(time) {
1276
- this.composer.render();
1719
+ updateStats(timeManager) {
1720
+ const { geometries, textures } = this.renderer.info.memory;
1721
+ const { triangles, calls } = this.renderer.info.render;
1722
+ statsData.triangles = triangles.toString();
1723
+ statsData.geometries = geometries.toString();
1724
+ statsData.textures = textures.toString();
1725
+ statsData.shaders = this.renderer.info.programs.length.toString();
1726
+ statsData.postPasses = this.composer.passes.length.toString();
1727
+ statsData.drawCalls = calls.toString();
1728
+ statsData.FPS = Math.round(timeManager.averageFPS).toString();
1729
+ }
1730
+ render(timeManager) {
1731
+ this.renderer.info.reset();
1732
+ this.normalPass.texture.needsUpdate = true;
1277
1733
  this.gaussGrainEffect.uniforms.resolution.value = this.resolution;
1278
- this.gaussGrainEffect.uniforms.time.value = time;
1734
+ this.gaussGrainEffect.uniforms.time.value = timeManager.time;
1279
1735
  this.gaussGrainEffect.uniforms.alpha.value = 1;
1280
- this.gaussGrainEffect.uniforms.amount.value = 0.035;
1281
- this.bloomEffect.intensity = 1;
1736
+ this.composer.render();
1737
+ this.updateStats(timeManager);
1738
+ }
1739
+ useHDRI(url) {
1740
+ if (this.isEnvHDRI || !this.renderer)
1741
+ return;
1742
+ const pmremGenerator = new PMREMGenerator(this.renderer);
1743
+ new RGBELoader(new LoadingManager2()).load(
1744
+ url,
1745
+ (texture) => {
1746
+ const envMap = pmremGenerator.fromEquirectangular(texture).texture;
1747
+ if (envMap) {
1748
+ envMap.colorSpace = LinearSRGBColorSpace;
1749
+ envMap.needsUpdate = true;
1750
+ this.scene.environment = envMap;
1751
+ this.scene.background = envMap;
1752
+ this.scene.backgroundIntensity = 0.5;
1753
+ this.isEnvHDRI = true;
1754
+ texture.dispose();
1755
+ pmremGenerator.dispose();
1756
+ }
1757
+ },
1758
+ () => {
1759
+ },
1760
+ (error) => {
1761
+ console.error(`Can't load ${url}: ${JSON.stringify(error)}`);
1762
+ }
1763
+ );
1282
1764
  }
1283
1765
  };
1284
1766
 
@@ -1288,8 +1770,10 @@ var TimeManager = class {
1288
1770
  constructor() {
1289
1771
  __publicField(this, "clock", new Clock());
1290
1772
  __publicField(this, "roundMagnitude", 2e5);
1291
- __publicField(this, "maxAverageFrames", 300);
1773
+ __publicField(this, "maxAverageFrames", 700);
1292
1774
  __publicField(this, "deltaTimes", []);
1775
+ __publicField(this, "fpsMaxSamples", 10);
1776
+ __publicField(this, "fpsSamples", []);
1293
1777
  __publicField(this, "targetAverageDeltaTime", 0);
1294
1778
  __publicField(this, "lerpedAverageMagDelta", 0);
1295
1779
  __publicField(this, "fpsUpdateTime", 0);
@@ -1299,15 +1783,15 @@ var TimeManager = class {
1299
1783
  __publicField(this, "rawDeltaTime", 0);
1300
1784
  __publicField(this, "frame", 0);
1301
1785
  __publicField(this, "fps", 0);
1786
+ __publicField(this, "averageFPS", 0);
1302
1787
  }
1303
1788
  update() {
1304
1789
  this.rawDeltaTime = this.clock.getDelta();
1305
1790
  this.frame++;
1306
1791
  this.time += this.rawDeltaTime;
1307
1792
  this.deltaTimes.push(this.rawDeltaTime);
1308
- if (this.deltaTimes.length > this.maxAverageFrames) {
1793
+ if (this.deltaTimes.length > this.maxAverageFrames)
1309
1794
  this.deltaTimes.shift();
1310
- }
1311
1795
  this.targetAverageDeltaTime = this.deltaTimes.reduce((prev, curr) => prev + curr, 0) / this.deltaTimes.length;
1312
1796
  this.lerpedAverageMagDelta += ease(
1313
1797
  this.targetAverageDeltaTime * this.roundMagnitude,
@@ -1318,8 +1802,14 @@ var TimeManager = class {
1318
1802
  const smoothDT = Math.round(revertMagnitude * this.roundMagnitude) / this.roundMagnitude;
1319
1803
  this.deltaTime = smoothDT > this.rawDeltaTime * 1.75 ? this.rawDeltaTime : smoothDT;
1320
1804
  this.framesSinceLastFPSUpdate++;
1321
- if (this.framesSinceLastFPSUpdate >= this.maxAverageFrames) {
1805
+ if (this.framesSinceLastFPSUpdate >= 60) {
1322
1806
  this.fps = Math.round(this.framesSinceLastFPSUpdate / (this.time - this.fpsUpdateTime) * 100) / 100;
1807
+ this.fpsSamples.push(this.fps);
1808
+ if (this.fpsSamples.length > this.fpsMaxSamples)
1809
+ this.fpsSamples.shift();
1810
+ this.averageFPS = this.fpsSamples.length === this.fpsMaxSamples ? Math.round(
1811
+ this.fpsSamples.reduce((prev, curr) => prev + curr, 0) / this.fpsSamples.length
1812
+ ) : this.fps;
1323
1813
  this.fpsUpdateTime = this.time;
1324
1814
  this.framesSinceLastFPSUpdate = 0;
1325
1815
  }
@@ -1332,9 +1822,10 @@ import {
1332
1822
  getRelativePositionAndRotationRelativeToObject
1333
1823
  } from "mml-web";
1334
1824
  import {
1335
- Color as Color3,
1825
+ Color as Color5,
1336
1826
  Euler,
1337
1827
  FrontSide,
1828
+ Group as Group3,
1338
1829
  Mesh as Mesh2,
1339
1830
  MeshStandardMaterial as MeshStandardMaterial2,
1340
1831
  Vector3 as Vector37
@@ -1349,9 +1840,24 @@ var CollisionsManager = class {
1349
1840
  __publicField(this, "tempVector2", new Vector37());
1350
1841
  __publicField(this, "collisionMeshState", /* @__PURE__ */ new Map());
1351
1842
  __publicField(this, "collisionTrigger");
1843
+ __publicField(this, "colliders", new Group3());
1844
+ __publicField(this, "collisionEnabledMeshes", {});
1352
1845
  this.scene = scene;
1353
1846
  this.collisionTrigger = MMLCollisionTrigger.init();
1354
1847
  }
1848
+ safeAddColliders(child) {
1849
+ if (!(child.uuid in this.collisionEnabledMeshes)) {
1850
+ const clone = child.clone();
1851
+ this.collisionEnabledMeshes[child.uuid] = clone;
1852
+ this.colliders.add(clone);
1853
+ }
1854
+ }
1855
+ removeFromColliders(child) {
1856
+ if (child.uuid in this.collisionEnabledMeshes) {
1857
+ this.colliders.remove(this.collisionEnabledMeshes[child.uuid]);
1858
+ delete this.collisionEnabledMeshes[child.uuid];
1859
+ }
1860
+ }
1355
1861
  createCollisionMeshState(group) {
1356
1862
  const geometries = [];
1357
1863
  group.traverse((child) => {
@@ -1359,6 +1865,7 @@ var CollisionsManager = class {
1359
1865
  const mesh = child;
1360
1866
  mesh.localToWorld(new Vector37());
1361
1867
  mesh.updateMatrixWorld();
1868
+ this.safeAddColliders(mesh);
1362
1869
  const clonedGeometry = mesh.geometry.clone();
1363
1870
  clonedGeometry.applyMatrix4(mesh.matrixWorld);
1364
1871
  for (const key in clonedGeometry.attributes) {
@@ -1384,7 +1891,7 @@ var CollisionsManager = class {
1384
1891
  );
1385
1892
  mergedMesh.geometry.boundsTree = meshBVH;
1386
1893
  const visualizer = new MeshBVHVisualizer(mergedMesh, 3);
1387
- visualizer.edgeMaterial.color = new Color3(255);
1894
+ visualizer.edgeMaterial.color = new Color5(255);
1388
1895
  visualizer.update();
1389
1896
  return { source: group, visualizer, meshBVH };
1390
1897
  }
@@ -1419,6 +1926,13 @@ var CollisionsManager = class {
1419
1926
  this.scene.remove(meshState.visualizer);
1420
1927
  }
1421
1928
  this.collisionMeshState.delete(group);
1929
+ if (group) {
1930
+ group.traverse((child) => {
1931
+ if (child.type === "Mesh") {
1932
+ this.removeFromColliders(child);
1933
+ }
1934
+ });
1935
+ }
1422
1936
  }
1423
1937
  }
1424
1938
  applyCollider(tempSegment, radius, boundingBox, meshState) {