MikaFil commited on
Commit
fd3c3d3
·
verified ·
1 Parent(s): 24d8580

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +146 -240
orbit-camera.js CHANGED
@@ -1,21 +1,4 @@
1
  // orbit-camera.js
2
- // ==============================
3
- // Logs de preuve que le fichier est bien chargé :
4
- console.log('[OrbitFILE] loaded orbit-camera.js');
5
-
6
- /*
7
- * Orbit Camera PlayCanvas (GSplat/SOGS/GLB)
8
- * - Conserve toutes tes fonctions et restrictions existantes
9
- * - Ajoute le contrôle clavier façon camera-controls.mjs :
10
- * ↑/↓ : orbite nord/sud (pitch)
11
- * ←/→ : orbite gauche/droite (yaw)
12
- * SHIFT+↑/↓ : zoom / dézoom
13
- * SHIFT+←/→ : déplacement latéral (pan sur les côtés)
14
- * - Respecte minY + inertie + clamps
15
- * - Ajout de logs et capture clavier robuste (window + focus canvas)
16
- */
17
-
18
- // ===================== Orbit Camera (TON SCRIPT D’ORIGINE, intact) =====================
19
 
20
  var OrbitCamera = pc.createScript('orbitCamera');
21
 
@@ -109,7 +92,7 @@ OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
109
  this._updatePosition();
110
  };
111
 
112
- // ==== Clamp helper for panning/orbiting pour ne jamais passer sous minY ====
113
  OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
114
  var quat = new pc.Quat();
115
  quat.setFromEulerAngles(this._pitch, this._yaw, 0);
@@ -129,18 +112,23 @@ OrbitCamera.prototype.initialize = function () {
129
  var onWindowResize = function () { self._checkAspectRatio(); };
130
  window.addEventListener('resize', onWindowResize, false);
131
  this._checkAspectRatio();
 
132
  this._modelsAabb = new pc.BoundingBox();
133
  this._buildAabb(this.focusEntity || this.app.root);
 
134
  this.entity.lookAt(this._modelsAabb.center);
135
  this._pivotPoint = new pc.Vec3();
136
  this._pivotPoint.copy(this._modelsAabb.center);
 
137
  var cameraQuat = this.entity.getRotation();
138
  this._yaw = this._calcYaw(cameraQuat);
139
  this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
140
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
 
141
  this._distance = 0;
142
  this._targetYaw = this._yaw;
143
  this._targetPitch = this._pitch;
 
144
  if (this.frameOnStart) {
145
  this.focus(this.focusEntity || this.app.root);
146
  } else {
@@ -149,22 +137,22 @@ OrbitCamera.prototype.initialize = function () {
149
  this._distance = this._clampDistance(distanceBetween.length());
150
  }
151
  this._targetDistance = this._distance;
 
152
  this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
153
  this.on('attr:distanceMax', function () { this._distance = this._clampDistance(this._distance); });
154
  this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
155
  this.on('attr:pitchAngleMax', function () { this._pitch = this._clampPitchAngle(this._pitch); });
 
156
  this.on('attr:focusEntity', function (value) {
157
  if (this.frameOnStart) { this.focus(value || this.app.root); }
158
  else { this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root); }
159
  });
 
160
  this.on('attr:frameOnStart', function (value) {
161
  if (value) { this.focus(this.focusEntity || this.app.root); }
162
  });
163
- this.on('destroy', function () { window.removeEventListener('resize', onWindowResize, false); });
164
 
165
- // log d’état initial
166
- console.log('[OrbitCam] init yaw=%s° pitch=%s° dist=%s',
167
- this._yaw.toFixed(2), this._pitch.toFixed(2), this._distance.toFixed(3));
168
  };
169
 
