MikaFil commited on
Commit
9b1c4ac
·
verified ·
1 Parent(s): dd7950b

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +238 -171
orbit-camera.js CHANGED
@@ -1,33 +1,47 @@
1
  // orbit-camera.js
2
  // ==============================
 
 
3
 
4
  /*
5
- * PlayCanvas Orbit Camera for GSplat/SOGS/GLB viewers.
6
- * - Min/Max zoom, pitch, yaw, minY clamping
7
- * - Mouse, Touch & Keyboard controls (orbit, pan, zoom)
8
- * - Robust keyboard capture (works even if canvas has no focus)
9
- * - Yaw conversion fixed (degrees everywhere)
10
- * - Optional debug logs
 
 
 
11
  */
12
 
 
 
13
  var OrbitCamera = pc.createScript('orbitCamera');
14
 
15
- OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
16
- OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
17
- OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
18
- OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
19
- OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
20
- OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360,title: 'Yaw Angle Min (degrees)' });
21
- OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y' });
22
- OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
23
- OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
24
- OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
 
 
25
 
26
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
27
  get: function () { return this._targetDistance; },
28
  set: function (value) { this._targetDistance = this._clampDistance(value); }
29
  });
30
 
 
 
 
 
 
31
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
32
  get: function () { return this._targetPitch; },
33
  set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
@@ -63,7 +77,7 @@ OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
63
  this.distance = distance.length();
64
  this.pivotPoint.copy(lookAtPoint);
65
  var cameraQuat = this.entity.getRotation();
66
- this.yaw = this._calcYaw(cameraQuat); // degrees
67
  this.pitch = this._calcPitch(cameraQuat, this.yaw);
68
  this._removeInertia();
69
  this._updatePosition();
@@ -89,12 +103,13 @@ OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
89
  distanceVec.sub2(position, lookAtPoint);
90
  this._targetDistance = this._distance = distanceVec.length();
91
  var cameraQuat = this.entity.getRotation();
92
- this._targetYaw = this._yaw = this._calcYaw(cameraQuat); // degrees
93
  this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
94
  this._removeInertia();
95
  this._updatePosition();
96
  };
97
 
 
98
  OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
99
  var quat = new pc.Quat();
100
  quat.setFromEulerAngles(this._pitch, this._yaw, 0);
@@ -105,29 +120,27 @@ OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
105
  return camPos.y;
106
  };
107
 
 
 
 
 
