@mml-io/3d-web-client-core 0.0.0-experimental-8e124c7-20240520 → 0.0.0-experimental-ff88de3-20240530

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
@@ -246,7 +246,8 @@ var camValues = {
246
246
  invertFOVMapping: false,
247
247
  damping: 0.091,
248
248
  dampingScale: 0.01,
249
- zoomScale: 0.01
249
+ zoomScale: 0.05,
250
+ zoomDamping: 0.3
250
251
  };
251
252
  var camOptions = {
252
253
  initialDistance: { min: 1, max: 5, step: 0.1 },
@@ -257,7 +258,8 @@ var camOptions = {
257
258
  minFOV: { min: 50, max: 100, step: 1 },
258
259
  damping: { min: 0.01, max: 0.15, step: 0.01 },
259
260
  dampingScale: { min: 1e-3, max: 0.02, step: 1e-3 },
260
- zoomScale: { min: 5e-3, max: 0.025, step: 1e-3 }
261
+ zoomScale: { min: 5e-3, max: 0.3, step: 1e-3 },
262
+ zoomDamping: { min: 0, max: 2, step: 0.01 }
261
263
  };
262
264
  var CameraFolder = class {
263
265
  constructor(parentFolder, expand = false) {
@@ -277,6 +279,7 @@ var CameraFolder = class {
277
279
  this.folder.addBinding(camValues, "damping", camOptions.damping);
278
280
  this.folder.addBinding(camValues, "dampingScale", camOptions.dampingScale);
279
281
  this.folder.addBinding(camValues, "zoomScale", camOptions.zoomScale);
282
+ this.folder.addBinding(camValues, "zoomDamping", camOptions.zoomDamping);
280
283
  }
281
284
  setupChangeEvent(cameraManager) {
282
285
  this.folder.on("change", (e) => {
@@ -342,6 +345,11 @@ var CameraFolder = class {
342
345
  cameraManager.zoomScale = value;
343
346
  break;
344
347
  }
348
+ case "zoomDamping": {
349
+ const value = e.value;
350
+ cameraManager.zoomDamping = value;
351
+ break;
352
+ }
345
353
  default:
346
354
  break;
347
355
  }
@@ -375,6 +383,7 @@ var CameraManager = class {
375
383
  this.damping = camValues.damping;
376
384
  this.dampingScale = 0.01;
377
385
  this.zoomScale = camValues.zoomScale;
386
+ this.zoomDamping = camValues.zoomDamping;
378
387
  this.invertFOVMapping = camValues.invertFOVMapping;
379
388
  this.fov = this.initialFOV;
380
389
  this.targetFOV = this.initialFOV;
@@ -400,7 +409,7 @@ var CameraManager = class {
400
409
  this.targetPhi = initialPhi;
401
410
  this.theta = initialTheta;
402
411
  this.targetTheta = initialTheta;
403
- this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 300);
412
+ this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 400);
404
413
  this.camera.position.set(0, 1.4, -this.initialDistance);
405
414
  this.rayCaster = new Raycaster();
406
415
  this.hasTouchControl = VirtualJoystick.checkForTouch();
@@ -408,7 +417,8 @@ var CameraManager = class {
408
417
  [targetElement, "mousedown", this.onMouseDown.bind(this)],
409
418
  [document, "mouseup", this.onMouseUp.bind(this)],
410
419
  [document, "mousemove", this.onMouseMove.bind(this)],
411
- [targetElement, "wheel", this.onMouseWheel.bind(this)]
420
+ [targetElement, "wheel", this.onMouseWheel.bind(this)],
421
+ [targetElement, "contextmenu", this.onContextMenu.bind(this)]
412
422
  ]);
413
423
  if (this.hasTouchControl) {
414
424
  this.eventHandlerCollection.add(targetElement, "touchstart", this.onTouchStart.bind(this), {
@@ -458,21 +468,29 @@ var CameraManager = class {
458
468
  }
459
469
  }
460
470
  }
461
- onMouseDown() {
462
- this.dragging = true;
471
+ onMouseDown(event) {
472
+ if (event.button === 0 || event.button === 2) {
473
+ this.dragging = true;
474
+ document.body.style.cursor = "none";
475
+ }
463
476
  }
464
- onMouseUp(_event) {
465
- this.dragging = false;
477
+ onMouseUp(event) {
478
+ if (event.button === 0 || event.button === 2) {
479
+ this.dragging = false;
480
+ document.body.style.cursor = "default";
481
+ }
466
482
  }
467
483
  onMouseMove(event) {
468
- if (!this.dragging || getTweakpaneActive())
484
+ if (getTweakpaneActive())
469
485
  return;
470
- if (this.targetTheta === null || this.targetPhi === null)
471
- return;
472
- this.targetTheta += event.movementX * this.dampingScale;
473
- this.targetPhi -= event.movementY * this.dampingScale;
474
- this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
475
- event.preventDefault();
486
+ if (this.dragging) {
487
+ if (this.targetTheta === null || this.targetPhi === null)
488
+ return;
489
+ this.targetTheta += event.movementX * this.dampingScale;
490
+ this.targetPhi -= event.movementY * this.dampingScale;
491
+ this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
492
+ event.preventDefault();
493
+ }
476
494
  }
477
495
  onMouseWheel(event) {
478
496
  const scrollAmount = event.deltaY * this.zoomScale * 0.1;
@@ -484,6 +502,9 @@ var CameraManager = class {
484
502
  this.desiredDistance = this.targetDistance;
485
503
  event.preventDefault();
486
504
  }
505
+ onContextMenu(event) {
506
+ event.preventDefault();
507
+ }
487
508
  setTarget(target) {
488
509
  if (!this.isLerping) {
489
510
  this.target.copy(target);
@@ -562,7 +583,7 @@ var CameraManager = class {
562
583
  this.adjustCameraPosition();
563
584
  }
564
585
  if (this.phi !== null && this.targetPhi !== null && this.theta !== null && this.targetTheta !== null) {
565
- this.distance += (this.targetDistance - this.distance) * this.damping * 0.21;
586
+ this.distance += (this.targetDistance - this.distance) * this.damping * (0.21 + this.zoomDamping);
566
587
  this.phi += (this.targetPhi - this.phi) * this.damping;
567
588
  this.theta += (this.targetTheta - this.theta) * this.damping;
568
589
  const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
@@ -871,6 +892,7 @@ var AnimationState = /* @__PURE__ */ ((AnimationState2) => {
871
892
  AnimationState2[AnimationState2["jumpToAir"] = 3] = "jumpToAir";
872
893
  AnimationState2[AnimationState2["air"] = 4] = "air";
873
894
  AnimationState2[AnimationState2["airToGround"] = 5] = "airToGround";
895
+ AnimationState2[AnimationState2["doubleJump"] = 6] = "doubleJump";
874
896
  return AnimationState2;
875
897
  })(AnimationState || {});
876
898
 
@@ -884,27 +906,36 @@ var _CharacterModel = class _CharacterModel {
884
906
  this.animations = {};
885
907
  this.animationMixer = null;
886
908
  this.currentAnimation = 0 /* idle */;
909
+ this.isPostDoubleJump = false;
887
910
  }
888
911
  async init() {
889
912
  await this.loadMainMesh();
890
- await Promise.all([
891
- this.setAnimationFromFile(
892
- this.config.animationConfig.idleAnimationFileUrl,
893
- 0 /* idle */
894
- ),
895
- this.setAnimationFromFile(
896
- this.config.animationConfig.jogAnimationFileUrl,
897
- 1 /* walking */
898
- ),
899
- this.setAnimationFromFile(
900
- this.config.animationConfig.sprintAnimationFileUrl,
901
- 2 /* running */
902
- ),
903
- this.setAnimationFromFile(
904
- this.config.animationConfig.airAnimationFileUrl,
905
- 4 /* air */
906
- )
907
- ]);
913
+ await this.setAnimationFromFile(
914
+ this.config.animationConfig.idleAnimationFileUrl,
915
+ 0 /* idle */,
916
+ true
917
+ );
918
+ await this.setAnimationFromFile(
919
+ this.config.animationConfig.jogAnimationFileUrl,
920
+ 1 /* walking */,
921
+ true
922
+ );
923
+ await this.setAnimationFromFile(
924
+ this.config.animationConfig.sprintAnimationFileUrl,
925
+ 2 /* running */,
926
+ true
927
+ );
928
+ await this.setAnimationFromFile(
929
+ this.config.animationConfig.airAnimationFileUrl,
930
+ 4 /* air */,
931
+ true
932
+ );
933
+ await this.setAnimationFromFile(
934
+ this.config.animationConfig.doubleJumpAnimationFileUrl,
935
+ 6 /* doubleJump */,
936
+ false,
937
+ 1.3
938
+ );
908
939
  this.applyCustomMaterials();
909
940
  }
910
941
  applyCustomMaterials() {
@@ -941,6 +972,13 @@ var _CharacterModel = class _CharacterModel {
941
972
  });
942
973
  }
943
974
  updateAnimation(targetAnimation) {
975
+ if (this.isPostDoubleJump) {
976
+ if (targetAnimation === 6 /* doubleJump */) {
977
+ targetAnimation = 4 /* air */;
978
+ } else {
979
+ this.isPostDoubleJump = false;
980
+ }
981
+ }
944
982
  if (this.currentAnimation !== targetAnimation) {
945
983
  this.transitionToAnimation(targetAnimation);
946
984
  }
@@ -1031,16 +1069,21 @@ var _CharacterModel = class _CharacterModel {
1031
1069
  });
1032
1070
  return animationClip;
1033
1071
  }
1034
- async setAnimationFromFile(animationFileUrl, animationType) {
1072
+ async setAnimationFromFile(animationFileUrl, animationType, loop = true, playbackSpeed = 1) {
1035
1073
  return new Promise(async (resolve, reject) => {
1036
1074
  const animation = await this.config.characterModelLoader.load(animationFileUrl, "animation");
1037
1075
  const cleanAnimation = this.cleanAnimationClips(this.mesh, animation);
1038
1076
  if (typeof animation !== "undefined" && cleanAnimation instanceof AnimationClip) {
1039
1077
  this.animations[animationType] = this.animationMixer.clipAction(cleanAnimation);
1040
1078
  this.animations[animationType].stop();
1079
+ this.animations[animationType].timeScale = playbackSpeed;
1041
1080
  if (animationType === 0 /* idle */) {
1042
1081
  this.animations[animationType].play();
1043
1082
  }
1083
+ if (!loop) {
1084
+ this.animations[animationType].setLoop(LoopRepeat, 1);
1085
+ this.animations[animationType].clampWhenFinished = true;
1086
+ }
1044
1087
  resolve();
1045
1088
  } else {
1046
1089
  reject(`failed to load ${animationType} from ${animationFileUrl}`);
@@ -1048,20 +1091,30 @@ var _CharacterModel = class _CharacterModel {
1048
1091
  });
1049
1092
  }
1050
1093
  transitionToAnimation(targetAnimation, transitionDuration = 0.15) {
1051
- if (!this.mesh)
1094
+ if (!this.mesh) {
1052
1095
  return;
1096
+ }
1053
1097
  const currentAction = this.animations[this.currentAnimation];
1054
1098
  this.currentAnimation = targetAnimation;
1055
1099
  const targetAction = this.animations[targetAnimation];
1056
- if (!targetAction)
1100
+ if (!targetAction) {
1057
1101
  return;
1102
+ }
1058
1103
  if (currentAction) {
1059
- currentAction.enabled = true;
1060
1104
  currentAction.fadeOut(transitionDuration);
1061
1105
  }
1062
- if (!targetAction.isRunning())
1106
+ targetAction.reset();
1107
+ if (!targetAction.isRunning()) {
1063
1108
  targetAction.play();
1064
- targetAction.setLoop(LoopRepeat, Infinity);
1109
+ }
1110
+ if (targetAnimation === 6 /* doubleJump */) {
1111
+ targetAction.getMixer().addEventListener("finished", (_event) => {
1112
+ if (this.currentAnimation === 6 /* doubleJump */) {
1113
+ this.isPostDoubleJump = true;
1114
+ this.updateAnimation(6 /* doubleJump */);
1115
+ }
1116
+ });
1117
+ }
1065
1118
  targetAction.enabled = true;
1066
1119
  targetAction.fadeIn(transitionDuration);
1067
1120
  }
@@ -1473,15 +1526,189 @@ import { Euler as Euler2, Group as Group2, Quaternion as Quaternion5, Vector3 as
1473
1526
 
1474
1527
  // src/character/LocalController.ts
1475
1528
  import { Euler, Line3, Matrix4, Quaternion as Quaternion2, Ray, Raycaster as Raycaster2, Vector3 as Vector35 } from "three";
1529
+
1530
+ // src/tweakpane/blades/characterControlsFolder.ts
1531
+ var characterControllerValues = {
1532
+ gravity: 28,
1533
+ jumpForce: 18,
1534
+ doubleJumpForce: 17.7,
1535
+ coyoteJump: 120,
1536
+ airResistance: 0.5,
1537
+ groundResistance: 0,
1538
+ airControlModifier: 0.05,
1539
+ groundWalkControl: 0.75,
1540
+ groundRunControl: 1,
1541
+ baseControlMultiplier: 200,
1542
+ minimumSurfaceAngle: 0.905
1543
+ };
1544
+ var characterControllerOptions = {
1545
+ gravity: { min: 1, max: 100, step: 0.05 },
1546
+ jumpForce: { min: 1, max: 50, step: 0.05 },
1547
+ doubleJumpForce: { min: 1, max: 50, step: 0.05 },
1548
+ coyoteJump: { min: 60, max: 200, step: 1 },
1549
+ airResistance: { min: 0.01, max: 0.9, step: 0.01 },
1550
+ groundResistance: { min: -100, max: 0, step: 1 },
1551
+ airControlModifier: { min: 1e-3, max: 0.15, step: 0.01 },
1552
+ groundWalkControl: { min: 0.1, max: 1.5, step: 0.01 },
1553
+ groundRunControl: { min: 0.5, max: 2, step: 0.01 },
1554
+ baseControlMultiplier: { min: 150, max: 300, step: 1 },
1555
+ minimumSurfaceAngle: { min: 0.254, max: 1, step: 1e-3 }
1556
+ };
1557
+ var CharacterControlsFolder = class {
1558
+ constructor(parentFolder, expand = false) {
1559
+ this.characterData = {
1560
+ position: "(0, 0, 0)",
1561
+ onGround: "false",
1562
+ canJump: "false",
1563
+ canDoubleJump: "false",
1564
+ jumpCount: "0",
1565
+ coyoteTime: "false",
1566
+ coyoteJumped: "false"
1567
+ };
1568
+ this.folder = parentFolder.addFolder({ title: "character", expanded: expand });
1569
+ this.folder.addBinding(this.characterData, "position", { readonly: true });
1570
+ this.folder.addBinding(this.characterData, "onGround", { readonly: true });
1571
+ this.folder.addBinding(this.characterData, "canJump", { readonly: true });
1572
+ this.folder.addBinding(this.characterData, "canDoubleJump", { readonly: true });
1573
+ this.folder.addBinding(this.characterData, "jumpCount", { readonly: true });
1574
+ this.folder.addBinding(this.characterData, "coyoteTime", { readonly: true });
1575
+ this.folder.addBinding(this.characterData, "coyoteJumped", { readonly: true });
1576
+ this.folder.addBinding(
1577
+ characterControllerValues,
1578
+ "gravity",
1579
+ characterControllerOptions.gravity
1580
+ );
1581
+ this.folder.addBinding(
1582
+ characterControllerValues,
1583
+ "jumpForce",
1584
+ characterControllerOptions.jumpForce
1585
+ );
1586
+ this.folder.addBinding(
1587
+ characterControllerValues,
1588
+ "doubleJumpForce",
1589
+ characterControllerOptions.doubleJumpForce
1590
+ );
1591
+ this.folder.addBinding(
1592
+ characterControllerValues,
1593
+ "coyoteJump",
1594
+ characterControllerOptions.coyoteJump
1595
+ );
1596
+ this.folder.addBinding(
1597
+ characterControllerValues,
1598
+ "airResistance",
1599
+ characterControllerOptions.airResistance
1600
+ );
1601
+ this.folder.addBinding(
1602
+ characterControllerValues,
1603
+ "groundResistance",
1604
+ characterControllerOptions.groundResistance
1605
+ );
1606
+ this.folder.addBinding(
1607
+ characterControllerValues,
1608
+ "airControlModifier",
1609
+ characterControllerOptions.airControlModifier
1610
+ );
1611
+ this.folder.addBinding(
1612
+ characterControllerValues,
1613
+ "groundWalkControl",
1614
+ characterControllerOptions.groundWalkControl
1615
+ );
1616
+ this.folder.addBinding(
1617
+ characterControllerValues,
1618
+ "groundRunControl",
1619
+ characterControllerOptions.groundRunControl
1620
+ );
1621
+ this.folder.addBinding(
1622
+ characterControllerValues,
1623
+ "baseControlMultiplier",
1624
+ characterControllerOptions.baseControlMultiplier
1625
+ );
1626
+ this.folder.addBinding(
1627
+ characterControllerValues,
1628
+ "minimumSurfaceAngle",
1629
+ characterControllerOptions.minimumSurfaceAngle
1630
+ );
1631
+ }
1632
+ setupChangeEvent(localController) {
1633
+ this.folder.on("change", (e) => {
1634
+ const target = e.target.key;
1635
+ if (!target)
1636
+ return;
1637
+ switch (target) {
1638
+ case "gravity": {
1639
+ const value = e.value;
1640
+ localController.gravity = value * -1;
1641
+ break;
1642
+ }
1643
+ case "jumpForce": {
1644
+ const value = e.value;
1645
+ localController.jumpForce = value;
1646
+ break;
1647
+ }
1648
+ case "doubleJumpForce": {
1649
+ const value = e.value;
1650
+ localController.doubleJumpForce = value;
1651
+ break;
1652
+ }
1653
+ case "coyoteJump": {
1654
+ const value = e.value;
1655
+ localController.coyoteTimeThreshold = value;
1656
+ break;
1657
+ }
1658
+ case "airResistance": {
1659
+ const value = e.value;
1660
+ localController.airResistance = value;
1661
+ break;
1662
+ }
1663
+ case "groundResistance": {
1664
+ const value = e.value;
1665
+ localController.groundResistance = 0.99999999 + value * 1e-6;
1666
+ break;
1667
+ }
1668
+ case "airControlModifier": {
1669
+ const value = e.value;
1670
+ localController.airControlModifier = value;
1671
+ break;
1672
+ }
1673
+ case "groundWalkControl": {
1674
+ const value = e.value;
1675
+ localController.groundWalkControl = value;
1676
+ break;
1677
+ }
1678
+ case "groundRunControl": {
1679
+ const value = e.value;
1680
+ localController.groundRunControl = value;
1681
+ break;
1682
+ }
1683
+ case "baseControlMultiplier": {
1684
+ const value = e.value;
1685
+ localController.baseControl = value;
1686
+ break;
1687
+ }
1688
+ case "minimumSurfaceAngle": {
1689
+ const value = e.value;
1690
+ localController.minimumSurfaceAngle = value;
1691
+ break;
1692
+ }
1693
+ default:
1694
+ break;
1695
+ }
1696
+ });
1697
+ }
1698
+ update(localController) {
1699
+ const { x, y, z } = localController.latestPosition;
1700
+ this.characterData.position = `(${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)})`;
1701
+ this.characterData.onGround = `${localController.characterOnGround}`;
1702
+ this.characterData.canJump = `${localController.canJump || localController.coyoteTime ? "true" : "false"}`;
1703
+ this.characterData.canDoubleJump = `${localController.canDoubleJump}`;
1704
+ this.characterData.jumpCount = `${localController.jumpCounter}`;
1705
+ this.characterData.coyoteTime = `${localController.coyoteTime}`;
1706
+ this.characterData.coyoteJumped = `${localController.coyoteJumped}`;
1707
+ }
1708
+ };
1709
+
1710
+ // src/character/LocalController.ts
1476
1711
  var downVector = new Vector35(0, -1, 0);
1477
- var airResistance = 0.5;
1478
- var groundResistance = 0.99999999;
1479
- var airControlModifier = 0.05;
1480
- var groundWalkControl = 0.75;
1481
- var groundRunControl = 1;
1482
- var baseControl = 200;
1483
- var collisionDetectionSteps = 15;
1484
- var minimumSurfaceAngle = 0.9;
1485
1712
  var LocalController = class {
1486
1713
  constructor(config) {
1487
1714
  this.config = config;
@@ -1489,12 +1716,26 @@ var LocalController = class {
1489
1716
  radius: 0.4,
1490
1717
  segment: new Line3(new Vector35(), new Vector35(0, 1.05, 0))
1491
1718
  };
1492
- this.gravity = -42;
1493
- this.jumpForce = 20;
1494
- this.coyoteTimeThreshold = 70;
1495
- this.coyoteTime = false;
1719
+ this.gravity = -characterControllerValues.gravity;
1720
+ this.jumpForce = characterControllerValues.jumpForce;
1721
+ this.doubleJumpForce = characterControllerValues.doubleJumpForce;
1722
+ this.coyoteTimeThreshold = characterControllerValues.coyoteJump;
1496
1723
  this.canJump = true;
1724
+ this.canDoubleJump = true;
1725
+ this.coyoteJumped = false;
1726
+ this.doubleJumpUsed = false;
1727
+ this.jumpCounter = 0;
1728
+ this.airResistance = characterControllerValues.airResistance;
1729
+ this.groundResistance = 0.99999999 + characterControllerValues.groundResistance * 1e-7;
1730
+ this.airControlModifier = characterControllerValues.airControlModifier;
1731
+ this.groundWalkControl = characterControllerValues.groundWalkControl;
1732
+ this.groundRunControl = characterControllerValues.groundRunControl;
1733
+ this.baseControl = characterControllerValues.baseControlMultiplier;
1734
+ this.minimumSurfaceAngle = characterControllerValues.minimumSurfaceAngle;
1735
+ this.latestPosition = new Vector35();
1497
1736
  this.characterOnGround = false;
1737
+ this.coyoteTime = false;
1738
+ this.collisionDetectionSteps = 15;
1498
1739
  this.characterWasOnGround = false;
1499
1740
  this.characterAirborneSince = 0;
1500
1741
  this.currentHeight = 0;
@@ -1521,6 +1762,9 @@ var LocalController = class {
1521
1762
  this.surfaceTempVector5 = new Vector35();
1522
1763
  this.surfaceTempRay = new Ray();
1523
1764
  this.lastFrameSurfaceState = null;
1765
+ this.jumpPressed = false;
1766
+ // Tracks if the jump button is pressed
1767
+ this.jumpReleased = true;
1524
1768
  this.networkState = {
1525
1769
  id: this.config.id,
1526
1770
  position: { x: 0, y: 0, z: 0 },
@@ -1538,6 +1782,9 @@ var LocalController = class {
1538
1782
  this.jump = this.config.keyInputManager.jump;
1539
1783
  this.anyDirection = this.config.keyInputManager.anyDirection || ((_e = this.config.virtualJoystick) == null ? void 0 : _e.hasDirection) || false;
1540
1784
  this.conflictingDirections = this.config.keyInputManager.conflictingDirection;
1785
+ if (!this.jump) {
1786
+ this.jumpReleased = true;
1787
+ }
1541
1788
  }
1542
1789
  update() {
1543
1790
  this.updateControllerState();
@@ -1556,10 +1803,10 @@ var LocalController = class {
1556
1803
  if (this.anyDirection) {
1557
1804
  this.updateRotation();
1558
1805
  }
1559
- for (let i = 0; i < collisionDetectionSteps; i++) {
1806
+ for (let i = 0; i < this.collisionDetectionSteps; i++) {
1560
1807
  this.updatePosition(
1561
1808
  this.config.timeManager.deltaTime,
1562
- this.config.timeManager.deltaTime / collisionDetectionSteps,
1809
+ this.config.timeManager.deltaTime / this.collisionDetectionSteps,
1563
1810
  i
1564
1811
  );
1565
1812
  }
@@ -1573,6 +1820,9 @@ var LocalController = class {
1573
1820
  return 0 /* idle */;
1574
1821
  const jumpHeight = this.characterVelocity.y > 0 ? 0.2 : 1.8;
1575
1822
  if (this.currentHeight > jumpHeight && !this.characterOnGround) {
1823
+ if (this.doubleJumpUsed) {
1824
+ return 6 /* doubleJump */;
1825
+ }
1576
1826
  return 4 /* air */;
1577
1827
  }
1578
1828
  if (this.conflictingDirections) {
@@ -1632,31 +1882,59 @@ var LocalController = class {
1632
1882
  const frameRotation = angularSpeed * this.config.timeManager.deltaTime;
1633
1883
  this.config.character.quaternion.rotateTowards(rotationQuaternion, frameRotation);
1634
1884
  }
1635
- applyControls(deltaTime) {
1636
- const resistance = this.characterOnGround ? groundResistance : airResistance;
1637
- const speedFactor = Math.pow(1 - resistance, deltaTime);
1638
- this.characterVelocity.multiplyScalar(speedFactor);
1639
- const acceleration = this.tempVector.set(0, 0, 0);
1885
+ processJump(currentAcceleration, deltaTime) {
1640
1886
  if (this.characterOnGround) {
1887
+ this.coyoteJumped = false;
1888
+ this.canDoubleJump = false;
1889
+ this.doubleJumpUsed = false;
1890
+ this.jumpCounter = 0;
1641
1891
  if (!this.jump) {
1892
+ this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1642
1893
  this.canJump = true;
1894
+ this.jumpReleased = true;
1643
1895
  }
1644
- if (this.jump && this.canJump) {
1645
- acceleration.y += this.jumpForce / deltaTime;
1896
+ if (this.jump && this.canJump && this.jumpReleased) {
1897
+ currentAcceleration.y += this.jumpForce / deltaTime;
1646
1898
  this.canJump = false;
1899
+ this.jumpReleased = false;
1900
+ this.jumpCounter++;
1647
1901
  } else {
1648
- if (this.currentSurfaceAngle.y < minimumSurfaceAngle) {
1649
- acceleration.y += this.gravity;
1902
+ if (this.currentSurfaceAngle.y < this.minimumSurfaceAngle) {
1903
+ currentAcceleration.y += this.gravity;
1650
1904
  }
1651
1905
  }
1652
- } else if (this.jump && this.coyoteTime) {
1653
- acceleration.y += this.jumpForce / deltaTime;
1654
- this.canJump = false;
1655
1906
  } else {
1656
- acceleration.y += this.gravity;
1657
- this.canJump = false;
1907
+ if (this.jump && !this.coyoteJumped && this.coyoteTime) {
1908
+ this.coyoteJumped = true;
1909
+ currentAcceleration.y += this.jumpForce / deltaTime;
1910
+ this.canJump = false;
1911
+ this.jumpReleased = false;
1912
+ this.jumpCounter++;
1913
+ } else if (this.jump && this.canDoubleJump) {
1914
+ currentAcceleration.y += this.doubleJumpForce / deltaTime;
1915
+ this.doubleJumpUsed = true;
1916
+ this.jumpReleased = false;
1917
+ this.jumpCounter++;
1918
+ } else {
1919
+ currentAcceleration.y += this.gravity;
1920
+ this.canJump = false;
1921
+ }
1922
+ }
1923
+ if (!this.jump) {
1924
+ this.jumpReleased = true;
1925
+ if (!this.characterOnGround) {
1926
+ currentAcceleration.y += this.gravity;
1927
+ }
1658
1928
  }
1659
- const control = (this.characterOnGround ? this.run ? groundRunControl : groundWalkControl : airControlModifier) * baseControl;
1929
+ }
1930
+ applyControls(deltaTime) {
1931
+ const resistance = this.characterOnGround ? this.groundResistance : this.airResistance;
1932
+ const speedFactor = Math.pow(1 - resistance, deltaTime);
1933
+ this.characterVelocity.multiplyScalar(speedFactor);
1934
+ const acceleration = this.tempVector.set(0, 0, 0);
1935
+ this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1936
+ this.processJump(acceleration, deltaTime);
1937
+ const control = (this.characterOnGround ? this.run ? this.groundRunControl : this.groundWalkControl : this.airControlModifier) * this.baseControl;
1660
1938
  const controlAcceleration = this.tempVector2.set(0, 0, 0);
1661
1939
  if (!this.conflictingDirections) {
1662
1940
  if (this.forward) {
@@ -1709,10 +1987,18 @@ var LocalController = class {
1709
1987
  this.config.character.position.copy(avatarSegment.start);
1710
1988
  const deltaCollisionPosition = avatarSegment.start.sub(positionBeforeCollisions);
1711
1989
  this.characterOnGround = deltaCollisionPosition.y > 0;
1990
+ if (this.characterOnGround) {
1991
+ this.doubleJumpUsed = false;
1992
+ this.jumpCounter = 0;
1993
+ }
1712
1994
  if (this.characterWasOnGround && !this.characterOnGround) {
1713
1995
  this.characterAirborneSince = Date.now();
1714
1996
  }
1997
+ if (!this.jump) {
1998
+ this.jumpReleased = true;
1999
+ }
1715
2000
  this.coyoteTime = this.characterVelocity.y < 0 && !this.characterOnGround && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
2001
+ this.latestPosition = this.config.character.position.clone();
1716
2002
  this.characterWasOnGround = this.characterOnGround;
1717
2003
  }
1718
2004
  getMovementFromSurfaces(userPosition, deltaTime) {
@@ -1797,6 +2083,9 @@ var LocalController = class {
1797
2083
  this.characterVelocity.y = 0;
1798
2084
  this.config.character.position.y = 3;
1799
2085
  this.characterOnGround = false;
2086
+ this.doubleJumpUsed = false;
2087
+ this.jumpReleased = true;
2088
+ this.jumpCounter = 0;
1800
2089
  }
1801
2090
  };
1802
2091
 
@@ -1917,6 +2206,9 @@ var CharacterManager = class {
1917
2206
  this.group.add(character);
1918
2207
  this.localCharacterSpawned = true;
1919
2208
  }
2209
+ setupTweakPane(tweakPane) {
2210
+ tweakPane.setupCharacterController(this.localController);
2211
+ }
1920
2212
  spawnRemoteCharacter(id, username, characterDescription, spawnPosition = new Vector38(), spawnRotation = new Euler2()) {
1921
2213
  const character = new Character({
1922
2214
  username,
@@ -3109,6 +3401,7 @@ var TweakPane = class {
3109
3401
  this.character = new CharacterFolder(this.gui, false);
3110
3402
  this.environment = new EnvironmentFolder(this.gui, false);
3111
3403
  this.camera = new CameraFolder(this.gui, false);
3404
+ this.characterControls = new CharacterControlsFolder(this.gui, false);
3112
3405
  this.toneMappingFolder.folder.hidden = rendererValues.toneMapping === 5 ? false : true;
3113
3406
  this.export = this.gui.addFolder({ title: "import / export", expanded: false });
3114
3407
  window.addEventListener("keydown", this.processKey.bind(this));
@@ -3162,12 +3455,18 @@ var TweakPane = class {
3162
3455
  setupCamPane(cameraManager) {
3163
3456
  this.camera.setupChangeEvent(cameraManager);
3164
3457
  }
3458
+ setupCharacterController(localController) {
3459
+ this.characterControls.setupChangeEvent(localController);
3460
+ }
3165
3461
  updateStats(timeManager) {
3166
3462
  this.renderStatsFolder.update(this.renderer, this.composer, timeManager);
3167
3463
  }
3168
3464
  updateCameraData(cameraManager) {
3169
3465
  this.camera.update(cameraManager);
3170
3466
  }
3467
+ updateCharacterData(localController) {
3468
+ this.characterControls.update(localController);
3469
+ }
3171
3470
  formatDateForFilename() {
3172
3471
  const date = /* @__PURE__ */ new Date();
3173
3472
  const year = date.getFullYear();