170
  OrbitCamera.prototype.update = function (dt) {
@@ -178,12 +166,15 @@ OrbitCamera.prototype.update = function (dt) {
178
  OrbitCamera.prototype._updatePosition = function () {
179
  this.entity.setLocalPosition(0, 0, 0);
180
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
 
181
  var position = this.entity.getPosition();
182
  position.copy(this.entity.forward);
183
  position.mulScalar(-this._distance);
184
  position.add(this.pivotPoint);
 
185
  // Clamp camera's Y position so it never goes below minY
186
  position.y = Math.max(position.y, this.minY);
 
187
  this.entity.setPosition(position);
188
  };
189
 
@@ -201,6 +192,7 @@ OrbitCamera.prototype._checkAspectRatio = function () {
201
 
202
  OrbitCamera.prototype._buildAabb = function (entity) {
203
  var i, m, meshInstances = [];
 
204
  var renders = entity.findComponents('render');
205
  for (i = 0; i < renders.length; i++) {
206
  var render = renders[i];
@@ -208,6 +200,7 @@ OrbitCamera.prototype._buildAabb = function (entity) {
208
  meshInstances.push(render.meshInstances[m]);
209
  }
210
  }
 
211
  var models = entity.findComponents('model');
212
  for (i = 0; i < models.length; i++) {
213
  var model = models[i];
@@ -215,6 +208,7 @@ OrbitCamera.prototype._buildAabb = function (entity) {
215
  meshInstances.push(model.meshInstances[m]);
216
  }
217
  }
 
218
  var gsplats = entity.findComponents('gsplat');
219
  for (i = 0; i < gsplats.length; i++) {
220
  var gsplat = gsplats[i];
@@ -223,6 +217,7 @@ OrbitCamera.prototype._buildAabb = function (entity) {
223
  meshInstances.push(instance.meshInstance);
224
  }
225
  }
 
226
  for (i = 0; i < meshInstances.length; i++) {
227
  if (i === 0) {
228
  this._modelsAabb.copy(meshInstances[i].aabb);
@@ -235,7 +230,7 @@ OrbitCamera.prototype._buildAabb = function (entity) {
235
  OrbitCamera.prototype._calcYaw = function (quat) {
236
  var transformedForward = new pc.Vec3();
237
  quat.transformVector(pc.Vec3.FORWARD, transformedForward);
238
- return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG; // DEGRÉS
239
  };
240
 
241
  OrbitCamera.prototype._clampDistance = function (distance) {
@@ -250,8 +245,7 @@ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
250
  };
251
 
252
  OrbitCamera.prototype._clampYawAngle = function (yaw) {
253
- // FIX: clamp correct (pas d'inversion min/max)
254
- return pc.math.clamp(yaw, this.yawAngleMin, this.yawAngleMax);
255
  };
256
 
257
  OrbitCamera.quatWithoutYaw = new pc.Quat();
@@ -267,22 +261,24 @@ OrbitCamera.prototype._calcPitch = function (quat, yaw) {
267
  return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
268
  };
269
 
270
- // ===================== Orbit Camera Input Mouse Script (inchangé) =====================
271
-
272
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
273
  OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3, title: 'Orbit Sensitivity' });
274
  OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4, title: 'Distance Sensitivity' });
275
 
276
  OrbitCameraInputMouse.prototype.initialize = function () {
277
  this.orbitCamera = this.entity.script.orbitCamera;
 
278
  if (this.orbitCamera) {
279
  var self = this;
280
- var onMouseOut = function (e) { self.onMouseOut(e); };
 
281
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
282
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
283
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
284
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
285
  window.addEventListener('mouseout', onMouseOut, false);
 
286
  this.on('destroy', function () {
287
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
288
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
@@ -291,9 +287,11 @@ OrbitCameraInputMouse.prototype.initialize = function () {
291
  window.removeEventListener('mouseout', onMouseOut, false);
292
  });
293
  }
 
294
  this.app.mouse.disableContextMenu();
 
295
  this.lookButtonDown = false;
296
- this.panButtonDown = false;
297
  this.lastPoint = new pc.Vec2();
298
  };
299
 
@@ -305,13 +303,16 @@ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
305
  var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
306
  var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
307
  var worldDiff = OrbitCameraInputMouse.worldDiff;
 
308
  var camera = this.entity.camera;
309
  var distance = this.orbitCamera.distance;
 
310
  camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
311
  camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
 
312
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
313
 
314
- // Clamp so camera never goes below minY
315
  var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
316
  var minY = this.orbitCamera.minY;
317
  var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
@@ -319,14 +320,13 @@ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
319
  if (resultingY >= minY - 1e-4) {
320
  this.orbitCamera.pivotPoint.add(worldDiff);
321
  } else {
322
- // Only allow horizontal pan if it would help
323
  worldDiff.y = 0;
324
  proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
325
  resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
326
  if (resultingY >= minY - 1e-4) {
327
  this.orbitCamera.pivotPoint.add(worldDiff);
328
  }
329
- // Otherwise, suppress pan
330
  }
331
  };
332
 
@@ -337,6 +337,7 @@ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
337
  case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
338
  }
339
  };
 
340
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
341
  switch (event.button) {
342
  case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
@@ -344,10 +345,12 @@ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
344
  case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
345
  }
346
  };
 
347
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
348
  if (this.lookButtonDown) {
349
  var sens = this.orbitSensitivity;
350
- var deltaPitch = event.dy * sens;
 
351
  var deltaYaw = event.dx * sens;
352
 
353
  var currPitch = this.orbitCamera.pitch;
@@ -370,6 +373,7 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
370
 
371
  var minY = this.orbitCamera.minY;
372
  var wouldGoBelow = proposedY < minY - 1e-4;
 
373
  if (wouldGoBelow && (proposedY < preY)) {
374
  this.orbitCamera.yaw = currYaw - deltaYaw;
375
  } else {
@@ -379,6 +383,7 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
379
  } else if (this.panButtonDown) {
380
  this.pan(new pc.Vec2(event.x, event.y));
381
  }
 
382
  this.lastPoint.set(event.x, event.y);
383
  };
384
 
@@ -390,25 +395,29 @@ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
390
  }
391
  event.event.preventDefault();
392
  };
 
393
  OrbitCameraInputMouse.prototype.onMouseOut = function () {
394
  this.lookButtonDown = false;
395
  this.panButtonDown = false;
396
  };
397
 
398
- // =================== Orbit Camera Input Touch Script (inchangé) ========================
399
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
400
  OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6, title: 'Orbit Sensitivity' });
401
  OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5, title: 'Distance Sensitivity' });
 
402
  OrbitCameraInputTouch.prototype.initialize = function () {
403
  this.orbitCamera = this.entity.script.orbitCamera;
404
  this.lastTouchPoint = new pc.Vec2();
405
  this.lastPinchMidPoint = new pc.Vec2();
406
  this.lastPinchDistance = 0;
 
407
  if (this.orbitCamera && this.app.touch) {
408
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
409
  this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
410
  this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
411
  this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
 
412
  this.on('destroy', function () {
413
  this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
414
  this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
@@ -417,17 +426,20 @@ OrbitCameraInputTouch.prototype.initialize = function () {
417
  });
418
  }
419
  };
 
420
  OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
421
  var dx = pointA.x - pointB.x;
422
  var dy = pointA.y - pointB.y;
423
  return Math.sqrt((dx * dx) + (dy * dy));
424
  };
 
