@mml-io/3d-web-client-core 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -82,172 +82,18 @@ 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,
241
88
  minDistance: 0.1,
242
- maxDistance: 8,
89
+ maxDistance: 5,
243
90
  initialFOV: 60,
244
- maxFOV: 85,
91
+ maxFOV: 70,
245
92
  minFOV: 60,
246
93
  invertFOVMapping: false,
247
- damping: 0.091,
248
- dampingScale: 0.01,
249
- zoomScale: 0.05,
250
- zoomDamping: 0.3
94
+ damping: 0.25,
95
+ zoomScale: 0.088,
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,136 +211,133 @@ 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
263
  }
435
- setupTweakPane(tweakPane) {
436
- tweakPane.setupCamPane(this);
437
- }
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) {
484
- if (getTweakpaneActive())
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) {
303
+ if (getTweakpaneActive()) {
485
304
  return;
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;
305
+ }
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;
491
322
  this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
492
323
  event.preventDefault();
493
324
  }
494
325
  }
495
326
  onMouseWheel(event) {
496
- const scrollAmount = event.deltaY * this.zoomScale * 0.1;
497
- this.targetDistance += scrollAmount;
327
+ if (getTweakpaneActive()) {
328
+ return;
329
+ }
330
+ event.preventDefault();
331
+ const scrollAmount = event.deltaY * this.zoomScale * scrollZoomSensitivity;
332
+ this.zoom(scrollAmount);
333
+ }
334
+ zoom(delta) {
335
+ this.targetDistance += delta;
498
336
  this.targetDistance = Math.max(
499
337
  this.minDistance,
500
338
  Math.min(this.maxDistance, this.targetDistance)
501
339
  );
502
340
  this.desiredDistance = this.targetDistance;
503
- event.preventDefault();
504
341
  }
505
342
  onContextMenu(event) {
506
343
  event.preventDefault();
@@ -529,12 +366,12 @@ var CameraManager = class {
529
366
  const dy = this.camera.position.y - this.target.y;
530
367
  const dz = this.camera.position.z - this.target.z;
531
368
  this.targetDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
532
- this.targetTheta = Math.atan2(dz, dx);
533
- this.targetPhi = Math.acos(dy / this.targetDistance);
534
- this.phi = this.targetPhi;
535
- this.theta = this.targetTheta;
536
369
  this.distance = this.targetDistance;
537
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;
538
375
  this.recomputeFoV(true);
539
376
  }
540
377
  adjustCameraPosition() {
@@ -542,19 +379,20 @@ var CameraManager = class {
542
379
  const offset = new Vector32(0, 0, offsetDistance);
543
380
  offset.applyEuler(this.camera.rotation);
544
381
  const rayOrigin = this.camera.position.clone().add(offset);
545
- const rayDirection = this.target.clone().sub(rayOrigin).normalize();
546
- this.rayCaster.set(rayOrigin, rayDirection);
382
+ const rayDirection = rayOrigin.sub(this.target.clone()).normalize();
383
+ this.rayCaster.set(this.target.clone(), rayDirection);
547
384
  const firstRaycastHit = this.collisionsManager.raycastFirst(this.rayCaster.ray);
548
- const cameraToPlayerDistance = this.camera.position.distanceTo(this.target);
549
- if (firstRaycastHit !== null && firstRaycastHit[0] <= cameraToPlayerDistance) {
550
- this.targetDistance = cameraToPlayerDistance - firstRaycastHit[0];
551
- 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;
552
389
  } else {
553
- this.targetDistance += (this.desiredDistance - this.targetDistance) * this.damping * 4;
390
+ this.targetDistance = this.desiredDistance;
554
391
  }
555
392
  }
556
393
  dispose() {
557
394
  this.eventHandlerCollection.clear();
395
+ document.body.style.cursor = "";
558
396
  }
559
397
  easeOutExpo(x) {
560
398
  return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
@@ -582,24 +420,25 @@ var CameraManager = class {
582
420
  } else {
583
421
  this.adjustCameraPosition();
584
422
  }
585
- if (this.phi !== null && this.targetPhi !== null && this.theta !== null && this.targetTheta !== null) {
586
- this.distance += (this.targetDistance - this.distance) * this.damping * (0.21 + this.zoomDamping);
587
- this.phi += (this.targetPhi - this.phi) * this.damping;
588
- this.theta += (this.targetTheta - this.theta) * this.damping;
589
- const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
590
- const y = this.target.y + this.distance * Math.cos(this.phi);
591
- const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
592
- this.recomputeFoV();
593
- this.fov += (this.targetFOV - this.fov) * this.damping;
594
- this.camera.fov = this.fov;
595
- this.camera.updateProjectionMatrix();
596
- this.camera.position.set(x, y, z);
597
- this.camera.lookAt(this.target);
598
- if (this.isLerping && this.lerpFactor >= 1) {
599
- this.isLerping = false;
600
- }
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;
601
437
  }
602
438
  }
439
+ hasActiveInput() {
440
+ return this.activePointers.size > 0;
441
+ }
603
442
  };
604
443
 
605
444
  // src/character/Character.ts
@@ -934,7 +773,7 @@ var _CharacterModel = class _CharacterModel {
934
773
  this.config.animationConfig.doubleJumpAnimationFileUrl,
935
774
  6 /* doubleJump */,
936
775
  false,
937
- 1.3
776
+ 1.45
938
777
  );
939
778
  this.applyCustomMaterials();
940
779
  }
