@mml-io/3d-web-client-core 0.0.0-experimental-42d909b-20240717 → 0.0.0-experimental-81deb84-20240724

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,114 @@ 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
- this.phi = Math.PI / 2;
396
- this.theta = Math.PI / 2;
397
- this.dragging = false;
398
238
  this.target = new Vector32(0, 1.55, 0);
399
239
  this.hadTarget = false;
400
- this.isLerping = false;
401
240
  this.finalTarget = new Vector32();
241
+ this.isLerping = false;
402
242
  this.lerpTarget = new Vector32();
403
243
  this.lerpFactor = 0;
404
244
  this.lerpDuration = 2.1;
405
- this.hasTouchControl = false;
406
- this.lastTouchX = 0;
407
- this.lastTouchY = 0;
245
+ this.activePointers = /* @__PURE__ */ new Map();
246
+ this.targetElement.style.touchAction = "pinch-zoom";
408
247
  this.phi = initialPhi;
409
- this.targetPhi = initialPhi;
248
+ this.targetPhi = this.phi;
410
249
  this.theta = initialTheta;
411
- this.targetTheta = initialTheta;
250
+ this.targetTheta = this.theta;
412
251
  this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 400);
413
252
  this.camera.position.set(0, 1.4, -this.initialDistance);
414
253
  this.rayCaster = new Raycaster();
415
- this.hasTouchControl = VirtualJoystick.checkForTouch();
416
254
  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)],
255
+ [targetElement, "pointerdown", this.onPointerDown.bind(this)],
256
+ [targetElement, "gesturestart", this.preventDefaultAndStopPropagation.bind(this)],
257
+ [document, "pointerup", this.onPointerUp.bind(this)],
258
+ [document, "pointercancel", this.onPointerUp.bind(this)],
259
+ [document, "pointermove", this.onPointerMove.bind(this)],
420
260
  [targetElement, "wheel", this.onMouseWheel.bind(this)],
421
261
  [targetElement, "contextmenu", this.onContextMenu.bind(this)]
422
262
  ]);
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
263
  }
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
- }
264
+ preventDefaultAndStopPropagation(evt) {
449
265
  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
- }
266
+ evt.stopPropagation();
462
267
  }
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
- }
268
+ setupTweakPane(tweakPane) {
269
+ tweakPane.setupCamPane(this);
470
270
  }
