MikaFil commited on
Commit
2ead98e
·
verified ·
1 Parent(s): e3a8ba6

Update deplacement_dans_env/ctrl_camera_pr_env.js

Browse files
deplacement_dans_env/ctrl_camera_pr_env.js CHANGED
@@ -1,714 +1,313 @@
1
  // ctrl_camera_pr_env.js
2
  // ============================================================================
3
- // ORBIT CAMERA + INPUTS (PlayCanvas) bbox + rotation libre & sens corrigés
4
- // - Zoom : uniquement un minimum (pas de maxZoom)
5
- // - Bounding Box : clamp SEULEMENT la position caméra, jamais l'orientation
6
- // - Yaw : pas de clamp -> rotations illimitées (±2000° etc.)
7
- // - Pitch : clamp via min/max (configurable), minY préservé
 
8
  // ============================================================================
9
 
10
- var OrbitCamera = pc.createScript('orbitCamera');
11
 
12
- // ======================== Attributs Caméra ===========================
13
- OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
14
 
15
- OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
16
- OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
 
17
 
18
- // NB: gardés pour compat, mais non utilisés pour brider le yaw (rotation libre)
19
- OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (unused clamp)' });
20
- OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (unused clamp)' });
21
 
22
- OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y (camera world Y)' });
 
 
 
23
 
24
- OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
25
- OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
26
- OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
 
 
 
 
27
 
28
- // ---- Bounding Box (active si Xmin < Xmax, etc.) ----
29
- OrbitCamera.attributes.add('Xmin', { type: 'number', default: -Infinity, title: 'BBox Xmin' });
30
- OrbitCamera.attributes.add('Xmax', { type: 'number', default: Infinity, title: 'BBox Xmax' });
31
- OrbitCamera.attributes.add('Ymin', { type: 'number', default: -Infinity, title: 'BBox Ymin' });
32
- OrbitCamera.attributes.add('Ymax', { type: 'number', default: Infinity, title: 'BBox Ymax' });
33
- OrbitCamera.attributes.add('Zmin', { type: 'number', default: -Infinity, title: 'BBox Zmin' });
34
- OrbitCamera.attributes.add('Zmax', { type: 'number', default: Infinity, title: 'BBox Zmax' });
35
 
36
- // ======================== Propriétés ===========================
37
- Object.defineProperty(OrbitCamera.prototype, 'distance', {
38
- get: function () { return this._targetDistance; },
39
- set: function (value) { this._targetDistance = this._clampDistance(value); }
40
- });
41
-
42
- Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
43
- get: function () { return this.entity.camera.orthoHeight; },
44
- set: function (value) { this.entity.camera.orthoHeight = Math.max(0, value); }
45
- });
46
-
47
- Object.defineProperty(OrbitCamera.prototype, 'pitch', {
48
  get: function () { return this._targetPitch; },
49
- set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
50
  });
51
-
52
- Object.defineProperty(OrbitCamera.prototype, 'yaw', {
53
  get: function () { return this._targetYaw; },
54
- set: function (value) {
55
- // Yaw LIBRE : pas de clamp pour permettre ±2000°, etc.
56
- this._targetYaw = value;
57
- }
58
- });
59
-
60
- Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
61
- get: function () { return this._pivotPoint; },
62
- set: function (value) { this._pivotPoint.copy(value); }
63
  });
64
 
65
- // ======================== API Publique ===========================
66
- OrbitCamera.prototype.focus = function (focusEntity) {
67
- this._buildAabb(focusEntity);
68
- var halfExtents = this._modelsAabb.halfExtents;
69
- var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
70
- // Distance initiale (pas de maxZoom)
71
- this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
72
- this._removeInertia();
73
- this._pivotPoint.copy(this._modelsAabb.center);
74
- };
75
-
76
- OrbitCamera.distanceBetween = new pc.Vec3();
77
-
78
- OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
79
- this.pivotPoint.copy(lookAtPoint);
80
-
81
- this.entity.setPosition(resetPoint);
82
- this.entity.lookAt(lookAtPoint);
83
-
84
- var distance = OrbitCamera.distanceBetween;
85
- distance.sub2(lookAtPoint, resetPoint);
86
- this.distance = distance.length();
87
-
88
- var cameraQuat = this.entity.getRotation();
89
- this.yaw = this._calcYaw(cameraQuat);
90
- this.pitch = this._calcPitch(cameraQuat, this.yaw);
91
-
92
- this._removeInertia();
93
- this._updatePosition();
94
- };
95
-
96
- OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
97
- this._buildAabb(entity);
98
- this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
99
- };
100
-
101
- OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
102
- this.pitch = pitch;
103
- this.yaw = yaw;
104
- this.distance = distance;
105
- this._removeInertia();
106
- };
107
-
108
- OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
109
- this.entity.setPosition(position);
110
- this.entity.lookAt(lookAtPoint);
111
- this._pivotPoint.copy(lookAtPoint);
112
 
113
- var distanceVec = new pc.Vec3();
114
- distanceVec.sub2(position, lookAtPoint);
115
- this._targetDistance = this._distance = Math.max(this.distanceMin, distanceVec.length());
116
 
117
- var cameraQuat = this.entity.getRotation();
118
- this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
119
- this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
120
 
121
- this._removeInertia();
122
- this._updatePosition();
123
- };
 
124
 