108
  OrbitCamera.prototype.initialize = function () {
109
  var self = this;
110
  var onWindowResize = function () { self._checkAspectRatio(); };
111
  window.addEventListener('resize', onWindowResize, false);
112
  this._checkAspectRatio();
113
-
114
  this._modelsAabb = new pc.BoundingBox();
115
  this._buildAabb(this.focusEntity || this.app.root);
116
-
117
  this.entity.lookAt(this._modelsAabb.center);
118
-
119
  this._pivotPoint = new pc.Vec3();
120
  this._pivotPoint.copy(this._modelsAabb.center);
121
-
122
  var cameraQuat = this.entity.getRotation();
123
- this._yaw = this._calcYaw(cameraQuat); // degrees
124
  this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
125
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
126
-
127
  this._distance = 0;
128
  this._targetYaw = this._yaw;
129
  this._targetPitch = this._pitch;
130
-
131
  if (this.frameOnStart) {
132
  this.focus(this.focusEntity || this.app.root);
133
  } else {
@@ -136,7 +149,6 @@ OrbitCamera.prototype.initialize = function () {
136
  this._distance = this._clampDistance(distanceBetween.length());
137
  }
138
  this._targetDistance = this._distance;
139
-
140
  this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
141
  this.on('attr:distanceMax', function () { this._distance = this._clampDistance(this._distance); });
142
  this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
@@ -148,32 +160,30 @@ OrbitCamera.prototype.initialize = function () {
148
  this.on('attr:frameOnStart', function (value) {
149
  if (value) { this.focus(this.focusEntity || this.app.root); }
150
  });
 
151
 
152
- this.on('destroy', function () {
153
- window.removeEventListener('resize', onWindowResize, false);
154
- });
155
  };
156
 
157
  OrbitCamera.prototype.update = function (dt) {
158
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
159
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
160
- this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
161
- this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
162
  this._updatePosition();
163
  };
164
 
165
  OrbitCamera.prototype._updatePosition = function () {
166
  this.entity.setLocalPosition(0, 0, 0);
167
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
168
-
169
  var position = this.entity.getPosition();
170
  position.copy(this.entity.forward);
171
  position.mulScalar(-this._distance);
172
  position.add(this.pivotPoint);
173
-
174
- // Never below ground
175
  position.y = Math.max(position.y, this.minY);
176
-
177
  this.entity.setPosition(position);
178
  };
179
 
@@ -191,7 +201,6 @@ OrbitCamera.prototype._checkAspectRatio = function () {
191
 
192
  OrbitCamera.prototype._buildAabb = function (entity) {
193
  var i, m, meshInstances = [];
194
-
195
  var renders = entity.findComponents('render');
196
  for (i = 0; i < renders.length; i++) {
197
  var render = renders[i];
@@ -199,7 +208,6 @@ OrbitCamera.prototype._buildAabb = function (entity) {
199
  meshInstances.push(render.meshInstances[m]);
200
  }
201
  }
202
-
203
  var models = entity.findComponents('model');
204
  for (i = 0; i < models.length; i++) {
205
  var model = models[i];
@@ -207,16 +215,14 @@ OrbitCamera.prototype._buildAabb = function (entity) {
207
  meshInstances.push(model.meshInstances[m]);
208
  }
209
  }
210
-
211
  var gsplats = entity.findComponents('gsplat');
212
  for (i = 0; i < gsplats.length; i++) {
213
  var gsplat = gsplats[i];
214
  var instance = gsplat.instance;
215
- if (instance && instance.meshInstance) {
216
  meshInstances.push(instance.meshInstance);
217
  }
218
  }
219
-
220
  for (i = 0; i < meshInstances.length; i++) {
221
  if (i === 0) {
222
  this._modelsAabb.copy(meshInstances[i].aabb);
@@ -227,10 +233,9 @@ OrbitCamera.prototype._buildAabb = function (entity) {
227
  };
228
 
229
  OrbitCamera.prototype._calcYaw = function (quat) {
230
- // return DEGREES
231
- var f = new pc.Vec3();
232
- quat.transformVector(pc.Vec3.FORWARD, f);
233
- return Math.atan2(-f.x, -f.z) * pc.math.RAD_TO_DEG;
234
  };
235
 
236
  OrbitCamera.prototype._clampDistance = function (distance) {
@@ -245,6 +250,7 @@ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
245
  };
246
 
247
  OrbitCamera.prototype._clampYawAngle = function (yaw) {
 
248
  return pc.math.clamp(yaw, this.yawAngleMin, this.yawAngleMax);
249
  };
250
 
@@ -256,61 +262,56 @@ OrbitCamera.prototype._calcPitch = function (quat, yaw) {
256
  var yawOffset = OrbitCamera.yawOffset;
257
  yawOffset.setFromEulerAngles(0, -yaw, 0);
258
  quatWithoutYaw.mul2(yawOffset, quat);
259
- var f = new pc.Vec3();
260
- quatWithoutYaw.transformVector(pc.Vec3.FORWARD, f);
261
- return Math.atan2(-f.y, -f.z) * pc.math.RAD_TO_DEG;
262
  };
263
 
264
- // =================== Orbit Camera Input Mouse Script ========================
265
 
266
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
267
- OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3, title: 'Orbit Sensitivity' });
268
  OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4, title: 'Distance Sensitivity' });
269
 
270
  OrbitCameraInputMouse.prototype.initialize = function () {
271
  this.orbitCamera = this.entity.script.orbitCamera;
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
-
281
  window.addEventListener('mouseout', onMouseOut, false);
282
-
283
  this.on('destroy', function () {
284
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
285
- this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
286
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
287
- this.app.mouse.off(pc.EVENT_MOUSEWHEEL,this.onMouseWheel,this);
288
  window.removeEventListener('mouseout', onMouseOut, false);
289
  });
290
  }
291
  this.app.mouse.disableContextMenu();
292
-
293
  this.lookButtonDown = false;
294
- this.panButtonDown = false;
295
  this.lastPoint = new pc.Vec2();
296
  };
 
297
  OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
298
- OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
299
- OrbitCameraInputMouse.worldDiff = new pc.Vec3();
300
 
301
  OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
302
  var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
303
- var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
304
- var worldDiff = OrbitCameraInputMouse.worldDiff;
305
-
306
- var camera = this.entity.camera;
307
  var distance = this.orbitCamera.distance;
308
-
309
- camera.screenToWorld(screenPoint.x, this.lastPoint.y, distance, fromWorldPoint);
310
- camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
311
  camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
 
312
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
313
 
 
314
  var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
315
  var minY = this.orbitCamera.minY;
316
  var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
@@ -318,62 +319,66 @@ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
318
  if (resultingY >= minY - 1e-4) {
319
  this.orbitCamera.pivotPoint.add(worldDiff);
320
  } else {
 
321
  worldDiff.y = 0;
322
  proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
323
  resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
324
  if (resultingY >= minY - 1e-4) {
325
  this.orbitCamera.pivotPoint.add(worldDiff);
326
  }
 
327
  }
328
  };
