MikaFil commited on
Commit
d6869e7
·
verified ·
1 Parent(s): 88e8e5e

Update deplacement_dans_env/ctrl_camera_pr_env.js

Browse files
deplacement_dans_env/ctrl_camera_pr_env.js CHANGED
@@ -1,19 +1,21 @@
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
 
@@ -25,7 +27,11 @@ FreeCamera.attributes.add('moveSpeed', { type: 'number', default: 2.0, title:
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,8 +39,8 @@ FreeCamera.attributes.add('Ymax', { type: 'number', default: Infinity, title: '
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)' });
@@ -56,9 +62,7 @@ FreeCamera.prototype.initialize = function () {
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,20 +76,27 @@ FreeCamera.prototype.initialize = function () {
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) {
@@ -95,8 +106,10 @@ FreeCamera.prototype.update = function (dt) {
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,6 +120,35 @@ FreeCamera.prototype._checkAspectRatio = function () {
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);
@@ -122,6 +164,73 @@ FreeCamera.prototype._clampPosition = function (p) {
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,7 +264,6 @@ FreeCameraInputMouse.prototype.initialize = function () {
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
  };
@@ -168,12 +276,11 @@ 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
  };
@@ -187,13 +294,17 @@ FreeCameraInputMouse.prototype.onMouseWheel = function (e) {
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' });
@@ -259,7 +370,11 @@ FreeCameraInputTouch.prototype.onTouchMove = function (e) {
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
  };
@@ -294,17 +409,19 @@ FreeCameraInputKeyboard.prototype.update = function (dt) {
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;
 
1
  // ctrl_camera_pr_env.js
2
  // ============================================================================
3
+ // FREE CAMERA + COLLISION STATIQUE (type FPS léger) pour PlayCanvas
4
+ // - Souris : rotation sur place
5
  // - ZQSD / Flèches : déplacement local (avant/arrière & strafe)
6
  // - Molette : dolly (avance/recul le long du regard)
7
+ // - "focusEntity" sert de RACINE de collision (typiquement : ton envEntity GLB)
8
+ // - Collision sphère (caméra) vs AABBs des meshInstances statiques du GLB
9
+ // - minY + Bounding Box globale (Xmin..Zmax) restent actives (post-résolution)
10
+ // - Pas de physique Ammo.js requise
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
 
 
27
  FreeCamera.attributes.add('strafeSpeed', { type: 'number', default: 2.0, title: 'Strafe Speed' });
28
  FreeCamera.attributes.add('dollySpeed', { type: 'number', default: 2.0, title: 'Mouse Wheel Dolly Speed' });
29
 
30
+ // Collision (caméra sphère) rayon et marge numérique
31
+ FreeCamera.attributes.add('collisionRadius', { type: 'number', default: 0.25, title: 'Camera Sphere Radius' });
32
+ FreeCamera.attributes.add('collisionEpsilon', { type: 'number', default: 0.0005, title: 'Collision Epsilon' });
33
+
34
+ // Bounding Box globale (active si Xmin<Xmax etc.)
35
  FreeCamera.attributes.add('Xmin', { type: 'number', default: -Infinity, title: 'BBox Xmin' });
36
  FreeCamera.attributes.add('Xmax', { type: 'number', default: Infinity, title: 'BBox Xmax' });
37
  FreeCamera.attributes.add('Ymin', { type: 'number', default: -Infinity, title: 'BBox Ymin' });
 
39
  FreeCamera.attributes.add('Zmin', { type: 'number', default: -Infinity, title: 'BBox Zmin' });
40
  FreeCamera.attributes.add('Zmax', { type: 'number', default: Infinity, title: 'BBox Zmax' });
41
 
42
+ // Compat (certaines non utilisées, gardées pour le viewer)
43
+ FreeCamera.attributes.add('focusEntity', { type: 'entity', title: 'Collision Root (ENV GLB)' });
44
  FreeCamera.attributes.add('frameOnStart', { type: 'boolean', default: false, title: 'Compat: Frame on Start (unused)' });
45
  FreeCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Compat: Yaw Min (unused)' });
46
  FreeCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Compat: Yaw Max (unused)' });
 
62
  var f = new pc.Vec3();
63
  q.transformVector(pc.Vec3.FORWARD, f);
64
 
 
65
  this._yaw = Math.atan2(f.x, f.z) * pc.math.RAD_TO_DEG;
 
66
  var yawQuat = new pc.Quat().setFromEulerAngles(0, -this._yaw, 0);
67
  var noYawQ = new pc.Quat().mul2(yawQuat, q);
68
  var fNoYaw = new pc.Vec3();
 
76
  // Appliquer orientation immédiatement
77
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
78
 
79
+ // Etat input partagé (conservé)
80
  this.app.systems.script.app.freeCamState = this.app.systems.script.app.freeCamState || {};
81
+ this.state = this.app.systems.script.app.freeCamState;
 
 
 
 
 
82
 
83
+ // Contrôles de fenêtre
84
  var self = this;
85
  this._onResize = function(){ self._checkAspectRatio(); };
86
  window.addEventListener('resize', this._onResize, false);
87
  this._checkAspectRatio();
88
+
89
+ // ====== COLLISIONS: construire la liste d'AABBs statiques à partir du focusEntity (ENV GLB) ======
90
+ this._colliders = []; // tableau d'objets { aabb: pc.BoundingBox }
91
+ if (this.focusEntity) {
92
+ this._buildStaticCollidersFromRoot(this.focusEntity);
93
+ }
94
+
95
+ // S’assure que la position courante respecte minY + bbox + collision (si déjà dans un mur)
96
+ var p = this.entity.getPosition().clone();
97
+ p = this._resolveCollisions(p, /*maxIters*/ 4);
98
+ this._clampPosition(p);
99
+ this.entity.setPosition(p);
100
  };
101
 
102
  FreeCamera.prototype.update = function (dt) {
 
106
  this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
107
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
108
 
109
+ // Reclamp soft chaque frame (utile si minY / bbox)
110
  var pos = this.entity.getPosition().clone();
111
+ // En cas de dérive numérique, resserrer contre les colliders
112
+ pos = this._resolveCollisions(pos, /*maxIters*/ 1);
113
  this._clampPosition(pos);
114
  this.entity.setPosition(pos);
115
  };
 
120
  this.entity.camera.horizontalFov = (gd.height > gd.width);
121
  };
122
 
123
+ // ======================== Gestion des colliders (AABBs) ===========================
124
+ FreeCamera.prototype._buildStaticCollidersFromRoot = function (root) {
125
+ var stack = [root];
126
+ var tempAabb = new pc.BoundingBox();
127
+ while (stack.length) {
128
+ var e = stack.pop();
129
+
130
+ // RenderComponent => récupérer les AABBs des meshInstances
131
+ var rc = e.render;
132
+ if (rc && rc.meshInstances && rc.meshInstances.length) {
133
+ for (var i = 0; i < rc.meshInstances.length; i++) {
134
+ var mi = rc.meshInstances[i];
135
+ // s'assurer que l'AABB world est à jour (PlayCanvas le met à jour à la phase de culling,
136
+ // on le recalcule une fois via "getWorldTransform" si besoin)
137
+ var aabb = mi.aabb;
138
+ // Copier l'AABB (pour le figer) : center & halfExtents
139
+ var aabbCopy = new pc.BoundingBox(aabb.center.clone(), aabb.halfExtents.clone());
140
+ this._colliders.push({ aabb: aabbCopy });
141
+ }
142
+ }
143
+
144
+ // Parcours enfants
145
+ var ch = e.children;
146
+ if (ch && ch.length) {
147
+ for (var c = 0; c < ch.length; c++) stack.push(ch[c]);
148
+ }
149
+ }
150
+ };
151
+
152
  // ======================== Contraintes ===========================
153
  FreeCamera.prototype._bboxEnabled = function () {
154
  return (this.Xmin < this.Xmax) && (this.Ymin < this.Ymax) && (this.Zmin < this.Zmax);
 
164
  p.z = pc.math.clamp(p.z, this.Zmin, this.Zmax);
165
  };
166
 
167
+ // ======================== Résolution collisions sphère vs AABB ====================
168
+ // Renvoie une position corrigée en repoussant la sphère de la caméra hors des AABBs.
169
+ // Simple "projection" itérative (plusieurs itérations pour empiler les corrections).
170
+ FreeCamera.prototype._resolveCollisions = function (desiredPos, maxIters) {
171
+ var pos = desiredPos.clone();
172
+ var R = Math.max(0, this.collisionRadius);
173
+ var eps = Math.max(1e-7, this.collisionEpsilon);
174
+
175
+ if (!this._colliders || this._colliders.length === 0 || R === 0) {
176
+ return pos;
177
+ }
178
+
179
+ for (var iter = 0; iter < (maxIters || 1); iter++) {
180
+ var moved = false;
181
+
182
+ for (var i = 0; i < this._colliders.length; i++) {
183
+ var aabb = this._colliders[i].aabb;
184
+
185
+ // Trouver le point le plus proche sur l'AABB par rapport au centre de la sphère
186
+ var min = aabb.getMin();
187
+ var max = aabb.getMax();
188
+
189
+ var cx = pc.math.clamp(pos.x, min.x, max.x);
190
+ var cy = pc.math.clamp(pos.y, min.y, max.y);
191
+ var cz = pc.math.clamp(pos.z, min.z, max.z);
192
+
193
+ var dx = pos.x - cx;
194
+ var dy = pos.y - cy;
195
+ var dz = pos.z - cz;
196
+
197
+ var distSq = dx*dx + dy*dy + dz*dz;
198
+
199
+ if (distSq < (R*R - eps)) {
200
+ // Collision : repousser la sphère en dehors
201
+ var dist = Math.sqrt(Math.max(distSq, 1e-12));
202
+ var pen = R - dist;
203
+
204
+ if (dist > 1e-6) {
205
+ // direction = depuis le point le plus proche vers le centre
206
+ pos.x += (dx / dist) * (pen + eps);
207
+ pos.y += (dy / dist) * (pen + eps);
208
+ pos.z += (dz / dist) * (pen + eps);
209
+ moved = true;
210
+ } else {
211
+ // sphère centre exactement sur une arête/coin : pousser le long de l'axe de moindre pénétration
212
+ var ex = Math.min(Math.abs(pos.x - min.x), Math.abs(max.x - pos.x));
213
+ var ey = Math.min(Math.abs(pos.y - min.y), Math.abs(max.y - pos.y));
214
+ var ez = Math.min(Math.abs(pos.z - min.z), Math.abs(max.z - pos.z));
215
+
216
+ if (ex <= ey && ex <= ez) {
217
+ pos.x += (pos.x - aabb.center.x >= 0 ? 1 : -1) * (pen + eps);
218
+ } else if (ey <= ex && ey <= ez) {
219
+ pos.y += (pos.y - aabb.center.y >= 0 ? 1 : -1) * (pen + eps);
220
+ } else {
221
+ pos.z += (pos.z - aabb.center.z >= 0 ? 1 : -1) * (pen + eps);
222
+ }
223
+ moved = true;
224
+ }
225
+ }
226
+ }
227
+
228
+ if (!moved) break;
229
+ }
230
+
231
+ return pos;
232
+ };
233
+
234
  // ===================== INPUT SOURIS (rotation + molette dolly) =====================
235
  var FreeCameraInputMouse = pc.createScript('orbitCameraInputMouse'); // garder le nom
236
  FreeCameraInputMouse.attributes.add('lookSensitivity', { type: 'number', default: 0.3, title: 'Look Sensitivity' });
 
264
  };
265
 
266
  FreeCameraInputMouse.prototype.onMouseDown = function (e) {
 
267
  this.isLooking = true;
268
  this.last.set(e.x, e.y);
269
  };
 
276
  if (!this.isLooking || !this.freeCam) return;
277
  var sens = this.lookSensitivity;
278
 
279
+ var deltaYaw = (e.dx) * sens;
280
+ var deltaPitch = (e.dy) * sens;
 
281
 
282
+ this.freeCam.yaw = this.freeCam.yaw - deltaYaw;
283
+ this.freeCam.pitch = this.freeCam.pitch - deltaPitch;
284
 
285
  this.last.set(e.x, e.y);
286
  };
 
294
  var forward = cam.forward.clone().normalize().mulScalar(move);
295
 
296
  var pos = cam.getPosition().clone().add(forward);
297
+
298
+ // ===== collision + contraintes =====
299
+ pos = this.freeCam._resolveCollisions(pos, /*maxIters*/ 3);
300
  this.freeCam._clampPosition(pos);
301
+
302
  cam.setPosition(pos);
303
 
304
  e.event.preventDefault();
305
  };
306
 
307
+ // ===================== INPUT TOUCH (look + pinch dolly) =====================
308
  var FreeCameraInputTouch = pc.createScript('orbitCameraInputTouch');
309
  FreeCameraInputTouch.attributes.add('lookSensitivity', { type: 'number', default: 0.5, title: 'Look Sensitivity' });
310
  FreeCameraInputTouch.attributes.add('pinchDollyFactor', { type: 'number', default: 0.02, title: 'Pinch Dolly Factor' });
 
370
  var cam = this.entity;
371
  var forward = cam.forward.clone().normalize().mulScalar(delta * this.pinchDollyFactor * this.freeCam.dollySpeed);
372
  var pos = cam.getPosition().clone().add(forward);
373
+
374
+ // ===== collision + contraintes =====
375
+ pos = this.freeCam._resolveCollisions(pos, /*maxIters*/ 3);
376
  this.freeCam._clampPosition(pos);
377
+
378
  cam.setPosition(pos);
379
  }
380
  };
 
409
  pos.add(forward.mulScalar(fwd * this.freeCam.moveSpeed * dt));
410
  pos.add(right .mulScalar(strf * this.freeCam.strafeSpeed * dt));
411
 
412
+ // ===== collision + contraintes =====
413
+ pos = this.freeCam._resolveCollisions(pos, /*maxIters*/ 3);
414
  this.freeCam._clampPosition(pos);
415
+
416
  cam.setPosition(pos);
417
  }
418
 
419
+ // Rotation au clavier (Shift + flèches)
420
  var shift = this.kb.isPressed(pc.KEY_SHIFT);
421
  if (shift) {
422
  var yawDir = (this.kb.isPressed(pc.KEY_LEFT) ? 1 : 0) - (this.kb.isPressed(pc.KEY_RIGHT) ? 1 : 0); // ← CCW / → CW
423
  var pitchDir = (this.kb.isPressed(pc.KEY_UP) ? 1 : 0) - (this.kb.isPressed(pc.KEY_DOWN) ? 1 : 0); // ↑ up / ↓ down
424
 
 
425
  var yawSpeed = 120; // deg/s
426
  var pitchSpeed = 90; // deg/s
427
  if (yawDir !== 0) this.freeCam.yaw = this.freeCam.yaw + yawDir * yawSpeed * dt;