@mml-io/3d-web-client-core 0.18.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,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
- }
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
263
  }
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,19 +379,20 @@ 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() {
561
394
  this.eventHandlerCollection.clear();
395
+ document.body.style.cursor = "";
562
396
  }
563
397
  easeOutExpo(x) {
564
398
  return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
@@ -586,24 +420,25 @@ var CameraManager = class {
586
420
  } else {
587
421
  this.adjustCameraPosition();
588
422
  }
589
- if (this.phi !== null && this.targetPhi !== null && this.theta !== null && this.targetTheta !== null) {
590
- this.distance += (this.targetDistance - this.distance) * this.damping * (0.21 + this.zoomDamping);
591
- this.phi += (this.targetPhi - this.phi) * this.damping;
592
- this.theta += (this.targetTheta - this.theta) * this.damping;
593
- const x = this.target.x + this.distance * Math.sin(this.phi) * Math.cos(this.theta);
594
- const y = this.target.y + this.distance * Math.cos(this.phi);
595
- const z = this.target.z + this.distance * Math.sin(this.phi) * Math.sin(this.theta);
596
- this.recomputeFoV();
597
- this.fov += (this.targetFOV - this.fov) * this.damping;
598
- this.camera.fov = this.fov;
599
- this.camera.updateProjectionMatrix();
600
- this.camera.position.set(x, y, z);
601
- this.camera.lookAt(this.target);
602
- if (this.isLerping && this.lerpFactor >= 1) {
603
- this.isLerping = false;
604
- }
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;
605
437
  }
606
438
  }
439
+ hasActiveInput() {
440
+ return this.activePointers.size > 0;
441
+ }
607
442
  };
608
443
 
609
444
  // src/character/Character.ts
@@ -1480,6 +1315,9 @@ var Character = class extends Group {
1480
1315
  }