329
 
330
  OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
331
  switch (event.button) {
332
- case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
333
  case pc.MOUSEBUTTON_MIDDLE:
334
- case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
335
  }
336
  };
337
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
338
  switch (event.button) {
339
- case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
340
  case pc.MOUSEBUTTON_MIDDLE:
341
- case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
342
  }
343
  };
344
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
345
  if (this.lookButtonDown) {
346
  var sens = this.orbitSensitivity;
347
  var deltaPitch = event.dy * sens;
348
- var deltaYaw = event.dx * sens;
349
 
350
  var currPitch = this.orbitCamera.pitch;
351
- var currYaw = this.orbitCamera.yaw;
352
- var currDist = this.orbitCamera.distance;
353
  var currPivot = this.orbitCamera.pivotPoint.clone();
354
 
355
- var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
356
- var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
 
 
357
  var preY = currPivot.y + (-forward.y) * currDist;
358
 
359
  var proposedPitch = currPitch - deltaPitch;
360
- var testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
361
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
 
 
362
  var proposedY = currPivot.y + (-testForward.y) * currDist;
363
 
364
  var minY = this.orbitCamera.minY;
365
  var wouldGoBelow = proposedY < minY - 1e-4;
366
-
367
  if (wouldGoBelow && (proposedY < preY)) {
368
- this.orbitCamera.yaw = currYaw - deltaYaw;
369
  } else {
370
  this.orbitCamera.pitch = proposedPitch;
371
- this.orbitCamera.yaw = currYaw - deltaYaw;
372
  }
373
  } else if (this.panButtonDown) {
374
  this.pan(new pc.Vec2(event.x, event.y));
375
  }
376
-
377
  this.lastPoint.set(event.x, event.y);
378
  };
379
 
@@ -387,32 +392,28 @@ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
387
  };
388
  OrbitCameraInputMouse.prototype.onMouseOut = function () {
389
  this.lookButtonDown = false;
390
- this.panButtonDown = false;
391
  };
392
 
393
- // =================== Orbit Camera Input Touch Script ========================
394
-
395
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
396
- OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6, title: 'Orbit Sensitivity' });
397
  OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5, title: 'Distance Sensitivity' });