425
  OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
426
  result.set(pointB.x - pointA.x, pointB.y - pointA.y);
427
  result.mulScalar(0.5);
428
  result.x += pointA.x;
429
  result.y += pointA.y;
430
  };
 
431
  OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
432
  var touches = event.touches;
433
  if (touches.length === 1) {
@@ -437,17 +449,22 @@ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
437
  this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
438
  }
439
  };
 
440
  OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
441
  OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
442
  OrbitCameraInputTouch.worldDiff = new pc.Vec3();
 
443
  OrbitCameraInputTouch.prototype.pan = function (midPoint) {
444
  var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
445
  var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
446
  var worldDiff = OrbitCameraInputTouch.worldDiff;
 
447
  var camera = this.entity.camera;
448
  var distance = this.orbitCamera.distance;
 
449
  camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
450
  camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
 
451
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
452
 
453
  var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
@@ -471,9 +488,11 @@ OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
471
  OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
472
  var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
473
  var touches = event.touches;
 
474
  if (touches.length === 1) {
475
  var touch = touches[0];
476
  var sens = this.orbitSensitivity;
 
477
  var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
478
  var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
479
 
@@ -497,254 +516,141 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
497
 
498
  var minY = this.orbitCamera.minY;
499
  var wouldGoBelow = proposedY < minY - 1e-4;
 
500
  if (wouldGoBelow && (proposedY < preY)) {
501
  this.orbitCamera.yaw = currYaw - deltaYaw;
502
  } else {
503
  this.orbitCamera.pitch = proposedPitch;
504
  this.orbitCamera.yaw = currYaw - deltaYaw;
505
  }
 
506
  this.lastTouchPoint.set(touch.x, touch.y);
507
  } else if (touches.length === 2) {
508
  var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
509
  var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
510
  this.lastPinchDistance = currentPinchDistance;
 
511
  this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
 
512
  this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
513
  this.pan(pinchMidPoint);
514
  this.lastPinchMidPoint.copy(pinchMidPoint);
515
  }
516
  };
517
 
518
- // =================== Orbit Camera Input Keyboard Script (nouveau, simple) ========================
 
 
 
 
519
  var OrbitCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
520
 
521
- /*
522
- * Contrôles :
523
- * - ↑ / ↓ : orbiter nord/sud (pitch)
524
- * - ← / → : orbiter gauche/droite (yaw)
525
- * - SHIFT + / : zoom / dézoom
526
- * - SHIFT + / : déplacement latéral (pan)
527
- *
528
- * Robuste : écoute `window` (style camera-controls.mjs), union avec app.keyboard,
529
- * preventDefault sur les flèches, focus auto du canvas pour voir les logs.
530
- */
531
-
532
- OrbitCameraInputKeyboard.attributes.add('orbitDegreesPerSecond', { type: 'number', default: 180, title: 'Orbit Degrees / s' });
533
- OrbitCameraInputKeyboard.attributes.add('zoomFactorPerSecond', { type: 'number', default: 2.0, title: 'Zoom Factor / s' });
534
- OrbitCameraInputKeyboard.attributes.add('panUnitsPerSecond', { type: 'number', default: 1.0, title: 'Pan (distance frac) / s' });
535
- OrbitCameraInputKeyboard.attributes.add('autoFocusCanvas', { type: 'boolean', default: true, title: 'Auto-focus Canvas on Click' });
536
- OrbitCameraInputKeyboard.attributes.add('debugLogs', { type: 'boolean', default: true, title: 'Enable Debug Logs' });
537
-
538
- OrbitCameraInputKeyboard.prototype._log = function () {
539
- if (!this.debugLogs) return;
540
- // eslint-disable-next-line no-console
541
- console.log.apply(console, arguments);
542
- };
543
 
