@mml-io/3d-web-client-core 0.0.0-experimental-42d909b-20240717 → 0.0.0-experimental-c5bc6e3-20240723

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
@@ -82,159 +82,6 @@ var EventHandlerCollection = class _EventHandlerCollection {
82
82
  }
83
83
  };
84
84
 
85
- // src/input/VirtualJoystick.ts
86
- var VirtualJoystick = class _VirtualJoystick {
87
- constructor(holderElement, attrs) {
88
- this.holderElement = holderElement;
89
- this.left = false;
90
- this.right = false;
91
- this.up = false;
92
- this.down = false;
93
- this.hasDirection = false;
94
- this.clearFlags = () => {
95
- this.left = false;
96
- this.right = false;
97
- this.up = false;
98
- this.down = false;
99
- this.hasDirection = false;
100
- this.control.style.left = `${this.width / 2 - this.inner_radius}px`;
101
- this.control.style.top = `${this.height / 2 - this.inner_radius}px`;
102
- };
103
- this.radius = attrs.radius || 50;
104
- this.inner_radius = attrs.inner_radius || this.radius / 2;
105
- this.anchor = attrs.anchor || "left";
106
- this.x = attrs.x || 0;
107
- this.y = attrs.y || 0;
108
- this.width = attrs.width || this.radius * 2 + this.inner_radius * 2;
109
- this.height = attrs.height || this.radius * 2 + this.inner_radius * 2;
110
- this.mouse_support = this.checkTouch() || attrs.mouse_support === true;
111
- this.div = document.createElement("div");
112
- const divStyle = this.div.style;
113
- divStyle.display = this.checkTouch() || this.mouse_support ? "visible" : "none";
114
- divStyle.position = "fixed";
115
- if (this.anchor === "left") {
116
- divStyle.left = `${this.x}px`;
117
- } else {
118
- divStyle.right = `${this.x}px`;
119
- }
120
- divStyle.bottom = `${this.y}px`;
121
- divStyle.width = `${this.width}px`;
122
- divStyle.height = `${this.height}px`;
123
- divStyle.zIndex = "10000";
124
- divStyle.overflow = "hidden";
125
- this.holderElement.appendChild(this.div);
126
- this.setupBaseAndControl();
127
- this.bindEvents();
128
- }
129
- static checkForTouch() {
130
- try {
131
- document.createEvent("TouchEvent");
132
- return true;
133
- } catch (e) {
134
- return false;
135
- }
136
- }
137
- checkTouch() {
138
- return _VirtualJoystick.checkForTouch();
139
- }
140
- setupBaseAndControl() {
141
- this.base = document.createElement("span");
142
- let divStyle = this.base.style;
143
- divStyle.width = `${this.radius * 2}px`;
144
- divStyle.height = `${this.radius * 2}px`;
145
- divStyle.position = "absolute";
146
- divStyle.left = `${this.width / 2 - this.radius}px`;
147
- divStyle.bottom = `${this.height / 2 - this.radius}px`;
148
- divStyle.borderRadius = "50%";
149
- divStyle.borderColor = "rgba(200,200,200,0.5)";
150
- divStyle.borderWidth = "2px";
151
- divStyle.borderStyle = "solid";
152
- this.div.appendChild(this.base);
153
- this.control = document.createElement("span");
154
- divStyle = this.control.style;
155
- divStyle.width = `${this.inner_radius * 2}px`;
156
- divStyle.height = `${this.inner_radius * 2}px`;
157
- divStyle.position = "absolute";
158
- divStyle.left = `${this.width / 2 - this.inner_radius}px`;
159
- divStyle.bottom = `${this.height / 2 - this.inner_radius}px`;
160
- divStyle.borderRadius = "50%";
161
- divStyle.backgroundColor = "rgba(200,200,200,0.3)";
162
- divStyle.borderWidth = "1px";
163
- divStyle.borderColor = "rgba(200,200,200,0.8)";
164
- divStyle.borderStyle = "solid";
165
- this.div.appendChild(this.control);
166
- }
167
- bindEvents() {
168
- this.div.addEventListener("touchstart", this.handleTouchStart.bind(this), false);
169
- this.div.addEventListener("touchmove", this.handleTouchMove.bind(this), false);
170
- this.div.addEventListener("touchend", this.clearFlags.bind(this), false);
171
- if (this.mouse_support) {
172
- this.div.addEventListener("mousedown", this.handleMouseDown.bind(this));
173
- this.div.addEventListener("mousemove", this.handleMouseMove.bind(this));
174
- this.div.addEventListener("mouseup", this.handleMouseUp.bind(this));
175
- }
176
- }
177
- handleTouchStart(evt) {
178
- evt.preventDefault();
179
- evt.stopPropagation();
180
- if (evt.touches) {
181
- const touch = evt.touches[0];
182
- this.updateControlAndDirection(touch);
183
- }
184
- }
185
- handleTouchMove(evt) {
186
- evt.preventDefault();
187
- evt.stopPropagation();
188
- if (evt.touches.length > 0) {
189
- const touch = evt.touches[0];
190
- this.updateControlAndDirection(touch);
191
- }
192
- }
193
- handleMouseDown(evt) {
194
- evt.preventDefault();
195
- evt.stopPropagation();
196
- this.updateControlAndDirection(evt);
197
- }
198
- handleMouseMove(evt) {
199
- if (evt.buttons === 1) {
200
- evt.preventDefault();
201
- evt.stopPropagation();
202
- this.updateControlAndDirection(evt);
203
- }
204
- }
205
- handleMouseUp(evt) {
206
- this.clearFlags();
207
- }
208
- updateControlAndDirection(input) {
209
- const rect = this.div.getBoundingClientRect();
210
- const dx = input.clientX - (rect.left + this.div.offsetWidth / 2);
211
- const dy = input.clientY - (rect.top + this.div.offsetHeight / 2);
212
- const distance = Math.min(Math.sqrt(dx * dx + dy * dy), this.radius);
213
- const angle = Math.atan2(dy, dx);
214
- const constrainedX = distance * Math.cos(angle);
215
- const constrainedY = distance * Math.sin(angle);
216
- this.control.style.left = `${constrainedX + this.width / 2 - this.inner_radius}px`;
217
- this.control.style.top = `${constrainedY + this.height / 2 - this.inner_radius}px`;
218
- this.up = this.isUp(dx, dy);
219
- this.down = this.isDown(dx, dy);
220
- this.left = this.isLeft(dx, dy);
221
- this.right = this.isRight(dx, dy);
222
- this.hasDirection = this.up || this.down || this.left || this.right;
223
- }
224
- isUp(dx, dy) {
225
- return dy < 0 && Math.abs(dx) <= 2 * Math.abs(dy);
226
- }
227
- isDown(dx, dy) {
228
- return dy > 0 && Math.abs(dx) <= 2 * Math.abs(dy);
229
- }
230
- isLeft(dx, dy) {
231
- return dx < 0 && Math.abs(dy) <= 2 * Math.abs(dx);
232
- }
233
- isRight(dx, dy) {
234
- return dx > 0 && Math.abs(dy) <= 2 * Math.abs(dx);
235
- }
236
- };
237
-
238
85
  // src/tweakpane/blades/cameraFolder.ts