398
-
399
  OrbitCameraInputTouch.prototype.initialize = function () {
400
  this.orbitCamera = this.entity.script.orbitCamera;
401
- this.lastTouchPoint = new pc.Vec2();
402
  this.lastPinchMidPoint = new pc.Vec2();
403
  this.lastPinchDistance = 0;
404
-
405
  if (this.orbitCamera && this.app.touch) {
406
- this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
407
- this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
408
  this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
409
- this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
410
-
411
  this.on('destroy', function () {
412
- this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
413
- this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
414
  this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
415
- this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
416
  });
417
  }
418
  };
@@ -437,17 +438,14 @@ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
437
  }
438
  };
439
  OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
440
- OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
441
- OrbitCameraInputTouch.worldDiff = new pc.Vec3();
442
-
443
  OrbitCameraInputTouch.prototype.pan = function (midPoint) {
444
  var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
445
- var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
446
- var worldDiff = OrbitCameraInputTouch.worldDiff;
447
-
448
- var camera = this.entity.camera;
449
  var distance = this.orbitCamera.distance;
450
-
451
  camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
452
  camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
453
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
@@ -473,124 +471,151 @@ OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
473
  OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
474
  var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
475
  var touches = event.touches;
476
-
477
  if (touches.length === 1) {
478
  var touch = touches[0];
479
-
480
  var sens = this.orbitSensitivity;
481
  var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
482
- var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
483
 
484
  var currPitch = this.orbitCamera.pitch;
485
- var currYaw = this.orbitCamera.yaw;
486
- var currDist = this.orbitCamera.distance;
487
  var currPivot = this.orbitCamera.pivotPoint.clone();
488
 
489
- var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
490
- var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
 
 
491
  var preY = currPivot.y + (-forward.y) * currDist;
492
 
493
  var proposedPitch = currPitch - deltaPitch;
494
- var testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
495
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
 
 
496
  var proposedY = currPivot.y + (-testForward.y) * currDist;
497
 
498
  var minY = this.orbitCamera.minY;
499
  var wouldGoBelow = proposedY < minY - 1e-4;
500
-
501
  if (wouldGoBelow && (proposedY < preY)) {
502
- this.orbitCamera.yaw = currYaw - deltaYaw;
503
  } else {
504
  this.orbitCamera.pitch = proposedPitch;
505
- this.orbitCamera.yaw = currYaw - deltaYaw;
506
  }
507
-
508
  this.lastTouchPoint.set(touch.x, touch.y);
509
  } else if (touches.length === 2) {
510
  var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
511
- var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
512
  this.lastPinchDistance = currentPinchDistance;
513
-
514
  this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
515
-
516
  this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
517
  this.pan(pinchMidPoint);
518
  this.lastPinchMidPoint.copy(pinchMidPoint);
519
  }
520
  };
521
 
522
- // =================== Orbit Camera Input Keyboard Script ========================
523
-
524
  var OrbitCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
525
 
526
  /*
527
- * Keyboard:
528
- * - ↑ / ↓ : orbit pitch (north/south)
529
- * - ← / → : orbit yaw (left/right)
530
- * - Shift + ↑ / ↓ : zoom in / out (persp: distance, ortho: orthoHeight)
531
- * - Shift + ← / → : pan sideways
532
  *
533
- * Robust capture: uses both pc.Keyboard and window key events
534
- * (works even if the canvas isn’t focused). Optionally focus canvas on pointer down.
535
  */
536
 
537
  OrbitCameraInputKeyboard.attributes.add('orbitDegreesPerSecond', { type: 'number', default: 180, title: 'Orbit Degrees / s' });
538
  OrbitCameraInputKeyboard.attributes.add('zoomFactorPerSecond', { type: 'number', default: 2.0, title: 'Zoom Factor / s' });
539
  OrbitCameraInputKeyboard.attributes.add('panUnitsPerSecond', { type: 'number', default: 1.0, title: 'Pan (distance frac) / s' });