125
- // ==== Helper: cam Y si le pivot = 'pivot' (sert pour minY) ====
126
- OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
127
- var quat = new pc.Quat();
128
- quat.setFromEulerAngles(this._pitch, this._yaw, 0);
129
- var forward = new pc.Vec3();
130
- quat.transformVector(pc.Vec3.FORWARD, forward);
131
- var camPos = pivot.clone();
132
- camPos.add(forward.clone().scale(-this._distance));
133
- return camPos.y;
134
- };
135
-
136
- // ======================== Privé ===========================
137
- OrbitCamera.prototype.initialize = function () {
138
  var self = this;
139
- var onWindowResize = function () { self._checkAspectRatio(); };
140
- window.addEventListener('resize', onWindowResize, false);
141
  this._checkAspectRatio();
142
-
143
- this._modelsAabb = new pc.BoundingBox();
144
- this._buildAabb(this.focusEntity || this.app.root);
145
-
146
- this.entity.lookAt(this._modelsAabb.center);
147
- this._pivotPoint = new pc.Vec3();
148
- this._pivotPoint.copy(this._modelsAabb.center);
149
-
150
- var cameraQuat = this.entity.getRotation();
151
- this._yaw = this._calcYaw(cameraQuat);
152
- this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
153
- this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
154
-
155
- this._distance = 0;
156
- this._targetYaw = this._yaw;
157
- this._targetPitch = this._pitch;
158
-
159
- if (this.frameOnStart) {
160
- this.focus(this.focusEntity || this.app.root);
161
- } else {
162
- var distanceBetween = new pc.Vec3();
163
- distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
164
- this._distance = this._clampDistance(distanceBetween.length());
165
- }
166
- this._targetDistance = this._distance;
167
-
168
- // Listeners d’attributs
169
- this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
170
- this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
171
- this.on('attr:pitchAngleMax', function () { this._pitch = this._clampPitchAngle(this._pitch); });
172
-
173
- this.on('attr:focusEntity', function (value) {
174
- if (this.frameOnStart) { this.focus(value || this.app.root); }
175
- else { this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root); }
176
- });
177
-
178
- this.on('attr:frameOnStart', function (value) {
179
- if (value) { this.focus(this.focusEntity || this.app.root); }
180
- });
181
-
182
- this.on('destroy', function () { window.removeEventListener('resize', onWindowResize, false); });
183
  };
184
 
185
- OrbitCamera.prototype.update = function (dt) {
 
186
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
187
- this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
188
- this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
189
- this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
190
- this._updatePosition();
191
- };
192
-
193
- OrbitCamera.prototype._updatePosition = function () {
194
- // 1) oriente la caméra
195
- this.entity.setLocalPosition(0, 0, 0);
196
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
197
 
198
- // 2) calcule la position théorique en fonction du pivot et de la distance
199
- var forward = this.entity.forward.clone(); // direction de visée
200
- var desired = this._pivotPoint.clone().add(forward.clone().mulScalar(-this._distance));
201
-
202
- // 3) applique minY (plan horizontal)
203
- if (desired.y < this.minY) {
204
- desired.y = this.minY;
205
- }
206
-
207
- // 4) contraint UNIQUEMENT la position caméra à la bbox
208
- var clamped = desired.clone();
209
- var wasClamped = this._applyBoundingBox(clamped); // true si clamp
210
-
211
- // 5) place la caméra
212
- this.entity.setPosition(clamped);
213
-
214
- // 6) si clamp (bbox/minY), recalcule un pivot cohérent sur le même rayon
215
- if (wasClamped || clamped.y !== desired.y) {
216
- // pos = pivot - forward * distance => pivot = pos + forward * distance
217
- this._pivotPoint.copy(clamped.clone().add(forward.mulScalar(this._distance)));
218
- }
219
- };
220
-
221
- OrbitCamera.prototype._removeInertia = function () {
222
- this._yaw = this._targetYaw;
223
- this._pitch = this._targetPitch;
224
- this._distance = this._targetDistance;
225
- };
226
-
227
- OrbitCamera.prototype._checkAspectRatio = function () {
228
- var height = this.app.graphicsDevice.height;
229
- var width = this.app.graphicsDevice.width;
230
- this.entity.camera.horizontalFov = (height > width);
231
- };
232
-
233
- OrbitCamera.prototype._buildAabb = function (entity) {
234
- var i, m, meshInstances = [];
235
-
236
- var renders = entity.findComponents('render');
237
- for (i = 0; i < renders.length; i++) {
238
- var render = renders[i];
239
- for (m = 0; m < render.meshInstances.length; m++) {
240
- meshInstances.push(render.meshInstances[m]);
241
- }
242
- }
243
-
244
- var models = entity.findComponents('model');
245
- for (i = 0; i < models.length; i++) {
246
- var model = models[i];
247
- for (m = 0; m < model.meshInstances.length; m++) {
248
- meshInstances.push(model.meshInstances[m]);
249
- }
250
- }
251
-
252
- var gsplats = entity.findComponents('gsplat');
253
- for (i = 0; i < gsplats.length; i++) {
254
- var gsplat = gsplats[i];
255
- var instance = gsplat.instance;
256
- if (instance?.meshInstance) {
257
- meshInstances.push(instance.meshInstance);
258
- }
259
- }
260
-
261
- for (i = 0; i < meshInstances.length; i++) {
262
- if (i === 0) this._modelsAabb.copy(meshInstances[i].aabb);
263
- else this._modelsAabb.add(meshInstances[i].aabb);
264
- }
265
  };
266
 
267
- OrbitCamera.prototype._calcYaw = function (quat) {
268
- var transformedForward = new pc.Vec3();
269
- quat.transformVector(pc.Vec3.FORWARD, transformedForward);
270
- // Convention: yaw croissant = rotation CCW vue de dessus
271
- return Math.atan2(transformedForward.x, transformedForward.z) * pc.math.RAD_TO_DEG;
272
- };
273
-
274
- OrbitCamera.prototype._clampDistance = function (distance) {
275
- return Math.max(distance, this.distanceMin);
276
- };
277
-
278
- OrbitCamera.prototype._clampPitchAngle = function (pitch) {
279
- return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
280
- };
281
-
282
- // Yaw: pas de clamp (rotation libre)
283
- OrbitCamera.prototype._clampYawAngle = function (yaw) { return yaw; };
284
-
285
- OrbitCamera.quatWithoutYaw = new pc.Quat();
286
- OrbitCamera.yawOffset = new pc.Quat();
287
-
288
- OrbitCamera.prototype._calcPitch = function (quat, yaw) {
289
- var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
290
- var yawOffset = OrbitCamera.yawOffset;
291
- yawOffset.setFromEulerAngles(0, -yaw, 0);
292
- quatWithoutYaw.mul2(yawOffset, quat);
293
- var transformedForward = new pc.Vec3();
294
- quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
295
- return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
296
  };