1481
1316
  async load() {
1482
1317
  const previousModel = this.model;
1318
+ if (previousModel && previousModel.mesh) {
1319
+ this.remove(previousModel.mesh);
1320
+ }
1483
1321
  this.model = new CharacterModel({
1484
1322
  characterDescription: this.config.characterDescription,
1485
1323
  animationConfig: this.config.animationConfig,
@@ -1489,9 +1327,6 @@ var Character = class extends Group {
1489
1327
  isLocal: this.config.isLocal
1490
1328
  });
1491
1329
  await this.model.init();
1492
- if (previousModel && previousModel.mesh) {
1493
- this.remove(previousModel.mesh);
1494
- }
1495
1330
  this.add(this.model.mesh);
1496
1331
  if (this.speakingIndicator === null) {
1497
1332
  this.speakingIndicator = new CharacterSpeakingIndicator(this.config.composer.postPostScene);
@@ -1769,6 +1604,7 @@ var LocalController = class {
1769
1604
  this.jumpPressed = false;
1770
1605
  // Tracks if the jump button is pressed
1771
1606
  this.jumpReleased = true;
1607
+ this.controlState = null;
1772
1608
  this.networkState = {
1773
1609
  id: this.config.id,
1774
1610
  position: { x: 0, y: 0, z: 0 },
@@ -1776,35 +1612,22 @@ var LocalController = class {
1776
1612
  state: 0 /* idle */
1777
1613
  };
1778
1614
  }
1779
- updateControllerState() {
1780
- var _a, _b, _c, _d, _e;
1781
- this.forward = this.config.keyInputManager.forward || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.up) || false;
1782
- this.backward = this.config.keyInputManager.backward || ((_b = this.config.virtualJoystick) == null ? void 0 : _b.down) || false;
1783
- this.left = this.config.keyInputManager.left || ((_c = this.config.virtualJoystick) == null ? void 0 : _c.left) || false;
1784
- this.right = this.config.keyInputManager.right || ((_d = this.config.virtualJoystick) == null ? void 0 : _d.right) || false;
1785
- this.run = this.config.keyInputManager.run;
1786
- this.jump = this.config.keyInputManager.jump;
1787
- this.anyDirection = this.config.keyInputManager.anyDirection || ((_e = this.config.virtualJoystick) == null ? void 0 : _e.hasDirection) || false;
1788
- this.conflictingDirections = this.config.keyInputManager.conflictingDirection;
1789
- if (!this.jump) {
1790
- this.jumpReleased = true;
1791
- }
1792
- }
1793
1615
  update() {
1794
- this.updateControllerState();
1616
+ var _a, _b;
1617
+ this.controlState = this.config.keyInputManager.getOutput() || ((_a = this.config.virtualJoystick) == null ? void 0 : _a.getOutput()) || null;
1795
1618
  this.rayCaster.set(this.config.character.position, this.vectorDown);
1796
1619
  const firstRaycastHit = this.config.collisionsManager.raycastFirst(this.rayCaster.ray);
1797
1620
  if (firstRaycastHit !== null) {
1798
1621
  this.currentHeight = firstRaycastHit[0];
1799
1622
  this.currentSurfaceAngle.copy(firstRaycastHit[1]);
1800
1623
  }
1801
- if (this.anyDirection || !this.characterOnGround) {
1624
+ if (((_b = this.controlState) == null ? void 0 : _b.direction) !== null || !this.characterOnGround) {
1802
1625
  const targetAnimation = this.getTargetAnimation();
1803
1626
  this.config.character.updateAnimation(targetAnimation);
1804
1627
  } else {
1805
1628
  this.config.character.updateAnimation(0 /* idle */);
1806
1629
  }
1807
- if (this.anyDirection) {
1630
+ if (this.controlState) {
1808
1631
  this.updateRotation();
1809
1632
  }
1810
1633
  for (let i = 0; i < this.collisionDetectionSteps; i++) {
@@ -1829,30 +1652,17 @@ var LocalController = class {
1829
1652
  }
1830
1653
  return 4 /* air */;
1831
1654
  }
1832
- if (this.conflictingDirections) {
1655
+ if (!this.controlState) {
1833
1656
  return 0 /* idle */;
1834
1657
  }
1835
- 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 */;
1836
1662
  }
1837
1663
  updateRotationOffset() {
1838
- if (this.conflictingDirections)
1839
- return;
1840
- if (this.forward) {
1841
- this.rotationOffset = Math.PI;
1842
- if (this.left)
1843
- this.rotationOffset = Math.PI + Math.PI / 4;
1844
- if (this.right)
1845
- this.rotationOffset = Math.PI - Math.PI / 4;
1846
- } else if (this.backward) {
1847
- this.rotationOffset = Math.PI * 2;
1848
- if (this.left)
1849
- this.rotationOffset = -Math.PI * 2 - Math.PI / 4;
1850
- if (this.right)
1851
- this.rotationOffset = Math.PI * 2 + Math.PI / 4;
1852
- } else if (this.left) {
1853
- this.rotationOffset = Math.PI * -0.5;
1854
- } else if (this.right) {
1855
- this.rotationOffset = Math.PI * 0.5;
1664
+ if (this.controlState && this.controlState.direction !== null) {
1665
+ this.rotationOffset = this.controlState.direction;
1856
1666
  }
1857
1667
  }
1858
1668
  updateAzimuthalAngle() {
@@ -1887,17 +1697,19 @@ var LocalController = class {
1887
1697
  this.config.character.quaternion.rotateTowards(rotationQuaternion, frameRotation);
1888
1698
  }
1889
1699
  processJump(currentAcceleration, deltaTime) {
1700
+ var _a;
1701
+ const jump = (_a = this.controlState) == null ? void 0 : _a.jump;
1890
1702
  if (this.characterOnGround) {
1891
1703
  this.coyoteJumped = false;
1892
1704
  this.canDoubleJump = false;
1893
1705
  this.doubleJumpUsed = false;
1894
1706
  this.jumpCounter = 0;
1895
- if (!this.jump) {
1707
+ if (!jump) {
1896
1708
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1897
1709
  this.canJump = true;
1898
1710
  this.jumpReleased = true;
1899
1711
  }
1900
- if (this.jump && this.canJump && this.jumpReleased) {
1712
+ if (jump && this.canJump && this.jumpReleased) {
1901
1713
  currentAcceleration.y += this.jumpForce / deltaTime;
1902
1714
  this.canJump = false;
1903
1715
  this.jumpReleased = false;
@@ -1908,13 +1720,13 @@ var LocalController = class {
1908
1720
  }
1909
1721
  }
1910
1722
  } else {
1911
- if (this.jump && !this.coyoteJumped && this.coyoteTime) {
1723
+ if (jump && !this.coyoteJumped && this.coyoteTime) {
1912
1724
  this.coyoteJumped = true;
1913
1725
  currentAcceleration.y += this.jumpForce / deltaTime;
1914
1726
  this.canJump = false;
1915
1727
  this.jumpReleased = false;
1916
1728
  this.jumpCounter++;
1917
- } else if (this.jump && this.canDoubleJump) {
1729
+ } else if (jump && this.canDoubleJump) {
1918
1730
  currentAcceleration.y += this.doubleJumpForce / deltaTime;
1919
1731
  this.doubleJumpUsed = true;
1920
1732
  this.jumpReleased = false;
@@ -1924,7 +1736,7 @@ var LocalController = class {
1924
1736
  this.canJump = false;
1925
1737
  }
1926
1738
  }
1927
- if (!this.jump) {
1739
+ if (!jump) {
1928
1740
  this.jumpReleased = true;
1929
1741
  if (!this.characterOnGround) {
1930
1742
  currentAcceleration.y += this.gravity;
@@ -1932,31 +1744,19 @@ var LocalController = class {
1932
1744
  }
1933
1745
  }
1934
1746
  applyControls(deltaTime) {
1747
+ var _a;
1935
1748
  const resistance = this.characterOnGround ? this.groundResistance : this.airResistance;
1936
1749
  const speedFactor = Math.pow(1 - resistance, deltaTime);
1937
1750
  this.characterVelocity.multiplyScalar(speedFactor);
1938
1751
  const acceleration = this.tempVector.set(0, 0, 0);
1939
1752
  this.canDoubleJump = !this.doubleJumpUsed && this.jumpReleased && this.jumpCounter === 1;
1940
1753
  this.processJump(acceleration, deltaTime);
1941
- 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;
1942
1755
  const controlAcceleration = this.tempVector2.set(0, 0, 0);
1943
- if (!this.conflictingDirections) {
1944
- if (this.forward) {
1945
- const forward = this.tempVector3.set(0, 0, -1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1946
- controlAcceleration.add(forward);
1947
- }
1948
- if (this.backward) {
1949
- const backward = this.tempVector3.set(0, 0, 1).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1950
- controlAcceleration.add(backward);
1951
- }
1952
- if (this.left) {
1953
- const left = this.tempVector3.set(-1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1954
- controlAcceleration.add(left);
1955
- }
1956
- if (this.right) {
1957
- const right = this.tempVector3.set(1, 0, 0).applyAxisAngle(this.vectorUp, this.azimuthalAngle);
1958
- controlAcceleration.add(right);
1959
- }
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);
1960
1760
  }
1961
1761
  if (controlAcceleration.length() > 0) {
1962
1762
  controlAcceleration.normalize();
@@ -1967,6 +1767,7 @@ var LocalController = class {
1967
1767
  this.config.character.position.addScaledVector(this.characterVelocity, deltaTime);
1968
1768
  }
1969
1769
  updatePosition(deltaTime, stepDeltaTime, iter) {
1770
+ var _a;
1970
1771
  this.applyControls(stepDeltaTime);
1971
1772
  if (iter === 0) {
1972
1773
  const lastMovement = this.getMovementFromSurfaces(this.config.character.position, deltaTime);
@@ -1998,7 +1799,7 @@ var LocalController = class {
1998
1799
  if (this.characterWasOnGround && !this.characterOnGround) {
1999
1800
  this.characterAirborneSince = Date.now();
2000
1801
  }
2001
- if (!this.jump) {
1802
+ if (!((_a = this.controlState) == null ? void 0 : _a.jump)) {
2002
1803
  this.jumpReleased = true;
2003
1804
  }
2004
1805
  this.coyoteTime = this.characterVelocity.y < 0 && !this.characterOnGround && Date.now() - this.characterAirborneSince < this.coyoteTimeThreshold;
@@ -2320,7 +2121,11 @@ var CharacterManager = class {
2320
2121
  this.remoteCharacterControllers.delete(id);
2321
2122
  }
2322
2123
  }
2323
- 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()) {
2324
2129
  const hash = encodeCharacterAndCamera(
2325
2130
  this.localCharacter,
2326
2131
  this.config.cameraManager.camera
@@ -2449,41 +2254,243 @@ var KeyInputManager = class {
2449
2254
  isMovementKeyPressed() {
2450
2255
  return ["w" /* W */, "a" /* A */, "s" /* S */, "d" /* D */].some((key) => this.isKeyPressed(key));
2451
2256
  }
2452
- get forward() {
2257
+ getForward() {
2453
2258
  return this.isKeyPressed("w" /* W */);
2454
2259
  }
2455
- get backward() {
2260
+ getBackward() {
2456
2261
  return this.isKeyPressed("s" /* S */);
2457
2262
  }
2458
- get left() {
2263
+ getLeft() {
2459
2264
  return this.isKeyPressed("a" /* A */);
2460
2265
  }
2461
- get right() {
2266
+ getRight() {
2462
2267
  return this.isKeyPressed("d" /* D */);
2463
2268
  }
2464
- get run() {
2269
+ getRun() {
2465
2270
  return this.isKeyPressed("shift" /* SHIFT */);
2466
2271
  }
2467
- get jump() {
2272
+ getJump() {
2468
2273
  return this.isKeyPressed(" " /* SPACE */);
2469
2274
  }
2470
- get anyDirection() {
2471
- return this.isMovementKeyPressed();
2472
- }
2473
- get conflictingDirection() {
2474
- 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 };
2475
2287
  }
2476
2288
  dispose() {
2477
2289
  this.eventHandlerCollection.clear();
2478
2290
  }
2479
2291
  };
2480
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
+
2481
2487
  // src/mml/MMLCompositionScene.ts
2482
2488
  import {
2483
2489
  InteractionManager,
2484
2490
  MMLClickTrigger,
2485
2491
  PromptManager,
2486
- LoadingProgressManager
2492
+ LoadingProgressManager,
2493
+ MMLDocumentTimeManager
2487
2494
  } from "mml-web";
2488
2495
  import { Group as Group3 } from "three";
2489
2496
  var MMLCompositionScene = class {
@@ -2500,6 +2507,7 @@ var MMLCompositionScene = class {
2500
2507
  this.interactionManager = interactionManager;
2501
2508
  this.interactionListener = interactionListener;
2502
2509
  this.loadingProgressManager = new LoadingProgressManager();
2510
+ this.documentTimeManager = new MMLDocumentTimeManager();
2503
2511
  this.mmlScene = {
2504
2512
  getAudioListener: () => this.config.audioListener,
2505
2513
  getRenderer: () => this.config.renderer,
@@ -2533,8 +2541,11 @@ var MMLCompositionScene = class {
2533
2541
  removeChatProbe: (chatProbe) => {
2534
2542
  this.chatProbes.delete(chatProbe);
2535
2543
  },
2536
- prompt: (promptProps, callback) => {
2537
- 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);
2538
2549
  },
2539
2550
  getLoadingProgressManager: () => {
2540
2551
  return this.loadingProgressManager;
@@ -3436,24 +3447,27 @@ var TweakPane = class {
3436
3447
  this.characterControls = new CharacterControlsFolder(this.gui, false);
3437
3448
  this.toneMappingFolder.folder.hidden = rendererValues.toneMapping === 5 ? false : true;
3438
3449
  this.export = this.gui.addFolder({ title: "import / export", expanded: false });
3439
- this.setupGUIListeners();
3440
- window.addEventListener("keydown", (e) => {
3441
- this.processKey(e);
3442
- });
3443
- }
3444
- setupGUIListeners() {
3450
+ this.eventHandlerCollection = new EventHandlerCollection();
3445
3451
  const gui = this.gui;
3446
3452
  const paneElement = gui.containerElem_;
3447
3453
  paneElement.style.right = this.guiVisible ? "0px" : "-450px";
3448
- this.gui.element.addEventListener("mouseenter", () => setTweakpaneActive(true));
3449
- this.gui.element.addEventListener("mousemove", () => setTweakpaneActive(true));
3450
- this.gui.element.addEventListener("mousedown", () => setTweakpaneActive(true));
3451
- this.gui.element.addEventListener("mouseleave", () => setTweakpaneActive(false));
3452
- this.gui.element.addEventListener("mouseup", () => 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
+ });
3453
3466
  }
3454
3467
  processKey(e) {
3455
- if (e.key === "p")
3468
+ if (e.key === "p") {
3456
3469
  this.toggleGUI();
3470
+ }
3457
3471
  }
3458
3472
  setupRenderPane(composer, normalPass, ppssaoEffect, ppssaoPass, n8aopass, toneMappingEffect, toneMappingPass, brightnessContrastSaturation, bloomEffect, gaussGrainEffect, hasLighting, sun, setHDR, setSkyboxAzimuthalAngle, setSkyboxPolarAngle, setAmbientLight, setFog) {
3459
3473
  this.rendererFolder.setupChangeEvent(
@@ -3487,6 +3501,7 @@ var TweakPane = class {
3487
3501
  });
3488
3502
  }
3489
3503
  dispose() {
3504
+ this.eventHandlerCollection.clear();
3490
3505
  this.gui.dispose();
3491
3506
  this.tweakPaneWrapper.remove();
3492
3507
  }
@@ -5144,12 +5159,12 @@ var Composer = class {
5144
5159
  this.width = 1;
5145
5160
  this.height = 1;
5146
5161
  this.resolution = new Vector27(this.width, this.height);
5147
- this.isEnvHDRI = false;
5148
5162
  this.bcs = BrightnessContrastSaturation;
5149
5163
  this.gaussGrainEffect = GaussGrainEffect;
5150
5164
  this.ambientLight = null;
5165
+ this.skyboxState = { src: {}, latestPromise: null };
5151
5166
  this.sun = null;
5152
- var _a;
5167
+ var _a, _b;
5153
5168
  this.scene = scene;
5154
5169
  this.postPostScene = new Scene4();
5155
5170
  this.camera = camera;
@@ -5262,6 +5277,13 @@ var Composer = class {
5262
5277
  this.sun = new Sun();
5263
5278
  this.scene.add(this.sun);
5264
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
+ }
5265
5287
  this.updateSunValues();
5266
5288
  this.resizeListener = () => {
5267
5289
  this.fitContainer();
@@ -5269,6 +5291,19 @@ var Composer = class {
5269
5291
  window.addEventListener("resize", this.resizeListener, false);
5270
5292
  this.fitContainer();
5271
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
+ }
5272
5307
  setupTweakPane(tweakPane) {
5273
5308
  tweakPane.setupRenderPane(
5274
5309
  this.effectComposer,
@@ -5359,71 +5394,71 @@ var Composer = class {
5359
5394
  0
5360
5395
  );
5361
5396
  }
5362
- useHDRJPG(url, fromFile = false) {
5363
- const pmremGenerator = new PMREMGenerator(this.renderer);
5364
- const hdrJpg = new HDRJPGLoader(this.renderer).load(url, () => {
5365
- const hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;
5366
- hdrJpgEquirectangularMap.mapping = EquirectangularReflectionMapping;
5367
- hdrJpgEquirectangularMap.needsUpdate = true;
5368
- const envMap = pmremGenerator.fromEquirectangular(hdrJpgEquirectangularMap).texture;
5369
- if (envMap) {
5370
- envMap.colorSpace = LinearSRGBColorSpace;
5371
- envMap.needsUpdate = true;
5372
- this.scene.environment = envMap;
5373
- this.scene.environmentIntensity = envValues.envMapIntensity;
5374
- this.scene.environmentRotation = new Euler3(
5375
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5376
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5377
- 0
5378
- );
5379
- this.scene.background = envMap;
5380
- this.scene.backgroundIntensity = envValues.skyboxIntensity;
5381
- this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
5382
- this.scene.backgroundRotation = new Euler3(
5383
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5384
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5385
- 0
5386
- );
5387
- 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;
5388
5405
  hdrJpgEquirectangularMap.dispose();
5389
5406
  pmremGenerator.dispose();
5390
- }
5391
- 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
+ });
5392
5416
  });
5393
5417
  }
5394
- useHDRI(url, fromFile = false) {
5395
- if (this.isEnvHDRI && fromFile === false || !this.renderer) {
5396
- return;
5397
- }
5398
- const pmremGenerator = new PMREMGenerator(this.renderer);
5399
- new RGBELoader(new LoadingManager()).load(
5400
- url,
5401
- (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) => {
5402
5422
  const envMap = pmremGenerator.fromEquirectangular(texture).texture;
5423
+ texture.dispose();
5424
+ pmremGenerator.dispose();
5403
5425
  if (envMap) {
5404
5426
  envMap.colorSpace = LinearSRGBColorSpace;
5405
5427
  envMap.needsUpdate = true;
5406
- this.scene.environment = envMap;
5407
- this.scene.environmentIntensity = envValues.envMapIntensity;
5408
- this.scene.environmentRotation = new Euler3(
5409
- MathUtils.degToRad(envValues.skyboxPolarAngle),
5410
- MathUtils.degToRad(envValues.skyboxAzimuthalAngle),
5411
- 0
5412
- );
5413
- this.scene.background = envMap;
5414
- this.scene.backgroundIntensity = envValues.skyboxIntensity;
5415
- this.scene.backgroundBlurriness = envValues.skyboxBlurriness;
5416
- this.isEnvHDRI = true;
5417
- texture.dispose();
5418
- pmremGenerator.dispose();
5428
+ resolve(envMap);
5429
+ } else {
5430
+ reject("Failed to generate environment map");
5419
5431
  }
5420
- },
5421
- () => {
5422
- },
5423
- (error) => {
5424
- 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;
5425
5445
  }
5426
- );
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
+ });
5427
5462
  }
5428
5463
  setHDRIFromFile() {
5429
5464
  if (!this.renderer)
@@ -5442,7 +5477,7 @@ var Composer = class {
5442
5477
  const fileURL = URL.createObjectURL(file);
5443
5478
  if (fileURL) {
5444
5479
  if (extension === "hdr") {
5445
- this.useHDRI(fileURL, true);
5480
+ this.useHDRI(fileURL);
5446
5481
  } else if (extension === "jpg") {
5447
5482
  this.useHDRJPG(fileURL);
5448
5483
  } else {
@@ -5524,6 +5559,23 @@ var Composer = class {
5524
5559
  }
5525
5560
  this.setAmbientLight();
5526
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
+ );
5578
+ }
5527
5579
  };
5528
5580
 
5529
5581
  // src/time/TimeManager.ts