544
  OrbitCameraInputKeyboard.prototype.initialize = function () {
545
- console.log('[OrbitKB] initialize()'); // log inconditionnel
546
  this.orbitCamera = this.entity.script.orbitCamera;
547
-
548
- // S'assure que le clavier PlayCanvas existe
549
- if (!this.app.keyboard) {
550
- this.app.keyboard = new pc.Keyboard(window);
551
- this._log('[OrbitKB] created pc.Keyboard(window)');
552
- } else {
553
- this._log('[OrbitKB] app.keyboard OK');
554
- }
555
-
556
- // Canvas focusable + focus auto au clic (utile pour capter les flèches sur certaines intégrations)
557
- this.canvas = this.app.graphicsDevice && this.app.graphicsDevice.canvas;
558
- if (this.canvas && this.autoFocusCanvas) {
559
- if (!this.canvas.hasAttribute('tabindex')) {
560
- this.canvas.setAttribute('tabindex', '0');
561
- this._log('[OrbitKB] set canvas tabindex=0');
562
- }
563
- var self = this;
564
- this._onPointerDown = function () {
565
- try { self.canvas.focus(); self._log('[OrbitKB] canvas focused via pointer'); } catch (e) {}
566
- };
567
- this.canvas.addEventListener('pointerdown', this._onPointerDown);
568
- }
569
-
570
- // État clavier façon camera-controls.mjs (au niveau window)
571
- this._keyState = Object.create(null);
572
- var self = this;
573
- this._onWinKeyDown = function (e) {
574
- self._keyState[e.key] = true;
575
- // éviter le scroll de page avec les flèches
576
- if (e.key && e.key.indexOf('Arrow') === 0) e.preventDefault();
577
- if (self.debugLogs) console.log('[OrbitKB] window.keydown', e.key);
578
- };
579
- this._onWinKeyUp = function (e) {
580
- self._keyState[e.key] = false;
581
- if (self.debugLogs) console.log('[OrbitKB] window.keyup ', e.key);
582
- };
583
- window.addEventListener('keydown', this._onWinKeyDown, { passive: false });
584
- window.addEventListener('keyup', this._onWinKeyUp);
585
-
586
- // preventDefault (séparé) pour les flèches
587
- this._preventDefault = function (e) {
588
- var tag = (e.target && e.target.tagName) ? e.target.tagName.toUpperCase() : '';
589
- if (tag === 'INPUT' || tag === 'TEXTAREA' || e.isContentEditable) return;
590
- var key = e.key || '';
591
- if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight') {
592
- e.preventDefault();
593
- if (self.debugLogs) console.log('[OrbitKB] preventDefault on', key);
594
- }
595
- };
596
- window.addEventListener('keydown', this._preventDefault, { passive: false });
597
-
598
- this.on('destroy', function () {
599
- window.removeEventListener('keydown', this._onWinKeyDown, { passive: false });
600
- window.removeEventListener('keyup', this._onWinKeyUp);
601
- window.removeEventListener('keydown', this._preventDefault, { passive: false });
602
- if (this.canvas && this._onPointerDown) this.canvas.removeEventListener('pointerdown', this._onPointerDown);
603
- });
604
-
605
- this._log('[OrbitKB] ready. yaw=%s pitch=%s dist=%s',
606
- this.orbitCamera ? this.orbitCamera.yaw.toFixed(2) : 'n/a',
607
- this.orbitCamera ? this.orbitCamera.pitch.toFixed(2) : 'n/a',
608
- this.orbitCamera ? this.orbitCamera.distance.toFixed(3) : 'n/a'
609
- );
610
- };
611
-
612
- OrbitCameraInputKeyboard.prototype._pressed = function (pcKey, keyString) {
613
- var pcPressed = this.app.keyboard && this.app.keyboard.isPressed ? this.app.keyboard.isPressed(pcKey) : false;
614
- var winPressed = !!this._keyState[keyString];
615
- return pcPressed || winPressed;
616
  };
617
 
618
- // Test minY identique à la souris pour le pitch
619
- OrbitCameraInputKeyboard.prototype._wouldGoBelowMinYWithPitch = function (proposedPitch) {
620
- var cam = this.orbitCamera;
621
- var currPitch = cam.pitch;
622
- var currYaw = cam.yaw;
623
- var currDist = cam.distance;
624
- var pivot = cam.pivotPoint.clone();
625
-
626
- var camQuat = new pc.Quat();
627
- camQuat.setFromEulerAngles(currPitch, currYaw, 0);
628
- var forward = new pc.Vec3();
629
- camQuat.transformVector(pc.Vec3.FORWARD, forward);
630
- var preY = pivot.y + (-forward.y) * currDist;
631
-
632
- var testQuat = new pc.Quat();
633
- testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
634
- var testForward = new pc.Vec3();
635
- testQuat.transformVector(pc.Vec3.FORWARD, testForward);
636
- var proposedY = pivot.y + (-testForward.y) * currDist;
637
-
638
- var minY = cam.minY;
639
- return { wouldGoBelow: proposedY < minY - 1e-4, preY: preY, proposedY: proposedY };
640
- };
641
 
