MikaFil commited on
Commit
e4957a0
·
verified ·
1 Parent(s): d7f7910

Update deplacement_dans_env/ctrl_camera_pr_env.js

Browse files
deplacement_dans_env/ctrl_camera_pr_env.js CHANGED
@@ -1,21 +1,19 @@
1
  // ctrl_camera_pr_env.js
2
  // ============================================================================
3
- // FREE CAMERA + COLLISION ROBUSTE (type FPS léger) pour PlayCanvas (sans Ammo)
4
- // - Souris : rotation (look around)
5
  // - ZQSD / Flèches : déplacement local (avant/arrière & strafe)
6
- // - Molette / Pinch : dolly (avance/recul le long du regard)
7
- // - Collisions : sphère (caméra) vs AABBs "épaissies & fusionnées" du GLB (focusEntity)
8
- // - Déplacements "swept" (sous-division en pas) pour éviter le tunneling
9
- // - minY + BBox globale optionnelle (Xmin..Zmax) restent actives
10
- // - Conserve les noms publics "orbitCamera*" pour compatibilité avec l'existant
11
  // ============================================================================
12
 
13
- var FreeCamera = pc.createScript('orbitCamera'); // garder le nom public "orbitCamera"
14
 
15
  // ======================== Attributs ===========================
16
  FreeCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.12, title: 'Inertia (rotation)' });
17
 
18
- // Limites de pitch
19
  FreeCamera.attributes.add('pitchAngleMin', { type: 'number', default: -89, title: 'Pitch Min (deg)' });
20
  FreeCamera.attributes.add('pitchAngleMax', { type: 'number', default: 89, title: 'Pitch Max (deg)' });
21
 
