MikaFil commited on
Commit
709960b
·
verified ·
1 Parent(s): 8f24226

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +85 -306
orbit-camera.js CHANGED
@@ -1,205 +1,63 @@
1
- // orbit-camera.js (fixed to clamp world Y at minY, with debug logs)
2
-
3
- ///////////////////////////////////////////////////////////////////////////////
4
- // Orbit Camera Script //
5
- ///////////////////////////////////////////////////////////////////////////////
6
 
 
7
  var OrbitCamera = pc.createScript('orbitCamera');
8
 
9
- OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max', description: 'Setting this at 0 will give an infinite distance limit' });
10
  OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
11
  OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
12
- // Note: This default will be overridden by your JSON config. In your JSON you set minAngle to -90.
13
- OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
14
  OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
15
  OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
16
- OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y', description: 'Minimum Y value for the camera during orbiting or translation' });
17
 
18
- OrbitCamera.attributes.add('inertiaFactor', {
19
- type: 'number',
20
- default: 0.2,
21
- title: 'Inertia Factor',
22
- description: 'Higher value means that the camera will continue moving after the user has stopped dragging. 0 is fully responsive.'
23
- });
24
-
25
- OrbitCamera.attributes.add('focusEntity', {
26
- type: 'entity',
27
- title: 'Focus Entity',
28
- description: 'Entity for the camera to focus on. If blank, then the camera will use the whole scene'
29
- });
30
 
31
- OrbitCamera.attributes.add('frameOnStart', {
32
- type: 'boolean',
33
- default: true,
34
- title: 'Frame on Start',
35
- description: 'Frames the entity or scene at the start of the application."'
36
- });
37
-
38
- // Property to get and set the distance between the pivot point and camera
39
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
40
- get: function () {
41
- return this._targetDistance;
42
- },
43
- set: function (value) {
44
- this._targetDistance = this._clampDistance(value);
45
- }
46
- });
47
-
48
- // Property to get and set the camera orthoHeight (clamped above 0)
49
- Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
50
- get: function () {
51
- return this.entity.camera.orthoHeight;
52
- },
53
- set: function (value) {
54
- this.entity.camera.orthoHeight = Math.max(0, value);
55
- }
56
  });
57
-
58
- // Property to get and set the pitch (in degrees) of the camera around the pivot.
59
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
60
- get: function () {
61
- return this._targetPitch;
62
- },
63
- set: function (value) {
64
- this._targetPitch = this._clampPitchAngle(value);
65
- }
66
  });
67
-
68
- // Property to get and set the yaw (in degrees) of the camera around the pivot.
69
  Object.defineProperty(OrbitCamera.prototype, 'yaw', {
70
- get: function () {
71
- return this._targetYaw;
72
- },
73
- set: function (value) {
74
- this._targetYaw = this._clampYawAngle(value);
75
- }
76
  });
77
-
78
- // Property to get and set the world position of the pivot point that the camera orbits around.
79
  Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
80
- get: function () {
81
- return this._pivotPoint;
82
- },
83
- set: function (value) {
84
- this._pivotPoint.copy(value);
85
- }
86
  });
87
 
88
- // Moves the camera to look at an entity and all its children so they are all in view.
89
- OrbitCamera.prototype.focus = function (focusEntity) {
90
- this._buildAabb(focusEntity);
91
- var halfExtents = this._modelsAabb.halfExtents;
92
- var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
93
- this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
94
- this._removeInertia();
95
- this._pivotPoint.copy(this._modelsAabb.center);
96
- };
97
-
98
- OrbitCamera.distanceBetween = new pc.Vec3();
99
-
100
- OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
101
- this.pivotPoint.copy(lookAtPoint);
102
- this.entity.setPosition(resetPoint);
103
- this.entity.lookAt(lookAtPoint);
104
- var distance = OrbitCamera.distanceBetween;
105
- distance.sub2(lookAtPoint, resetPoint);
106
- this.distance = distance.length();
107
- this.pivotPoint.copy(lookAtPoint);
108
- var cameraQuat = this.entity.getRotation();
109
- this.yaw = this._calcYaw(cameraQuat);
110
- this.pitch = this._calcPitch(cameraQuat, this.yaw);
111
- this._removeInertia();
112
- this._updatePosition();
113
  };
114
-
115
- OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
116
- this._buildAabb(entity);
117
- this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
118
  };