540
  OrbitCameraInputKeyboard.attributes.add('autoFocusCanvas', { type: 'boolean', default: true, title: 'Auto-focus Canvas on Click' });
541
- OrbitCameraInputKeyboard.attributes.add('debugLogs', { type: 'boolean', default: false, title: 'Enable Debug Logs' });
542
 
543
  OrbitCameraInputKeyboard.prototype._log = function () {
544
  if (!this.debugLogs) return;
545
- console.log.apply(console, arguments); // eslint-disable-line no-console
 
546
  };
547
 
548
  OrbitCameraInputKeyboard.prototype.initialize = function () {
 
549
  this.orbitCamera = this.entity.script.orbitCamera;
550
 
551
- // Ensure PlayCanvas keyboard exists
552
  if (!this.app.keyboard) {
553
  this.app.keyboard = new pc.Keyboard(window);
554
  this._log('[OrbitKB] created pc.Keyboard(window)');
 
 
555
  }
556
 
557
- // Window-level key state (works regardless of focus target)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  this._keyState = Object.create(null);
559
- this._onWinKeyDown = (e) => {
560
- this._keyState[e.key] = true;
561
- // prevent page scroll on arrows
 
562
  if (e.key && e.key.indexOf('Arrow') === 0) e.preventDefault();
563
- this._log('[OrbitKB] down', e.key);
564
  };
565
- this._onWinKeyUp = (e) => {
566
- this._keyState[e.key] = false;
567
- this._log('[OrbitKB] up', e.key);
568
  };
569
  window.addEventListener('keydown', this._onWinKeyDown, { passive: false });
570
- window.addEventListener('keyup', this._onWinKeyUp);
571
-
572
- // Optional: make canvas focusable and auto-focus on click
573
- this.canvas = this.app.graphicsDevice && this.app.graphicsDevice.canvas;
574
- if (this.canvas && this.autoFocusCanvas) {
575
- if (!this.canvas.hasAttribute('tabindex')) this.canvas.setAttribute('tabindex', '0');
576
- this._onPointerDown = () => { try { this.canvas.focus(); } catch (e) {} };
577
- this.canvas.addEventListener('pointerdown', this._onPointerDown);
578
- }
 
 
 
 
579
 
580
  this.on('destroy', function () {
581
  window.removeEventListener('keydown', this._onWinKeyDown, { passive: false });
582
- window.removeEventListener('keyup', this._onWinKeyUp);
 
583
  if (this.canvas && this._onPointerDown) this.canvas.removeEventListener('pointerdown', this._onPointerDown);
584
  });
 
 
 
 
 
 
585
  };
586
 
587
  OrbitCameraInputKeyboard.prototype._pressed = function (pcKey, keyString) {
588
- // true if either PlayCanvas reports it or our window listener does
589
  var pcPressed = this.app.keyboard && this.app.keyboard.isPressed ? this.app.keyboard.isPressed(pcKey) : false;
590
  var winPressed = !!this._keyState[keyString];
591
  return pcPressed || winPressed;
592
  };