@@ -23,23 +21,11 @@ FreeCamera.attributes.add('pitchAngleMax', { type: 'number', default: 89, title
23
  FreeCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum camera Y' });
24
 
25
  // Vitesse de déplacement (m/s)
26
- FreeCamera.attributes.add('moveSpeed', { type: 'number', default: 2.2, title: 'Move Speed' });
27
- FreeCamera.attributes.add('strafeSpeed', { type: 'number', default: 2.2, title: 'Strafe Speed' });
28
- FreeCamera.attributes.add('dollySpeed', { type: 'number', default: 2.0, title: 'Mouse/Pinch Dolly Speed' });
29
 
30
- // Collision (caméra sphère)
31
- FreeCamera.attributes.add('collisionRadius', { type: 'number', default: 0.30, title: 'Camera Sphere Radius' });
32
- FreeCamera.attributes.add('collisionEpsilon', { type: 'number', default: 0.001, title: 'Collision Epsilon' });
33
-
34
- // Sous-division du déplacement (swept)
35
- FreeCamera.attributes.add('maxStepDistance', { type: 'number', default: 0.20, title: 'Max step distance (swept move)' });
36
- FreeCamera.attributes.add('maxResolveIters', { type: 'number', default: 6, title: 'Max resolve iterations per step' });
37
-
38
- // Construction des colliders
39
- FreeCamera.attributes.add('mergeGap', { type: 'number', default: 0.03, title: 'Merge AABBs gap tolerance (m)' });
40
- FreeCamera.attributes.add('inflateBias',{ type: 'number', default: 0.02, title: 'Extra inflate beyond radius (m)' });
41
-
42
- // Bounding Box globale (active si Xmin<Xmax etc.)
43
  FreeCamera.attributes.add('Xmin', { type: 'number', default: -Infinity, title: 'BBox Xmin' });
44
  FreeCamera.attributes.add('Xmax', { type: 'number', default: Infinity, title: 'BBox Xmax' });
45
  FreeCamera.attributes.add('Ymin', { type: 'number', default: -Infinity, title: 'BBox Ymin' });
@@ -47,12 +33,12 @@ FreeCamera.attributes.add('Ymax', { type: 'number', default: Infinity, title: '
47
  FreeCamera.attributes.add('Zmin', { type: 'number', default: -Infinity, title: 'BBox Zmin' });
48
  FreeCamera.attributes.add('Zmax', { type: 'number', default: Infinity, title: 'BBox Zmax' });
49
 
50
- // Compat (certaines non utilisées, gardées pour le viewer)
51
- FreeCamera.attributes.add('focusEntity', { type: 'entity', title: 'Collision Root (ENV GLB)' });
52
  FreeCamera.attributes.add('frameOnStart', { type: 'boolean', default: false, title: 'Compat: Frame on Start (unused)' });
53
  FreeCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Compat: Yaw Min (unused)' });
54
  FreeCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Compat: Yaw Max (unused)' });
55
- FreeCamera.attributes.add('distanceMin', { type: 'number', default: 0.1, title: 'Compat: Distance Min (unused)' });
56
 
57
  // ======================== Initialisation ===========================
58
  Object.defineProperty(FreeCamera.prototype, 'pitch', {
@@ -70,7 +56,9 @@ FreeCamera.prototype.initialize = function () {
70
  var f = new pc.Vec3();
71
  q.transformVector(pc.Vec3.FORWARD, f);
72
 
 
73
  this._yaw = Math.atan2(f.x, f.z) * pc.math.RAD_TO_DEG;
 
74
  var yawQuat = new pc.Quat().setFromEulerAngles(0, -this._yaw, 0);
75
  var noYawQ = new pc.Quat().mul2(yawQuat, q);
76
  var fNoYaw = new pc.Vec3();
@@ -84,16 +72,12 @@ FreeCamera.prototype.initialize = function () {
84
  // Appliquer orientation immédiatement
85
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
86
 
87
- // Etat input partagé
88
  this.app.systems.script.app.freeCamState = this.app.systems.script.app.freeCamState || {};
89
- this.state = this.app.systems.script.app.freeCamState;
90
 
91
- // Construire colliders robustes depuis le GLB (focusEntity)
92
- this._buildRobustColliders();
93
-
94
- // S’assure que la position courante respecte collisions + minY + bbox
95
  var p = this.entity.getPosition().clone();
96
- p = this._moveSweptTo(p, p); // passe par le résolveur même si delta nul
97
  this._clampPosition(p);
98
  this.entity.setPosition(p);
99
 
@@ -111,9 +95,8 @@ FreeCamera.prototype.update = function (dt) {
111
  this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
112
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
113
 
114
- // Recadrage léger
115
  var pos = this.entity.getPosition().clone();
116
- pos = this._resolveCollisions(pos, this.maxResolveIters);
117
  this._clampPosition(pos);
118
  this.entity.setPosition(pos);
119
  };
@@ -124,118 +107,7 @@ FreeCamera.prototype._checkAspectRatio = function () {
124
  this.entity.camera.horizontalFov = (gd.height > gd.width);
125
  };
126
 
127
- // ======================== Colliders robustes ===========================
128
- FreeCamera.prototype._buildRobustColliders = function () {
129
- this._colliders = []; // { aabb: BoundingBox }
130
- this._worldAabb = null; // BoundingBox globale du décor (utile debug / clamp)
131
-
132
- if (!this.focusEntity) return;
133
-
134
- // 1) Récupère toutes les AABBs world des meshInstances du GLB
135
- var tmp = [];
136
- var stack = [this.focusEntity];
137
- while (stack.length) {
138
- var e = stack.pop();
139
- var rc = e.render;
140
- if (rc && rc.meshInstances && rc.meshInstances.length) {
141
- for (var i = 0; i < rc.meshInstances.length; i++) {
142
- var aabb = rc.meshInstances[i].aabb;
143
- // clone
144
- tmp.push(new pc.BoundingBox(aabb.center.clone(), aabb.halfExtents.clone()));
145
- }
146
- }
147
- var ch = e.children;
148
- if (ch && ch.length) for (var c = 0; c < ch.length; c++) stack.push(ch[c]);
149
- }
150
-
151
- if (tmp.length === 0) return;
152
-
153
- // 2) Gonfle chaque AABB du rayon de la caméra + biais
154
- var inflate = Math.max(0, this.collisionRadius) + Math.max(0, this.inflateBias);
155
- for (var k = 0; k < tmp.length; k++) {
156
- var bb = tmp[k];
157
- bb.halfExtents.add(new pc.Vec3(inflate, inflate, inflate));
158
- }
159
-
160
- // 3) Fusionne les AABBs qui se touchent/quasi-se touchent (mergeGap)
161
- var merged = this._mergeAabbs(tmp, this.mergeGap);
162
-
163
- // 4) Enregistre
164
- for (var m = 0; m < merged.length; m++) {
165
- this._colliders.push({ aabb: merged[m] });
166
- if (!this._worldAabb) {
167
- this._worldAabb = new pc.BoundingBox(merged[m].center.clone(), merged[m].halfExtents.clone());
168
- } else {
169
- this._worldAabb.add(merged[m]);
170
- }
171
- }
172
- };
173
-
174
- // Fusion simple O(n^2) jusqu’à stabilisation
175
- FreeCamera.prototype._mergeAabbs = function (boxes, gap) {
176
- var out = boxes.slice();
177
- var changed = true;
178
- var g = Math.max(0, gap || 0);
179
-
180
- function overlapsOrTouches(a, b, tol) {
181
- var amin = a.getMin(), amax = a.getMax();
182
- var bmin = b.getMin(), bmax = b.getMax();
183
- return !(
184
- amax.x < bmin.x - tol || amin.x > bmax.x + tol ||
185
- amax.y < bmin.y - tol || amin.y > bmax.y + tol ||
186
- amax.z < bmin.z - tol || amin.z > bmax.z + tol
187
- );
188
- }
189
-
190
- while (changed) {
191
- changed = false;
192
- var next = [];
193
- var used = new Array(out.length).fill(false);
194
-
195
- for (var i = 0; i < out.length; i++) {
196
- if (used[i]) continue;
197
- var acc = out[i];
198
- var mergedAny = false;
199
-
200
- for (var j = i + 1; j < out.length; j++) {
201
- if (used[j]) continue;
202
- if (overlapsOrTouches(acc, out[j], g)) {
203
- // fusion : créer une nouvelle AABB couvrant acc et out[j]
204
- var accMin = acc.getMin(), accMax = acc.getMax();
205
- var bMin = out[j].getMin(), bMax = out[j].getMax();
206
-
207
- var nMin = new pc.Vec3(
208
- Math.min(accMin.x, bMin.x),
209
- Math.min(accMin.y, bMin.y),
210
- Math.min(accMin.z, bMin.z)
211
- );
212
- var nMax = new pc.Vec3(
213
- Math.max(accMax.x, bMax.x),
214
- Math.max(accMax.y, bMax.y),
215
- Math.max(accMax.z, bMax.z)
216
- );
217
-
218
- var nCenter = nMin.clone().add(nMax).mulScalar(0.5);
219
- var nHalf = nMax.clone().sub(nCenter).abs();
220
- acc = new pc.BoundingBox(nCenter, nHalf);
221
-
222
- used[j] = true;
223
- mergedAny = true;
224
- changed = true;
225
- }
226
- }
227
-
228
- used[i] = true;
229
- next.push(acc);
230
- }
231
-
232
- out = next;
233
- }
234
-
235
- return out;
236
- };
237
-
238
- // ======================== Contraintes génériques ===========================
239
  FreeCamera.prototype._bboxEnabled = function () {
240
  return (this.Xmin < this.Xmax) && (this.Ymin < this.Ymax) && (this.Zmin < this.Zmax);
241
  };
@@ -250,116 +122,6 @@ FreeCamera.prototype._clampPosition = function (p) {
250
  p.z = pc.math.clamp(p.z, this.Zmin, this.Zmax);
251
  };
252
 
253
- // ======================== Mouvement swept + résolution =====================
254
- // Déplace "de -> to" par petits pas pour éviter le tunneling.
255
- FreeCamera.prototype._moveSweptTo = function (from, to) {
256
- var maxStep = Math.max(0.01, this.maxStepDistance || 0.2);
257
- var delta = to.clone().sub(from);
258
- var dist = delta.length();
259
-
260
- if (dist <= maxStep) {
261
- var p = from.clone().add(delta);
262
- p = this._resolveCollisions(p, this.maxResolveIters);
263
- return p;
264
- }
265
-
266
- var steps = Math.ceil(dist / maxStep);
267
- var stepVec = delta.divScalar(steps);
268
- var cur = from.clone();
269
-
270
- for (var i = 0; i < steps; i++) {
271
- cur.add(stepVec);
272
- cur = this._resolveCollisions(cur, this.maxResolveIters);
273
- }
274
- return cur;
275
- };
276
-
277
- // Résolution itérative sphère (caméra) vs AABBs "épaissies"
278
- FreeCamera.prototype._resolveCollisions = function (pos, maxIters) {
279
- var p = pos.clone();
280
- var eps = Math.max(1e-7, this.collisionEpsilon);
281
-
282
- if (!this._colliders || this._colliders.length === 0) return p;
283
-
284
- var iters = Math.max(1, maxIters || 1);
285
- for (var iter = 0; iter < iters; iter++) {
286
- var moved = false;
287
-
288
- for (var i = 0; i < this._colliders.length; i++) {
289
- var aabb = this._colliders[i].aabb;
290
-
291
- // Point le plus proche de p sur l'AABB
292
- var min = aabb.getMin();
293
- var max = aabb.getMax();
294
-
295
- var cx = pc.math.clamp(p.x, min.x, max.x);
296
- var cy = pc.math.clamp(p.y, min.y, max.y);
297
- var cz = pc.math.clamp(p.z, min.z, max.z);
298
-
299
- var dx = p.x - cx, dy = p.y - cy, dz = p.z - cz;
300
- var distSq = dx*dx + dy*dy + dz*dz;
301
-
302
- // Comme les AABBs sont déjà "épaissies" du rayon, ici on traite une sphère ~ de rayon ~0
303
- // => il suffit de sortir du volume (point dans AABB élargie)
304
- if (distSq < eps*eps && // p quasi dedans et très proche
305
- (p.x > min.x - eps && p.x < max.x + eps &&
306
- p.y > min.y - eps && p.y < max.y + eps &&
307
- p.z > min.z - eps && p.z < max.z + eps)) {
308
-
309
- // Choisir l'axe de moindre déplacement pour sortir
310
- var ex = Math.min(Math.abs(p.x - min.x), Math.abs(max.x - p.x));
311
- var ey = Math.min(Math.abs(p.y - min.y), Math.abs(max.y - p.y));
312
- var ez = Math.min(Math.abs(p.z - min.z), Math.abs(max.z - p.z));
313
-
314
- if (ex <= ey && ex <= ez) {
315
- // pousser sur X
316
- if (Math.abs(p.x - min.x) < Math.abs(max.x - p.x)) p.x = min.x - eps; else p.x = max.x + eps;
317
- } else if (ey <= ex && ey <= ez) {
318
- // pousser sur Y (gère bord/nez de marche)
319
- if (Math.abs(p.y - min.y) < Math.abs(max.y - p.y)) p.y = Math.max(this.minY, min.y - eps); else p.y = max.y + eps;
320
- } else {
321
- // pousser sur Z
322
- if (Math.abs(p.z - min.z) < Math.abs(max.z - p.z)) p.z = min.z - eps; else p.z = max.z + eps;
323
- }
324
- moved = true;
325
- continue;
326
- }
327
-
328
- // Cas général : p dans l'AABB "épaissie" (ou très proche) -> projeter vers l'extérieur
329
- var inside =
330
- (p.x > min.x - eps && p.x < max.x + eps &&
331
- p.y > min.y - eps && p.y < max.y + eps &&
332
- p.z > min.z - eps && p.z < max.z + eps);
333
-
334
- if (inside) {
335
- var dxMin = Math.abs(p.x - min.x), dxMax = Math.abs(max.x - p.x);
336
- var dyMin = Math.abs(p.y - min.y), dyMax = Math.abs(max.y - p.y);
337
- var dzMin = Math.abs(p.z - min.z), dzMax = Math.abs(max.z - p.z);
338
-
339
- var ax = Math.min(dxMin, dxMax);
340
- var ay = Math.min(dyMin, dyMax);
341
- var az = Math.min(dzMin, dzMax);
342
-
343
- if (ax <= ay && ax <= az) {
344
- // sortir par X
345
- if (dxMin < dxMax) p.x = min.x - eps; else p.x = max.x + eps;
346
- } else if (ay <= ax && ay <= az) {
347
- // sortir par Y
348
- if (dyMin < dyMax) p.y = Math.max(this.minY, min.y - eps); else p.y = max.y + eps;
349
- } else {
350
- // sortir par Z
351
- if (dzMin < dzMax) p.z = min.z - eps; else p.z = max.z + eps;
352
- }
353
- moved = true;
354
- }
355
- }
356
-
357
- if (!moved) break;
358
- }
359
-
360
- return p;
361
- };
362
-
363
  // ===================== INPUT SOURIS (rotation + molette dolly) =====================
364
  var FreeCameraInputMouse = pc.createScript('orbitCameraInputMouse'); // garder le nom
365
  FreeCameraInputMouse.attributes.add('lookSensitivity', { type: 'number', default: 0.3, title: 'Look Sensitivity' });
@@ -393,38 +155,45 @@ FreeCameraInputMouse.prototype.initialize = function () {
393
  };
394
 
395
  FreeCameraInputMouse.prototype.onMouseDown = function (e) {
 
396
  this.isLooking = true;
397
  this.last.set(e.x, e.y);
398
  };
399
 
400
- FreeCameraInputMouse.prototype.onMouseUp = function () { this.isLooking = false; };
 
 
401
 
402
  FreeCameraInputMouse.prototype.onMouseMove = function (e) {
403
  if (!this.isLooking || !this.freeCam) return;
404
  var sens = this.lookSensitivity;
405
- this.freeCam.yaw = this.freeCam.yaw - e.dx * sens;
406
- this.freeCam.pitch = this.freeCam.pitch - e.dy * sens;
 
 
 
 
 
 
407
  this.last.set(e.x, e.y);
408
  };
409
 
410
  FreeCameraInputMouse.prototype.onMouseWheel = function (e) {
411
  if (!this.freeCam) return;
412
 
 
413
  var cam = this.entity;
414
- var move = -e.wheelDelta * this.wheelSensitivity * this.freeCam.dollySpeed * 0.05;
415
- var forward = cam.forward.clone(); if (forward.lengthSq() > 1e-8) forward.normalize();
416
 
417
- var from = cam.getPosition().clone();
418
- var to = from.clone().add(forward.mulScalar(move));
419
-
420
- var next = this.freeCam._moveSweptTo(from, to);
421
- this.freeCam._clampPosition(next);
422
- cam.setPosition(next);
423
 
424
  e.event.preventDefault();
425
  };
426
 
427
- // ===================== INPUT TOUCH (look + pinch dolly) =====================
428
  var FreeCameraInputTouch = pc.createScript('orbitCameraInputTouch');
429
  FreeCameraInputTouch.attributes.add('lookSensitivity', { type: 'number', default: 0.5, title: 'Look Sensitivity' });
430
  FreeCameraInputTouch.attributes.add('pinchDollyFactor', { type: 'number', default: 0.02, title: 'Pinch Dolly Factor' });
@@ -488,13 +257,10 @@ FreeCameraInputTouch.prototype.onTouchMove = function (e) {
488
  this.lastPinch = dist;
489
 
490
  var cam = this.entity;
491
- var forward = cam.forward.clone(); if (forward.lengthSq() > 1e-8) forward.normalize();
492
- var from = cam.getPosition().clone();
493
- var to = from.clone().add(forward.mulScalar(delta * this.pinchDollyFactor * this.freeCam.dollySpeed));
494
-
495
- var next = this.freeCam._moveSweptTo(from, to);
496
- this.freeCam._clampPosition(next);
497
- cam.setPosition(next);
498
  }
499
  };
500
 
@@ -520,27 +286,25 @@ FreeCameraInputKeyboard.prototype.update = function (dt) {
520
 
521
  if (fwd !== 0 || strf !== 0) {
522
  var cam = this.entity;
523
- var from = cam.getPosition().clone();
524
 
525
  var forward = cam.forward.clone(); if (forward.lengthSq() > 1e-8) forward.normalize();
526
  var right = cam.right.clone(); if (right.lengthSq() > 1e-8) right.normalize();
527
 
528
- var delta = new pc.Vec3();
529
- delta.add(forward.mulScalar(fwd * this.freeCam.moveSpeed * dt));
530
- delta.add(right .mulScalar(strf * this.freeCam.strafeSpeed * dt));
531
 
532
- var to = from.clone().add(delta);
533
- var next = this.freeCam._moveSweptTo(from, to);
534
- this.freeCam._clampPosition(next);
535
- cam.setPosition(next);
536
  }
537
 
538
- // Rotation au clavier (Shift + flèches)
539
  var shift = this.kb.isPressed(pc.KEY_SHIFT);
540
  if (shift) {
541
  var yawDir = (this.kb.isPressed(pc.KEY_LEFT) ? 1 : 0) - (this.kb.isPressed(pc.KEY_RIGHT) ? 1 : 0); // ← CCW / → CW
542
  var pitchDir = (this.kb.isPressed(pc.KEY_UP) ? 1 : 0) - (this.kb.isPressed(pc.KEY_DOWN) ? 1 : 0); // ↑ up / ↓ down
543
 
 
544
  var yawSpeed = 120; // deg/s
545
  var pitchSpeed = 90; // deg/s
546
  if (yawDir !== 0) this.freeCam.yaw = this.freeCam.yaw + yawDir * yawSpeed * dt;
 
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
 
 
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' });
 
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', {
 
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();
 
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
 
 
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
  };
 
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
  };
 
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' });
 
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' });
 
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
 
 
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;