119
-
120
- OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
121
- this.pitch = pitch;
122
- this.yaw = yaw;
123
- this.distance = distance;
124
- this._removeInertia();
125
- };
126
-
127
- OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
128
- this.entity.setPosition(position);
129
- this.entity.lookAt(lookAtPoint);
130
- this._pivotPoint.copy(lookAtPoint);
131
- var distanceVec = new pc.Vec3();
132
- distanceVec.sub2(position, lookAtPoint);
133
- this._targetDistance = this._distance = distanceVec.length();
134
- var cameraQuat = this.entity.getRotation();
135
- this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
136
- this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
137
- this._removeInertia();
138
- this._updatePosition();
139
  };
140
 
141
- ////////////////////////////////////////////////////////////////////////////////
142
- // Private Methods //
143
- ////////////////////////////////////////////////////////////////////////////////
144
-
145
  OrbitCamera.prototype.initialize = function () {
146
- var self = this;
147
- var onWindowResize = function () {
148
- self._checkAspectRatio();
149
- };
150
-
151
- window.addEventListener('resize', onWindowResize, false);
152
- this._checkAspectRatio();
153
- this._modelsAabb = new pc.BoundingBox();
154
- this._buildAabb(this.focusEntity || this.app.root);
155
- this.entity.lookAt(this._modelsAabb.center);
156
  this._pivotPoint = new pc.Vec3();
157
- this._pivotPoint.copy(this._modelsAabb.center);
158
  var cameraQuat = this.entity.getRotation();
159
  this._yaw = this._calcYaw(cameraQuat);
160
  this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
161
- this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
162
- this._distance = 0;
163
  this._targetYaw = this._yaw;
164
  this._targetPitch = this._pitch;
165
-
166
- if (this.frameOnStart) {
167
- this.focus(this.focusEntity || this.app.root);
168
- } else {
169
- var distanceBetween = new pc.Vec3();
170
- distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
171
- this._distance = this._clampDistance(distanceBetween.length());
172
- }
173
-
174
  this._targetDistance = this._distance;
175
-
176
- this.on('attr:distanceMin', function (value, prev) {
177
- this._distance = this._clampDistance(this._distance);
178
- });
179
- this.on('attr:distanceMax', function (value, prev) {
180
- this._distance = this._clampDistance(this._distance);
181
- });
182
- this.on('attr:pitchAngleMin', function (value, prev) {
183
- this._pitch = this._clampPitchAngle(this._pitch);
184
- });
185
- this.on('attr:pitchAngleMax', function (value, prev) {
186
- this._pitch = this._clampPitchAngle(this._pitch);
187
- });
188
- this.on('attr:focusEntity', function (value, prev) {
189
- if (this.frameOnStart) {
190
- this.focus(value || this.app.root);
191
- } else {
192
- this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);
193
- }
194
- });
195
- this.on('attr:frameOnStart', function (value, prev) {
196
- if (value) {
197
- this.focus(this.focusEntity || this.app.root);
198
- }
199
- });
200
- this.on('destroy', function () {
201
- window.removeEventListener('resize', onWindowResize, false);
202
- });
203
  };
204
 