471
- onMouseDown(event) {
271
+ onPointerDown(event) {
472
272
  if (event.button === 0 || event.button === 2) {
473
- this.dragging = true;
273
+ const pointerInfo = { x: event.clientX, y: event.clientY };
274
+ this.activePointers.set(event.pointerId, pointerInfo);
474
275
  document.body.style.cursor = "none";
475
276
  }
476
277
  }
477
- onMouseUp(event) {
478
- if (event.button === 0 || event.button === 2) {
479
- this.dragging = false;
480
- document.body.style.cursor = "default";
278
+ onPointerUp(event) {
279
+ const existingPointer = this.activePointers.get(event.pointerId);
280
+ if (existingPointer) {
281
+ this.activePointers.delete(event.pointerId);
282
+ if (this.activePointers.size === 0) {
283
+ document.body.style.cursor = "default";
284
+ }
481
285
  }
482
286
  }
483
- onMouseMove(event) {
287
+ getAveragePointerPositionAndSpread() {
288
+ const existingSum = { x: 0, y: 0 };
289
+ this.activePointers.forEach((p) => {
290
+ existingSum.x += p.x;
291
+ existingSum.y += p.y;
292
+ });
293
+ const aX = existingSum.x / this.activePointers.size;
294
+ const aY = existingSum.y / this.activePointers.size;
295
+ let sumOfDistances = 0;
296
+ this.activePointers.forEach((p) => {
297
+ const distance = Math.sqrt((p.x - aX) ** 2 + (p.y - aY) ** 2);
298
+ sumOfDistances += distance;
299
+ });
300
+ return { pos: { x: aX, y: aY }, spread: sumOfDistances / this.activePointers.size };
301
+ }
302
+ onPointerMove(event) {
484
303
  if (getTweakpaneActive()) {
485
304
  return;
486
305
  }
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;
306
+ const existingPointer = this.activePointers.get(event.pointerId);
307
+ if (existingPointer) {
308
+ const previous = this.getAveragePointerPositionAndSpread();
309
+ existingPointer.x = event.clientX;
310
+ existingPointer.y = event.clientY;
311
+ const latest = this.getAveragePointerPositionAndSpread();
312
+ const sX = latest.pos.x - previous.pos.x;
313
+ const sY = latest.pos.y - previous.pos.y;
314
+ const dx = sX / this.targetElement.clientWidth * cameraPanSensitivity;
315
+ const dy = sY / this.targetElement.clientHeight * cameraPanSensitivity;
316
+ if (this.activePointers.size > 1) {
317
+ const zoomDelta = latest.spread - previous.spread;
318
+ this.zoom(-zoomDelta * pinchZoomSensitivity);
319
+ }
320
+ this.targetTheta += dx;
321
+ this.targetPhi -= dy;
492
322
  this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
493
323
  event.preventDefault();
494
324
  }
@@ -497,14 +327,17 @@ var CameraManager = class {
497
327
  if (getTweakpaneActive()) {
498
328
  return;
499
329
  }
500
- const scrollAmount = event.deltaY * this.zoomScale * 0.1;
501
- this.targetDistance += scrollAmount;
330
+ event.preventDefault();
331
+ const scrollAmount = event.deltaY * this.zoomScale * scrollZoomSensitivity;
332
+ this.zoom(scrollAmount);
333
+ }
334
+ zoom(delta) {
335
+ this.targetDistance += delta;
502
336
  this.targetDistance = Math.max(
503
337
  this.minDistance,
504
338
  Math.min(this.maxDistance, this.targetDistance)
505
339
  );
506
340
  this.desiredDistance = this.targetDistance;
507
- event.preventDefault();
508
341
  }
509
342
  onContextMenu(event) {
510
343
  event.preventDefault();
@@ -533,12 +366,12 @@ var CameraManager = class {
533
366
  const dy = this.camera.position.y - this.target.y;
534
367
  const dz = this.camera.position.z - this.target.z;
535
368
  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
369
  this.distance = this.targetDistance;
541
370
  this.desiredDistance = this.targetDistance;
371
+ this.theta = Math.atan2(dz, dx);
372
+ this.targetTheta = this.theta;
373
+ this.phi = Math.acos(dy / this.targetDistance);
374
+ this.targetPhi = this.phi;
542
375
  this.recomputeFoV(true);
543
376
  }
544
377
  adjustCameraPosition() {
@@ -546,15 +379,15 @@ var CameraManager = class {
546
379
  const offset = new Vector32(0, 0, offsetDistance);
547
380
  offset.applyEuler(this.camera.rotation);
548
381
  const rayOrigin = this.camera.position.clone().add(offset);
549
- const rayDirection = this.target.clone().sub(rayOrigin).normalize();
550
- this.rayCaster.set(rayOrigin, rayDirection);
382
+ const rayDirection = rayOrigin.sub(this.target.clone()).normalize();
383
+ this.rayCaster.set(this.target.clone(), rayDirection);
551
384
  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;
385
+ if (firstRaycastHit !== null && firstRaycastHit[0] <= this.desiredDistance) {
386
+ const distanceToCollision = firstRaycastHit[0] - 0.1;
387
+ this.targetDistance = distanceToCollision;
388
+ this.distance = distanceToCollision;
556
389
  } else {
557
- this.targetDistance += (this.desiredDistance - this.targetDistance) * this.damping * 4;
390
+ this.targetDistance = this.desiredDistance;
558
391
  }
559
392
  }
560
393
  dispose() {
@@ -587,24 +420,25 @@ var CameraManager = class {
587
420
  } else {
588
421
  this.adjustCameraPosition();
589
422
  }
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
- }
423
+ this.distance += (this.targetDistance - this.distance) * this.zoomDamping;
424
+ this.theta += (this.targetTheta - this.theta) * this.damping;
425
+ this.phi += (this.targetPhi - this.phi) * this.damping;
426
+ const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
427
+ const y = this.target.y + this.distance * Math.cos(this.phi);
428
+ const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
429
+ this.recomputeFoV();
430
+ this.fov += (this.targetFOV - this.fov) * this.zoomDamping;
431
+ this.camera.fov = this.fov;
432
+ this.camera.updateProjectionMatrix();
433
+ this.camera.position.set(x, y, z);
434
+ this.camera.lookAt(this.target);
435
+ if (this.isLerping && this.lerpFactor >= 1) {
436
+ this.isLerping = false;
606
437
  }
607
438
  }
439
+ hasActiveInput() {
440
+ return this.activePointers.size > 0;
441
+ }
608
442
  };
609
443
 
610
444
  // src/character/Character.ts
@@ -1770,6 +1604,7 @@ var LocalController = class {
1770
1604
  this.jumpPressed = false;
1771
1605
  // Tracks if the jump button is pressed
1772
1606
  this.jumpReleased = true;
1607
+ this.controlState = null;
1773
1608
  this.networkState = {
1774
1609
  id: this.config.id,
1775
1610
  position: { x: 0, y: 0, z: 0 },
@@ -1777,35 +1612,22 @@ var LocalController = class {
1777
1612
  state: 0 /* idle */
1778
1613
  };
1779
1614
  }
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
1615
  update() {
1795
- this.updateControllerState();
1616
+ var _a, _b;
1617
+ this.controlState = this.config.keyInputManager.getOutput() || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.getOutput()) || null;
1796
1618
  this.rayCaster.set(this.config.character.position, this.vectorDown);
1797
1619
  const firstRaycastHit = this.config.collisionsManager.raycastFirst(this.rayCaster.ray);
1798
1620
  if (firstRaycastHit !== null) {
1799
1621
  this.currentHeight = firstRaycastHit[0];
1800
1622
  this.currentSurfaceAngle.copy(firstRaycastHit[1]);
1801
1623
  }
1802
- if (this.anyDirection || !this.characterOnGround) {
1624
+ if (((_b = this.controlState) == null ? void 0 : _b.direction) !== null || !this.characterOnGround) {
1803
1625
  const targetAnimation = this.getTargetAnimation();
1804
1626
  this.config.character.updateAnimation(targetAnimation);
1805
1627
  } else {
1806
1628
  this.config.character.updateAnimation(0 /* idle */);
1807
1629
  }
1808
- if (this.anyDirection) {
1630
+ if (this.controlState) {
1809
1631
  this.updateRotation();
1810
1632
  }
1811
1633
  for (let i = 0; i < this.collisionDetectionSteps; i++) {
@@ -1830,30 +1652,17 @@ var LocalController = class {
1830
1652
  }
1831
1653
  return 4 /* air */;
1832
1654
  }
1833
- if (this.conflictingDirections) {
1655
+ if (!this.controlState) {
1834
1656
  return 0 /* idle */;
1835
1657
  }
1836
- return this.run && this.anyDirection ? 2 /* running */ : this.anyDirection ? 1 /* walking */ : 0 /* idle */;
1658
+ if (this.controlState.isSprinting) {
1659
+ return 2 /* running */;
1660
+ }
1661
+ return 1 /* walking */;
1837
1662
  }
1838
1663
  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;
1664
+ if (this.controlState && this.controlState.direction !== null) {
1665
+ this.rotationOffset = this.controlState.direction;
1857
1666
  }
1858
1667
  }
1859
1668
  updateAzimuthalAngle() {
@@ -1888,17 +1697,19 @@ var LocalController = class {
1888
1697
  this.config.character.quaternion.rotateTowards(rotationQuaternion, frameRotation);
1889
1698
  }
1890
1699
  processJump(currentAcceleration, deltaTime) {
1700
+ var _a;
1701
+ const jump = (_a = this.controlState) == null ? void 0 : _a.jump;
1891
1702
  if (this.characterOnGround) {
1892
1703
  this.coyoteJumped = false;
1893
1704
  this.canDoubleJump = false;
1894
1705
  this.doubleJumpUsed = false;
1895
1706
  this.jumpCounter = 0;
1896
- if (!this.jump) {
1707
+ if (!jump) {
1897
1708
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1898
1709
  this.canJump = true;
1899
1710
  this.jumpReleased = true;
1900
1711
  }
1901
- if (this.jump && this.canJump && this.jumpReleased) {
1712
+ if (jump && this.canJump && this.jumpReleased) {
1902
1713
  currentAcceleration.y += this.jumpForce / deltaTime;
1903
1714
  this.canJump = false;
1904
1715
  this.jumpReleased = false;
@@ -1909,13 +1720,13 @@ var LocalController = class {
1909
1720
  }
1910
1721
  }
1911
1722
  } else {
1912
- if (this.jump && !this.coyoteJumped && this.coyoteTime) {
1723
+ if (jump && !this.coyoteJumped && this.coyoteTime) {
1913
1724
  this.coyoteJumped = true;
1914
1725
  currentAcceleration.y += this.jumpForce / deltaTime;
1915
1726
  this.canJump = false;
1916
1727
  this.jumpReleased = false;
1917
1728
  this.jumpCounter++;
1918
- } else if (this.jump && this.canDoubleJump) {
1729
+ } else if (jump && this.canDoubleJump) {
1919
1730
  currentAcceleration.y += this.doubleJumpForce / deltaTime;
1920
1731
  this.doubleJumpUsed = true;
1921
1732
  this.jumpReleased = false;
@@ -1925,7 +1736,7 @@ var LocalController = class {
1925
1736
  this.canJump = false;
1926
1737
  }
1927
1738
  }
1928
- if (!this.jump) {
1739
+ if (!jump) {
1929
1740
  this.jumpReleased = true;
1930
1741
  if (!this.characterOnGround) {
1931
1742
  currentAcceleration.y += this.gravity;
@@ -1933,31 +1744,19 @@ var LocalController = class {
1933
1744
  }
1934
1745
  }
1935
1746
  applyControls(deltaTime) {
1747
+ var _a;
1936
1748
  const resistance = this.characterOnGround ? this.groundResistance : this.airResistance;
1937
1749
  const speedFactor = Math.pow(1 - resistance, deltaTime);
1938
1750
  this.characterVelocity.multiplyScalar(speedFactor);
1939
1751
  const acceleration = this.tempVector.set(0, 0, 0);
1940
1752
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1941
1753
  this.processJump(acceleration, deltaTime);
1942
- const control = (this.characterOnGround ? this.run ? this.groundRunControl : this.groundWalkControl : this.airControlModifier) * this.baseControl;
1754
+ const control = (this.characterOnGround ? ((_a = this.controlState) == null ? void 0 : _a.isSprinting) ? this.groundRunControl : this.groundWalkControl : this.airControlModifier) * this.baseControl;
1943
1755
  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
- }
1756
+ if (this.controlState && this.controlState.direction !== null) {
1757
+ const heading = this.controlState.direction;
1758
+ const headingVector = this.tempVector3.set(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle + heading);
1759
+ controlAcceleration.add(headingVector);
1961
1760
  }
1962
1761
  if (controlAcceleration.length() > 0) {
1963
1762
  controlAcceleration.normalize();
@@ -1968,6 +1767,7 @@ var LocalController = class {
1968
1767
  this.config.character.position.addScaledVector(this.characterVelocity, deltaTime);
1969
1768
  }
1970
1769
  updatePosition(deltaTime, stepDeltaTime, iter) {
1770
+ var _a;
1971
1771
  this.applyControls(stepDeltaTime);
1972
1772
  if (iter === 0) {
1973
1773
  const lastMovement = this.getMovementFromSurfaces(this.config.character.position, deltaTime);
@@ -1999,7 +1799,7 @@ var LocalController = class {
1999
1799
  if (this.characterWasOnGround && !this.characterOnGround) {
2000
1800
  this.characterAirborneSince = Date.now();
2001
1801
  }
2002
- if (!this.jump) {
1802
+ if (!((_a = this.controlState) == null ? void 0 : _a.jump)) {
2003
1803
  this.jumpReleased = true;
2004
1804
  }
2005
1805
  this.coyoteTime = this.characterVelocity.y < 0 && !this.characterOnGround && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
@@ -2321,7 +2121,11 @@ var CharacterManager = class {
2321
2121
  this.remoteCharacterControllers.delete(id);
2322
2122
  }
2323
2123
  }
2324
- if (this.config.updateURLLocation && this.config.timeManager.frame % 60 === 0 && document.hasFocus()) {
2124
+ if (this.config.updateURLLocation && this.config.timeManager.frame % 60 === 0 && document.hasFocus() && /*
2125
+ Don't update the URL if the camera is being controlled as some browsers (e.g. Chrome) cause a hitch to Pointer
2126
+ events when the url is updated
2127
+ */
2128
+ !this.config.cameraManager.hasActiveInput()) {
2325
2129
  const hash = encodeCharacterAndCamera(
2326
2130
  this.localCharacter,
2327
2131
  this.config.cameraManager.camera
@@ -2450,35 +2254,236 @@ var KeyInputManager = class {
2450
2254
  isMovementKeyPressed() {
2451
2255
  return ["w" /* W */, "a" /* A */, "s" /* S */, "d" /* D */].some((key) => this.isKeyPressed(key));
2452
2256
  }
2453
- get forward() {
2257
+ getForward() {
2454
2258
  return this.isKeyPressed("w" /* W */);
2455
2259
  }
2456
- get backward() {
2260
+ getBackward() {
2457
2261
  return this.isKeyPressed("s" /* S */);
2458
2262
  }
2459
- get left() {
2263
+ getLeft() {
2460
2264
  return this.isKeyPressed("a" /* A */);
2461
2265
  }
2462
- get right() {
2266
+ getRight() {
2463
2267
  return this.isKeyPressed("d" /* D */);
2464
2268
  }
2465
- get run() {
2269
+ getRun() {
2466
2270
  return this.isKeyPressed("shift" /* SHIFT */);
2467
2271
  }
2468
- get jump() {
2272
+ getJump() {
2469
2273
  return this.isKeyPressed(" " /* SPACE */);
2470
2274
  }
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 */);
2275
+ getOutput() {
2276
+ const dx = (this.getRight() ? 1 : 0) - (this.getLeft() ? 1 : 0);
2277
+ const dy = (this.getBackward() ? 1 : 0) - (this.getForward() ? 1 : 0);
2278
+ const jump = this.getJump();
2279
+ if (dx === 0 && dy === 0) {
2280
+ if (this.getJump()) {
2281
+ return { direction: null, isSprinting: false, jump };
2282
+ }
2283
+ return null;
2284
+ }
2285
+ const direction = Math.atan2(dx, dy);
2286
+ return { direction, isSprinting: this.getRun(), jump };
2476
2287
  }
2477
2288
  dispose() {
2478
2289
  this.eventHandlerCollection.clear();
2479
2290
  }
2480
2291
  };
2481
2292
 
2293
+ // src/input/VirtualJoystick.ts
2294
+ var sprintingThreshold = 0.6;
2295
+ var VirtualJoystick = class _VirtualJoystick {
2296
+ constructor(holderElement, config) {
2297
+ this.holderElement = holderElement;
2298
+ this.config = config;
2299
+ this.joystickPointerId = null;
2300
+ this.joystickOutput = null;
2301
+ this.jumpPointerId = null;
2302
+ this.clearJoystickState = () => {
2303
+ this.joystickOutput = null;
2304
+ this.joystickCenterElement.style.left = `${this.radius - this.innerRadius}px`;
2305
+ this.joystickCenterElement.style.top = `${this.radius - this.innerRadius}px`;
2306
+ };
2307
+ this.radius = config.radius || 50;
2308
+ this.innerRadius = config.innerRadius || this.radius / 2;
2309
+ this.mouseSupport = this.checkTouch() || config.mouseSupport === true;
2310
+ this.element = document.createElement("div");
2311
+ const style = this.element.style;
2312
+ style.display = this.mouseSupport ? "flex" : "none";
2313
+ style.position = "absolute";
2314
+ style.width = `100%`;
2315
+ style.height = `200px`;
2316
+ style.bottom = "50px";
2317
+ style.zIndex = "10000";
2318
+ style.alignItems = "center";
2319
+ style.justifyContent = "space-between";
2320
+ style.pointerEvents = "none";
2321
+ style.padding = "20px";
2322
+ style.boxSizing = "border-box";
2323
+ style.userSelect = "none";
2324
+ this.holderElement.appendChild(this.element);
2325
+ this.joystickBaseElement = this.createBase();
2326
+ this.element.appendChild(this.joystickBaseElement);
2327
+ this.joystickCenterElement = this.createCenter();
2328
+ this.joystickBaseElement.appendChild(this.joystickCenterElement);
2329
+ this.jumpButton = this.createJumpButton();
2330
+ this.element.appendChild(this.jumpButton);
2331
+ this.bindEvents();
2332
+ this.clearJoystickState();
2333
+ }
2334
+ static checkForTouch() {
2335
+ try {
2336
+ document.createEvent("TouchEvent");
2337
+ return true;
2338
+ } catch (e) {
2339
+ return false;
2340
+ }
2341
+ }
2342
+ checkTouch() {
2343
+ return _VirtualJoystick.checkForTouch();
2344
+ }
2345
+ createBase() {
2346
+ const base = document.createElement("span");
2347
+ const style = base.style;
2348
+ style.touchAction = "pinch-zoom";
2349
+ style.width = `${this.radius * 2}px`;
2350
+ style.height = `${this.radius * 2}px`;
2351
+ style.position = "relative";
2352
+ style.display = "block";
2353
+ style.borderRadius = "50%";
2354
+ style.borderColor = "rgba(200,200,200,0.5)";
2355
+ style.borderWidth = "2px";
2356
+ style.borderStyle = "solid";
2357
+ style.pointerEvents = "auto";
2358
+ style.userSelect = "none";
2359
+ return base;
2360
+ }
2361
+ createCenter() {
2362
+ const center = document.createElement("div");
2363
+ const style = center.style;
2364
+ style.width = `${this.innerRadius * 2}px`;
2365
+ style.height = `${this.innerRadius * 2}px`;
2366
+ style.position = "absolute";
2367
+ style.borderRadius = "50%";
2368
+ style.backgroundColor = "rgba(200,200,200,0.3)";
2369
+ style.borderWidth = "1px";
2370
+ style.borderColor = "rgba(200,200,200,0.8)";
2371
+ style.borderStyle = "solid";
2372
+ style.userSelect = "none";
2373
+ return center;
2374
+ }
2375
+ createJumpButton() {
2376
+ const button = document.createElement("button");
2377
+ button.textContent = "JUMP";
2378
+ const style = button.style;
2379
+ style.touchAction = "pinch-zoom";
2380
+ style.width = `100px`;
2381
+ style.height = `100px`;
2382
+ style.borderRadius = "20px";
2383
+ style.color = "white";
2384
+ style.font = "Helvetica, sans-serif";
2385
+ style.fontSize = "16px";
2386
+ style.backgroundColor = "rgba(200,200,200,0.3)";
2387
+ style.color = "rgba(220,220,220,1)";
2388
+ style.borderWidth = "1px";
2389
+ style.borderColor = "rgba(200,200,200,0.8)";
2390
+ style.borderStyle = "solid";
2391
+ style.pointerEvents = "auto";
2392
+ style.userSelect = "none";
2393
+ return button;
2394
+ }
2395
+ bindEvents() {
2396
+ this.joystickBaseElement.addEventListener("pointerdown", this.onJoystickPointerDown.bind(this));
2397
+ this.joystickBaseElement.addEventListener(
2398
+ "contextmenu",
2399
+ this.preventDefaultAndStopPropagation.bind(this)
2400
+ );
2401
+ this.joystickBaseElement.addEventListener(
2402
+ "touchstart",
2403
+ this.preventDefaultAndStopPropagation.bind(this)
2404
+ );
2405
+ this.jumpButton.addEventListener("pointerdown", this.onJumpPointerDown.bind(this));
2406
+ this.jumpButton.addEventListener(
2407
+ "contextmenu",
2408
+ this.preventDefaultAndStopPropagation.bind(this)
2409
+ );
2410
+ this.jumpButton.addEventListener(
2411
+ "touchstart",
2412
+ this.preventDefaultAndStopPropagation.bind(this)
2413
+ );
2414
+ document.addEventListener("pointermove", this.onPointerMove.bind(this));
2415
+ document.addEventListener("pointerup", this.onPointerUp.bind(this));
2416
+ document.addEventListener("pointercancel", this.onPointerUp.bind(this));
2417
+ }
2418
+ preventDefaultAndStopPropagation(evt) {
2419
+ evt.preventDefault();
2420
+ evt.stopPropagation();
2421
+ }
2422
+ onJumpPointerDown(evt) {
2423
+ if (this.jumpPointerId === null) {
2424
+ this.jumpPointerId = evt.pointerId;
2425
+ }
2426
+ }
2427
+ onJoystickPointerDown(evt) {
2428
+ evt.preventDefault();
2429
+ evt.stopPropagation();
2430
+ if (evt.buttons !== 1) {
2431
+ return;
2432
+ }
2433
+ if (this.joystickPointerId === null) {
2434
+ this.joystickPointerId = evt.pointerId;
2435
+ this.updateControlAndDirection(evt);
2436
+ }
2437
+ }
2438
+ onPointerMove(evt) {
2439
+ evt.preventDefault();
2440
+ evt.stopPropagation();
2441
+ if (evt.pointerId !== this.joystickPointerId) {
2442
+ return;
2443
+ }
2444
+ this.updateControlAndDirection(evt);
2445
+ }
2446
+ onPointerUp(evt) {
2447
+ evt.preventDefault();
2448
+ evt.stopPropagation();
2449
+ if (evt.pointerId === this.jumpPointerId) {
2450
+ this.jumpPointerId = null;
2451
+ }
2452
+ if (evt.pointerId === this.joystickPointerId) {
2453
+ this.joystickPointerId = null;
2454
+ this.clearJoystickState();
2455
+ }
2456
+ }
2457
+ updateControlAndDirection(input) {
2458
+ const rect = this.joystickBaseElement.getBoundingClientRect();
2459
+ const dx = input.clientX - (rect.left + this.radius);
2460
+ const dy = input.clientY - (rect.top + this.radius);
2461
+ const distance = Math.min(Math.sqrt(dx * dx + dy * dy), this.radius);
2462
+ const angle = Math.atan2(dy, dx);
2463
+ const constrainedX = distance * Math.cos(angle);
2464
+ const constrainedY = distance * Math.sin(angle);
2465
+ this.joystickCenterElement.style.left = `${constrainedX + this.radius - this.innerRadius}px`;
2466
+ this.joystickCenterElement.style.top = `${constrainedY + this.radius - this.innerRadius}px`;
2467
+ const direction = Math.atan2(dx, dy);
2468
+ const speed = distance / this.radius;
2469
+ const isSprinting = speed > sprintingThreshold;
2470
+ this.joystickOutput = { direction, isSprinting };
2471
+ }
2472
+ getOutput() {
2473
+ const jump = this.jumpPointerId !== null;
2474
+ if (!this.joystickOutput) {
2475
+ if (jump) {
2476
+ return { direction: null, isSprinting: false, jump };
2477
+ }
2478
+ return null;
2479
+ }
2480
+ return {
2481
+ ...this.joystickOutput,
2482
+ jump
2483
+ };
2484
+ }
2485
+ };
2486
+
2482
2487
  // src/mml/MMLCompositionScene.ts
2483
2488
  import {
2484
2489
  InteractionManager,