593
 
 
594
  OrbitCameraInputKeyboard.prototype._wouldGoBelowMinYWithPitch = function (proposedPitch) {
595
  var cam = this.orbitCamera;
596
  var currPitch = cam.pitch;
@@ -598,23 +623,32 @@ OrbitCameraInputKeyboard.prototype._wouldGoBelowMinYWithPitch = function (propos
598
  var currDist = cam.distance;
599
  var pivot = cam.pivotPoint.clone();
600
 
601
- var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
602
- var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
 
 
603
  var preY = pivot.y + (-forward.y) * currDist;
604
 
605
- var testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
606
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
 
 
607
  var proposedY = pivot.y + (-testForward.y) * currDist;
608
 
609
  var minY = cam.minY;
610
  return { wouldGoBelow: proposedY < minY - 1e-4, preY: preY, proposedY: proposedY };
611
  };
612
 
 
613
  OrbitCameraInputKeyboard.prototype._panSideways = function (delta) {
614
  var cam = this.orbitCamera;
615
 
616
- var quat = new pc.Quat().setFromEulerAngles(cam.pitch, cam.yaw, 0);
617
- var right = new pc.Vec3(); quat.transformVector(pc.Vec3.RIGHT, right);
 
 
 
 
618
  var move = right.scale(delta);
619
 
620
  var minY = cam.minY;
@@ -623,11 +657,21 @@ OrbitCameraInputKeyboard.prototype._panSideways = function (delta) {
623
 
624
  if (resultingY >= minY - 1e-4) {
625
  cam.pivotPoint.add(move);
 
 
 
 
 
626
  } else {
627
  move.y = 0;
628
  proposedPivot = cam.pivotPoint.clone().add(move);
629
  resultingY = cam.worldCameraYForPivot(proposedPivot);
630
- if (resultingY >= minY - 1e-4) cam.pivotPoint.add(move);
 
 
 
 
 
631
  }
632
  };
633
 
@@ -636,48 +680,71 @@ OrbitCameraInputKeyboard.prototype.update = function (dt) {
636
 
637
  var cam = this.orbitCamera;
638
 
 
639
  var shift = this._pressed(pc.KEY_SHIFT, 'Shift') || this._pressed(pc.KEY_SHIFT, 'ShiftLeft') || this._pressed(pc.KEY_SHIFT, 'ShiftRight');
640
  var up = this._pressed(pc.KEY_UP, 'ArrowUp');
641
  var down = this._pressed(pc.KEY_DOWN, 'ArrowDown');
642
  var left = this._pressed(pc.KEY_LEFT, 'ArrowLeft');
643
  var right = this._pressed(pc.KEY_RIGHT, 'ArrowRight');
644
 
 
 
 
 
645
  if (!(up || down || left || right)) return;
646
 
647
  if (shift) {
648
- // Zoom (Shift + Up/Down)
649
  if (up || down) {
650
  var k = this.zoomFactorPerSecond;
651
  if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
652
  var dz = k * dt * (cam.distance * 0.8);
653
- cam.distance = cam.distance + (down ? +dz : -dz);
 
 
 
654
  } else {
655
  var dh = k * dt * (this.entity.camera.orthoHeight * 0.8);
656
- this.entity.camera.orthoHeight += (down ? +dh : -dh);
 
 
 
657
  }
658
  }
659
- // Pan lateral (Shift + Left/Right)
 
660
  if (left || right) {
661
- var speed = this.panUnitsPerSecond * cam.distance; // world units / s
662
  var delta = speed * dt * (left ? -1 : +1);
 
663
  this._panSideways(delta);
664
  }
665
- return;
 
666
  }
667
 
668
- // Orbit (no Shift)
669
  var orbitRate = this.orbitDegreesPerSecond * dt;
670
 
 
671
  if (up || down) {
672
- var dir = up ? -1 : +1; // Up: decrease pitch
673
- var currPitch = cam.pitch;
674
- var proposedPitch = currPitch + dir * orbitRate;
675
  var test = this._wouldGoBelowMinYWithPitch(proposedPitch);
676
  if (!(test.wouldGoBelow && (test.proposedY < test.preY))) {
677
  cam.pitch = proposedPitch;
 
 
 
678
  }
679
  }
680
 
681
- if (left) cam.yaw = cam.yaw + orbitRate;
682
- if (right) cam.yaw = cam.yaw - orbitRate;
 
 
 
 
 
683
  };
 
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
 
22
+ // Camera attributes
23
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
24
+ OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
25
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
26
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
27
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
28
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
29
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y', description: 'Minimum Y value for camera world position' });
30
+
31
+ OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
32
+ OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
33
+ OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
34
 
35
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
36
  get: function () { return this._targetDistance; },
37
  set: function (value) { this._targetDistance = this._clampDistance(value); }
38
  });