205
  OrbitCamera.prototype.update = function (dt) {
@@ -207,133 +65,88 @@ OrbitCamera.prototype.update = function (dt) {
207
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
208
  this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
209
  this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
210
- this._updatePosition();
211
- };
212
 
213
- OrbitCamera.prototype._updatePosition = function () {
214
- this.entity.setLocalPosition(0, 0, 0);
215
- this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
216
- var position = this.entity.getPosition();
217
- position.copy(this.entity.forward);
218
- position.mulScalar(-this._distance);
219
- position.add(this.pivotPoint);
220
- // Clamp camera's Y position so it never goes below minY (after all rotations)
221
- position.y = Math.max(position.y, this.minY);
222
- this.entity.setPosition(position);
223
- };
224
-
225
- OrbitCamera.prototype._removeInertia = function () {
226
- this._yaw = this._targetYaw;
227
- this._pitch = this._targetPitch;
228
- this._distance = this._targetDistance;
229
- };
230
-
231
- OrbitCamera.prototype._checkAspectRatio = function () {
232
- var height = this.app.graphicsDevice.height;
233
- var width = this.app.graphicsDevice.width;
234
- this.entity.camera.horizontalFov = (height > width);
235
- };
236
-
237
- OrbitCamera.prototype._buildAabb = function (entity) {
238
- var i, m, meshInstances = [];
239
- var renders = entity.findComponents('render');
240
- for (i = 0; i < renders.length; i++) {
241
- var render = renders[i];
242
- for (m = 0; m < render.meshInstances.length; m++) {
243
- meshInstances.push(render.meshInstances[m]);
244
- }
245
- }
246
- var models = entity.findComponents('model');
247
- for (i = 0; i < models.length; i++) {
248
- var model = models[i];
249
- for (m = 0; m < model.meshInstances.length; m++) {
250
- meshInstances.push(model.meshInstances[m]);
251
- }
252
- }
253
- var gsplats = entity.findComponents('gsplat');
254
- for (i = 0; i < gsplats.length; i++) {
255
- var gsplat = gsplats[i];
256
- var instance = gsplat.instance;
257
- if (instance?.meshInstance) {
258
- meshInstances.push(instance.meshInstance);
259
  }
260
- }
261
- for (i = 0; i < meshInstances.length; i++) {
262
- if (i === 0) {
263
- this._modelsAabb.copy(meshInstances[i].aabb);
264
- } else {
265
- this._modelsAabb.add(meshInstances[i].aabb);
266
- }
267
- }
268
- };
269
 
270
- OrbitCamera.prototype._calcYaw = function (quat) {
271
- var transformedForward = new pc.Vec3();
272
- quat.transformVector(pc.Vec3.FORWARD, transformedForward);
273
- return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
274
- };
275
 
276
- OrbitCamera.prototype._clampDistance = function (distance) {
277
- if (this.distanceMax > 0) {
278
- return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
279
  }
280
- return Math.max(distance, this.distanceMin);
281
- };
282
 
283
- OrbitCamera.prototype._clampPitchAngle = function (pitch) {
284
- return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
285
  };
286
 
287
- OrbitCamera.prototype._clampYawAngle = function (yaw) {
288
- return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
 
 
289
  };
290
-
291
- OrbitCamera.quatWithoutYaw = new pc.Quat();
292
- OrbitCamera.yawOffset = new pc.Quat();
293
-
294
  OrbitCamera.prototype._calcPitch = function (quat, yaw) {
295
- var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
296
- var yawOffset = OrbitCamera.yawOffset;
297
  yawOffset.setFromEulerAngles(0, -yaw, 0);
298
- quatWithoutYaw.mul2(yawOffset, quat);
299
- var transformedForward = new pc.Vec3();
300
- quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
301
- return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
 
302
  };
303
 
304
- ////////////////////////////////////////////////////////////////////////////////
305
- // Orbit Camera Mouse Input Script //
306
- ////////////////////////////////////////////////////////////////////////////////
307
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
308
 
309
  OrbitCameraInputMouse.attributes.add('orbitSensitivity', {
310
  type: 'number',
311
  default: 0.3,
312
- title: 'Orbit Sensitivity',
313
- description: 'How fast the camera moves around the orbit. Higher is faster'
314
  });
315
  OrbitCameraInputMouse.attributes.add('distanceSensitivity', {
316
  type: 'number',
317
  default: 0.4,
318
- title: 'Distance Sensitivity',
319
- description: 'How fast the camera moves in and out. Higher is faster'
320
  });
 
321
  OrbitCameraInputMouse.prototype.initialize = function () {
322
  this.orbitCamera = this.entity.script.orbitCamera;
323
  if (this.orbitCamera) {
324
- var self = this;
325
- var onMouseOut = function (e) { self.onMouseOut(e); };
326
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
327
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
328
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
329
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
330
- window.addEventListener('mouseout', onMouseOut, false);
331
  this.on('destroy', function () {
332
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
333
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
334
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
335
  this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
336
- window.removeEventListener('mouseout', onMouseOut, false);
337
  });
338
  }
339
  this.app.mouse.disableContextMenu();
@@ -354,11 +167,6 @@ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
354
  camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
355
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
356
  this.orbitCamera.pivotPoint.add(worldDiff);
357
- var pitchRadians = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
358
- var minPivotY = this.orbitCamera.distance * Math.sin(pitchRadians);
359
- if (this.orbitCamera.pivotPoint.y < minPivotY) {
360
- this.orbitCamera.pivotPoint.y = minPivotY;
361
- }
362
  };
363
  OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
364
  switch (event.button) {
@@ -370,6 +178,7 @@ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
370
  this.lookButtonDown = true;
371
  break;
372
  }
 
373
  };