297
 
298
- // -------- Helpers Bounding Box --------
299
- OrbitCamera.prototype._bboxEnabled = function () {
300
  return (this.Xmin < this.Xmax) && (this.Ymin < this.Ymax) && (this.Zmin < this.Zmax);
301
  };
302
 
303
- /** Clamp position caméra à la bbox. @returns {boolean} true si clamp appliqué */
304
- OrbitCamera.prototype._applyBoundingBox = function (v3) {
305
- if (!this._bboxEnabled()) return false;
306
- var clamped = false;
307
- var nx = pc.math.clamp(v3.x, this.Xmin, this.Xmax);
308
- var ny = pc.math.clamp(v3.y, this.Ymin, this.Ymax);
309
- var nz = pc.math.clamp(v3.z, this.Zmin, this.Zmax);
310
- if (nx !== v3.x || ny !== v3.y || nz !== v3.z) clamped = true;
311
- v3.x = nx; v3.y = ny; v3.z = nz;
312
- return clamped;
313
- };
314
 
315
- // ===================== Orbit Camera Input Mouse ========================
316
- var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
317
- OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3, title: 'Orbit Sensitivity' });
318
- OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4, title: 'Distance Sensitivity' });
319
 
320
- OrbitCameraInputMouse.prototype.initialize = function () {
321
- this.orbitCamera = this.entity.script.orbitCamera;
 
 
322
 
323
- if (this.orbitCamera) {
324
- var self = this;
325
- var onMouseOut = function () { self.onMouseOut(); };
 
326
 
 
327
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
328
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
329
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
330
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
331
- window.addEventListener('mouseout', onMouseOut, false);
 
 
 
 
332
 
333
- this.on('destroy', function () {
 
334
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
335
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
336
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
337
  this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
338
- window.removeEventListener('mouseout', onMouseOut, false);
339
- });
340
- }
341
-
342
- this.app.mouse.disableContextMenu();
343
-
344
- this.lookButtonDown = false;
345
- this.panButtonDown = false;
346
- this.lastPoint = new pc.Vec2();
347
- };
348
-
349
- OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
350
- OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
351
- OrbitCameraInputMouse.worldDiff = new pc.Vec3();
352
-
353
- OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
354
- var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
355
- var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
356
- var worldDiff = OrbitCameraInputMouse.worldDiff;
357
-
358
- var camera = this.entity.camera;
359
- var distance = this.orbitCamera.distance;
360
-
361
- camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
362
- camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
363
-
364
- worldDiff.sub2(toWorldPoint, fromWorldPoint);
365
-
366
- // minY via test sur la position caméra résultante
367
- var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
368
- var camY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
369
-
370
- if (camY >= this.orbitCamera.minY - 1e-4) {
371
- this.orbitCamera.pivotPoint.copy(proposedPivot);
372
- } else {
373
- worldDiff.y = 0;
374
- proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
375
- camY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
376
- if (camY >= this.orbitCamera.minY - 1e-4) {
377
- this.orbitCamera.pivotPoint.copy(proposedPivot);
378
  }
379
- }
 
380
  };
381
 
382
- OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
383
- switch (event.button) {
384
- case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
385
- case pc.MOUSEBUTTON_MIDDLE:
386
- case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
387
- }
388
  };
389
 
390
- OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
391
- switch (event.button) {
392
- case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
393
- case pc.MOUSEBUTTON_MIDDLE:
394
- case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
395
- }
396
  };
397
 
398
- OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
399
- if (this.lookButtonDown) {
400
- var sens = this.orbitSensitivity;
401
-
402
- // Souris : droite => orbite à droite ; gauche => orbite à gauche
403
- var deltaYaw = event.dx * sens;
404
- // Pitch : dy<0 (monte) => pitch augmente (look up)
405
- var deltaPitch = event.dy * sens;
406
-
407
- var currPitch = this.orbitCamera.pitch;
408
- var currYaw = this.orbitCamera.yaw;
409
- var currDist = this.orbitCamera.distance;
410
- var currPivot = this.orbitCamera.pivotPoint.clone();
411
-
412
- var camQuat = new pc.Quat();
413
- camQuat.setFromEulerAngles(currPitch, currYaw, 0);
414
- var forward = new pc.Vec3();
415
- camQuat.transformVector(pc.Vec3.FORWARD, forward);
416
- var preY = currPivot.y + (-forward.y) * currDist;
417
-
418
- // Conventions:
419
- // - Yaw: +deltaYaw = tourner gauche (CCW). On veut "drag droite => tourner droite (CW)" -> yaw -= deltaYaw
420
- // - Pitch: "drag haut (dy<0) => look up" -> pitch -= deltaPitch (car deltaPitch<0)
421
- var proposedPitch = currPitch - deltaPitch;
422
- var proposedYaw = currYaw - deltaYaw;
423
-
424
- // Vérif minY pour pitch uniquement (comme avant)
425
- var testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
426
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
427
- var proposedY = currPivot.y + (-testForward.y) * currDist;
428
-
429
- var minY = this.orbitCamera.minY;
430
- var wouldGoBelow = proposedY < minY - 1e-4;
431
-
432
- if (!(wouldGoBelow && (proposedY < preY))) {
433
- this.orbitCamera.pitch = proposedPitch;
434
- }
435
- // Yaw toujours libre
436
- this.orbitCamera.yaw = proposedYaw;
437
 
438
- } else if (this.panButtonDown) {
439
- this.pan(new pc.Vec2(event.x, event.y));
440
- }
441
 