642
- // Pan latéral (SHIFT+←/→) en respectant minY
643
- OrbitCameraInputKeyboard.prototype._panSideways = function (delta) {
644
- var cam = this.orbitCamera;
 
645
 
646
- var quat = new pc.Quat();
647
- quat.setFromEulerAngles(cam.pitch, cam.yaw, 0);
 
 
648
 
649
- var right = new pc.Vec3();
650
- quat.transformVector(pc.Vec3.RIGHT, right);
 
651
 
652
- var move = right.scale(delta);
 
 
 
653
 
654
- var minY = cam.minY;
655
- var proposedPivot = cam.pivotPoint.clone().add(move);
656
- var resultingY = cam.worldCameraYForPivot(proposedPivot);
657
 
658
- if (resultingY >= minY - 1e-4) {
659
- cam.pivotPoint.add(move);
660
- this._log('[OrbitKB] panSideways OK delta=%s -> pivot=(%s,%s,%s)',
661
- delta.toFixed(3),
662
- cam.pivotPoint.x.toFixed(3),
663
- cam.pivotPoint.y.toFixed(3),
664
- cam.pivotPoint.z.toFixed(3));
665
- } else {
666
- move.y = 0;
667
- proposedPivot = cam.pivotPoint.clone().add(move);
668
- resultingY = cam.worldCameraYForPivot(proposedPivot);
669
- if (resultingY >= minY - 1e-4) {
670
- cam.pivotPoint.add(move);
671
- this._log('[OrbitKB] panSideways horizontal-only delta=%s', delta.toFixed(3));
672
- } else {
673
- this._log('[OrbitKB] panSideways blocked by minY delta=%s', delta.toFixed(3));
674
  }
 
675
  }
676
- };
677
-
678
- OrbitCameraInputKeyboard.prototype.update = function (dt) {
679
- if (!this.orbitCamera) return;
680
-
681
- var cam = this.orbitCamera;
682
-
683
- // états de touches (union window+pc)
684
- var shift = this._pressed(pc.KEY_SHIFT, 'Shift') || this._pressed(pc.KEY_SHIFT, 'ShiftLeft') || this._pressed(pc.KEY_SHIFT, 'ShiftRight');
685
- var up = this._pressed(pc.KEY_UP, 'ArrowUp');
686
- var down = this._pressed(pc.KEY_DOWN, 'ArrowDown');
687
- var left = this._pressed(pc.KEY_LEFT, 'ArrowLeft');
688
- var right = this._pressed(pc.KEY_RIGHT, 'ArrowRight');
689
 
690
- if (this.debugLogs && (up || down || left || right)) {
691
- console.log('[OrbitKB] pressed: shift=%s up=%s down=%s left=%s right=%s', !!shift, !!up, !!down, !!left, !!right);
692
- }
693
-
694
- if (!(up || down || left || right)) return;
695
-
696
- if (shift) {
697
- // --- Zoom (Shift + ↑/↓) ---
698
- if (up || down) {
699
- var k = this.zoomFactorPerSecond;
700
  if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
701
- var dz = k * dt * (cam.distance * 0.8);
702
- var before = cam.distance;
703
- cam.distance = before + (down ? +dz : -dz);
704
- this._log('[OrbitKB] SHIFT Zoom persp %s -> %s (dz=%s)',
705
- before.toFixed(3), cam.distance.toFixed(3), dz.toFixed(3));
706
  } else {
707
- var dh = k * dt * (this.entity.camera.orthoHeight * 0.8);
708
- var beforeH = this.entity.camera.orthoHeight;
709
- this.entity.camera.orthoHeight = beforeH + (down ? +dh : -dh);
710
- this._log('[OrbitKB] SHIFT Zoom ortho %s -> %s (dh=%s)',
711
- beforeH.toFixed(3), this.entity.camera.orthoHeight.toFixed(3), dh.toFixed(3));
712
  }
713
  }
 
 
714
 
715
- // --- Pan latéral (Shift + ←/→) ---
716
- if (left || right) {
717
- var speed = this.panUnitsPerSecond * cam.distance; // unités monde / s
718
- var delta = speed * dt * (left ? -1 : +1);
719
- this._log('[OrbitKB] SHIFT Pan delta=%s (speed=%s, dist=%s)', delta.toFixed(3), speed.toFixed(3), cam.distance.toFixed(3));
720
- this._panSideways(delta);
721
- }
722
 
723
- return; // pas d’orbite quand SHIFT est enfoncé
724
- }
725
 
726
- // --- Orbite sans SHIFT ---
727
- var orbitRate = this.orbitDegreesPerSecond * dt;
728
-
729
- // Pitch (↑/↓) même garde minY que la souris
730
- if (up || down) {
731
- var dir = up ? -1 : +1; // = réduire le pitch (orbiter vers le nord)
732
- var beforePitch = cam.pitch;
733
- var proposedPitch = beforePitch + dir * orbitRate;
734
- var test = this._wouldGoBelowMinYWithPitch(proposedPitch);
735
- if (!(test.wouldGoBelow && (test.proposedY < test.preY))) {
736
- cam.pitch = proposedPitch;
737
- this._log('[OrbitKB] Orbit PITCH %s -> %s (rate=%s)', beforePitch.toFixed(3), cam.pitch.toFixed(3), orbitRate.toFixed(3));
738
- } else {
739
- this._log('[OrbitKB] Orbit PITCH blocked by minY (before=%s proposed=%s)', beforePitch.toFixed(3), proposedPitch.toFixed(3));
 
 
740
  }
741
  }
742
 
743
- // Yaw (←/→)
744
- if (left || right) {
745
- var beforeYaw = cam.yaw;
746
- if (left) cam.yaw = cam.yaw + orbitRate; // tourner à gauche
747
- if (right) cam.yaw = cam.yaw - orbitRate; // tourner à droite
748
- this._log('[OrbitKB] Orbit YAW %s -> %s (rate=%s)', beforeYaw.toFixed(3), cam.yaw.toFixed(3), orbitRate.toFixed(3));
749
  }
750
- };
 
1
  // orbit-camera.js
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  var OrbitCamera = pc.createScript('orbitCamera');
4
 
 
92
  this._updatePosition();
93
  };
94
 
95
+ // ==== Helper: calc cam Y if pivot became 'pivot' (used to enforce minY) ====
96
  OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
97
  var quat = new pc.Quat();
98
  quat.setFromEulerAngles(this._pitch, this._yaw, 0);
 
112
  var onWindowResize = function () { self._checkAspectRatio(); };
113
  window.addEventListener('resize', onWindowResize, false);
114
  this._checkAspectRatio();
115
+
116
  this._modelsAabb = new pc.BoundingBox();
117
  this._buildAabb(this.focusEntity || this.app.root);
118
+
119
  this.entity.lookAt(this._modelsAabb.center);
120
  this._pivotPoint = new pc.Vec3();
121
  this._pivotPoint.copy(this._modelsAabb.center);