374
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
375
  switch (event.button) {
@@ -382,8 +191,6 @@ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
382
  break;
383
  }
384
  };
385
-
386
- // ---------- PATCHED: orbit suppression at minY (mouse) ---------------
387
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
388
  if (this.lookButtonDown) {
389
  var sens = this.orbitSensitivity;
@@ -395,14 +202,14 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
395
  var currDist = this.orbitCamera.distance;
396
  var currPivot = this.orbitCamera.pivotPoint.clone();
397
 
398
- // Compute the world position before the change
399
  var camQuat = new pc.Quat();
400
  camQuat.setFromEulerAngles(currPitch, currYaw, 0);
401
  var forward = new pc.Vec3();
402
  camQuat.transformVector(pc.Vec3.FORWARD, forward);
403
  var preY = currPivot.y + (-forward.y) * currDist;
404
 
405
- // Compute the world position after applying the vertical delta
406
  var proposedPitch = currPitch - deltaPitch;
407
  var testQuat = new pc.Quat();
408
  testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
@@ -411,27 +218,21 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
411
  var proposedY = currPivot.y + (-testForward.y) * currDist;
412
 
413
  var minY = this.orbitCamera.minY;
414
- var isAtMinY = preY <= minY + 1e-4;
415
  var wouldGoBelow = proposedY < minY - 1e-4;
416
- var suppressed = false;
417
 
418
- // Only suppress if going *further* below minY (upward)
419
  if (wouldGoBelow && (proposedY < preY)) {
420
- // Suppress vertical, allow horizontal (yaw)
421
  this.orbitCamera.yaw = currYaw - deltaYaw;
422
- suppressed = true;
423
  } else {
424
  this.orbitCamera.pitch = proposedPitch;
425
  this.orbitCamera.yaw = currYaw - deltaYaw;
426
  }
427
-
428
  } else if (this.panButtonDown) {
429
  this.pan(new pc.Vec2(event.x, event.y));
430
  }
431
  this.lastPoint.set(event.x, event.y);
432
  };
433
- // ---------------------------------------------------------------------
434
-
435
  OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
436
  if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
437
  this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
@@ -440,27 +241,19 @@ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
440
  }
441
  event.event.preventDefault();
442
  };
443
- OrbitCameraInputMouse.prototype.onMouseOut = function (event) {
444
- this.lookButtonDown = false;
445
- this.panButtonDown = false;
446
- };
447
 
448
- ////////////////////////////////////////////////////////////////////////////////
449
- // Orbit Camera Touch Input Script //
450
- ////////////////////////////////////////////////////////////////////////////////
451
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
452
 
453
  OrbitCameraInputTouch.attributes.add('orbitSensitivity', {
454
  type: 'number',
455
  default: 0.6,
456
- title: 'Orbit Sensitivity',
457
- description: 'How fast the camera moves around the orbit. Higher is faster'
458
  });
459
  OrbitCameraInputTouch.attributes.add('distanceSensitivity', {
460
  type: 'number',
461
  default: 0.5,
462
- title: 'Distance Sensitivity',
463
- description: 'How fast the camera moves in and out. Higher is faster'
464
  });
465
  OrbitCameraInputTouch.prototype.initialize = function () {
466
  this.orbitCamera = this.entity.script.orbitCamera;
@@ -513,15 +306,9 @@ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
513
  camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
514
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
515
  this.orbitCamera.pivotPoint.add(worldDiff);
516
- var pitchRadians = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
517
- var minPivotY = this.orbitCamera.distance * Math.sin(pitchRadians);
518
- if (this.orbitCamera.pivotPoint.y < minPivotY) {
519
- this.orbitCamera.pivotPoint.y = minPivotY;
520
- }
521
  };
522
  OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
523
 
524
- // ---------- PATCHED: orbit suppression at minY (touch) ---------------
525
  OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
526
  var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
527
  var touches = event.touches;
@@ -536,14 +323,12 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
536
  var currDist = this.orbitCamera.distance;
537
  var currPivot = this.orbitCamera.pivotPoint.clone();
538
 