442
- this.lastPoint.set(event.x, event.y);
443
- };
444
 
445
- OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
446
- if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
447
- this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
448
- } else {
449
- this.orbitCamera.orthoHeight -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight * 0.1);
450
- }
451
- event.event.preventDefault();
452
  };
453
 
454
- OrbitCameraInputMouse.prototype.onMouseOut = function () {
455
- this.lookButtonDown = false;
456
- this.panButtonDown = false;
 
 
 
 
 
 
 
 
 
 
457
  };
458
 
459
- // =================== Orbit Camera Input Touch ========================
460
- var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
461
- OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6, title: 'Orbit Sensitivity' });
462
- OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5, title: 'Distance Sensitivity' });
463
 
464
- OrbitCameraInputTouch.prototype.initialize = function () {
465
- this.orbitCamera = this.entity.script.orbitCamera;
466
- this.lastTouchPoint = new pc.Vec2();
467
- this.lastPinchMidPoint = new pc.Vec2();
468
- this.lastPinchDistance = 0;
469
 
470
- if (this.orbitCamera && this.app.touch) {
471
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
472
  this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
473
  this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
474
  this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
 
475
 
476
- this.on('destroy', function () {
 
477
  this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
478
  this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
479
  this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
480
  this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
481
- });
482
- }
483
- };
484
-
485
- OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
486
- var dx = pointA.x - pointB.x;
487
- var dy = pointA.y - pointB.y;
488
- return Math.sqrt((dx * dx) + (dy * dy));
489
- };
490
-
491
- OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
492
- result.set(pointB.x - pointA.x, pointB.y - pointA.y);
493
- result.mulScalar(0.5);
494
- result.x += pointA.x;
495
- result.y += pointA.y;
496
- };
497
-
498
- OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
499
- var touches = event.touches;
500
- if (touches.length === 1) {
501
- this.lastTouchPoint.set(touches[0].x, touches[0].y);
502
- } else if (touches.length === 2) {
503
- this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
504
- this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
505
- }
506
  };
507
 
508
- OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
509
- OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
510
- OrbitCameraInputTouch.worldDiff = new pc.Vec3();
511
-
512
- OrbitCameraInputTouch.prototype.pan = function (midPoint) {
513
- var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
514
- var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
515
- var worldDiff = OrbitCameraInputTouch.worldDiff;
516
-
517
- var camera = this.entity.camera;
518
- var distance = this.orbitCamera.distance;
519
-
520
- camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
521
- camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
522
-
523
- worldDiff.sub2(toWorldPoint, fromWorldPoint);
524
-
525
- var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
526
- var minY = this.orbitCamera.minY;
527
- var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
528
-
529
- if (resultingY >= minY - 1e-4) {
530
- this.orbitCamera.pivotPoint.copy(proposedPivot);
531
  } else {
532
- worldDiff.y = 0;
533
- proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
534
- resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
535
- if (resultingY >= minY - 1e-4) {
536
- this.orbitCamera.pivotPoint.copy(proposedPivot);
537
- }
538
  }
539
  };
540
 
541
- OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
542
-
543
- OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
544
- var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
545
- var touches = event.touches;
546
-
547
- if (touches.length === 1) {
548
- var touch = touches[0];
549
- var sens = this.orbitSensitivity;
550
-
551
- var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
552
- var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
553
-
554
- var currPitch = this.orbitCamera.pitch;
555
- var currYaw = this.orbitCamera.yaw;
556
- var currDist = this.orbitCamera.distance;
557
- var currPivot = this.orbitCamera.pivotPoint.clone();
558
-
559
- var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
560
- var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
561
- var preY = currPivot.y + (-forward.y) * currDist;
562
-
563
- // mêmes conventions que souris
564
- var proposedPitch = currPitch - deltaPitch;
565
- var proposedYaw = currYaw - deltaYaw;
566
-
567
- var testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
568
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
569
- var proposedY = currPivot.y + (-testForward.y) * currDist;
570
-
571
- var minY = this.orbitCamera.minY;
572
- var wouldGoBelow = proposedY < minY - 1e-4;
573
-
574
- if (!(wouldGoBelow && (proposedY < preY))) {
575
- this.orbitCamera.pitch = proposedPitch;
576
- }
577
- this.orbitCamera.yaw = proposedYaw;
578
-
579
- this.lastTouchPoint.set(touch.x, touch.y);
580
- } else if (touches.length === 2) {
581
- var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
582
- var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
583
- this.lastPinchDistance = currentPinchDistance;
584
-
585
- this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
586
-
587
- this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
588
- this.pan(pinchMidPoint);
589
- this.lastPinchMidPoint.copy(pinchMidPoint);
590
  }
591
  };
592
 