122
+
123
  var cameraQuat = this.entity.getRotation();
124
  this._yaw = this._calcYaw(cameraQuat);
125
  this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
126
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
127
+
128
  this._distance = 0;
129
  this._targetYaw = this._yaw;
130
  this._targetPitch = this._pitch;
131
+
132
  if (this.frameOnStart) {
133
  this.focus(this.focusEntity || this.app.root);
134
  } else {
 
137
  this._distance = this._clampDistance(distanceBetween.length());
138
  }
139
  this._targetDistance = this._distance;
140
+
141
  this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
142
  this.on('attr:distanceMax', function () { this._distance = this._clampDistance(this._distance); });
143
  this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
144
  this.on('attr:pitchAngleMax', function () { this._pitch = this._clampPitchAngle(this._pitch); });
145
+
146
  this.on('attr:focusEntity', function (value) {
147
  if (this.frameOnStart) { this.focus(value || this.app.root); }
148
  else { this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root); }
149
  });
150
+
151
  this.on('attr:frameOnStart', function (value) {
152
  if (value) { this.focus(this.focusEntity || this.app.root); }
153
  });
 
154
 
155
+ this.on('destroy', function () { window.removeEventListener('resize', onWindowResize, false); });
 
 
156
  };
157
 
158
  OrbitCamera.prototype.update = function (dt) {
 
166
  OrbitCamera.prototype._updatePosition = function () {
167
  this.entity.setLocalPosition(0, 0, 0);
168
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
169
+
170
  var position = this.entity.getPosition();
171
  position.copy(this.entity.forward);
172
  position.mulScalar(-this._distance);
173
  position.add(this.pivotPoint);
174
+
175
  // Clamp camera's Y position so it never goes below minY
176
  position.y = Math.max(position.y, this.minY);
177
+
178
  this.entity.setPosition(position);
179
  };
180
 
 
192
 
193
  OrbitCamera.prototype._buildAabb = function (entity) {
194
  var i, m, meshInstances = [];
195
+
196
  var renders = entity.findComponents('render');
197
  for (i = 0; i < renders.length; i++) {
198
  var render = renders[i];
 
200
  meshInstances.push(render.meshInstances[m]);
201
  }
202
  }
203
+
204
  var models = entity.findComponents('model');
205
  for (i = 0; i < models.length; i++) {
206
  var model = models[i];
 
208
  meshInstances.push(model.meshInstances[m]);
209
  }
210
  }
211
+
212
  var gsplats = entity.findComponents('gsplat');
213
  for (i = 0; i < gsplats.length; i++) {
214
  var gsplat = gsplats[i];
 
217
  meshInstances.push(instance.meshInstance);
218
  }
219
  }
220
+
221
  for (i = 0; i < meshInstances.length; i++) {
222
  if (i === 0) {
223
  this._modelsAabb.copy(meshInstances[i].aabb);
 
230
  OrbitCamera.prototype._calcYaw = function (quat) {
231
  var transformedForward = new pc.Vec3();
232
  quat.transformVector(pc.Vec3.FORWARD, transformedForward);
233
+ return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
234
  };
235
 
236
  OrbitCamera.prototype._clampDistance = function (distance) {
 
245
  };
246
 
247
  OrbitCamera.prototype._clampYawAngle = function (yaw) {
248
+ return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
 
249
  };
250
 
251
  OrbitCamera.quatWithoutYaw = new pc.Quat();
 
261
  return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
262
  };
263
 
264
+ // ===================== Orbit Camera Input Mouse Script ========================
 
265
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
266
  OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3, title: 'Orbit Sensitivity' });
267
  OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4, title: 'Distance Sensitivity' });
268
 
269
  OrbitCameraInputMouse.prototype.initialize = function () {
270
  this.orbitCamera = this.entity.script.orbitCamera;
271
+
272
  if (this.orbitCamera) {
273
  var self = this;
274
+ var onMouseOut = function () { self.onMouseOut(); };
275
+
276
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
277
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
278
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
279
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
280
  window.addEventListener('mouseout', onMouseOut, false);
281
+
282
  this.on('destroy', function () {
283
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
284
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
 
287
  window.removeEventListener('mouseout', onMouseOut, false);
288
  });
289
  }
290
+
291
  this.app.mouse.disableContextMenu();
292
+
293
  this.lookButtonDown = false;
294
+ this.panButtonDown = false;
295
  this.lastPoint = new pc.Vec2();
296
  };
297
 
 
303
  var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
304
  var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
305
  var worldDiff = OrbitCameraInputMouse.worldDiff;
306
+
307
  var camera = this.entity.camera;
308
  var distance = this.orbitCamera.distance;
309
+
310
  camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
311
  camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
312
+
313
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
314
 
315
+ // Enforce minY like vertical/horizontal pan with arrows
316
  var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
317
  var minY = this.orbitCamera.minY;
318
  var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
 
320
  if (resultingY >= minY - 1e-4) {
321
  this.orbitCamera.pivotPoint.add(worldDiff);
322
  } else {
323
+ // Try horizontal-only
324
  worldDiff.y = 0;
325
  proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
326
  resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
327
  if (resultingY >= minY - 1e-4) {
328
  this.orbitCamera.pivotPoint.add(worldDiff);
329
  }
 
330
  }
331
  };
332
 
 
337
  case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
338
  }
339
  };
340
+
341
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
342
  switch (event.button) {
343
  case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
 
345
  case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
346
  }