539
- // Compute the world position before the change
540
  var camQuat = new pc.Quat();
541
  camQuat.setFromEulerAngles(currPitch, currYaw, 0);
542
  var forward = new pc.Vec3();
543
  camQuat.transformVector(pc.Vec3.FORWARD, forward);
544
  var preY = currPivot.y + (-forward.y) * currDist;
545
 
546
- // Compute the world position after applying the vertical delta
547
  var proposedPitch = currPitch - deltaPitch;
548
  var testQuat = new pc.Quat();
549
  testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
@@ -552,19 +337,14 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
552
  var proposedY = currPivot.y + (-testForward.y) * currDist;
553
 
554
  var minY = this.orbitCamera.minY;
555
- var isAtMinY = preY <= minY + 1e-4;
556
  var wouldGoBelow = proposedY < minY - 1e-4;
557
- var suppressed = false;
558
 
559
  if (wouldGoBelow && (proposedY < preY)) {
560
- // Suppress vertical, allow horizontal (yaw)
561
  this.orbitCamera.yaw = currYaw - deltaYaw;
562
- suppressed = true;
563
  } else {
564
  this.orbitCamera.pitch = proposedPitch;
565
  this.orbitCamera.yaw = currYaw - deltaYaw;
566
  }
567
-
568
  this.lastTouchPoint.set(touch.x, touch.y);
569
  } else if (touches.length === 2) {
570
  var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
@@ -576,4 +356,3 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
576
  this.lastPinchMidPoint.copy(pinchMidPoint);
577
  }
578
  };
579
- // ---------------------------------------------------------------------
 
1
+ // orbit-camera.js
 
 
 
 
2
 
3
+ // Orbit Camera Component
4
  var OrbitCamera = pc.createScript('orbitCamera');
5
 
6
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
7
  OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
8
  OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
9
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: -90, title: 'Pitch Angle Min (degrees)' });
 
10
  OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
11
  OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
12
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y' });
13
 
14
+ OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
15
+ OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
16
+ OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
 
 
 
 
 
 
 
 
 
17
 
 
 
 
 
 
 
 
 
18
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
19
+ get: function () { return this._targetDistance; },
20
+ set: function (v) { this._targetDistance = this._clampDistance(v); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  });
 
 
22
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
23
+ get: function () { return this._targetPitch; },
24
+ set: function (v) { this._targetPitch = this._clampPitchAngle(v); }
 
 
 
 
25
  });
 
 
26
  Object.defineProperty(OrbitCamera.prototype, 'yaw', {
27
+ get: function () { return this._targetYaw; },
28
+ set: function (v) { this._targetYaw = this._clampYawAngle(v); }
 
 
 
 
29
  });
 
 
30
  Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
31
+ get: function () { return this._pivotPoint; },
32
+ set: function (v) { this._pivotPoint.copy(v); }
 
 
 
 
33
  });
34
 
35
+ OrbitCamera.prototype._clampDistance = function (d) {
36
+ if (this.distanceMax > 0) return pc.math.clamp(d, this.distanceMin, this.distanceMax);
37
+ return Math.max(d, this.distanceMin);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  };
39
+ OrbitCamera.prototype._clampPitchAngle = function (p) {
40
+ return pc.math.clamp(p, this.pitchAngleMin, this.pitchAngleMax);
 
 
41
  };
42
+ OrbitCamera.prototype._clampYawAngle = function (y) {
43
+ return pc.math.clamp(y, this.yawAngleMin, this.yawAngleMax);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  };
45
 
 
 
 
 