593
- // =================== Orbit Camera Input Keyboard ========================
594
- // Flèches : déplacement avant/arrière & droite/gauche (repère caméra, 3D)
595
- // Ctrl+flèches : orbiter autour du pivot (sens conforme ← CCW / → CW)
596
- // Maj+flèches : rotation libre sur place (pas de limites yaw/pitch)
597
- var OrbitCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
598
-
599
- // Vitesses (relatives à la distance)
600
- OrbitCameraInputKeyboard.attributes.add('forwardSpeed', { type: 'number', default: 1.8, title: 'Forward Speed (rel. to distance)' });
601
- OrbitCameraInputKeyboard.attributes.add('strafeSpeed', { type: 'number', default: 1.8, title: 'Strafe Speed (rel. to distance)' });
602
-
603
- // Réglages d’orbite / rotation
604
- OrbitCameraInputKeyboard.attributes.add('orbitPitchSpeedDeg', { type: 'number', default: 90, title: 'Orbit Pitch Speed (deg/s) [Ctrl+Up/Down]' });
605
- OrbitCameraInputKeyboard.attributes.add('orbitYawSpeedDeg', { type: 'number', default: 120, title: 'Orbit Yaw Speed (deg/s) [Ctrl+Left/Right]' });
606
- OrbitCameraInputKeyboard.attributes.add('rotatePitchSpeedDeg', { type: 'number', default: 90, title: 'Self Rotate Pitch (deg/s) [Shift+Up/Down]' });
607
- OrbitCameraInputKeyboard.attributes.add('rotateYawSpeedDeg', { type: 'number', default: 120, title: 'Self Rotate Yaw (deg/s) [Shift+Left/Right]' });
608
-
609
- OrbitCameraInputKeyboard.prototype.initialize = function () {
610
- this.orbitCamera = this.entity.script.orbitCamera;
611
- this.keyboard = this.app.keyboard || null;
612
- };
613
-
614
- OrbitCameraInputKeyboard.prototype.update = function (dt) {
615
- if (!this.orbitCamera || !this.keyboard) return;
616
-
617
- var up = this.keyboard.isPressed(pc.KEY_UP);
618
- var dn = this.keyboard.isPressed(pc.KEY_DOWN);
619
- var lt = this.keyboard.isPressed(pc.KEY_LEFT);
620
- var rt = this.keyboard.isPressed(pc.KEY_RIGHT);
621
-
622
- var shift = this.keyboard.isPressed(pc.KEY_SHIFT);
623
- var ctrl = this.keyboard.isPressed(pc.KEY_CONTROL);
624
 
625
- // -------- Ctrl + flèches : ORBIT autour du pivot --------
626
- if (ctrl && (up || dn || lt || rt)) {
627
- // Convention : ← = CCW => yaw augmente ; → = CW => yaw diminue
628
- var yawDirLeftRight = (lt ? 1 : 0) - (rt ? 1 : 0); // ← +1, → -1
629
- var pitchDirUpDown = (up ? 1 : 0) - (dn ? 1 : 0); // ↑ +1, ↓ -1
630
 
631
- if (yawDirLeftRight !== 0) {
632
- this.orbitCamera.yaw = this.orbitCamera.yaw + yawDirLeftRight * this.orbitYawSpeedDeg * dt;
633
- }
634
-
635
- if (pitchDirUpDown !== 0) {
636
- var currPitch = this.orbitCamera.pitch;
637
- var currYaw = this.orbitCamera.yaw;
638
- var currDist = this.orbitCamera.distance;
639
- var currPivot = this.orbitCamera.pivotPoint.clone();
640
-
641
- var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
642
- var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
643
- var preY = currPivot.y + (-forward.y) * currDist;
644
-
645
- var testPitch = currPitch + pitchDirUpDown * this.orbitPitchSpeedDeg * dt;
646
- var testQuat = new pc.Quat().setFromEulerAngles(testPitch, currYaw, 0);
647
- var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
648
- var proposedY = currPivot.y + (-testForward.y) * currDist;
649
-
650
- var minY = this.orbitCamera.minY;
651
- var wouldGoBelow = proposedY < minY - 1e-4;
652
 
653
- if (!(wouldGoBelow && (proposedY < preY))) {
654
- this.orbitCamera.pitch = testPitch;
655
- }
656
- }
657
- return;
658
- }
659
 
660
- // -------- Maj + flèches : ROTATION SUR PLACE --------
661
- if (shift && (up || dn || lt || rt)) {
662
- var yawDirR = (lt ? 1 : 0) - (rt ? 1 : 0); // ← +1 (CCW), → -1 (CW)
663
- var pitchDirR = (up ? 1 : 0) - (dn ? 1 : 0); // ↑ +1, ↓ -1
664
 
665
- var camPos = this.entity.getPosition().clone();
666
- var dist = this.orbitCamera._distance;
667
 
668
- // bypass clamps pour rotation libre
669
- this.orbitCamera._yaw += yawDirR * this.rotateYawSpeedDeg * dt;
670
- this.orbitCamera._pitch += pitchDirR * this.rotatePitchSpeedDeg * dt;
671
- this.orbitCamera._targetYaw = this.orbitCamera._yaw;
672
- this.orbitCamera._targetPitch = this.orbitCamera._pitch;
673
 
674
- // Recalcule le pivot pour garder la même position de caméra
675
- var q = new pc.Quat().setFromEulerAngles(this.orbitCamera._pitch, this.orbitCamera._yaw, 0);
676
- var forward = new pc.Vec3(); q.transformVector(pc.Vec3.FORWARD, forward);
677
- var newPivot = camPos.clone().add(forward.clone().mulScalar(dist));
678
- this.orbitCamera._pivotPoint.copy(newPivot);
679
 
680
- this.orbitCamera._removeInertia();
681
- this.orbitCamera._updatePosition();
682
- this.entity.setPosition(camPos);
683
 
684
- return;
 
685
  }
686
 
687
- // -------- Flèches seules : DÉPLACEMENT REPÈRE CAMÉRA --------
688
- var moveForward = (up ? 1 : 0) - (dn ? 1 : 0);
689
- var moveRight = (rt ? 1 : 0) - (lt ? 1 : 0);
690
- if (moveForward === 0 && moveRight === 0) return;
691
-
692
- var fwd = this.entity.forward.clone();
693
- var right = this.entity.right.clone();
694
- if (fwd.lengthSq() > 1e-8) fwd.normalize();
695
- if (right.lengthSq() > 1e-8) right.normalize();
696
-
697
- var camDist = Math.max(0.1, this.orbitCamera._distance);
698
- var speedF = this.forwardSpeed * camDist;
699
- var speedR = this.strafeSpeed * camDist;
700
-
701
- var delta = new pc.Vec3();
702
- if (moveForward !== 0) delta.add(fwd.mulScalar(moveForward * speedF * dt));
703
- if (moveRight !== 0) delta.add(right.mulScalar(moveRight * speedR * dt));
704
-
705
- if (delta.lengthSq() > 0) {
706
- // Respect minY (camY_new = camY_cur + delta.y)
707
- var currCamY = this.orbitCamera.worldCameraYForPivot(this.orbitCamera._pivotPoint);
708
- var minY = this.orbitCamera.minY;
709
- if (currCamY + delta.y < minY) {
710
- delta.y = minY - currCamY;
711
- }
712
- this.orbitCamera._pivotPoint.add(delta);
713
  }
714
  };
 