347
  };
348
+
349
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
350
  if (this.lookButtonDown) {
351
  var sens = this.orbitSensitivity;
352
+
353
+ var deltaPitch = event.dy * sens; // mouse up (dy < 0) raises pitch
354
  var deltaYaw = event.dx * sens;
355
 
356
  var currPitch = this.orbitCamera.pitch;
 
373
 
374
  var minY = this.orbitCamera.minY;
375
  var wouldGoBelow = proposedY < minY - 1e-4;
376
+
377
  if (wouldGoBelow && (proposedY < preY)) {
378
  this.orbitCamera.yaw = currYaw - deltaYaw;
379
  } else {
 
383
  } else if (this.panButtonDown) {
384
  this.pan(new pc.Vec2(event.x, event.y));
385
  }
386
+
387
  this.lastPoint.set(event.x, event.y);
388
  };
389
 
 
395
  }
396
  event.event.preventDefault();
397
  };
398
+
399
  OrbitCameraInputMouse.prototype.onMouseOut = function () {
400
  this.lookButtonDown = false;
401
  this.panButtonDown = false;
402
  };
403
 
404
+ // =================== Orbit Camera Input Touch Script ========================
405
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
406
  OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6, title: 'Orbit Sensitivity' });
407
  OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5, title: 'Distance Sensitivity' });
408
+
409
  OrbitCameraInputTouch.prototype.initialize = function () {
410
  this.orbitCamera = this.entity.script.orbitCamera;
411
  this.lastTouchPoint = new pc.Vec2();
412
  this.lastPinchMidPoint = new pc.Vec2();
413
  this.lastPinchDistance = 0;
414
+
415
  if (this.orbitCamera && this.app.touch) {
416
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
417
  this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
418
  this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
419
  this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
420
+
421
  this.on('destroy', function () {
422
  this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
423
  this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
 
426
  });
427
  }
428
  };
429
+
430
  OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
431
  var dx = pointA.x - pointB.x;
432
  var dy = pointA.y - pointB.y;
433
  return Math.sqrt((dx * dx) + (dy * dy));
434
  };
435
+
436
  OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
437
  result.set(pointB.x - pointA.x, pointB.y - pointA.y);
438
  result.mulScalar(0.5);
439
  result.x += pointA.x;
440
  result.y += pointA.y;
441
  };
442
+
443
  OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
444
  var touches = event.touches;
445
  if (touches.length === 1) {
 
449
  this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
450
  }
451
  };
452
+
453
  OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
454
  OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
455
  OrbitCameraInputTouch.worldDiff = new pc.Vec3();
456
+
457
  OrbitCameraInputTouch.prototype.pan = function (midPoint) {
458
  var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
459
  var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
460
  var worldDiff = OrbitCameraInputTouch.worldDiff;
461
+
462
  var camera = this.entity.camera;
463
  var distance = this.orbitCamera.distance;
464
+
465
  camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
466
  camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
467
+
468
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
469
 
470
  var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
 
488
  OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
489
  var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
490
  var touches = event.touches;
491
+
492
  if (touches.length === 1) {
493
  var touch = touches[0];
494
  var sens = this.orbitSensitivity;
495
+
496
  var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
497
  var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
498
 
 
516
 
517
  var minY = this.orbitCamera.minY;
518
  var wouldGoBelow = proposedY < minY - 1e-4;
519
+
520
  if (wouldGoBelow && (proposedY < preY)) {
521
  this.orbitCamera.yaw = currYaw - deltaYaw;
522
  } else {
523
  this.orbitCamera.pitch = proposedPitch;
524
  this.orbitCamera.yaw = currYaw - deltaYaw;
525
  }
526
+
527
  this.lastTouchPoint.set(touch.x, touch.y);
528
  } else if (touches.length === 2) {
529
  var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
530
  var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
531
  this.lastPinchDistance = currentPinchDistance;
532
+
533
  this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
534
+
535
  this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
536
  this.pan(pinchMidPoint);
537
  this.lastPinchMidPoint.copy(pinchMidPoint);
538
  }
539
  };
540
 
541
+ // =================== Orbit Camera Input Keyboard ========================
542
+ // Arrows:
543
+ // - No modifier: Up/Down = vertical pan; Left/Right = strafe
544
+ // - With Shift: Up/Down = orbit pitch; Left/Right = orbit yaw (inverted as requested)
545
+ // - With Ctrl: Up/Down = zoom in/out
546
  var OrbitCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
547
 