46
  OrbitCamera.prototype.initialize = function () {
 
 
 
 
 
 
 
 
 
 
47
  this._pivotPoint = new pc.Vec3();
48
+ this._pivotPoint.copy(this.entity.getPosition());
49
  var cameraQuat = this.entity.getRotation();
50
  this._yaw = this._calcYaw(cameraQuat);
51
  this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
 
 
52
  this._targetYaw = this._yaw;
53
  this._targetPitch = this._pitch;
54
+ var dist = new pc.Vec3().sub2(this.entity.getPosition(), this._pivotPoint).length();
55
+ this._distance = this._clampDistance(dist);
 
 
 
 
 
 
 
56
  this._targetDistance = this._distance;
57
+ this.on('attr:distanceMin', () => { this._distance = this._clampDistance(this._distance); });
58
+ this.on('attr:distanceMax', () => { this._distance = this._clampDistance(this._distance); });
59
+ this.on('attr:pitchAngleMin', () => { this._pitch = this._clampPitchAngle(this._pitch); });
60
+ this.on('attr:pitchAngleMax', () => { this._pitch = this._clampPitchAngle(this._pitch); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  };
62
 
63
  OrbitCamera.prototype.update = function (dt) {
 
65
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
66
  this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
67
  this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
 
 
68
 
69
+ // 1. Clamp pitch to pitchAngleMin/max
70
+ this._pitch = this._clampPitchAngle(this._pitch);
71
+
72
+ // 2. Compute camera position
73
+ var quat = new pc.Quat();
74
+ quat.setFromEulerAngles(this._pitch, this._yaw, 0);
75
+ var forward = new pc.Vec3();
76
+ quat.transformVector(pc.Vec3.FORWARD, forward);
77
+
78
+ var position = new pc.Vec3();
79
+ position.copy(this._pivotPoint);
80
+ position.addScaledVector(forward, -this._distance);
81
+
82
+ // 3. If Y would be below minY, clamp pitch upward to keep Y >= minY
83
+ var minY = this.minY;
84
+ if (position.y < minY) {
85
+ // Calculate max downward pitch that keeps camera.y >= minY
86
+ var dy = minY - this._pivotPoint.y;
87
+ var safePitch = 0;
88
+ if (Math.abs(this._distance) > 1e-5) {
89
+ var ratio = pc.math.clamp(dy / this._distance, -1, 1);
90
+ safePitch = -Math.asin(ratio) * pc.math.RAD_TO_DEG;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
+ safePitch = pc.math.clamp(safePitch, this.pitchAngleMin, this.pitchAngleMax);
93
+ this._pitch = safePitch;
 
 
 
 
 
 
 
94
 
95
+ // Recompute position with safe pitch
96
+ quat.setFromEulerAngles(this._pitch, this._yaw, 0);
97
+ quat.transformVector(pc.Vec3.FORWARD, forward);
98
+ position.copy(this._pivotPoint);
99
+ position.addScaledVector(forward, -this._distance);
100
 
101
+ if (position.y < minY) position.y = minY;
 
 
102
  }
 
 
103
 
104
+ this.entity.setPosition(position);
105
+ this.entity.lookAt(this._pivotPoint);
106
  };
107
 
108
+ OrbitCamera.prototype._calcYaw = function (quat) {
109
+ var v = new pc.Vec3();
110
+ quat.transformVector(pc.Vec3.FORWARD, v);
111
+ return Math.atan2(-v.x, -v.z) * pc.math.RAD_TO_DEG;
112
  };
 
 
 
 
113
  OrbitCamera.prototype._calcPitch = function (quat, yaw) {
114
+ var yawOffset = new pc.Quat();
 
115
  yawOffset.setFromEulerAngles(0, -yaw, 0);
116
+ var q = new pc.Quat();
117
+ q.mul2(yawOffset, quat);
118
+ var v = new pc.Vec3();
119
+ q.transformVector(pc.Vec3.FORWARD, v);
120
+ return Math.atan2(-v.y, -v.z) * pc.math.RAD_TO_DEG;
121
  };
122
 
123
+
124
+ // Orbit Camera Mouse Input
 
125
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
126
 
127
  OrbitCameraInputMouse.attributes.add('orbitSensitivity', {
128
  type: 'number',
129
  default: 0.3,
130
+ title: 'Orbit Sensitivity'
 
131
  });
132
  OrbitCameraInputMouse.attributes.add('distanceSensitivity', {
133
  type: 'number',
134
  default: 0.4,
135
+ title: 'Distance Sensitivity'
 
136
  });
137
+
138
  OrbitCameraInputMouse.prototype.initialize = function () {
139
  this.orbitCamera = this.entity.script.orbitCamera;
140
  if (this.orbitCamera) {
 
 
141
  this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
142
  this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
143
  this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
144
  this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
 
145
  this.on('destroy', function () {
146
  this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
147
  this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
148
  this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
149
  this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
 
150
  });
151
  }
152
  this.app.mouse.disableContextMenu();
 
167
  camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
168
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
169
  this.orbitCamera.pivotPoint.add(worldDiff);
 
 
 
 
 
170
  };
171
  OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
172
  switch (event.button) {
 
178
  this.lookButtonDown = true;
179
  break;
180
  }
181
+ this.lastPoint.set(event.x, event.y);
182
  };
183
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
184
  switch (event.button) {
 
191
  break;
192
  }
193
  };
 
 
194
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
195
  if (this.lookButtonDown) {
196
  var sens = this.orbitSensitivity;
 
202
  var currDist = this.orbitCamera.distance;
203
  var currPivot = this.orbitCamera.pivotPoint.clone();
204
 
205
+ // Compute world position before move
206
  var camQuat = new pc.Quat();
207
  camQuat.setFromEulerAngles(currPitch, currYaw, 0);
208
  var forward = new pc.Vec3();
209
  camQuat.transformVector(pc.Vec3.FORWARD, forward);
210
  var preY = currPivot.y + (-forward.y) * currDist;
211
 
212
+ // Compute world position after vertical move
213
  var proposedPitch = currPitch - deltaPitch;
214
  var testQuat = new pc.Quat();
215
  testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
 
218
  var proposedY = currPivot.y + (-testForward.y) * currDist;
219
 
220
  var minY = this.orbitCamera.minY;
 
221
  var wouldGoBelow = proposedY < minY - 1e-4;
 
222
 
223
+ // Suppress only downward movement beyond minY
224
  if (wouldGoBelow && (proposedY < preY)) {
225
+ // Only yaw allowed
226
  this.orbitCamera.yaw = currYaw - deltaYaw;
 
227
  } else {
228
  this.orbitCamera.pitch = proposedPitch;
229
  this.orbitCamera.yaw = currYaw - deltaYaw;
230
  }
 
231
  } else if (this.panButtonDown) {
232
  this.pan(new pc.Vec2(event.x, event.y));
233
  }
234
  this.lastPoint.set(event.x, event.y);
235
  };
 
 