1
  // ctrl_camera_pr_env.js
2
  // ============================================================================
3
+ // FREE CAMERA (sans orbite) pour PlayCanvas
4
+ // - Souris : rotation sur place (comme Shift+flèches auparavant)
5
+ // - ZQSD / Flèches : déplacement local (avant/arrière & strafe)
6
+ // - Molette : dolly (avance/recul le long du regard)
7
+ // - Bounding Box + minY : contraintes sur la POSITION caméra uniquement
8
+ // - AUCUNE logique d’orbite (pas de pivot, pas de distance d’orbite)
9
  // ============================================================================
10
 
11
+ var FreeCamera = pc.createScript('orbitCamera'); // garder le nom public "orbitCamera" pour compat viewer
12
 
13
+ // ======================== Attributs ===========================
14
+ FreeCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.12, title: 'Inertia (rotation)' });
15
 
16
+ // Limites de pitch (conseil: -89..89 si vue libre)
17
+ FreeCamera.attributes.add('pitchAngleMin', { type: 'number', default: -89, title: 'Pitch Min (deg)' });
18
+ FreeCamera.attributes.add('pitchAngleMax', { type: 'number', default: 89, title: 'Pitch Max (deg)' });
19
 
20
+ // minY (altitude min du point CAMÉRA)
21
+ FreeCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum camera Y' });
 
22
 
23
+ // Vitesse de déplacement (m/s)
24
+ FreeCamera.attributes.add('moveSpeed', { type: 'number', default: 2.0, title: 'Move Speed' });
25
+ FreeCamera.attributes.add('strafeSpeed', { type: 'number', default: 2.0, title: 'Strafe Speed' });
26
+ FreeCamera.attributes.add('dollySpeed', { type: 'number', default: 2.0, title: 'Mouse Wheel Dolly Speed' });
27
 
28
+ // Bounding Box (active si Xmin<Xmax etc.)
29
+ FreeCamera.attributes.add('Xmin', { type: 'number', default: -Infinity, title: 'BBox Xmin' });
30
+ FreeCamera.attributes.add('Xmax', { type: 'number', default: Infinity, title: 'BBox Xmax' });
31
+ FreeCamera.attributes.add('Ymin', { type: 'number', default: -Infinity, title: 'BBox Ymin' });
32
+ FreeCamera.attributes.add('Ymax', { type: 'number', default: Infinity, title: 'BBox Ymax' });
33
+ FreeCamera.attributes.add('Zmin', { type: 'number', default: -Infinity, title: 'BBox Zmin' });
34
+ FreeCamera.attributes.add('Zmax', { type: 'number', default: Infinity, title: 'BBox Zmax' });
35
 
36
+ // Compat (ignorés mais laissés pour ne pas casser le viewer existant)
37
+ FreeCamera.attributes.add('focusEntity', { type: 'entity', title: 'Compat: Focus Entity (unused)' });
38
+ FreeCamera.attributes.add('frameOnStart', { type: 'boolean', default: false, title: 'Compat: Frame on Start (unused)' });
39
+ FreeCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Compat: Yaw Min (unused)' });
40
+ FreeCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Compat: Yaw Max (unused)' });
41
+ FreeCamera.attributes.add('distanceMin', { type: 'number', default: 0.1, title: 'Compat: Distance Min (unused)' });
 
42
 
43
+ // ======================== Initialisation ===========================
44
+ Object.defineProperty(FreeCamera.prototype, 'pitch', {
 
 
 
 
 
 
 
 
 
 
45
  get: function () { return this._targetPitch; },
46
+ set: function (v) { this._targetPitch = pc.math.clamp(v, this.pitchAngleMin, this.pitchAngleMax); }
47
  });
48
+ Object.defineProperty(FreeCamera.prototype, 'yaw', {
 
49
  get: function () { return this._targetYaw; },
50
+ set: function (v) { this._targetYaw = v; } // yaw libre (pas de clamp)
 
 
 
 
 
 
 
 
51
  });
52
 