548
+ OrbitCameraInputKeyboard.attributes.add('forwardSpeed', { type: 'number', default: 1.2, title: 'Vertical Speed (rel. to distance)' });
549
+ OrbitCameraInputKeyboard.attributes.add('strafeSpeed', { type: 'number', default: 1.2, title: 'Left/Right Speed (rel. to distance)' });
550
+
551
+ // Fine-tuning
552
+ OrbitCameraInputKeyboard.attributes.add('orbitPitchSpeedDeg', { type: 'number', default: 90, title: 'Orbit Pitch Speed (deg/s) [Shift+Up/Down]' });
553
+ OrbitCameraInputKeyboard.attributes.add('orbitYawSpeedDeg', { type: 'number', default: 120, title: 'Orbit Yaw Speed (deg/s) [Shift+Left/Right]' });
554
+ OrbitCameraInputKeyboard.attributes.add('zoomKeySensitivity', { type: 'number', default: 3.0, title: 'Zoom Sensitivity (1/s) [Ctrl+Up/Down]' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
 
556
  OrbitCameraInputKeyboard.prototype.initialize = function () {
 
557
  this.orbitCamera = this.entity.script.orbitCamera;
558
+ this.keyboard = this.app.keyboard || null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  };
560
 
561
+ OrbitCameraInputKeyboard.prototype.update = function (dt) {
562
+ if (!this.orbitCamera || !this.keyboard) return;
563
+
564
+ var up = this.keyboard.isPressed(pc.KEY_UP);
565
+ var dn = this.keyboard.isPressed(pc.KEY_DOWN);
566
+ var lt = this.keyboard.isPressed(pc.KEY_LEFT);
567
+ var rt = this.keyboard.isPressed(pc.KEY_RIGHT);
568
+
569
+ var shift = this.keyboard.isPressed(pc.KEY_SHIFT);
570
+ var ctrl = this.keyboard.isPressed(pc.KEY_CONTROL);
571
+
572
+ // ---- SHIFT: Orbit (pitch / yaw) ----
573
+ if (shift && (up || dn || lt || rt)) {
574
+ // Yaw (left/right) — inverted per request: Shift+Right => orbit to the right
575
+ var yawDir = (rt ? 1 : 0) - (lt ? 1 : 0); // right=+1, left=-1
576
+ if (yawDir !== 0) {
577
+ var dYaw = yawDir * this.orbitYawSpeedDeg * dt;
578
+ this.orbitCamera.yaw = this.orbitCamera.yaw + dYaw; // NOTE: '+' (inversion)
579
+ }
 
 
 
 
580
 
581
+ // Pitch (up/down) Shift+Up => orbit up, Shift+Down => down
582
+ var pitchDir = (up ? 1 : 0) - (dn ? 1 : 0); // up=+1, down=-1
583
+ if (pitchDir !== 0) {
584
+ var dPitch = pitchDir * this.orbitPitchSpeedDeg * dt;
585
 
586
+ var currPitch = this.orbitCamera.pitch;
587
+ var currYaw = this.orbitCamera.yaw;
588
+ var currDist = this.orbitCamera.distance;
589
+ var currPivot = this.orbitCamera.pivotPoint.clone();
590
 
591
+ var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
592
+ var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
593
+ var preY = currPivot.y + (-forward.y) * currDist;
594
 
595
+ var testPitch = currPitch + dPitch;
596
+ var testQuat = new pc.Quat().setFromEulerAngles(testPitch, currYaw, 0);
597
+ var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
598
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
599
 
600
+ var minY = this.orbitCamera.minY;
601
+ var wouldGoBelow = proposedY < minY - 1e-4;
 
602
 
603
+ if (!(wouldGoBelow && (proposedY < preY))) {
604
+ this.orbitCamera.pitch = testPitch; // clamped by setter
605
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  }
607
+ return;
608
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
609
 
610
+ // ---- CTRL: Zoom (up/down) ----
611
+ if (ctrl && (up || dn)) {
612
+ var zoomSign = (up ? 1 : 0) - (dn ? 1 : 0); // up=zoom in, down=zoom out
613
+ if (zoomSign !== 0) {
 
 
 
 
 
 
614
  if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
615
+ var dz = zoomSign * this.zoomKeySensitivity * (this.orbitCamera.distance * 0.5) * dt;
616
+ this.orbitCamera.distance -= dz; // clamped
 
 
 
617
  } else {
618
+ var doh = zoomSign * this.zoomKeySensitivity * (this.orbitCamera.orthoHeight * 0.5) * dt;
619
+ this.orbitCamera.orthoHeight -= doh; // clamped >= 0
 
 
 
620
  }
621
  }
622
+ return;
623
+ }
624
 
625
+ // ---- No modifier: translate (vertical + strafe) ----
626
+ var moveVert = (up ? 1 : 0) - (dn ? 1 : 0); // Up=+1 => go up
627
+ var moveRight = (rt ? 1 : 0) - (lt ? 1 : 0);
 
 
 
 
628
 
629
+ if (moveVert === 0 && moveRight === 0) return;
 
630
 
631
+ // Base speeds relative to distance (consistent feel near/far)
632
+ var dist = Math.max(0.1, this.orbitCamera.distance);
633
+ var speedV = this.forwardSpeed * dist; // vertical speed
634
+ var speedR = this.strafeSpeed * dist; // strafe speed
635
+
636
+ // Vertical move (pure Y), respect minY
637
+ var dy = moveVert * speedV * dt;
638
+ if (dy !== 0) {
639
+ var currentCamY = this.orbitCamera.worldCameraYForPivot(this.orbitCamera.pivotPoint);
640
+ var minY = this.orbitCamera.minY;
641
+ var proposedCamY = currentCamY + dy;
642
+ if (proposedCamY < minY) {
643
+ dy = Math.max(dy, minY - currentCamY);
644
+ }
645
+ if (dy !== 0) {
646
+ this.orbitCamera._pivotPoint.y += dy;
647
  }
648
  }
649
 
650
+ // Strafe on XZ
651
+ var right = this.entity.right.clone(); right.y = 0; if (right.lengthSq() > 1e-8) right.normalize();
652
+ var dx = moveRight * speedR * dt;
653
+ if (dx !== 0) {
654
+ this.orbitCamera._pivotPoint.add(right.mulScalar(dx));
 
655
  }
656
+ };