@@ -1231,7 +1070,7 @@ import {
1231
1070
  } from "three";
1232
1071
 
1233
1072
  // src/character/CanvasText.ts
1234
- import { Texture as Texture2, LinearFilter, RGBAFormat, MeshBasicMaterial } from "three";
1073
+ import { Texture, LinearFilter, RGBAFormat, MeshBasicMaterial } from "three";
1235
1074
  function getTextAlignOffset(textAlign, width) {
1236
1075
  switch (textAlign) {
1237
1076
  case "center":
@@ -1332,7 +1171,7 @@ function CanvasText(message, options) {
1332
1171
  }
1333
1172
  function THREECanvasTextTexture(text, options) {
1334
1173
  const canvas2 = CanvasText(text, options);
1335
- const texture = new Texture2(canvas2);
1174
+ const texture = new Texture(canvas2);
1336
1175
  texture.minFilter = LinearFilter;
1337
1176
  texture.magFilter = LinearFilter;
1338
1177
  texture.format = RGBAFormat;
@@ -1476,6 +1315,9 @@ var Character = class extends Group {
1476
1315
  }
1477
1316
  async load() {
1478
1317
  const previousModel = this.model;
1318
+ if (previousModel && previousModel.mesh) {
1319
+ this.remove(previousModel.mesh);
1320
+ }
1479
1321
  this.model = new CharacterModel({
1480
1322
  characterDescription: this.config.characterDescription,
1481
1323
  animationConfig: this.config.animationConfig,
@@ -1485,9 +1327,6 @@ var Character = class extends Group {
1485
1327
  isLocal: this.config.isLocal
1486
1328
  });
1487
1329
  await this.model.init();
1488
- if (previousModel && previousModel.mesh) {
1489
- this.remove(previousModel.mesh);
1490
- }
1491
1330
  this.add(this.model.mesh);
1492
1331
  if (this.speakingIndicator === null) {
1493
1332
  this.speakingIndicator = new CharacterSpeakingIndicator(this.config.composer.postPostScene);
@@ -1529,7 +1368,7 @@ import { Euler, Line3, Matrix4, Quaternion as Quaternion2, Ray, Raycaster as Ray
1529
1368
 
1530
1369
  // src/tweakpane/blades/characterControlsFolder.ts
1531
1370
  var characterControllerValues = {
1532
- gravity: 28,
1371
+ gravity: 35.5,
1533
1372
  jumpForce: 18,
1534
1373
  doubleJumpForce: 17.7,
1535
1374
  coyoteJump: 120,
@@ -1765,6 +1604,7 @@ var LocalController = class {
1765
1604
  this.jumpPressed = false;
1766
1605
  // Tracks if the jump button is pressed
1767
1606
  this.jumpReleased = true;
1607
+ this.controlState = null;
1768
1608
  this.networkState = {
1769
1609
  id: this.config.id,
1770
1610
  position: { x: 0, y: 0, z: 0 },
@@ -1772,35 +1612,22 @@ var LocalController = class {
1772
1612
  state: 0 /* idle */
1773
1613
  };
1774
1614
  }
1775
- updateControllerState() {
1776
- var _a, _b, _c, _d, _e;
1777
- this.forward = this.config.keyInputManager.forward || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.up) || false;
1778
- this.backward = this.config.keyInputManager.backward || ((_b = this.config.virtualJoystick) == null ? void 0 : _b.down) || false;
1779
- this.left = this.config.keyInputManager.left || ((_c = this.config.virtualJoystick) == null ? void 0 : _c.left) || false;
1780
- this.right = this.config.keyInputManager.right || ((_d = this.config.virtualJoystick) == null ? void 0 : _d.right) || false;
1781
- this.run = this.config.keyInputManager.run;
1782
- this.jump = this.config.keyInputManager.jump;
1783
- this.anyDirection = this.config.keyInputManager.anyDirection || ((_e = this.config.virtualJoystick) == null ? void 0 : _e.hasDirection) || false;
1784
- this.conflictingDirections = this.config.keyInputManager.conflictingDirection;
1785
- if (!this.jump) {
1786
- this.jumpReleased = true;
1787
- }
1788
- }
1789
1615
  update() {
1790
- this.updateControllerState();
1616
+ var _a, _b;
1617
+ this.controlState = this.config.keyInputManager.getOutput() || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.getOutput()) || null;
1791
1618
  this.rayCaster.set(this.config.character.position, this.vectorDown);
1792
1619
  const firstRaycastHit = this.config.collisionsManager.raycastFirst(this.rayCaster.ray);
1793
1620
  if (firstRaycastHit !== null) {
1794
1621
  this.currentHeight = firstRaycastHit[0];
1795
1622
  this.currentSurfaceAngle.copy(firstRaycastHit[1]);
1796
1623
  }
1797
- if (this.anyDirection || !this.characterOnGround) {
1624
+ if (((_b = this.controlState) == null ? void 0 : _b.direction) !== null || !this.characterOnGround) {
1798
1625
  const targetAnimation = this.getTargetAnimation();
1799
1626
  this.config.character.updateAnimation(targetAnimation);
1800
1627
  } else {
1801
1628
  this.config.character.updateAnimation(0 /* idle */);
1802
1629
  }
1803
- if (this.anyDirection) {
1630
+ if (this.controlState) {
1804
1631
  this.updateRotation();
1805
1632
  }
1806
1633
  for (let i = 0; i < this.collisionDetectionSteps; i++) {
@@ -1825,30 +1652,17 @@ var LocalController = class {
1825
1652
  }
1826
1653
  return 4 /* air */;
1827
1654
  }
1828
- if (this.conflictingDirections) {
1655
+ if (!this.controlState) {
1829
1656
  return 0 /* idle */;
1830
1657
  }
1831
- 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 */;
1832
1662
  }
1833
1663
  updateRotationOffset() {
1834
- if (this.conflictingDirections)
1835
- return;
1836
- if (this.forward) {
1837
- this.rotationOffset = Math.PI;
1838
- if (this.left)
1839
- this.rotationOffset = Math.PI + Math.PI / 4;
1840
- if (this.right)
1841
- this.rotationOffset = Math.PI - Math.PI / 4;
1842
- } else if (this.backward) {
1843
- this.rotationOffset = Math.PI * 2;
1844
- if (this.left)
1845
- this.rotationOffset = -Math.PI * 2 - Math.PI / 4;
1846
- if (this.right)
1847
- this.rotationOffset = Math.PI * 2 + Math.PI / 4;
1848
- } else if (this.left) {
1849
- this.rotationOffset = Math.PI * -0.5;
1850
- } else if (this.right) {
1851
- this.rotationOffset = Math.PI * 0.5;
1664
+ if (this.controlState && this.controlState.direction !== null) {
1665
+ this.rotationOffset = this.controlState.direction;
1852
1666
  }
1853
1667
  }
1854
1668
  updateAzimuthalAngle() {
@@ -1883,17 +1697,19 @@ var LocalController = class {
1883
1697
  this.config.character.quaternion.rotateTowards(rotationQuaternion, frameRotation);
1884
1698
  }
1885
1699
  processJump(currentAcceleration, deltaTime) {
1700
+ var _a;
1701
+ const jump = (_a = this.controlState) == null ? void 0 : _a.jump;
1886
1702
  if (this.characterOnGround) {
1887
1703
  this.coyoteJumped = false;
1888
1704
  this.canDoubleJump = false;
1889
1705
  this.doubleJumpUsed = false;
1890
1706
  this.jumpCounter = 0;
1891
- if (!this.jump) {
1707
+ if (!jump) {
1892
1708
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1893
1709
  this.canJump = true;
1894
1710
  this.jumpReleased = true;
1895
1711
  }
1896
- if (this.jump && this.canJump && this.jumpReleased) {
1712
+ if (jump && this.canJump && this.jumpReleased) {
1897
1713
  currentAcceleration.y += this.jumpForce / deltaTime;
1898
1714
  this.canJump = false;
1899
1715
  this.jumpReleased = false;
@@ -1904,13 +1720,13 @@ var LocalController = class {
1904
1720
  }
1905
1721
  }
1906
1722
  } else {
1907
- if (this.jump && !this.coyoteJumped && this.coyoteTime) {
1723
+ if (jump && !this.coyoteJumped && this.coyoteTime) {
1908
1724
  this.coyoteJumped = true;
1909
1725
  currentAcceleration.y += this.jumpForce / deltaTime;
1910
1726
  this.canJump = false;
1911
1727
  this.jumpReleased = false;
1912
1728
  this.jumpCounter++;
1913
- } else if (this.jump && this.canDoubleJump) {
1729
+ } else if (jump && this.canDoubleJump) {
1914
1730
  currentAcceleration.y += this.doubleJumpForce / deltaTime;
1915
1731
  this.doubleJumpUsed = true;
1916
1732
  this.jumpReleased = false;
@@ -1920,7 +1736,7 @@ var LocalController = class {
1920
1736
  this.canJump = false;
1921
1737
  }
1922
1738
  }
1923
- if (!this.jump) {
1739
+ if (!jump) {
1924
1740
  this.jumpReleased = true;
1925
1741
  if (!this.characterOnGround) {
1926
1742
  currentAcceleration.y += this.gravity;
@@ -1928,31 +1744,19 @@ var LocalController = class {
1928
1744
  }
1929
1745
  }
1930
1746
  applyControls(deltaTime) {
1747
+ var _a;
1931
1748
  const resistance = this.characterOnGround ? this.groundResistance : this.airResistance;
1932
1749
  const speedFactor = Math.pow(1 - resistance, deltaTime);
1933
1750
  this.characterVelocity.multiplyScalar(speedFactor);
1934
1751
  const acceleration = this.tempVector.set(0, 0, 0);
1935
1752
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1936
1753
  this.processJump(acceleration, deltaTime);
1937
- 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;
1938
1755
  const controlAcceleration = this.tempVector2.set(0, 0, 0);
1939
- if (!this.conflictingDirections) {
1940
- if (this.forward) {
1941
- const forward = this.tempVector3.set(0, 0, -1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1942
- controlAcceleration.add(forward);
1943
- }
1944
- if (this.backward) {
1945
- const backward = this.tempVector3.set(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1946
- controlAcceleration.add(backward);
1947
- }
1948
- if (this.left) {
1949
- const left = this.tempVector3.set(-1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1950
- controlAcceleration.add(left);
1951
- }
1952
- if (this.right) {
1953
- const right = this.tempVector3.set(1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1954
- controlAcceleration.add(right);
1955
- }
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);
1956
1760
  }
1957
1761
  if (controlAcceleration.length() > 0) {
1958
1762
  controlAcceleration.normalize();
@@ -1963,6 +1767,7 @@ var LocalController = class {
1963
1767
  this.config.character.position.addScaledVector(this.characterVelocity, deltaTime);
1964
1768
  }
1965
1769
  updatePosition(deltaTime, stepDeltaTime, iter) {
1770
+ var _a;
1966
1771
  this.applyControls(stepDeltaTime);
1967
1772
  if (iter === 0) {
1968
1773
  const lastMovement = this.getMovementFromSurfaces(this.config.character.position, deltaTime);
@@ -1994,7 +1799,7 @@ var LocalController = class {
1994
1799
  if (this.characterWasOnGround && !this.characterOnGround) {
1995
1800
  this.characterAirborneSince = Date.now();
1996
1801
  }
1997
- if (!this.jump) {
1802
+ if (!((_a = this.controlState) == null ? void 0 : _a.jump)) {
1998
1803
  this.jumpReleased = true;
1999
1804
  }
2000
1805
  this.coyoteTime = this.characterVelocity.y < 0 && !this.characterOnGround && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
@@ -2316,7 +2121,11 @@ var CharacterManager = class {
2316
2121
  this.remoteCharacterControllers.delete(id);
2317
2122
  }
2318
2123
  }
2319
- 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()) {
2320
2129
  const hash = encodeCharacterAndCamera(
2321
2130
  this.localCharacter,
2322
2131
  this.config.cameraManager.camera
@@ -2445,41 +2254,243 @@ var KeyInputManager = class {
2445
2254
  isMovementKeyPressed() {
2446
2255
  return ["w" /* W */, "a" /* A */, "s" /* S */, "d" /* D */].some((key) => this.isKeyPressed(key));
2447
2256
  }
2448
- get forward() {
2257
+ getForward() {
2449
2258
  return this.isKeyPressed("w" /* W */);
2450
2259
  }
2451
- get backward() {
2260
+ getBackward() {
2452
2261
  return this.isKeyPressed("s" /* S */);
2453
2262
  }
2454
- get left() {
2263
+ getLeft() {
2455
2264
  return this.isKeyPressed("a" /* A */);
2456
2265
  }
2457
- get right() {
2266
+ getRight() {
2458
2267
  return this.isKeyPressed("d" /* D */);
2459
2268
  }
2460
- get run() {
2269
+ getRun() {
2461
2270
  return this.isKeyPressed("shift" /* SHIFT */);
2462
2271
  }
2463
- get jump() {
2272
+ getJump() {
2464
2273
  return this.isKeyPressed(" " /* SPACE */);
2465
2274
  }
2466
- get anyDirection() {
2467
- return this.isMovementKeyPressed();
2468
- }
2469
- get conflictingDirection() {
2470
- 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 };
2471
2287
  }
2472
2288
  dispose() {
2473
2289
  this.eventHandlerCollection.clear();
2474
2290
  }
2475
2291
  };
2476
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
+
2477
2487
  // src/mml/MMLCompositionScene.ts
2478
2488
  import {
2479
2489
  InteractionManager,
2480
2490
  MMLClickTrigger,
2481
2491
  PromptManager,
2482
- LoadingProgressManager
2492
+ LoadingProgressManager,
2493
+ MMLDocumentTimeManager
2483
2494
  } from "mml-web";
2484
2495
  import { Group as Group3 } from "three";
2485
2496
  var MMLCompositionScene = class {
@@ -2496,6 +2507,7 @@ var MMLCompositionScene = class {
2496
2507
  this.interactionManager = interactionManager;
2497
2508
  this.interactionListener = interactionListener;
2498
2509
  this.loadingProgressManager = new LoadingProgressManager();
2510
+ this.documentTimeManager = new MMLDocumentTimeManager();
2499
2511
  this.mmlScene = {
2500
2512
  getAudioListener: () => this.config.audioListener,
2501
2513
  getRenderer: () => this.config.renderer,
@@ -2529,8 +2541,11 @@ var MMLCompositionScene = class {
2529
2541
  removeChatProbe: (chatProbe) => {
2530
2542
  this.chatProbes.delete(chatProbe);
2531
2543
  },
2532
- prompt: (promptProps, callback) => {
2533
- this.promptManager.prompt(promptProps, callback);
2544
+ prompt: (promptProps, abortSignal, callback) => {
2545
+ this.promptManager.prompt(promptProps, abortSignal, callback);
2546
+ },
2547
+ link: (linkProps, abortSignal, windowCallback) => {
2548
+ this.promptManager.link(linkProps, abortSignal, windowCallback);
2534
2549
  },
2535
2550
  getLoadingProgressManager: () => {
2536
2551
  return this.loadingProgressManager;
@@ -3323,9 +3338,6 @@ var tweakPaneStyle = `
3323
3338
  --tp-label-foreground-color: hsla(0, 0%, 100%, 0.6);
3324
3339
  --tp-monitor-background-color: hsla(0, 0%, 0%, 0.3);
3325
3340
  --tp-monitor-foreground-color: hsla(0, 0%, 100%, 0.3);
3326
- -webkit-user-select: none;
3327
- -ms-user-select: none;
3328
- user-select: none;
3329
3341
  }
3330
3342
 
3331
3343
  .tp-brkv {
@@ -3394,20 +3406,20 @@ var TweakPane = class {
3394
3406
  this.composer = composer;
3395
3407
  this.saveVisibilityInLocalStorage = true;
3396
3408
  this.guiVisible = false;
3397
- const tweakPaneWrapper = document.createElement("div");
3398
- tweakPaneWrapper.style.position = "fixed";
3399
- tweakPaneWrapper.style.width = "400px";
3400
- tweakPaneWrapper.style.height = "100%";
3401
- tweakPaneWrapper.style.top = "0px";
3402
- tweakPaneWrapper.style.right = "calc(-50vw)";
3403
- tweakPaneWrapper.style.zIndex = "99";
3404
- tweakPaneWrapper.style.overflow = "auto";
3405
- tweakPaneWrapper.style.backgroundColor = "rgba(0, 0, 0, 0.66)";
3406
- tweakPaneWrapper.style.paddingLeft = "5px";
3407
- tweakPaneWrapper.style.boxShadow = "-7px 0px 12px rgba(0, 0, 0, 0.5)";
3408
- tweakPaneWrapper.style.transition = "right cubic-bezier(0.83, 0, 0.17, 1) 0.7s";
3409
- holderElement.appendChild(tweakPaneWrapper);
3410
- this.gui = new Pane({ container: tweakPaneWrapper });
3409
+ this.tweakPaneWrapper = document.createElement("div");
3410
+ this.tweakPaneWrapper.style.position = "fixed";
3411
+ this.tweakPaneWrapper.style.width = "400px";
3412
+ this.tweakPaneWrapper.style.height = "100%";
3413
+ this.tweakPaneWrapper.style.top = "0px";
3414
+ this.tweakPaneWrapper.style.right = "calc(-50vw)";
3415
+ this.tweakPaneWrapper.style.zIndex = "99";
3416
+ this.tweakPaneWrapper.style.overflow = "auto";
3417
+ this.tweakPaneWrapper.style.backgroundColor = "rgba(0, 0, 0, 0.66)";
3418
+ this.tweakPaneWrapper.style.paddingLeft = "5px";
3419
+ this.tweakPaneWrapper.style.boxShadow = "-7px 0px 12px rgba(0, 0, 0, 0.5)";
3420
+ this.tweakPaneWrapper.style.transition = "right cubic-bezier(0.83, 0, 0.17, 1) 0.7s";
3421
+ holderElement.appendChild(this.tweakPaneWrapper);
3422
+ this.gui = new Pane({ container: this.tweakPaneWrapper });
3411
3423
  this.gui.registerPlugin(EssentialsPlugin);
3412
3424
  if (this.saveVisibilityInLocalStorage) {
3413
3425
  const localStorageGuiVisible = localStorage.getItem("guiVisible");
@@ -3435,23 +3447,27 @@ var TweakPane = class {
3435
3447
  this.characterControls = new CharacterControlsFolder(this.gui, false);
3436
3448
  this.toneMappingFolder.folder.hidden = rendererValues.toneMapping === 5 ? false : true;
3437
3449
  this.export = this.gui.addFolder({ title: "import / export", expanded: false });
3438
- window.addEventListener("keydown", (e) => {
3439
- this.processKey(e);
3440
- });
3441
- this.setupGUIListeners();
3442
- }
3443
- setupGUIListeners() {
3450
+ this.eventHandlerCollection = new EventHandlerCollection();
3444
3451
  const gui = this.gui;
3445
3452
  const paneElement = gui.containerElem_;
3446
3453
  paneElement.style.right = this.guiVisible ? "0px" : "-450px";
3447
- this.gui.element.addEventListener("mouseenter", () => setTweakpaneActive(true));
3448
- this.gui.element.addEventListener("mousedown", () => setTweakpaneActive(true));
3449
- this.gui.element.addEventListener("mouseup", () => setTweakpaneActive(false));
3450
- this.gui.element.addEventListener("mouseleave", () => setTweakpaneActive(false));
3454
+ this.eventHandlerCollection.add(this.gui.element, "mouseenter", () => setTweakpaneActive(true));
3455
+ this.eventHandlerCollection.add(this.gui.element, "mousemove", () => setTweakpaneActive(true));
3456
+ this.eventHandlerCollection.add(this.gui.element, "mousedown", () => setTweakpaneActive(true));
3457
+ this.eventHandlerCollection.add(
3458
+ this.gui.element,
3459
+ "mouseleave",
3460
+ () => setTweakpaneActive(false)
3461
+ );
3462
+ this.eventHandlerCollection.add(this.gui.element, "mouseup", () => setTweakpaneActive(false));
3463
+ this.eventHandlerCollection.add(window, "keydown", (e) => {
3464
+ this.processKey(e);
3465
+ });
3451
3466
  }
3452
3467
  processKey(e) {
3453
- if (e.key === "p")
3468
+ if (e.key === "p") {
3454
3469
  this.toggleGUI();
3470
+ }
3455
3471
  }
3456
3472
  setupRenderPane(composer, normalPass, ppssaoEffect, ppssaoPass, n8aopass, toneMappingEffect, toneMappingPass, brightnessContrastSaturation, bloomEffect, gaussGrainEffect, hasLighting, sun, setHDR, setSkyboxAzimuthalAngle, setSkyboxPolarAngle, setAmbientLight, setFog) {
3457
3473
  this.rendererFolder.setupChangeEvent(
@@ -3484,6 +3500,11 @@ var TweakPane = class {
3484
3500
  });
3485
3501
  });
3486
3502
  }
3503
+ dispose() {
3504
+ this.eventHandlerCollection.clear();
3505
+ this.gui.dispose();
3506
+ this.tweakPaneWrapper.remove();
3507
+ }
3487
3508
  setupCamPane(cameraManager) {
3488
3509
  this.camera.setupChangeEvent(cameraManager);
3489
3510
  }
@@ -5138,12 +5159,12 @@ var Composer = class {
5138
5159
  this.width = 1;
5139
5160
  this.height = 1;
5140
5161
  this.resolution = new Vector27(this.width, this.height);
5141
- this.isEnvHDRI = false;
5142
5162
  this.bcs = BrightnessContrastSaturation;
5143
5163
  this.gaussGrainEffect = GaussGrainEffect;
5144
5164
  this.ambientLight = null;
5165
+ this.skyboxState = { src: {}, latestPromise: null };
5145
5166
  this.sun = null;
5146
- var _a;
5167
+ var _a, _b;
5147
5168
  this.scene = scene;
5148
5169
  this.postPostScene = new Scene4();
5149
5170
  this.camera = camera;
@@ -5256,6 +5277,13 @@ var Composer = class {
5256
5277
  this.sun = new Sun();
5257
5278
  this.scene.add(this.sun);
5258
5279
  }
5280
+ if ((_b = this.environmentConfiguration) == null ? void 0 : _b.skybox) {
5281
+ if ("hdrJpgUrl" in this.environmentConfiguration.skybox) {
5282
+ this.useHDRJPG(this.environmentConfiguration.skybox.hdrJpgUrl);
5283
+ } else if ("hdrUrl" in this.environmentConfiguration.skybox) {
5284
+ this.useHDRI(this.environmentConfiguration.skybox.hdrUrl);
5285
+ }
5286
+ }
5259
5287
  this.updateSunValues();
5260
5288
  this.resizeListener = () => {
5261
5289
  this.fitContainer();
@@ -5263,6 +5291,19 @@ var Composer = class {
5263
5291
  window.addEventListener("resize", this.resizeListener, false);
5264
5292
  this.fitContainer();
5265
5293
  }
5294
+ updateEnvironmentConfiguration(environmentConfiguration) {
5295
+ this.environmentConfiguration = environmentConfiguration;
5296
+ if (environmentConfiguration.skybox) {
5297
+ if ("hdrJpgUrl" in environmentConfiguration.skybox) {
5298
+ this.useHDRJPG(environmentConfiguration.skybox.hdrJpgUrl);
5299
+ } else if ("hdrUrl" in environmentConfiguration.skybox) {
5300
+ this.useHDRI(environmentConfiguration.skybox.hdrUrl);
5301
+ }
5302
+ }
5303
+ this.updateSkyboxAndEnvValues();
5304
+ this.updateAmbientLightValues();
5305
+ this.updateSunValues();
5306
+ }
5266
5307
  setupTweakPane(tweakPane) {
5267
5308
  tweakPane.setupRenderPane(
5268
5309
  this.effectComposer,
@@ -5292,6 +5333,7 @@ var Composer = class {
5292
5333
  }
5293
5334
  dispose() {
5294
5335
  window.removeEventListener("resize", this.resizeListener);
5336
+ this.renderer.dispose();
5295
5337
  }
5296
5338
  fitContainer() {
5297
5339
  if (!this) {
@@ -5352,71 +5394,71 @@ var Composer = class {
5352
5394
  0
5353
5395
  );
5354
5396
  }
5355
- useHDRJPG(url, fromFile = false) {
5356
- const pmremGenerator = new PMREMGenerator(this.renderer);
5357
- const hdrJpg = new HDRJPGLoader(this.renderer).load(url, () => {
5358
- const hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;
5359
- hdrJpgEquirectangularMap.mapping = EquirectangularReflectionMapping;
5360
- hdrJpgEquirectangularMap.needsUpdate = true;
5361
- const envMap = pmremGenerator.fromEquirectangular(hdrJpgEquirectangularMap).texture;
5362
- if (envMap) {
5363
- envMap.colorSpace = LinearSRGBColorSpace;
5364
- envMap.needsUpdate = true;
5365
- this.scene.environment = envMap;
5366
- this.scene.environmentIntensity = envValues.envMapIntensity;
5367
- this.scene.environmentRotation = new Euler3(
5368
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5369
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5370
- 0
5371
- );
5372
- this.scene.background = envMap;
5373
- this.scene.backgroundIntensity = envValues.skyboxIntensity;
5374
- this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
5375
- this.scene.backgroundRotation = new Euler3(
5376
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5377
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5378
- 0
5379
- );
5380
- this.isEnvHDRI = true;
5397
+ async loadHDRJPG(url) {
5398
+ return new Promise((resolve, reject) => {
5399
+ const pmremGenerator = new PMREMGenerator(this.renderer);
5400
+ const hdrJpg = new HDRJPGLoader(this.renderer).load(url, () => {
5401
+ const hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;
5402
+ hdrJpgEquirectangularMap.mapping = EquirectangularReflectionMapping;
5403
+ hdrJpgEquirectangularMap.needsUpdate = true;
5404
+ const envMap = pmremGenerator.fromEquirectangular(hdrJpgEquirectangularMap).texture;
5381
5405
  hdrJpgEquirectangularMap.dispose();
5382
5406
  pmremGenerator.dispose();
5383
- }
5384
- hdrJpg.dispose();
5407
+ hdrJpg.dispose();
5408
+ if (envMap) {
5409
+ envMap.colorSpace = LinearSRGBColorSpace;
5410
+ envMap.needsUpdate = true;
5411
+ resolve(envMap);
5412
+ } else {
5413
+ reject("Failed to generate environment map");
5414
+ }
5415
+ });
5385
5416
  });
5386
5417
  }
5387
- useHDRI(url, fromFile = false) {
5388
- if (this.isEnvHDRI && fromFile === false || !this.renderer) {
5389
- return;
5390
- }
5391
- const pmremGenerator = new PMREMGenerator(this.renderer);
5392
- new RGBELoader(new LoadingManager()).load(
5393
- url,
5394
- (texture) => {
5418
+ async loadHDRi(url) {
5419
+ return new Promise((resolve, reject) => {
5420
+ const pmremGenerator = new PMREMGenerator(this.renderer);
5421
+ new RGBELoader(new LoadingManager()).load(url, (texture) => {
5395
5422
  const envMap = pmremGenerator.fromEquirectangular(texture).texture;
5423
+ texture.dispose();
5424
+ pmremGenerator.dispose();
5396
5425
  if (envMap) {
5397
5426
  envMap.colorSpace = LinearSRGBColorSpace;
5398
5427
  envMap.needsUpdate = true;
5399
- this.scene.environment = envMap;
5400
- this.scene.environmentIntensity = envValues.envMapIntensity;
5401
- this.scene.environmentRotation = new Euler3(
5402
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5403
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5404
- 0
5405
- );
5406
- this.scene.background = envMap;
5407
- this.scene.backgroundIntensity = envValues.skyboxIntensity;
5408
- this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
5409
- this.isEnvHDRI = true;
5410
- texture.dispose();
5411
- pmremGenerator.dispose();
5428
+ resolve(envMap);
5429
+ } else {
5430
+ reject("Failed to generate environment map");
5412
5431
  }
5413
- },
5414
- () => {
5415
- },
5416
- (error) => {
5417
- console.error(`Can't load ${url}: ${JSON.stringify(error)}`);
5432
+ });
5433
+ });
5434
+ }
5435
+ useHDRJPG(url, fromFile = false) {
5436
+ if (this.skyboxState.src.hdrJpgUrl === url) {
5437
+ return;
5438
+ }
5439
+ const hdrJPGPromise = this.loadHDRJPG(url);
5440
+ this.skyboxState.src = { hdrJpgUrl: url };
5441
+ this.skyboxState.latestPromise = hdrJPGPromise;
5442
+ hdrJPGPromise.then((envMap) => {
5443
+ if (this.skyboxState.latestPromise !== hdrJPGPromise) {
5444
+ return;
5418
5445
  }
5419
- );
5446
+ this.applyEnvMap(envMap);
5447
+ });
5448
+ }
5449
+ useHDRI(url) {
5450
+ if (this.skyboxState.src.hdrUrl === url) {
5451
+ return;
5452
+ }
5453
+ const hdrPromise = this.loadHDRi(url);
5454
+ this.skyboxState.src = { hdrUrl: url };
5455
+ this.skyboxState.latestPromise = hdrPromise;
5456
+ hdrPromise.then((envMap) => {
5457
+ if (this.skyboxState.latestPromise !== hdrPromise) {
5458
+ return;
5459
+ }
5460
+ this.applyEnvMap(envMap);
5461
+ });
5420
5462
  }
5421
5463
  setHDRIFromFile() {
5422
5464
  if (!this.renderer)
@@ -5435,7 +5477,7 @@ var Composer = class {
5435
5477
  const fileURL = URL.createObjectURL(file);
5436
5478
  if (fileURL) {
5437
5479
  if (extension === "hdr") {
5438
- this.useHDRI(fileURL, true);
5480
+ this.useHDRI(fileURL);
5439
5481
  } else if (extension === "jpg") {
5440
5482
  this.useHDRJPG(fileURL);
5441
5483
  } else {
@@ -5514,8 +5556,25 @@ var Composer = class {
5514
5556
  var _a, _b;
5515
5557
  if (typeof ((_b = (_a = this.environmentConfiguration) == null ? void 0 : _a.ambientLight) == null ? void 0 : _b.intensity) === "number") {
5516
5558
  envValues.ambientLight.ambientLightIntensity = this.environmentConfiguration.ambientLight.intensity;
5517
- this.setAmbientLight();
5518
5559
  }
5560
+ this.setAmbientLight();
5561
+ }
5562
+ applyEnvMap(envMap) {
5563
+ this.scene.environment = envMap;
5564
+ this.scene.environmentIntensity = envValues.envMapIntensity;
5565
+ this.scene.environmentRotation = new Euler3(
5566
+ MathUtils.degToRad(envValues.skyboxPolarAngle),
5567
+ MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5568
+ 0
5569
+ );
5570
+ this.scene.background = envMap;
5571
+ this.scene.backgroundIntensity = envValues.skyboxIntensity;
5572
+ this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
5573
+ this.scene.backgroundRotation = new Euler3(
5574
+ MathUtils.degToRad(envValues.skyboxPolarAngle),
5575
+ MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5576
+ 0
5577
+ );
5519
5578
  }
5520
5579
  };
5521
5580
 
@@ -5804,13 +5863,13 @@ var CollisionsManager = class {
5804
5863
  // src/ground-plane/GroundPlane.ts
5805
5864
  import {
5806
5865
  CanvasTexture,
5807
- CircleGeometry as CircleGeometry2,
5808
5866
  FrontSide as FrontSide2,
5809
5867
  Group as Group6,
5810
5868
  LinearMipMapLinearFilter,
5811
5869
  Mesh as Mesh6,
5812
5870
  MeshStandardMaterial as MeshStandardMaterial3,
5813
5871
  NearestFilter as NearestFilter2,
5872
+ PlaneGeometry as PlaneGeometry2,
5814
5873
  RepeatWrapping as RepeatWrapping2
5815
5874
  } from "three";
5816
5875
  var canvas = document.createElement("canvas");
@@ -5827,7 +5886,7 @@ var GroundPlane = class extends Group6 {
5827
5886
  super();
5828
5887
  this.floorSize = 210;
5829
5888
  this.floorTexture = null;
5830
- this.floorGeometry = new CircleGeometry2(this.floorSize, this.floorSize);
5889
+ this.floorGeometry = new PlaneGeometry2(this.floorSize, this.floorSize, 1, 1);
5831
5890
  this.floorMesh = null;
5832
5891
  this.floorMaterial = new MeshStandardMaterial3({
5833
5892
  color: 16777215,
@@ -5856,6 +5915,7 @@ var LoadingScreen = class {
5856
5915
  constructor(loadingProgressManager) {
5857
5916
  this.loadingProgressManager = loadingProgressManager;
5858
5917
  this.hasCompleted = false;
5918
+ this.disposed = false;
5859
5919
  this.element = document.createElement("div");
5860
5920
  this.element.style.position = "absolute";
5861
5921
  this.element.style.top = "0";
@@ -5990,10 +6050,47 @@ var LoadingScreen = class {
5990
6050
  this.loadingProgressManager.addProgressCallback(this.loadingCallback);
5991
6051
  }
5992
6052
  dispose() {
6053
+ if (this.disposed) {
6054
+ return;
6055
+ }
6056
+ this.disposed = true;
5993
6057
  this.loadingProgressManager.removeProgressCallback(this.loadingCallback);
5994
6058
  this.element.remove();
5995
6059
  }
5996
6060
  };
6061
+
6062
+ // src/error-screen/ErrorScreen.ts
6063
+ var ErrorScreen = class {
6064
+ constructor(title, message) {
6065
+ this.element = document.createElement("div");
6066
+ this.element.style.position = "absolute";
6067
+ this.element.style.top = "0";
6068
+ this.element.style.left = "0";
6069
+ this.element.style.display = "flex";
6070
+ this.element.style.alignItems = "center";
6071
+ this.element.style.justifyContent = "center";
6072
+ this.element.style.flexDirection = "column";
6073
+ this.element.style.width = "100%";
6074
+ this.element.style.height = "100%";
6075
+ this.element.style.background = "linear-gradient(45deg, #111111 0%, #444444 100%)";
6076
+ this.element.style.color = "white";
6077
+ this.titleBannerText = document.createElement("div");
6078
+ this.titleBannerText.textContent = title;
6079
+ this.titleBannerText.style.fontSize = "40px";
6080
+ this.titleBannerText.style.fontWeight = "bold";
6081
+ this.titleBannerText.style.fontFamily = "sans-serif";
6082
+ this.element.append(this.titleBannerText);
6083
+ this.messageText = document.createElement("div");
6084
+ this.messageText.style.textAlign = "center";
6085
+ this.messageText.style.fontFamily = "sans-serif";
6086
+ this.messageText.style.fontWeight = "bold";
6087
+ this.messageText.textContent = message;
6088
+ this.element.append(this.messageText);
6089
+ }
6090
+ dispose() {
6091
+ this.element.remove();
6092
+ }
6093
+ };
5997
6094
  export {
5998
6095
  AnimationState,
5999
6096
  CameraManager,
@@ -6001,6 +6098,7 @@ export {
6001
6098
  CharacterModelLoader,
6002
6099
  CollisionsManager,
6003
6100
  Composer,
6101
+ ErrorScreen,
6004
6102
  GroundPlane,
6005
6103
  KeyInputManager,
6006
6104
  LoadingScreen,