53
+ FreeCamera.prototype.initialize = function () {
54
+ // angles init depuis l'orientation actuelle
55
+ var q = this.entity.getRotation();
56
+ var f = new pc.Vec3();
57
+ q.transformVector(pc.Vec3.FORWARD, f);
58
+
59
+ // Convention PlayCanvas: setLocalEulerAngles(pitch, yaw, 0)
60
+ this._yaw = Math.atan2(f.x, f.z) * pc.math.RAD_TO_DEG;
61
+ // retirer le yaw pour extraire un pitch cohérent
62
+ var yawQuat = new pc.Quat().setFromEulerAngles(0, -this._yaw, 0);
63
+ var noYawQ = new pc.Quat().mul2(yawQuat, q);
64
+ var fNoYaw = new pc.Vec3();
65
+ noYawQ.transformVector(pc.Vec3.FORWARD, fNoYaw);
66
+ this._pitch = Math.atan2(-fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
67
+ this._pitch = pc.math.clamp(this._pitch, this.pitchAngleMin, this.pitchAngleMax);
68
+
69
+ this._targetYaw = this._yaw;
70
+ this._targetPitch = this._pitch;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ // Appliquer orientation immédiatement
73
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
 
74
 
75
+ // Etat input (partagé par scripts d’input)
76
+ this.app.systems.script.app.freeCamState = this.app.systems.script.app.freeCamState || {};
77
+ this.state = this.app.systems.script.app.freeCamState; // pointeur commun
78
 
79
+ // S’assure que la position courante respecte minY + bbox
80
+ var p = this.entity.getPosition().clone();
81
+ this._clampPosition(p);
82
+ this.entity.setPosition(p);
83
 
84
+ // Aspect ratio (comme avant)
 
 
 
 
 
 
 
 
 
 
 
 
85
  var self = this;
86
+ this._onResize = function(){ self._checkAspectRatio(); };
87
+ window.addEventListener('resize', this._onResize, false);
88
  this._checkAspectRatio();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  };
90
 
91
+ FreeCamera.prototype.update = function (dt) {
92
+ // Rotation inertielle
93
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
94
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
95
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
 
 
 
 
 
 
 
96
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
97
 
98
+ // Déplacements clavier (gérés dans Keyboard, mais on reclampe chaque frame)
99
+ var pos = this.entity.getPosition().clone();
100
+ this._clampPosition(pos);
101
+ this.entity.setPosition(pos);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  };
103
 
104
+ FreeCamera.prototype._checkAspectRatio = function () {
105
+ var gd = this.app.graphicsDevice;
106
+ if (!gd) return;
107
+ this.entity.camera.horizontalFov = (gd.height > gd.width);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  };
109
 
110
+ // ======================== Contraintes ===========================
111
+ FreeCamera.prototype._bboxEnabled = function () {
112
  return (this.Xmin < this.Xmax) && (this.Ymin < this.Ymax) && (this.Zmin < this.Zmax);
113
  };
114
 
115
+ FreeCamera.prototype._clampPosition = function (p) {
116
+ // minY prioritaire
117
+ if (p.y < this.minY) p.y = this.minY;
118
+ if (!this._bboxEnabled()) return;
 
 
 
 
 
 
 
119
 
120
+ p.x = pc.math.clamp(p.x, this.Xmin, this.Xmax);
121
+ p.y = pc.math.clamp(p.y, Math.max(this.Ymin, this.minY), this.Ymax);
122
+ p.z = pc.math.clamp(p.z, this.Zmin, this.Zmax);
123
+ };
124
 
125
+ // ===================== INPUT SOURIS (rotation + molette dolly) =====================
126
+ var FreeCameraInputMouse = pc.createScript('orbitCameraInputMouse'); // garder le nom
127
+ FreeCameraInputMouse.attributes.add('lookSensitivity', { type: 'number', default: 0.3, title: 'Look Sensitivity' });
128
+ FreeCameraInputMouse.attributes.add('wheelSensitivity',{ type: 'number', default: 1.0, title: 'Wheel Sensitivity' });
129
 
130
+ FreeCameraInputMouse.prototype.initialize = function () {
131
+ this.freeCam = this.entity.script.orbitCamera; // instance FreeCamera
132
+ this.last = new pc.Vec2();
133
+ this.isLooking = false;
134
 
135
+ if (this.app.mouse) {
136
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
137
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
138
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
139
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
140
+ this.app.mouse.disableContextMenu();
141
+ }
142
+ var self = this;
143
+ this._onOut = function(){ self.isLooking = false; };
144
+ window.addEventListener('mouseout', this._onOut, false);
145
 
146
+ this.on('destroy', () => {
147
+ if (this.app.mouse) {
148
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
149
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
150
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
151
  this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
+ window.removeEventListener('mouseout', this._onOut, false);
154
+ });
155
  };
156
 
157
+ FreeCameraInputMouse.prototype.onMouseDown = function (e) {
158
+ // Bouton gauche/milieu/droit -> tous = "look"
159
+ this.isLooking = true;
160
+ this.last.set(e.x, e.y);
 
 
161
  };
162
 
163
+ FreeCameraInputMouse.prototype.onMouseUp = function () {
164
+ this.isLooking = false;
 
 
 
 
165
  };
166
 
167
+ FreeCameraInputMouse.prototype.onMouseMove = function (e) {
168
+ if (!this.isLooking || !this.freeCam) return;
169
+ var sens = this.lookSensitivity;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ // Souris = même logique que "Shift + flèches" (rotation sur place)
172
+ var deltaYaw = (e.dx) * sens; // droite => +dx => on veut TURN RIGHT (CW) => yaw diminue
173
+ var deltaPitch = (e.dy) * sens; // haut => dy<0 => look up => pitch augmente => pitch -= deltaPitch
174
 
175
+ this.freeCam.yaw = this.freeCam.yaw - deltaYaw; // yaw libre
176
+ this.freeCam.pitch = this.freeCam.pitch - deltaPitch; // pitch clampé
177
 
178
+ this.last.set(e.x, e.y);
 
 
 
 
 
 
179
  };
180
 
181
+ FreeCameraInputMouse.prototype.onMouseWheel = function (e) {
182
+ if (!this.freeCam) return;
183
+
184
+ // Dolly : avancer/reculer dans l'axe du regard
185
+ var cam = this.entity;
186
+ var move = -e.wheelDelta * this.wheelSensitivity * this.freeCam.dollySpeed * 0.05; // échelle douce
187
+ var forward = cam.forward.clone().normalize().mulScalar(move);
188
+
189
+ var pos = cam.getPosition().clone().add(forward);
190
+ this.freeCam._clampPosition(pos);
191
+ cam.setPosition(pos);
192
+
193
+ e.event.preventDefault();
194
  };
195
 
196
+ // ===================== INPUT TOUCH (optionnel : look + pinch dolly simple) =====================
197
+ var FreeCameraInputTouch = pc.createScript('orbitCameraInputTouch');
198
+ FreeCameraInputTouch.attributes.add('lookSensitivity', { type: 'number', default: 0.5, title: 'Look Sensitivity' });
199
+ FreeCameraInputTouch.attributes.add('pinchDollyFactor', { type: 'number', default: 0.02, title: 'Pinch Dolly Factor' });
200
 
201
+ FreeCameraInputTouch.prototype.initialize = function () {
202
+ this.freeCam = this.entity.script.orbitCamera;
203
+ this.last = new pc.Vec2();
204
+ this.isLooking = false;
205
+ this.lastPinch = 0;
206
 
207
+ if (this.app.touch) {
208
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
209
  this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
210
  this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
211
  this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
212
+ }
213
 
214
+ this.on('destroy', () => {
215
+ if (this.app.touch) {
216
  this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
217
  this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
218
  this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
219
  this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
220
+ }
221
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  };
223
 
224
+ FreeCameraInputTouch.prototype.onTouchStartEndCancel = function (e) {
225
+ var t = e.touches;
226
+ if (t.length === 1) {
227
+ this.isLooking = (e.event.type === 'touchstart');
228
+ this.last.set(t[0].x, t[0].y);
229
+ } else if (t.length === 2) {
230
+ var dx = t[0].x - t[1].x;
231
+ var dy = t[0].y - t[1].y;
232
+ this.lastPinch = Math.sqrt(dx*dx + dy*dy);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  } else {
234
+ this.isLooking = false;
 
 
 
 
 
235
  }
236
  };
237
 
238
+ FreeCameraInputTouch.prototype.onTouchMove = function (e) {
239
+ var t = e.touches;
240
+ if (!this.freeCam) return;
241
+
242
+ if (t.length === 1 && this.isLooking) {
243
+ var sens = this.lookSensitivity;
244
+ var dx = t[0].x - this.last.x;
245
+ var dy = t[0].y - this.last.y;
246
+
247
+ this.freeCam.yaw = this.freeCam.yaw - dx * sens;
248
+ this.freeCam.pitch = this.freeCam.pitch - dy * sens;
249
+
250
+ this.last.set(t[0].x, t[0].y);
251
+ } else if (t.length === 2) {
252
+ // Pinch dolly
253
+ var dx = t[0].x - t[1].x;
254
+ var dy = t[0].y - t[1].y;
255
+ var dist = Math.sqrt(dx*dx + dy*dy);
256
+ var delta = dist - this.lastPinch;
257
+ this.lastPinch = dist;
258
+
259
+ var cam = this.entity;
260
+ var forward = cam.forward.clone().normalize().mulScalar(delta * this.pinchDollyFactor * this.freeCam.dollySpeed);
261
+ var pos = cam.getPosition().clone().add(forward);
262
+ this.freeCam._clampPosition(pos);
263
+ cam.setPosition(pos);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
  };
266
 
267
+ // ===================== INPUT CLAVIER (ZQSD + flèches) =====================
268
+ var FreeCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ FreeCameraInputKeyboard.attributes.add('acceleration', { type: 'number', default: 1.0, title: 'Accel (unused, future)' });
 
 
 
 
271
 
272
+ FreeCameraInputKeyboard.prototype.initialize = function () {
273
+ this.freeCam = this.entity.script.orbitCamera;
274
+ this.kb = this.app.keyboard || null;
275
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ FreeCameraInputKeyboard.prototype.update = function (dt) {
278
+ if (!this.freeCam || !this.kb) return;
 
 
 
 
279
 
280
+ // Déplacements : flèches OU ZQSD (AZERTY)
281
+ var fwd = (this.kb.isPressed(pc.KEY_UP) || this.kb.isPressed(pc.KEY_Z)) ? 1 :
282
+ (this.kb.isPressed(pc.KEY_DOWN) || this.kb.isPressed(pc.KEY_S)) ? -1 : 0;
 
283
 
284
+ var strf = (this.kb.isPressed(pc.KEY_RIGHT) || this.kb.isPressed(pc.KEY_D)) ? 1 :
285
+ (this.kb.isPressed(pc.KEY_LEFT) || this.kb.isPressed(pc.KEY_Q)) ? -1 : 0;
286
 
287
+ if (fwd !== 0 || strf !== 0) {
288
+ var cam = this.entity;
289
+ var pos = cam.getPosition().clone();
 
 
290
 
291
+ var forward = cam.forward.clone(); if (forward.lengthSq() > 1e-8) forward.normalize();
292
+ var right = cam.right.clone(); if (right.lengthSq() > 1e-8) right.normalize();
 
 
 
293
 
294
+ pos.add(forward.mulScalar(fwd * this.freeCam.moveSpeed * dt));
295
+ pos.add(right .mulScalar(strf * this.freeCam.strafeSpeed * dt));
 
296
 
297
+ this.freeCam._clampPosition(pos);
298
+ cam.setPosition(pos);
299
  }
300
 
301
+ // Rotation au clavier (Shift + flèches) conservée
302
+ var shift = this.kb.isPressed(pc.KEY_SHIFT);
303
+ if (shift) {
304
+ var yawDir = (this.kb.isPressed(pc.KEY_LEFT) ? 1 : 0) - (this.kb.isPressed(pc.KEY_RIGHT) ? 1 : 0); // ← CCW / → CW
305
+ var pitchDir = (this.kb.isPressed(pc.KEY_UP) ? 1 : 0) - (this.kb.isPressed(pc.KEY_DOWN) ? 1 : 0); // ↑ up / ↓ down
306
+
307
+ // Même logique que la souris : rotation sur place
308
+ var yawSpeed = 120; // deg/s
309
+ var pitchSpeed = 90; // deg/s
310
+ if (yawDir !== 0) this.freeCam.yaw = this.freeCam.yaw + yawDir * yawSpeed * dt;
311
+ if (pitchDir !== 0) this.freeCam.pitch = this.freeCam.pitch + pitchDir * pitchSpeed * dt;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  }
313
  };