39
 
40
+ Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
41
+ get: function () { return this.entity.camera.orthoHeight; },
42
+ set: function (value) { this.entity.camera.orthoHeight = Math.max(0, value); }
43
+ });
44
+
45
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
46
  get: function () { return this._targetPitch; },
47
  set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
 
77
  this.distance = distance.length();
78
  this.pivotPoint.copy(lookAtPoint);
79
  var cameraQuat = this.entity.getRotation();
80
+ this.yaw = this._calcYaw(cameraQuat);
81
  this.pitch = this._calcPitch(cameraQuat, this.yaw);
82
  this._removeInertia();
83
  this._updatePosition();
 
103
  distanceVec.sub2(position, lookAtPoint);
104
  this._targetDistance = this._distance = distanceVec.length();
105
  var cameraQuat = this.entity.getRotation();
106
+ this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
107
  this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
108
  this._removeInertia();
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);
 
120
  return camPos.y;
121
  };
122
 
123
+ ////////////////////////////////////////////////////////////////////////////////
124
+ // Private Methods //
125
+ ////////////////////////////////////////////////////////////////////////////////
126
+
127
  OrbitCamera.prototype.initialize = function () {
128
  var self = this;
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
  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); });
 
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) {
171
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
172
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
173
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
174
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
175
  this._updatePosition();
176
  };
177
 
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
 
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
  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
  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];
221
  var instance = gsplat.instance;
222
+ if (instance?.meshInstance) {
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);
 
233
  };
234
 
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
  };
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
 
 
262
  var yawOffset = OrbitCamera.yawOffset;
263
  yawOffset.setFromEulerAngles(0, -yaw, 0);
264
  quatWithoutYaw.mul2(yawOffset, quat);
265
+ var transformedForward = new pc.Vec3();
266
+ quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
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);
289
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
290
+ this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
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
+
300
  OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
301
+ OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
302
+ OrbitCameraInputMouse.worldDiff = new pc.Vec3();
303
 
304
  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
  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
 
333
  OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
334
  switch (event.button) {
335
+ case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
336
  case pc.MOUSEBUTTON_MIDDLE:
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;
343
  case pc.MOUSEBUTTON_MIDDLE:
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;
354
+ var currYaw = this.orbitCamera.yaw;
355
+ var currDist = this.orbitCamera.distance;
356
  var currPivot = this.orbitCamera.pivotPoint.clone();
357
 
358
+ var camQuat = new pc.Quat();
359
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
360
+ var forward = new pc.Vec3();
361
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
362
  var preY = currPivot.y + (-forward.y) * currDist;
363
 
364
  var proposedPitch = currPitch - deltaPitch;
365
+ var testQuat = new pc.Quat();
366
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
367
+ var testForward = new pc.Vec3();
368
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
369
  var proposedY = currPivot.y + (-testForward.y) * currDist;
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 {
376
  this.orbitCamera.pitch = proposedPitch;
377
+ this.orbitCamera.yaw = currYaw - deltaYaw;
378
  }
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
 
 
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);
415
  this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
416
+ this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
417
  });
418
  }
419
  };
 
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);
 
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
 
480
  var currPitch = this.orbitCamera.pitch;
481
+ var currYaw = this.orbitCamera.yaw;
482
+ var currDist = this.orbitCamera.distance;
483
  var currPivot = this.orbitCamera.pivotPoint.clone();
484
 
485
+ var camQuat = new pc.Quat();
486
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
487
+ var forward = new pc.Vec3();
488
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
489
  var preY = currPivot.y + (-forward.y) * currDist;
490
 
491
  var proposedPitch = currPitch - deltaPitch;
492
+ var testQuat = new pc.Quat();
493
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
494
+ var testForward = new pc.Vec3();
495
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
496
  var proposedY = currPivot.y + (-testForward.y) * currDist;
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;
 
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;
 
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
 
 
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
  };