236
  OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
237
  if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
238
  this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
 
241
  }
242
  event.event.preventDefault();
243
  };
 
 
 
 
244
 
245
+ // Orbit Camera Touch Input
 
 
246
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
247
 
248
  OrbitCameraInputTouch.attributes.add('orbitSensitivity', {
249
  type: 'number',
250
  default: 0.6,
251
+ title: 'Orbit Sensitivity'
 
252
  });
253
  OrbitCameraInputTouch.attributes.add('distanceSensitivity', {
254
  type: 'number',
255
  default: 0.5,
256
+ title: 'Distance Sensitivity'
 
257
  });
258
  OrbitCameraInputTouch.prototype.initialize = function () {
259
  this.orbitCamera = this.entity.script.orbitCamera;
 
306
  camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
307
  worldDiff.sub2(toWorldPoint, fromWorldPoint);
308
  this.orbitCamera.pivotPoint.add(worldDiff);
 
 
 
 
 
309
  };
310
  OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
311
 
 
312
  OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
313
  var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
314
  var touches = event.touches;
 
323
  var currDist = this.orbitCamera.distance;
324
  var currPivot = this.orbitCamera.pivotPoint.clone();
325
 
 
326
  var camQuat = new pc.Quat();
327
  camQuat.setFromEulerAngles(currPitch, currYaw, 0);
328
  var forward = new pc.Vec3();
329
  camQuat.transformVector(pc.Vec3.FORWARD, forward);
330
  var preY = currPivot.y + (-forward.y) * currDist;
331
 
 
332
  var proposedPitch = currPitch - deltaPitch;
333
  var testQuat = new pc.Quat();
334
  testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
 
337
  var proposedY = currPivot.y + (-testForward.y) * currDist;
338
 
339
  var minY = this.orbitCamera.minY;
 
340
  var wouldGoBelow = proposedY < minY - 1e-4;
 
341
 
342
  if (wouldGoBelow && (proposedY < preY)) {
 
343
  this.orbitCamera.yaw = currYaw - deltaYaw;
 
344
  } else {
345
  this.orbitCamera.pitch = proposedPitch;
346
  this.orbitCamera.yaw = currYaw - deltaYaw;
347
  }
 
348
  this.lastTouchPoint.set(touch.x, touch.y);
349
  } else if (touches.length === 2) {
350
  var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
 
356
  this.lastPinchMidPoint.copy(pinchMidPoint);
357
  }
358
  };