239
86
  var camValues = {
240
87
  initialDistance: 3.3,
@@ -244,10 +91,9 @@ var camValues = {
244
91
  maxFOV: 70,
245
92
  minFOV: 60,
246
93
  invertFOVMapping: false,
247
- damping: 0.15,
248
- dampingScale: 5e-3,
94
+ damping: 0.25,
249
95
  zoomScale: 0.088,
250
- zoomDamping: 0.16
96
+ zoomDamping: 0.4
251
97
  };
252
98
  var camOptions = {
253
99
  initialDistance: { min: 1, max: 5, step: 0.1 },
@@ -256,8 +102,7 @@ var camOptions = {
256
102
  initialFOV: { min: 60, max: 85, step: 1 },
257
103
  maxFOV: { min: 50, max: 100, step: 1 },
258
104
  minFOV: { min: 50, max: 100, step: 1 },
259
- damping: { min: 0.01, max: 0.15, step: 0.01 },
260
- dampingScale: { min: 1e-3, max: 0.02, step: 1e-3 },
105
+ damping: { min: 0.01, max: 1, step: 1e-3 },
261
106
  zoomScale: { min: 5e-3, max: 0.3, step: 1e-3 },
262
107
  zoomDamping: { min: 0, max: 2, step: 0.01 }
263
108
  };
@@ -277,7 +122,6 @@ var CameraFolder = class {
277
122
  this.folder.addBinding(camValues, "maxFOV", camOptions.maxFOV);
278
123
  this.folder.addBinding({ invertFOVMapping: camValues.invertFOVMapping }, "invertFOVMapping");
279
124
  this.folder.addBinding(camValues, "damping", camOptions.damping);
280
- this.folder.addBinding(camValues, "dampingScale", camOptions.dampingScale);
281
125
  this.folder.addBinding(camValues, "zoomScale", camOptions.zoomScale);
282
126
  this.folder.addBinding(camValues, "zoomDamping", camOptions.zoomDamping);
283
127
  }
@@ -326,20 +170,16 @@ var CameraFolder = class {
326
170
  cameraManager.recomputeFoV();
327
171
  break;
328
172
  }
329
- case "invertFOVMapping":
173
+ case "invertFOVMapping": {
330
174
  const boolValue = e.value;
331
175
  cameraManager.invertFOVMapping = boolValue;
332
176
  break;
177
+ }
333
178
  case "damping": {
334
179
  const value = e.value;
335
180
  cameraManager.damping = value;
336
181
  break;
337
182
  }
338
- case "dampingScale": {
339
- const value = e.value;
340
- cameraManager.dampingScale = value;
341
- break;
342
- }
343
183
  case "zoomScale": {
344
184
  const value = e.value;
345
185
  cameraManager.zoomScale = value;
@@ -371,124 +211,116 @@ function getTweakpaneActive() {
371
211
  }
372
212
 
373
213
  // src/camera/CameraManager.ts
214
+ var cameraPanSensitivity = 20;
215
+ var scrollZoomSensitivity = 0.1;
216
+ var pinchZoomSensitivity = 0.025;
374
217
  var CameraManager = class {
375
218
  constructor(targetElement, collisionsManager, initialPhi = Math.PI / 2, initialTheta = -Math.PI / 2) {
219
+ this.targetElement = targetElement;
376
220
  this.collisionsManager = collisionsManager;
377
221
  this.initialDistance = camValues.initialDistance;
378
222
  this.minDistance = camValues.minDistance;
379
223
  this.maxDistance = camValues.maxDistance;
380
- this.initialFOV = camValues.initialFOV;
381
- this.maxFOV = camValues.maxFOV;
382
- this.minFOV = camValues.minFOV;
383
224
  this.damping = camValues.damping;
384
- this.dampingScale = 0.01;
385
225
  this.zoomScale = camValues.zoomScale;
386
226
  this.zoomDamping = camValues.zoomDamping;
227
+ this.initialFOV = camValues.initialFOV;
228
+ this.maxFOV = camValues.maxFOV;
229
+ this.minFOV = camValues.minFOV;
387
230
  this.invertFOVMapping = camValues.invertFOVMapping;
388
231
  this.fov = this.initialFOV;
389
232
  this.targetFOV = this.initialFOV;
390
233
  this.minPolarAngle = Math.PI * 0.25;
391
234
  this.maxPolarAngle = Math.PI * 0.95;
392
- this.targetDistance = this.initialDistance;
393
235
  this.distance = this.initialDistance;
236
+ this.targetDistance = this.initialDistance;
394
237
  this.desiredDistance = this.initialDistance;
395
238
  this.phi = Math.PI / 2;
239
+ this.targetPhi = this.phi;
396
240
  this.theta = Math.PI / 2;
397
- this.dragging = false;
241
+ this.targetTheta = this.theta;
398
242
  this.target = new Vector32(0, 1.55, 0);
399
243
  this.hadTarget = false;
400
- this.isLerping = false;
401
244
  this.finalTarget = new Vector32();
245
+ this.isLerping = false;
402
246
  this.lerpTarget = new Vector32();
403
247
  this.lerpFactor = 0;
404
248
  this.lerpDuration = 2.1;
405
- this.hasTouchControl = false;
406
- this.lastTouchX = 0;
407
- this.lastTouchY = 0;
249
+ this.activePointers = /* @__PURE__ */ new Map();
250
+ this.targetElement.style.touchAction = "pinch-zoom";
408
251
  this.phi = initialPhi;
409
- this.targetPhi = initialPhi;
410
252
  this.theta = initialTheta;
411
- this.targetTheta = initialTheta;
412
253
  this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 400);
413
254
  this.camera.position.set(0, 1.4, -this.initialDistance);
414
255
  this.rayCaster = new Raycaster();
415
- this.hasTouchControl = VirtualJoystick.checkForTouch();
416
256
  this.eventHandlerCollection = EventHandlerCollection.create([
417
- [targetElement, "mousedown", this.onMouseDown.bind(this)],
418
- [document, "mouseup", this.onMouseUp.bind(this)],
419
- [document, "mousemove", this.onMouseMove.bind(this)],
257
+ [targetElement, "pointerdown", this.onPointerDown.bind(this)],
258
+ [targetElement, "gesturestart", this.preventDefaultAndStopPropagation.bind(this)],
259
+ [document, "pointerup", this.onPointerUp.bind(this)],
260
+ [document, "pointercancel", this.onPointerUp.bind(this)],
261
+ [document, "pointermove", this.onPointerMove.bind(this)],
420
262
  [targetElement, "wheel", this.onMouseWheel.bind(this)],
421
263
  [targetElement, "contextmenu", this.onContextMenu.bind(this)]
422
264
  ]);
423
- if (this.hasTouchControl) {
424
- this.eventHandlerCollection.add(targetElement, "touchstart", this.onTouchStart.bind(this), {
425
- passive: false
426
- });
427
- this.eventHandlerCollection.add(document, "touchmove", this.onTouchMove.bind(this), {
428
- passive: false
429
- });
430
- this.eventHandlerCollection.add(document, "touchend", this.onTouchEnd.bind(this), {
431
- passive: false
432
- });
433
- }
434
- }
435
- setupTweakPane(tweakPane) {
436
- tweakPane.setupCamPane(this);
437
265
  }
438
- onTouchStart(evt) {
439
- Array.from(evt.touches).forEach((touch) => {
440
- this.dragging = true;
441
- this.lastTouchX = touch.clientX;
442
- this.lastTouchY = touch.clientY;
443
- });
444
- }
445
- onTouchMove(evt) {
446
- if (!this.dragging || getTweakpaneActive()) {
447
- return;
448
- }
266
+ preventDefaultAndStopPropagation(evt) {
449
267
  evt.preventDefault();
450
- const touch = Array.from(evt.touches).find((t) => true);
451
- if (touch) {
452
- const dx = touch.clientX - this.lastTouchX;
453
- const dy = touch.clientY - this.lastTouchY;
454
- this.lastTouchX = touch.clientX;
455
- this.lastTouchY = touch.clientY;
456
- if (this.targetTheta !== null && this.targetPhi !== null) {
457
- this.targetTheta += dx * this.dampingScale;
458
- this.targetPhi -= dy * this.dampingScale;
459
- this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
460
- }
461
- }
268
+ evt.stopPropagation();
462
269
  }
463
- onTouchEnd(evt) {
464
- if (this.dragging) {
465
- const touchEnded = Array.from(evt.changedTouches).some((t) => true);
466
- if (touchEnded) {
467
- this.dragging = false;
468
- }
469
- }
270
+ setupTweakPane(tweakPane) {
271
+ tweakPane.setupCamPane(this);
470
272
  }
471
- onMouseDown(event) {
273
+ onPointerDown(event) {
472
274
  if (event.button === 0 || event.button === 2) {
473
- this.dragging = true;
275
+ const pointerInfo = { x: event.clientX, y: event.clientY };
276
+ this.activePointers.set(event.pointerId, pointerInfo);
474
277
  document.body.style.cursor = "none";
475
278
  }
476
279
  }
477
- onMouseUp(event) {
478
- if (event.button === 0 || event.button === 2) {
479
- this.dragging = false;
480
- document.body.style.cursor = "default";
280
+ onPointerUp(event) {
281
+ const existingPointer = this.activePointers.get(event.pointerId);
282
+ if (existingPointer) {
283
+ this.activePointers.delete(event.pointerId);
284
+ if (this.activePointers.size === 0) {
285
+ document.body.style.cursor = "default";
286
+ }
481
287
  }
482
288
  }
483
- onMouseMove(event) {
289
+ getAveragePointerPositionAndSpread() {
290
+ const existingSum = { x: 0, y: 0 };
291
+ this.activePointers.forEach((p) => {
292
+ existingSum.x += p.x;
293
+ existingSum.y += p.y;
294
+ });
295
+ const aX = existingSum.x / this.activePointers.size;
296
+ const aY = existingSum.y / this.activePointers.size;
297
+ let sumOfDistances = 0;
298
+ this.activePointers.forEach((p) => {
299
+ const distance = Math.sqrt((p.x - aX) ** 2 + (p.y - aY) ** 2);
300
+ sumOfDistances += distance;
301
+ });
302
+ return { pos: { x: aX, y: aY }, spread: sumOfDistances / this.activePointers.size };
303
+ }
304
+ onPointerMove(event) {
484
305
  if (getTweakpaneActive()) {
485
306
  return;
486
307
  }
487
- if (this.dragging) {
488
- if (this.targetTheta === null || this.targetPhi === null)
489
- return;
490
- this.targetTheta += event.movementX * this.dampingScale;
491
- this.targetPhi -= event.movementY * this.dampingScale;
308
+ const existingPointer = this.activePointers.get(event.pointerId);
309
+ if (existingPointer) {
310
+ const previous = this.getAveragePointerPositionAndSpread();
311
+ existingPointer.x = event.clientX;
312
+ existingPointer.y = event.clientY;
313
+ const latest = this.getAveragePointerPositionAndSpread();
314
+ const sX = latest.pos.x - previous.pos.x;
315
+ const sY = latest.pos.y - previous.pos.y;
316
+ const dx = sX / this.targetElement.clientWidth * cameraPanSensitivity;
317
+ const dy = sY / this.targetElement.clientHeight * cameraPanSensitivity;
318
+ if (this.activePointers.size > 1) {
319
+ const zoomDelta = latest.spread - previous.spread;
320
+ this.zoom(-zoomDelta * pinchZoomSensitivity);
321
+ }
322
+ this.targetTheta += dx;
323
+ this.targetPhi -= dy;
492
324
  this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
493
325
  event.preventDefault();
494
326
  }
@@ -497,14 +329,17 @@ var CameraManager = class {
497
329
  if (getTweakpaneActive()) {
498
330
  return;
499
331
  }
500
- const scrollAmount = event.deltaY * this.zoomScale * 0.1;
501
- this.targetDistance += scrollAmount;
332
+ event.preventDefault();
333
+ const scrollAmount = event.deltaY * this.zoomScale * scrollZoomSensitivity;
334
+ this.zoom(scrollAmount);
335
+ }
336
+ zoom(delta) {
337
+ this.targetDistance += delta;
502
338
  this.targetDistance = Math.max(
503
339
  this.minDistance,
504
340
  Math.min(this.maxDistance, this.targetDistance)
505
341
  );
506
342
  this.desiredDistance = this.targetDistance;
507
- event.preventDefault();
508
343
  }
509
344
  onContextMenu(event) {
510
345
  event.preventDefault();
@@ -533,12 +368,12 @@ var CameraManager = class {
533
368
  const dy = this.camera.position.y - this.target.y;
534
369
  const dz = this.camera.position.z - this.target.z;
535
370
  this.targetDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
536
- this.targetTheta = Math.atan2(dz, dx);
537
- this.targetPhi = Math.acos(dy / this.targetDistance);
538
- this.phi = this.targetPhi;
539
- this.theta = this.targetTheta;
540
371
  this.distance = this.targetDistance;
541
372
  this.desiredDistance = this.targetDistance;
373
+ this.theta = Math.atan2(dz, dx);
374
+ this.targetTheta = this.theta;
375
+ this.phi = Math.acos(dy / this.targetDistance);
376
+ this.targetPhi = this.phi;
542
377
  this.recomputeFoV(true);
543
378
  }
544
379
  adjustCameraPosition() {
@@ -546,15 +381,15 @@ var CameraManager = class {
546
381
  const offset = new Vector32(0, 0, offsetDistance);
547
382
  offset.applyEuler(this.camera.rotation);
548
383
  const rayOrigin = this.camera.position.clone().add(offset);
549
- const rayDirection = this.target.clone().sub(rayOrigin).normalize();
550
- this.rayCaster.set(rayOrigin, rayDirection);
384
+ const rayDirection = rayOrigin.sub(this.target.clone()).normalize();
385
+ this.rayCaster.set(this.target.clone(), rayDirection);
551
386
  const firstRaycastHit = this.collisionsManager.raycastFirst(this.rayCaster.ray);
552
- const cameraToPlayerDistance = this.camera.position.distanceTo(this.target);
553
- if (firstRaycastHit !== null && firstRaycastHit[0] <= cameraToPlayerDistance) {
554
- this.targetDistance = cameraToPlayerDistance - firstRaycastHit[0];
555
- this.distance = this.targetDistance;
387
+ if (firstRaycastHit !== null && firstRaycastHit[0] <= this.desiredDistance) {
388
+ const distanceToCollision = firstRaycastHit[0] - 0.1;
389
+ this.targetDistance = distanceToCollision;
390
+ this.distance = distanceToCollision;
556
391
  } else {
557
- this.targetDistance += (this.desiredDistance - this.targetDistance) * this.damping * 4;
392
+ this.targetDistance = this.desiredDistance;
558
393
  }
559
394
  }
560
395
  dispose() {
@@ -587,24 +422,25 @@ var CameraManager = class {
587
422
  } else {
588
423
  this.adjustCameraPosition();
589
424
  }
590
- if (this.phi !== null && this.targetPhi !== null && this.theta !== null && this.targetTheta !== null) {
591
- this.distance += (this.targetDistance - this.distance) * this.damping * (0.21 + this.zoomDamping);
592
- this.phi += (this.targetPhi - this.phi) * this.damping;
593
- this.theta += (this.targetTheta - this.theta) * this.damping;
594
- const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
595
- const y = this.target.y + this.distance * Math.cos(this.phi);
596
- const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
597
- this.recomputeFoV();
598
- this.fov += (this.targetFOV - this.fov) * this.damping;
599
- this.camera.fov = this.fov;
600
- this.camera.updateProjectionMatrix();
601
- this.camera.position.set(x, y, z);
602
- this.camera.lookAt(this.target);
603
- if (this.isLerping && this.lerpFactor >= 1) {
604
- this.isLerping = false;
605
- }
425
+ this.distance += (this.targetDistance - this.distance) * this.zoomDamping;
426
+ this.theta += (this.targetTheta - this.theta) * this.damping;
427
+ this.phi += (this.targetPhi - this.phi) * this.damping;
428
+ const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
429
+ const y = this.target.y + this.distance * Math.cos(this.phi);
430
+ const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
431
+ this.recomputeFoV();
432
+ this.fov += (this.targetFOV - this.fov) * this.zoomDamping;
433
+ this.camera.fov = this.fov;
434
+ this.camera.updateProjectionMatrix();
435
+ this.camera.position.set(x, y, z);
436
+ this.camera.lookAt(this.target);
437
+ if (this.isLerping && this.lerpFactor >= 1) {
438
+ this.isLerping = false;
606
439
  }
607
440
  }
441
+ hasActiveInput() {
442
+ return this.activePointers.size > 0;
443
+ }
608
444
  };
609
445
 
610
446
  // src/character/Character.ts
@@ -1770,6 +1606,7 @@ var LocalController = class {
1770
1606
  this.jumpPressed = false;
1771
1607
  // Tracks if the jump button is pressed
1772
1608
  this.jumpReleased = true;
1609
+ this.controlState = null;
1773
1610
  this.networkState = {
1774
1611
  id: this.config.id,
1775
1612
  position: { x: 0, y: 0, z: 0 },
@@ -1777,35 +1614,22 @@ var LocalController = class {
1777
1614
  state: 0 /* idle */
1778
1615
  };
1779
1616
  }
1780
- updateControllerState() {
1781
- var _a, _b, _c, _d, _e;
1782
- this.forward = this.config.keyInputManager.forward || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.up) || false;
1783
- this.backward = this.config.keyInputManager.backward || ((_b = this.config.virtualJoystick) == null ? void 0 : _b.down) || false;
1784
- this.left = this.config.keyInputManager.left || ((_c = this.config.virtualJoystick) == null ? void 0 : _c.left) || false;
1785
- this.right = this.config.keyInputManager.right || ((_d = this.config.virtualJoystick) == null ? void 0 : _d.right) || false;
1786
- this.run = this.config.keyInputManager.run;
1787
- this.jump = this.config.keyInputManager.jump;
1788
- this.anyDirection = this.config.keyInputManager.anyDirection || ((_e = this.config.virtualJoystick) == null ? void 0 : _e.hasDirection) || false;
1789
- this.conflictingDirections = this.config.keyInputManager.conflictingDirection;
1790
- if (!this.jump) {
1791
- this.jumpReleased = true;
1792
- }
1793
- }
1794
1617
  update() {
1795
- this.updateControllerState();
1618
+ var _a, _b;
1619
+ this.controlState = this.config.keyInputManager.getOutput() || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.getOutput()) || null;
1796
1620
  this.rayCaster.set(this.config.character.position, this.vectorDown);
1797
1621
  const firstRaycastHit = this.config.collisionsManager.raycastFirst(this.rayCaster.ray);
1798
1622
  if (firstRaycastHit !== null) {
1799
1623
  this.currentHeight = firstRaycastHit[0];
1800
1624
  this.currentSurfaceAngle.copy(firstRaycastHit[1]);
1801
1625
  }
1802
- if (this.anyDirection || !this.characterOnGround) {
1626
+ if (((_b = this.controlState) == null ? void 0 : _b.direction) !== null || !this.characterOnGround) {
1803
1627
  const targetAnimation = this.getTargetAnimation();
1804
1628
  this.config.character.updateAnimation(targetAnimation);
1805
1629
  } else {
1806
1630
  this.config.character.updateAnimation(0 /* idle */);
1807
1631
  }
1808
- if (this.anyDirection) {
1632
+ if (this.controlState) {
1809
1633
  this.updateRotation();
1810
1634
  }
1811
1635
  for (let i = 0; i < this.collisionDetectionSteps; i++) {
@@ -1830,30 +1654,17 @@ var LocalController = class {
1830
1654
  }
1831
1655
  return 4 /* air */;
1832
1656
  }
1833
- if (this.conflictingDirections) {
1657
+ if (!this.controlState) {
1834
1658
  return 0 /* idle */;
1835
1659
  }
1836
- return this.run && this.anyDirection ? 2 /* running */ : this.anyDirection ? 1 /* walking */ : 0 /* idle */;
1660
+ if (this.controlState.isSprinting) {
1661
+ return 2 /* running */;
1662
+ }
1663
+ return 1 /* walking */;
1837
1664
  }
1838
1665
  updateRotationOffset() {
1839
- if (this.conflictingDirections)
1840
- return;
1841
- if (this.forward) {
1842
- this.rotationOffset = Math.PI;
1843
- if (this.left)
1844
- this.rotationOffset = Math.PI + Math.PI / 4;
1845
- if (this.right)
1846
- this.rotationOffset = Math.PI - Math.PI / 4;
1847
- } else if (this.backward) {
1848
- this.rotationOffset = Math.PI * 2;
1849
- if (this.left)
1850
- this.rotationOffset = -Math.PI * 2 - Math.PI / 4;
1851
- if (this.right)
1852
- this.rotationOffset = Math.PI * 2 + Math.PI / 4;
1853
- } else if (this.left) {
1854
- this.rotationOffset = Math.PI * -0.5;
1855
- } else if (this.right) {
1856
- this.rotationOffset = Math.PI * 0.5;
1666
+ if (this.controlState && this.controlState.direction !== null) {
1667
+ this.rotationOffset = this.controlState.direction;
1857
1668
  }
1858
1669
  }
1859
1670
  updateAzimuthalAngle() {
@@ -1888,17 +1699,19 @@ var LocalController = class {
1888
1699
  this.config.character.quaternion.rotateTowards(rotationQuaternion, frameRotation);
1889
1700
  }
1890
1701
  processJump(currentAcceleration, deltaTime) {
1702
+ var _a;
1703
+ const jump = (_a = this.controlState) == null ? void 0 : _a.jump;
1891
1704
  if (this.characterOnGround) {
1892
1705
  this.coyoteJumped = false;
1893
1706
  this.canDoubleJump = false;
1894
1707
  this.doubleJumpUsed = false;
1895
1708
  this.jumpCounter = 0;
1896
- if (!this.jump) {
1709
+ if (!jump) {
1897
1710
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1898
1711
  this.canJump = true;
1899
1712
  this.jumpReleased = true;
1900
1713
  }
1901
- if (this.jump && this.canJump && this.jumpReleased) {
1714
+ if (jump && this.canJump && this.jumpReleased) {
1902
1715
  currentAcceleration.y += this.jumpForce / deltaTime;
1903
1716
  this.canJump = false;
1904
1717
  this.jumpReleased = false;
@@ -1909,13 +1722,13 @@ var LocalController = class {
1909
1722
  }
1910
1723
  }
1911
1724
  } else {
1912
- if (this.jump && !this.coyoteJumped && this.coyoteTime) {
1725
+ if (jump && !this.coyoteJumped && this.coyoteTime) {
1913
1726
  this.coyoteJumped = true;
1914
1727
  currentAcceleration.y += this.jumpForce / deltaTime;
1915
1728
  this.canJump = false;
1916
1729
  this.jumpReleased = false;
1917
1730
  this.jumpCounter++;
1918
- } else if (this.jump && this.canDoubleJump) {
1731
+ } else if (jump && this.canDoubleJump) {
1919
1732
  currentAcceleration.y += this.doubleJumpForce / deltaTime;
1920
1733
  this.doubleJumpUsed = true;
1921
1734
  this.jumpReleased = false;
@@ -1925,7 +1738,7 @@ var LocalController = class {
1925
1738
  this.canJump = false;
1926
1739
  }
1927
1740
  }
1928
- if (!this.jump) {
1741
+ if (!jump) {
1929
1742
  this.jumpReleased = true;
1930
1743
  if (!this.characterOnGround) {
1931
1744
  currentAcceleration.y += this.gravity;
@@ -1933,31 +1746,19 @@ var LocalController = class {
1933
1746
  }
1934
1747
  }
1935
1748
  applyControls(deltaTime) {
1749
+ var _a;
1936
1750
  const resistance = this.characterOnGround ? this.groundResistance : this.airResistance;
1937
1751
  const speedFactor = Math.pow(1 - resistance, deltaTime);
1938
1752
  this.characterVelocity.multiplyScalar(speedFactor);
1939
1753
  const acceleration = this.tempVector.set(0, 0, 0);
1940
1754
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1941
1755
  this.processJump(acceleration, deltaTime);
1942
- const control = (this.characterOnGround ? this.run ? this.groundRunControl : this.groundWalkControl : this.airControlModifier) * this.baseControl;
1756
+ const control = (this.characterOnGround ? ((_a = this.controlState) == null ? void 0 : _a.isSprinting) ? this.groundRunControl : this.groundWalkControl : this.airControlModifier) * this.baseControl;
1943
1757
  const controlAcceleration = this.tempVector2.set(0, 0, 0);
1944
- if (!this.conflictingDirections) {
1945
- if (this.forward) {
1946
- const forward = this.tempVector3.set(0, 0, -1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1947
- controlAcceleration.add(forward);
1948
- }
1949
- if (this.backward) {
1950
- const backward = this.tempVector3.set(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1951
- controlAcceleration.add(backward);
1952
- }
1953
- if (this.left) {
1954
- const left = this.tempVector3.set(-1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1955
- controlAcceleration.add(left);
1956
- }
1957
- if (this.right) {
1958
- const right = this.tempVector3.set(1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1959
- controlAcceleration.add(right);
1960
- }
1758
+ if (this.controlState && this.controlState.direction !== null) {
1759
+ const heading = this.controlState.direction;
1760
+ const headingVector = this.tempVector3.set(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle + heading);
1761
+ controlAcceleration.add(headingVector);
1961
1762
  }
1962
1763
  if (controlAcceleration.length() > 0) {
1963
1764
  controlAcceleration.normalize();
@@ -1968,6 +1769,7 @@ var LocalController = class {
1968
1769
  this.config.character.position.addScaledVector(this.characterVelocity, deltaTime);
1969
1770
  }
1970
1771
  updatePosition(deltaTime, stepDeltaTime, iter) {
1772
+ var _a;
1971
1773
  this.applyControls(stepDeltaTime);
1972
1774
  if (iter === 0) {
1973
1775
  const lastMovement = this.getMovementFromSurfaces(this.config.character.position, deltaTime);
@@ -1999,7 +1801,7 @@ var LocalController = class {
1999
1801
  if (this.characterWasOnGround && !this.characterOnGround) {
2000
1802
  this.characterAirborneSince = Date.now();
2001
1803
  }
2002
- if (!this.jump) {
1804
+ if (!((_a = this.controlState) == null ? void 0 : _a.jump)) {
2003
1805
  this.jumpReleased = true;
2004
1806
  }
2005
1807
  this.coyoteTime = this.characterVelocity.y < 0 && !this.characterOnGround && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
@@ -2321,7 +2123,11 @@ var CharacterManager = class {
2321
2123
  this.remoteCharacterControllers.delete(id);
2322
2124
  }
2323
2125
  }
2324
- if (this.config.updateURLLocation && this.config.timeManager.frame % 60 === 0 && document.hasFocus()) {
2126
+ if (this.config.updateURLLocation && this.config.timeManager.frame % 60 === 0 && document.hasFocus() && /*
2127
+ Don't update the URL if the camera is being controlled as some browsers (e.g. Chrome) cause a hitch to Pointer
2128
+ events when the url is updated
2129
+ */
2130
+ !this.config.cameraManager.hasActiveInput()) {
2325
2131
  const hash = encodeCharacterAndCamera(
2326
2132
  this.localCharacter,
2327
2133
  this.config.cameraManager.camera
@@ -2450,35 +2256,236 @@ var KeyInputManager = class {
2450
2256
  isMovementKeyPressed() {
2451
2257
  return ["w" /* W */, "a" /* A */, "s" /* S */, "d" /* D */].some((key) => this.isKeyPressed(key));
2452
2258
  }
2453
- get forward() {
2259
+ getForward() {
2454
2260
  return this.isKeyPressed("w" /* W */);
2455
2261
  }
2456
- get backward() {
2262
+ getBackward() {
2457
2263
  return this.isKeyPressed("s" /* S */);
2458
2264
  }
2459
- get left() {
2265
+ getLeft() {
2460
2266
  return this.isKeyPressed("a" /* A */);
2461
2267
  }
2462
- get right() {
2268
+ getRight() {
2463
2269
  return this.isKeyPressed("d" /* D */);
2464
2270
  }
2465
- get run() {
2271
+ getRun() {
2466
2272
  return this.isKeyPressed("shift" /* SHIFT */);
2467
2273
  }
2468
- get jump() {
2274
+ getJump() {
2469
2275
  return this.isKeyPressed(" " /* SPACE */);
2470
2276
  }
2471
- get anyDirection() {
2472
- return this.isMovementKeyPressed();
2473
- }
2474
- get conflictingDirection() {
2475
- return this.isKeyPressed("w" /* W */) && this.isKeyPressed("s" /* S */) || this.isKeyPressed("a" /* A */) && this.isKeyPressed("d" /* D */);
2277
+ getOutput() {
2278
+ const dx = (this.getRight() ? 1 : 0) - (this.getLeft() ? 1 : 0);
2279
+ const dy = (this.getBackward() ? 1 : 0) - (this.getForward() ? 1 : 0);
2280
+ const jump = this.getJump();
2281
+ if (dx === 0 && dy === 0) {
2282
+ if (this.getJump()) {
2283
+ return { direction: null, isSprinting: false, jump };
2284
+ }
2285
+ return null;
2286
+ }
2287
+ const direction = Math.atan2(dx, dy);
2288
+ return { direction, isSprinting: this.getRun(), jump };
2476
2289
  }
2477
2290
  dispose() {
2478
2291
  this.eventHandlerCollection.clear();
2479
2292
  }
2480
2293
  };
2481
2294
 
2295
+ // src/input/VirtualJoystick.ts
2296
+ var sprintingThreshold = 0.6;
2297
+ var VirtualJoystick = class _VirtualJoystick {
2298
+ constructor(holderElement, config) {
2299
+ this.holderElement = holderElement;
2300
+ this.config = config;
2301
+ this.joystickPointerId = null;
2302
+ this.joystickOutput = null;
2303
+ this.jumpPointerId = null;
2304
+ this.clearJoystickState = () => {
2305
+ this.joystickOutput = null;
2306
+ this.joystickCenterElement.style.left = `${this.radius - this.innerRadius}px`;
2307
+ this.joystickCenterElement.style.top = `${this.radius - this.innerRadius}px`;
2308
+ };
2309
+ this.radius = config.radius || 50;
2310
+ this.innerRadius = config.innerRadius || this.radius / 2;
2311
+ this.mouseSupport = this.checkTouch() || config.mouseSupport === true;
2312
+ this.element = document.createElement("div");
2313
+ const style = this.element.style;
2314
+ style.display = this.mouseSupport ? "flex" : "none";
2315
+ style.position = "absolute";
2316
+ style.width = `100%`;
2317
+ style.height = `200px`;
2318
+ style.bottom = "50px";
2319
+ style.zIndex = "10000";
2320
+ style.alignItems = "center";
2321
+ style.justifyContent = "space-between";
2322
+ style.pointerEvents = "none";
2323
+ style.padding = "20px";
2324
+ style.boxSizing = "border-box";
2325
+ style.userSelect = "none";
2326
+ this.holderElement.appendChild(this.element);
2327
+ this.joystickBaseElement = this.createBase();
2328
+ this.element.appendChild(this.joystickBaseElement);
2329
+ this.joystickCenterElement = this.createCenter();
2330
+ this.joystickBaseElement.appendChild(this.joystickCenterElement);
2331
+ this.jumpButton = this.createJumpButton();
2332
+ this.element.appendChild(this.jumpButton);
2333
+ this.bindEvents();
2334
+ this.clearJoystickState();
2335
+ }
2336
+ static checkForTouch() {
2337
+ try {
2338
+ document.createEvent("TouchEvent");
2339
+ return true;
2340
+ } catch (e) {
2341
+ return false;
2342
+ }
2343
+ }
2344
+ checkTouch() {
2345
+ return _VirtualJoystick.checkForTouch();
2346
+ }
2347
+ createBase() {
2348
+ const base = document.createElement("span");
2349
+ const style = base.style;
2350
+ style.touchAction = "pinch-zoom";
2351
+ style.width = `${this.radius * 2}px`;
2352
+ style.height = `${this.radius * 2}px`;
2353
+ style.position = "relative";
2354
+ style.display = "block";
2355
+ style.borderRadius = "50%";
2356
+ style.borderColor = "rgba(200,200,200,0.5)";
2357
+ style.borderWidth = "2px";
2358
+ style.borderStyle = "solid";
2359
+ style.pointerEvents = "auto";
2360
+ style.userSelect = "none";
2361
+ return base;
2362
+ }
2363
+ createCenter() {
2364
+ const center = document.createElement("div");
2365
+ const style = center.style;
2366
+ style.width = `${this.innerRadius * 2}px`;
2367
+ style.height = `${this.innerRadius * 2}px`;
2368
+ style.position = "absolute";
2369
+ style.borderRadius = "50%";
2370
+ style.backgroundColor = "rgba(200,200,200,0.3)";
2371
+ style.borderWidth = "1px";
2372
+ style.borderColor = "rgba(200,200,200,0.8)";
2373
+ style.borderStyle = "solid";
2374
+ style.userSelect = "none";
2375
+ return center;
2376
+ }
2377
+ createJumpButton() {
2378
+ const button = document.createElement("button");
2379
+ button.textContent = "JUMP";
2380
+ const style = button.style;
2381
+ style.touchAction = "pinch-zoom";
2382
+ style.width = `100px`;
2383
+ style.height = `100px`;
2384
+ style.borderRadius = "20px";
2385
+ style.color = "white";
2386
+ style.font = "Helvetica, sans-serif";
2387
+ style.fontSize = "16px";
2388
+ style.backgroundColor = "rgba(200,200,200,0.3)";
2389
+ style.color = "rgba(220,220,220,1)";
2390
+ style.borderWidth = "1px";
2391
+ style.borderColor = "rgba(200,200,200,0.8)";
2392
+ style.borderStyle = "solid";
2393
+ style.pointerEvents = "auto";
2394
+ style.userSelect = "none";
2395
+ return button;
2396
+ }
2397
+ bindEvents() {
2398
+ this.joystickBaseElement.addEventListener("pointerdown", this.onJoystickPointerDown.bind(this));
2399
+ this.joystickBaseElement.addEventListener(
2400
+ "contextmenu",
2401
+ this.preventDefaultAndStopPropagation.bind(this)
2402
+ );
2403
+ this.joystickBaseElement.addEventListener(
2404
+ "touchstart",
2405
+ this.preventDefaultAndStopPropagation.bind(this)
2406
+ );
2407
+ this.jumpButton.addEventListener("pointerdown", this.onJumpPointerDown.bind(this));
2408
+ this.jumpButton.addEventListener(
2409
+ "contextmenu",
2410
+ this.preventDefaultAndStopPropagation.bind(this)
2411
+ );
2412
+ this.jumpButton.addEventListener(
2413
+ "touchstart",
2414
+ this.preventDefaultAndStopPropagation.bind(this)
2415
+ );
2416
+ document.addEventListener("pointermove", this.onPointerMove.bind(this));
2417
+ document.addEventListener("pointerup", this.onPointerUp.bind(this));
2418
+ document.addEventListener("pointercancel", this.onPointerUp.bind(this));
2419
+ }
2420
+ preventDefaultAndStopPropagation(evt) {
2421
+ evt.preventDefault();
2422
+ evt.stopPropagation();
2423
+ }
2424
+ onJumpPointerDown(evt) {
2425
+ if (this.jumpPointerId === null) {
2426
+ this.jumpPointerId = evt.pointerId;
2427
+ }
2428
+ }
2429
+ onJoystickPointerDown(evt) {
2430
+ evt.preventDefault();
2431
+ evt.stopPropagation();
2432
+ if (evt.buttons !== 1) {
2433
+ return;
2434
+ }
2435
+ if (this.joystickPointerId === null) {
2436
+ this.joystickPointerId = evt.pointerId;
2437
+ this.updateControlAndDirection(evt);
2438
+ }
2439
+ }
2440
+ onPointerMove(evt) {
2441
+ evt.preventDefault();
2442
+ evt.stopPropagation();
2443
+ if (evt.pointerId !== this.joystickPointerId) {
2444
+ return;
2445
+ }
2446
+ this.updateControlAndDirection(evt);
2447
+ }
2448
+ onPointerUp(evt) {
2449
+ evt.preventDefault();
2450
+ evt.stopPropagation();
2451
+ if (evt.pointerId === this.jumpPointerId) {
2452
+ this.jumpPointerId = null;
2453
+ }
2454
+ if (evt.pointerId === this.joystickPointerId) {
2455
+ this.joystickPointerId = null;
2456
+ this.clearJoystickState();
2457
+ }
2458
+ }
2459
+ updateControlAndDirection(input) {
2460
+ const rect = this.joystickBaseElement.getBoundingClientRect();
2461
+ const dx = input.clientX - (rect.left + this.radius);
2462
+ const dy = input.clientY - (rect.top + this.radius);
2463
+ const distance = Math.min(Math.sqrt(dx * dx + dy * dy), this.radius);
2464
+ const angle = Math.atan2(dy, dx);
2465
+ const constrainedX = distance * Math.cos(angle);
2466
+ const constrainedY = distance * Math.sin(angle);
2467
+ this.joystickCenterElement.style.left = `${constrainedX + this.radius - this.innerRadius}px`;
2468
+ this.joystickCenterElement.style.top = `${constrainedY + this.radius - this.innerRadius}px`;
2469
+ const direction = Math.atan2(dx, dy);
2470
+ const speed = distance / this.radius;
2471
+ const isSprinting = speed > sprintingThreshold;
2472
+ this.joystickOutput = { direction, isSprinting };
2473
+ }
2474
+ getOutput() {
2475
+ const jump = this.jumpPointerId !== null;
2476
+ if (!this.joystickOutput) {
2477
+ if (jump) {
2478
+ return { direction: null, isSprinting: false, jump };
2479
+ }
2480
+ return null;
2481
+ }
2482
+ return {
2483
+ ...this.joystickOutput,
2484
+ jump
2485
+ };
2486
+ }
2487
+ };
2488
+
2482
2489
  // src/mml/MMLCompositionScene.ts
2483
2490
  import {
2484
2491
  InteractionManager,