MikaFil commited on
Commit
39d2d34
·
verified ·
1 Parent(s): 909063b

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +416 -235
orbit-camera.js CHANGED
@@ -1,60 +1,176 @@
1
- // orbit-camera.js
2
  ///////////////////////////////////////////////////////////////////////////////
3
- // Orbit Camera + Input //
4
  ////////////////////////////////////////////////////////////////////////////////
5
 
6
- // -- OrbitCamera Script --
7
  var OrbitCamera = pc.createScript('orbitCamera');
8
 
9
- OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
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' });
12
- OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min' });
13
- OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max' });
14
- OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min' });
15
- OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y' });
16
- OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
17
- OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
18
- OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
 
 
 
20
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
21
- get: function () { return this._targetDistance; },
22
- set: function (v) { this._targetDistance = this._clampDistance(v); }
 
 
 
 
23
  });
 
 
24
  Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
25
- get: function () { return this.entity.camera.orthoHeight; },
26
- set: function (v) { this.entity.camera.orthoHeight = Math.max(0, v); }
 
 
 
 
27
  });
 
 
 
 
 
 
28
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
29
- get: function () { return this._targetPitch; },
30
- set: function (v) { this._targetPitch = this._clampPitchAngle(v); }
 
 
 
 
31
  });
 
 
 
32
  Object.defineProperty(OrbitCamera.prototype, 'yaw', {
33
- get: function () { return this._targetYaw; },
34
- set: function (v) { this._targetYaw = this._clampYawAngle(v); }
 
 
 
 
35
  });
 
 
 
36
  Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
37
- get: function () { return this._pivotPoint; },
38
- set: function (v) { this._pivotPoint.copy(v); }
 
 
 
 
39
  });
40
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  OrbitCamera.distanceBetween = new pc.Vec3();
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  OrbitCamera.prototype.initialize = function () {
44
  var self = this;
45
- window.addEventListener('resize', function () { self._checkAspectRatio(); }, false);
46
- this._checkAspectRatio();
 
47
 
 
 
48
  this._modelsAabb = new pc.BoundingBox();
49
  this._buildAabb(this.focusEntity || this.app.root);
50
  this.entity.lookAt(this._modelsAabb.center);
51
- this._pivotPoint = this._modelsAabb.center.clone();
52
-
53
- var cq = this.entity.getRotation();
54
- this._yaw = this._calcYaw(cq);
55
- this._pitch = this._clampPitchAngle(this._calcPitch(cq, this._yaw));
 
56
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
57
-
58
  this._distance = 0;
59
  this._targetYaw = this._yaw;
60
  this._targetPitch = this._pitch;
@@ -62,282 +178,347 @@ OrbitCamera.prototype.initialize = function () {
62
  if (this.frameOnStart) {
63
  this.focus(this.focusEntity || this.app.root);
64
  } else {
65
- var dv = new pc.Vec3();
66
- dv.sub2(this.entity.getPosition(), this._pivotPoint);
67
- this._distance = this._clampDistance(dv.length());
68
  }
 
69
  this._targetDistance = this._distance;
70
 
71
- this.on('attr:distanceMin', () => this._distance = this._clampDistance(this._distance));
72
- this.on('attr:distanceMax', () => this._distance = this._clampDistance(this._distance));
73
- this.on('attr:pitchAngleMin', () => this._pitch = this._clampPitchAngle(this._pitch));
74
- this.on('attr:pitchAngleMax', () => this._pitch = this._clampPitchAngle(this._pitch));
75
- this.on('attr:focusEntity', (v) => {
76
- if (this.frameOnStart) this.focus(v || this.app.root);
77
- else this.resetAndLookAtEntity(this.entity.getPosition(), v || this.app.root);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  });
79
- this.on('attr:frameOnStart', (v) => { if (v) this.focus(this.focusEntity || this.app.root); });
80
- this.on('destroy', function () { window.removeEventListener('resize', self._checkAspectRatio); });
81
  };
82
 
83
  OrbitCamera.prototype.update = function (dt) {
84
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
85
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
86
- this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
87
- this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
88
- this._updatePosition();
89
- };
90
-
91
- OrbitCamera.prototype.focus = function (entity) {
92
- this._buildAabb(entity);
93
- var half = this._modelsAabb.halfExtents;
94
- var radius = Math.max(half.x, Math.max(half.y, half.z));
95
- this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
96
- this._removeInertia();
97
- this._pivotPoint.copy(this._modelsAabb.center);
98
- };
99
-
100
- OrbitCamera.prototype.resetAndLookAtPoint = function (rp, lp) {
101
- this.pivotPoint = lp.clone();
102
- this.entity.setPosition(rp);
103
- this.entity.lookAt(lp);
104
- var d = OrbitCamera.distanceBetween;
105
- d.sub2(lp, rp);
106
- this.distance = d.length();
107
- this.pivotPoint = lp.clone();
108
- var q = this.entity.getRotation();
109
- this.yaw = this._calcYaw(q);
110
- this.pitch = this._calcPitch(q, this.yaw);
111
- this._removeInertia();
112
- this._updatePosition();
113
- };
114
-
115
- OrbitCamera.prototype.resetAndLookAtEntity = function (rp, entity) {
116
- this._buildAabb(entity);
117
- this.resetAndLookAtPoint(rp, this._modelsAabb.center.clone());
118
- };
119
-
120
- OrbitCamera.prototype.reset = function (y, p, d) {
121
- this.pitch = p;
122
- this.yaw = y;
123
- this.distance = d;
124
- this._removeInertia();
125
- this._updatePosition();
126
- };
127
-
128
- OrbitCamera.prototype.resetToPosition = function (pos, lookAt) {
129
- this.entity.setPosition(pos);
130
- this.entity.lookAt(lookAt);
131
- this._pivotPoint.copy(lookAt);
132
- var dv = new pc.Vec3();
133
- dv.sub2(pos, lookAt);
134
- this._targetDistance = this._distance = dv.length();
135
- var cq = this.entity.getRotation();
136
- this._targetYaw = this._yaw = this._calcYaw(cq);
137
- this._targetPitch = this._pitch = this._calcPitch(cq, this._yaw);
138
- this._removeInertia();
139
  this._updatePosition();
140
  };
141
 
142
  OrbitCamera.prototype._updatePosition = function () {
143
- this.entity.setLocalPosition(0,0,0);
144
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
145
- var pos = this.entity.getPosition().copy(this.entity.forward).mulScalar(-this._distance).add(this._pivotPoint);
146
- pos.y = Math.max(pos.y, this.minY);
147
- this.entity.setPosition(pos);
 
 
 
 
148
  };
149
 
150
  OrbitCamera.prototype._removeInertia = function () {
151
- this._yaw = this._targetYaw;
152
- this._pitch = this._targetPitch;
153
  this._distance = this._targetDistance;
154
  };
155
 
156
  OrbitCamera.prototype._checkAspectRatio = function () {
157
- this.entity.camera.horizontalFov = (this.app.graphicsDevice.height > this.app.graphicsDevice.width);
 
 
158
  };
159
 
160
- OrbitCamera.prototype._buildAabb = function (e) {
161
- this._modelsAabb = this._modelsAabb || new pc.BoundingBox();
162
- var meshes = [];
163
- e.findComponents('render').forEach(r => meshes.push(...r.meshInstances));
164
- e.findComponents('model').forEach(m => meshes.push(...m.meshInstances));
165
- e.findComponents('gsplat').forEach(g => g.instance?.meshInstance && meshes.push(g.instance.meshInstance));
166
- meshes.forEach((mi, i) => i===0 ? this._modelsAabb.copy(mi.aabb) : this._modelsAabb.add(mi.aabb));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  };
168
 
169
  OrbitCamera.prototype._calcYaw = function (quat) {
170
- var v = new pc.Vec3(); quat.transformVector(pc.Vec3.FORWARD, v);
171
- return Math.atan2(-v.x, -v.z) * pc.math.RAD_TO_DEG;
 
172
  };
173
 
174
- OrbitCamera.prototype._clampDistance = function (d) {
175
- return this.distanceMax>0 ? pc.math.clamp(d, this.distanceMin, this.distanceMax) : Math.max(d, this.distanceMin);
 
 
 
176
  };
177
 
178
- OrbitCamera.prototype._clampPitchAngle = function (p) {
179
- return pc.math.clamp(p, this.pitchAngleMin, this.pitchAngleMax);
 
 
 
 
180
  };
181
 
182
- OrbitCamera.prototype._clampYawAngle = function (y) {
183
- return pc.math.clamp(y, -this.yawAngleMax, -this.yawAngleMin);
184
  };
185
 
 
 
 
 
 
 
 
186
  OrbitCamera.prototype._calcPitch = function (quat, yaw) {
187
- var qny = OrbitCamera.quatWithoutYaw, yo=OrbitCamera.yawOffset;
188
- yo.setFromEulerAngles(0,-yaw,0); qny.mul2(yo,quat);
189
- var f = new pc.Vec3(); qny.transformVector(pc.Vec3.FORWARD, f);
190
- return Math.atan2(-f.y, -f.z) * pc.math.RAD_TO_DEG;
 
 
 
 
 
 
191
  };
192
 
193
- OrbitCamera.quatWithoutYaw = new pc.Quat();
194
- OrbitCamera.yawOffset = new pc.Quat();
195
 
196
- // -- Mouse Input Script --
 
 
197
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
198
 
199
- OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type:'number', default:0.3 });
200
- OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type:'number', default:0.4 });
201
-
 
 
 
 
 
 
 
 
 
202
  OrbitCameraInputMouse.prototype.initialize = function () {
203
  this.orbitCamera = this.entity.script.orbitCamera;
204
- var self = this;
205
- this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
206
- this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
207
- this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
208
- this.app.mouse.on(pc.EVENT_MOUSEWHEEL,this.onMouseWheel,this);
209
- window.addEventListener('mouseout', this.onMouseOut.bind(this), false);
 
 
 
 
 
 
 
 
 
 
210
  this.app.mouse.disableContextMenu();
211
  this.lookButtonDown = false;
212
- this.panButtonDown = false;
213
- this.lastPoint = new pc.Vec2();
214
  };
215
-
216
- OrbitCameraInputMouse.prototype.onMouseDown = function (e) {
217
- if (e.button===pc.MOUSEBUTTON_LEFT) this.panButtonDown = true;
218
- else if (e.button===pc.MOUSEBUTTON_MIDDLE||e.button===pc.MOUSEBUTTON_RIGHT) this.lookButtonDown = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  };
220
-
221
- OrbitCameraInputMouse.prototype.onMouseUp = function (e) {
222
- if (e.button===pc.MOUSEBUTTON_LEFT) this.panButtonDown = false;
223
- else if (e.button===pc.MOUSEBUTTON_MIDDLE||e.button===pc.MOUSEBUTTON_RIGHT) this.lookButtonDown = false;
 
 
 
 
 
 
224
  };
225
-
226
- OrbitCameraInputMouse.prototype.onMouseMove = function (e) {
 
 
 
 
 
 
 
 
 
 
227
  if (this.lookButtonDown) {
228
- // always apply yaw
229
- this.orbitCamera.yaw -= e.dx * this.orbitSensitivity;
230
- // compute proposed pitch
231
- var proposed = this.orbitCamera.pitch - e.dy * this.orbitSensitivity;
232
- // clamp if would go below minY
233
- var rad = proposed * pc.math.DEG_TO_RAD;
234
- var candY = this.orbitCamera.pivotPoint.y + Math.sin(rad) * this.orbitCamera.distance;
235
- if (candY < this.orbitCamera.minY) {
236
- var maxRad = Math.asin((this.orbitCamera.minY - this.orbitCamera.pivotPoint.y)/this.orbitCamera.distance);
237
- this.orbitCamera.pitch = maxRad * pc.math.RAD_TO_DEG;
238
- } else {
239
- this.orbitCamera.pitch = proposed;
240
- }
241
  } else if (this.panButtonDown) {
242
- this.pan(new pc.Vec2(e.x, e.y));
243
  }
244
- this.lastPoint.set(e.x, e.y);
245
  };
246
-
247
- OrbitCameraInputMouse.prototype.onMouseWheel = function (e) {
248
- if (this.entity.camera.projection===pc.PROJECTION_PERSPECTIVE) {
249
- this.orbitCamera.distance -= e.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance*0.1);
250
  } else {
251
- this.orbitCamera.orthoHeight -= e.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight*0.1);
252
  }
253
- e.event.preventDefault();
254
- };
255
-
256
- OrbitCameraInputMouse.prototype.onMouseOut = function () {
257
- this.lookButtonDown = this.panButtonDown = false;
258
  };
259
-
260
- OrbitCameraInputMouse.prototype.pan = function (pt) {
261
- var cam = this.entity.camera, dist = this.orbitCamera.distance;
262
- var fw = OrbitCameraInputMouse.fromWorldPoint, tw = OrbitCameraInputMouse.toWorldPoint, wd = OrbitCameraInputMouse.worldDiff;
263
- cam.screenToWorld(pt.x, pt.y, dist, fw);
264
- cam.screenToWorld(this.lastPoint.x, this.lastPoint.y, dist, tw);
265
- wd.sub2(tw, fw);
266
- this.orbitCamera.pivotPoint.add(wd);
267
- var pitchRad = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
268
- var minPy = this.orbitCamera.distance * Math.sin(pitchRad);
269
- if (this.orbitCamera.pivotPoint.y < minPy) this.orbitCamera.pivotPoint.y = minPy;
270
  };
271
 
272
- OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
273
- OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
274
- OrbitCameraInputMouse.worldDiff = new pc.Vec3();
275
 
276
- // -- Touch Input Script --
 
 
277
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
278
 
279
- OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type:'number', default:0.6 });
280
- OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type:'number', default:0.5 });
281
-
 
 
 
 
 
 
 
 
 
282
  OrbitCameraInputTouch.prototype.initialize = function () {
283
  this.orbitCamera = this.entity.script.orbitCamera;
284
  this.lastTouchPoint = new pc.Vec2();
285
  this.lastPinchMidPoint = new pc.Vec2();
286
  this.lastPinchDistance = 0;
287
- if (this.app.touch) {
288
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
289
- this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
290
- this.app.touch.on(pc.EVENT_TOUCHCANCEL,this.onTouchStartEndCancel, this);
291
- this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
 
 
 
 
 
 
292
  }
293
  };
294
-
295
- OrbitCameraInputTouch.prototype.getPinchDistance = function (a,b) {
296
- var dx=a.x-b.x, dy=a.y-b.y; return Math.sqrt(dx*dx+dy*dy);
 
297
  };
298
-
299
- OrbitCameraInputTouch.prototype.calcMidPoint = function (a,b,res) {
300
- res.set(b.x-a.x,b.y-a.y).mulScalar(0.5).add(new pc.Vec2(a.x,a.y));
 
 
301
  };
302
-
303
- OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (e) {
304
- var t = e.touches;
305
- if (t.length===1) this.lastTouchPoint.set(t[0].x,t[0].y);
306
- else if (t.length===2) {
307
- this.lastPinchDistance = this.getPinchDistance(t[0],t[1]);
308
- this.calcMidPoint(t[0],t[1], this.lastPinchMidPoint);
309
  }
310
  };
311
-
312
- OrbitCameraInputTouch.prototype.onTouchMove = function (e) {
313
- var touches = e.touches;
314
- if (touches.length===1) {
315
- var t0 = touches[0];
316
- // always yaw
317
- this.orbitCamera.yaw -= (t0.x - this.lastTouchPoint.x) * this.orbitSensitivity;
318
- // compute proposed pitch
319
- var proposed = this.orbitCamera.pitch - (t0.y - this.lastTouchPoint.y) * this.orbitSensitivity;
320
- var rad = proposed * pc.math.DEG_TO_RAD;
321
- var candY = this.orbitCamera.pivotPoint.y + Math.sin(rad) * this.orbitCamera.distance;
322
- if (candY < this.orbitCamera.minY) {
323
- var maxRad = Math.asin((this.orbitCamera.minY - this.orbitCamera.pivotPoint.y)/this.orbitCamera.distance);
324
- this.orbitCamera.pitch = maxRad * pc.math.RAD_TO_DEG;
325
- } else {
326
- this.orbitCamera.pitch = proposed;
327
- }
328
- this.lastTouchPoint.set(t0.x, t0.y);
329
- } else if (touches.length===2) {
330
- var curDist = this.getPinchDistance(touches[0],touches[1]);
331
- var diff = curDist - this.lastPinchDistance;
332
- this.lastPinchDistance = curDist;
333
- this.orbitCamera.distance -= diff * this.distanceSensitivity * 0.1 * (this.orbitCamera.distance*0.1);
334
- var mid = OrbitCameraInputTouch.pinchMidPoint;
335
- this.calcMidPoint(touches[0], touches[1], mid);
336
- this.pan(mid);
337
- this.lastPinchMidPoint.copy(mid);
338
  }
339
  };
340
-
341
- OrbitCameraInputTouch.prototype.pan = OrbitCameraInputMouse.prototype.pan;
342
-
343
  OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ///////////////////////////////////////////////////////////////////////////////
2
+ // Orbit Camera Script qui marche presque //
3
  ////////////////////////////////////////////////////////////////////////////////
4
 
 
5
  var OrbitCamera = pc.createScript('orbitCamera');
6
 
7
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max', description: 'Setting this at 0 will give an infinite distance limit' });
8
  OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
9
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
10
+ // Note: This default will be overridden by your JSON config. In your JSON you set minAngle to -90.
11
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
12
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
13
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
14
+ // NEW: Added minY attribute to define the minimum allowed Y value for the camera.
15
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y', description: 'Minimum Y value for the camera during orbiting or translation' });
16
+
17
+ OrbitCamera.attributes.add('inertiaFactor', {
18
+ type: 'number',
19
+ default: 0.2,
20
+ title: 'Inertia Factor',
21
+ description: 'Higher value means that the camera will continue moving after the user has stopped dragging. 0 is fully responsive.'
22
+ });
23
+
24
+ OrbitCamera.attributes.add('focusEntity', {
25
+ type: 'entity',
26
+ title: 'Focus Entity',
27
+ description: 'Entity for the camera to focus on. If blank, then the camera will use the whole scene'
28
+ });
29
+
30
+ OrbitCamera.attributes.add('frameOnStart', {
31
+ type: 'boolean',
32
+ default: true,
33
+ title: 'Frame on Start',
34
+ description: 'Frames the entity or scene at the start of the application."'
35
+ });
36
 
37
+
38
+ // Property to get and set the distance between the pivot point and camera
39
+ // Clamped between this.distanceMin and this.distanceMax
40
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
41
+ get: function () {
42
+ return this._targetDistance;
43
+ },
44
+ set: function (value) {
45
+ this._targetDistance = this._clampDistance(value);
46
+ }
47
  });
48
+
49
+ // Property to get and set the camera orthoHeight (clamped above 0)
50
  Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
51
+ get: function () {
52
+ return this.entity.camera.orthoHeight;
53
+ },
54
+ set: function (value) {
55
+ this.entity.camera.orthoHeight = Math.max(0, value);
56
+ }
57
  });
58
+
59
+
60
+ // Property to get and set the pitch (in degrees) of the camera around the pivot.
61
+ // The pitch value is clamped between pitchAngleMin and pitchAngleMax.
62
+ // With your JSON (minAngle: -90, maxAngle: 0), the allowed pitch will be from -90 (overhead)
63
+ // to 0 (horizontal).
64
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
65
+ get: function () {
66
+ return this._targetPitch;
67
+ },
68
+ set: function (value) {
69
+ this._targetPitch = this._clampPitchAngle(value);
70
+ }
71
  });
72
+
73
+
74
+ // Property to get and set the yaw (in degrees) of the camera around the pivot.
75
  Object.defineProperty(OrbitCamera.prototype, 'yaw', {
76
+ get: function () {
77
+ return this._targetYaw;
78
+ },
79
+ set: function (value) {
80
+ this._targetYaw = this._clampYawAngle(value);
81
+ }
82
  });
83
+
84
+
85
+ // Property to get and set the world position of the pivot point that the camera orbits around.
86
  Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
87
+ get: function () {
88
+ return this._pivotPoint;
89
+ },
90
+ set: function (value) {
91
+ this._pivotPoint.copy(value);
92
+ }
93
  });
94
 
95
+
96
+ // Moves the camera to look at an entity and all its children so they are all in view.
97
+ OrbitCamera.prototype.focus = function (focusEntity) {
98
+ // Calculate a bounding box that encompasses all models to frame in the camera view.
99
+ this._buildAabb(focusEntity);
100
+ var halfExtents = this._modelsAabb.halfExtents;
101
+ var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
102
+ this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
103
+ this._removeInertia();
104
+ this._pivotPoint.copy(this._modelsAabb.center);
105
+ };
106
+
107
  OrbitCamera.distanceBetween = new pc.Vec3();
108
 
109
+ // Set the camera position to a world position and look at a world position.
110
+ OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
111
+ this.pivotPoint.copy(lookAtPoint);
112
+ this.entity.setPosition(resetPoint);
113
+ this.entity.lookAt(lookAtPoint);
114
+ var distance = OrbitCamera.distanceBetween;
115
+ distance.sub2(lookAtPoint, resetPoint);
116
+ this.distance = distance.length();
117
+ this.pivotPoint.copy(lookAtPoint);
118
+ var cameraQuat = this.entity.getRotation();
119
+ this.yaw = this._calcYaw(cameraQuat);
120
+ this.pitch = this._calcPitch(cameraQuat, this.yaw);
121
+ this._removeInertia();
122
+ this._updatePosition();
123
+ };
124
+
125
+ OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
126
+ this._buildAabb(entity);
127
+ this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
128
+ };
129
+
130
+ OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
131
+ this.pitch = pitch;
132
+ this.yaw = yaw;
133
+ this.distance = distance;
134
+ this._removeInertia();
135
+ };
136
+
137
+ OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
138
+ this.entity.setPosition(position);
139
+ this.entity.lookAt(lookAtPoint);
140
+ this._pivotPoint.copy(lookAtPoint);
141
+ var distanceVec = new pc.Vec3();
142
+ distanceVec.sub2(position, lookAtPoint);
143
+ this._targetDistance = this._distance = distanceVec.length();
144
+ var cameraQuat = this.entity.getRotation();
145
+ this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
146
+ this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
147
+ this._removeInertia();
148
+ this._updatePosition();
149
+ };
150
+
151
+
152
+ ////////////////////////////////////////////////////////////////////////////////
153
+ // Private Methods //
154
+ ////////////////////////////////////////////////////////////////////////////////
155
+
156
  OrbitCamera.prototype.initialize = function () {
157
  var self = this;
158
+ var onWindowResize = function () {
159
+ self._checkAspectRatio();
160
+ };
161
 
162
+ window.addEventListener('resize', onWindowResize, false);
163
+ this._checkAspectRatio();
164
  this._modelsAabb = new pc.BoundingBox();
165
  this._buildAabb(this.focusEntity || this.app.root);
166
  this.entity.lookAt(this._modelsAabb.center);
167
+ this._pivotPoint = new pc.Vec3();
168
+ this._pivotPoint.copy(this._modelsAabb.center);
169
+ var cameraQuat = this.entity.getRotation();
170
+ this._yaw = this._calcYaw(cameraQuat);
171
+ // Compute pitch and clamp it immediately.
172
+ this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
173
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
 
174
  this._distance = 0;
175
  this._targetYaw = this._yaw;
176
  this._targetPitch = this._pitch;
 
178
  if (this.frameOnStart) {
179
  this.focus(this.focusEntity || this.app.root);
180
  } else {
181
+ var distanceBetween = new pc.Vec3();
182
+ distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
183
+ this._distance = this._clampDistance(distanceBetween.length());
184
  }
185
+
186
  this._targetDistance = this._distance;
187
 
188
+ this.on('attr:distanceMin', function (value, prev) {
189
+ this._distance = this._clampDistance(this._distance);
190
+ });
191
+ this.on('attr:distanceMax', function (value, prev) {
192
+ this._distance = this._clampDistance(this._distance);
193
+ });
194
+ this.on('attr:pitchAngleMin', function (value, prev) {
195
+ this._pitch = this._clampPitchAngle(this._pitch);
196
+ });
197
+ this.on('attr:pitchAngleMax', function (value, prev) {
198
+ this._pitch = this._clampPitchAngle(this._pitch);
199
+ });
200
+ this.on('attr:focusEntity', function (value, prev) {
201
+ if (this.frameOnStart) {
202
+ this.focus(value || this.app.root);
203
+ } else {
204
+ this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);
205
+ }
206
+ });
207
+ this.on('attr:frameOnStart', function (value, prev) {
208
+ if (value) {
209
+ this.focus(this.focusEntity || this.app.root);
210
+ }
211
+ });
212
+ this.on('destroy', function () {
213
+ window.removeEventListener('resize', onWindowResize, false);
214
  });
 
 
215
  };
216
 
217
  OrbitCamera.prototype.update = function (dt) {
218
  var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
219
  this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
220
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
221
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  this._updatePosition();
223
  };
224
 
225
  OrbitCamera.prototype._updatePosition = function () {
226
+ this.entity.setLocalPosition(0, 0, 0);
227
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
228
+ var position = this.entity.getPosition();
229
+ position.copy(this.entity.forward);
230
+ position.mulScalar(-this._distance);
231
+ position.add(this.pivotPoint);
232
+ // NEW: Clamp the camera's Y position so it never goes below the specified minY value.
233
+ position.y = Math.max(position.y, this.minY);
234
+ this.entity.setPosition(position);
235
  };
236
 
237
  OrbitCamera.prototype._removeInertia = function () {
238
+ this._yaw = this._targetYaw;
239
+ this._pitch = this._targetPitch;
240
  this._distance = this._targetDistance;
241
  };
242
 
243
  OrbitCamera.prototype._checkAspectRatio = function () {
244
+ var height = this.app.graphicsDevice.height;
245
+ var width = this.app.graphicsDevice.width;
246
+ this.entity.camera.horizontalFov = (height > width);
247
  };
248
 
249
+ OrbitCamera.prototype._buildAabb = function (entity) {
250
+ var i, m, meshInstances = [];
251
+ var renders = entity.findComponents('render');
252
+ for (i = 0; i < renders.length; i++) {
253
+ var render = renders[i];
254
+ for (m = 0; m < render.meshInstances.length; m++) {
255
+ meshInstances.push(render.meshInstances[m]);
256
+ }
257
+ }
258
+ var models = entity.findComponents('model');
259
+ for (i = 0; i < models.length; i++) {
260
+ var model = models[i];
261
+ for (m = 0; m < model.meshInstances.length; m++) {
262
+ meshInstances.push(model.meshInstances[m]);
263
+ }
264
+ }
265
+ var gsplats = entity.findComponents('gsplat');
266
+ for (i = 0; i < gsplats.length; i++) {
267
+ var gsplat = gsplats[i];
268
+ var instance = gsplat.instance;
269
+ if (instance?.meshInstance) {
270
+ meshInstances.push(instance.meshInstance);
271
+ }
272
+ }
273
+ for (i = 0; i < meshInstances.length; i++) {
274
+ if (i === 0) {
275
+ this._modelsAabb.copy(meshInstances[i].aabb);
276
+ } else {
277
+ this._modelsAabb.add(meshInstances[i].aabb);
278
+ }
279
+ }
280
  };
281
 
282
  OrbitCamera.prototype._calcYaw = function (quat) {
283
+ var transformedForward = new pc.Vec3();
284
+ quat.transformVector(pc.Vec3.FORWARD, transformedForward);
285
+ return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
286
  };
287
 
288
+ OrbitCamera.prototype._clampDistance = function (distance) {
289
+ if (this.distanceMax > 0) {
290
+ return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
291
+ }
292
+ return Math.max(distance, this.distanceMin);
293
  };
294
 
295
+
296
+ // ----- FIXED PITCH CLAMPING -----
297
+ // Clamp the pitch between pitchAngleMin and pitchAngleMax so that with your JSON values
298
+ // (minAngle: -90, maxAngle: 0) the allowed pitch is between -90 (overhead) and 0 (horizontal).
299
+ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
300
+ return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
301
  };
302
 
303
+ OrbitCamera.prototype._clampYawAngle = function (yaw) {
304
+ return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
305
  };
306
 
307
+ OrbitCamera.quatWithoutYaw = new pc.Quat();
308
+ OrbitCamera.yawOffset = new pc.Quat();
309
+
310
+ // ----- REVISED PITCH CALCULATION -----
311
+ // Modify _calcPitch so that horizontal (looking straight ahead) returns 0,
312
+ // and looking overhead returns -90.
313
+ // This change ensures that with JSON (minAngle: -90, maxAngle: 0) the allowed pitch is strictly between -90 and 0.
314
  OrbitCamera.prototype._calcPitch = function (quat, yaw) {
315
+ var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
316
+ var yawOffset = OrbitCamera.yawOffset;
317
+ yawOffset.setFromEulerAngles(0, -yaw, 0);
318
+ quatWithoutYaw.mul2(yawOffset, quat);
319
+ var transformedForward = new pc.Vec3();
320
+ quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
321
+ // Here we swap the sign of the computed angle so that:
322
+ // - When the camera is horizontal, transformedForward.y is 0 and the result is 0.
323
+ // - When the camera is overhead, transformedForward.y is negative and the result is -90.
324
+ return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
325
  };
326
 
 
 
327
 
328
+ ////////////////////////////////////////////////////////////////////////////////
329
+ // Orbit Camera Mouse Input Script //
330
+ ////////////////////////////////////////////////////////////////////////////////
331
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
332
 
333
+ OrbitCameraInputMouse.attributes.add('orbitSensitivity', {
334
+ type: 'number',
335
+ default: 0.3,
336
+ title: 'Orbit Sensitivity',
337
+ description: 'How fast the camera moves around the orbit. Higher is faster'
338
+ });
339
+ OrbitCameraInputMouse.attributes.add('distanceSensitivity', {
340
+ type: 'number',
341
+ default: 0.4,
342
+ title: 'Distance Sensitivity',
343
+ description: 'How fast the camera moves in and out. Higher is faster'
344
+ });
345
  OrbitCameraInputMouse.prototype.initialize = function () {
346
  this.orbitCamera = this.entity.script.orbitCamera;
347
+ if (this.orbitCamera) {
348
+ var self = this;
349
+ var onMouseOut = function (e) { self.onMouseOut(e); };
350
+ this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
351
+ this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
352
+ this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
353
+ this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
354
+ window.addEventListener('mouseout', onMouseOut, false);
355
+ this.on('destroy', function () {
356
+ this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
357
+ this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
358
+ this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
359
+ this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
360
+ window.removeEventListener('mouseout', onMouseOut, false);
361
+ });
362
+ }
363
  this.app.mouse.disableContextMenu();
364
  this.lookButtonDown = false;
365
+ this.panButtonDown = false;
366
+ this.lastPoint = new pc.Vec2();
367
  };
368
+ OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
369
+ OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
370
+ OrbitCameraInputMouse.worldDiff = new pc.Vec3();
371
+ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
372
+ var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
373
+ var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
374
+ var worldDiff = OrbitCameraInputMouse.worldDiff;
375
+ var camera = this.entity.camera;
376
+ var distance = this.orbitCamera.distance;
377
+ camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
378
+ camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
379
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
380
+ this.orbitCamera.pivotPoint.add(worldDiff);
381
+ var pitchRadians = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
382
+ var minPivotY = this.orbitCamera.distance * Math.sin(pitchRadians);
383
+ if (this.orbitCamera.pivotPoint.y < minPivotY) {
384
+ this.orbitCamera.pivotPoint.y = minPivotY;
385
+ }
386
  };
387
+ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
388
+ switch (event.button) {
389
+ case pc.MOUSEBUTTON_LEFT:
390
+ this.panButtonDown = true;
391
+ break;
392
+ case pc.MOUSEBUTTON_MIDDLE:
393
+ case pc.MOUSEBUTTON_RIGHT:
394
+ this.lookButtonDown = true;
395
+ break;
396
+ }
397
  };
398
+ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
399
+ switch (event.button) {
400
+ case pc.MOUSEBUTTON_LEFT:
401
+ this.panButtonDown = false;
402
+ break;
403
+ case pc.MOUSEBUTTON_MIDDLE:
404
+ case pc.MOUSEBUTTON_RIGHT:
405
+ this.lookButtonDown = false;
406
+ break;
407
+ }
408
+ };
409
+ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
410
  if (this.lookButtonDown) {
411
+ this.orbitCamera.pitch -= event.dy * this.orbitSensitivity;
412
+ this.orbitCamera.yaw -= event.dx * this.orbitSensitivity;
 
 
 
 
 
 
 
 
 
 
 
413
  } else if (this.panButtonDown) {
414
+ this.pan(new pc.Vec2(event.x, event.y));
415
  }
416
+ this.lastPoint.set(event.x, event.y);
417
  };
418
+ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
419
+ if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
420
+ this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
 
421
  } else {
422
+ this.orbitCamera.orthoHeight -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight * 0.1);
423
  }
424
+ event.event.preventDefault();
 
 
 
 
425
  };
426
+ OrbitCameraInputMouse.prototype.onMouseOut = function (event) {
427
+ this.lookButtonDown = false;
428
+ this.panButtonDown = false;
 
 
 
 
 
 
 
 
429
  };
430
 
 
 
 
431
 
432
+ ////////////////////////////////////////////////////////////////////////////////
433
+ // Orbit Camera Touch Input Script //
434
+ ////////////////////////////////////////////////////////////////////////////////
435
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
436
 
437
+ OrbitCameraInputTouch.attributes.add('orbitSensitivity', {
438
+ type: 'number',
439
+ default: 0.6,
440
+ title: 'Orbit Sensitivity',
441
+ description: 'How fast the camera moves around the orbit. Higher is faster'
442
+ });
443
+ OrbitCameraInputTouch.attributes.add('distanceSensitivity', {
444
+ type: 'number',
445
+ default: 0.5,
446
+ title: 'Distance Sensitivity',
447
+ description: 'How fast the camera moves in and out. Higher is faster'
448
+ });
449
  OrbitCameraInputTouch.prototype.initialize = function () {
450
  this.orbitCamera = this.entity.script.orbitCamera;
451
  this.lastTouchPoint = new pc.Vec2();
452
  this.lastPinchMidPoint = new pc.Vec2();
453
  this.lastPinchDistance = 0;
454
+ if (this.orbitCamera && this.app.touch) {
455
  this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
456
+ this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
457
+ this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
458
+ this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
459
+ this.on('destroy', function () {
460
+ this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
461
+ this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
462
+ this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
463
+ this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
464
+ });
465
  }
466
  };
467
+ OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
468
+ var dx = pointA.x - pointB.x;
469
+ var dy = pointA.y - pointB.y;
470
+ return Math.sqrt((dx * dx) + (dy * dy));
471
  };
472
+ OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
473
+ result.set(pointB.x - pointA.x, pointB.y - pointA.y);
474
+ result.mulScalar(0.5);
475
+ result.x += pointA.x;
476
+ result.y += pointA.y;
477
  };
478
+ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
479
+ var touches = event.touches;
480
+ if (touches.length === 1) {
481
+ this.lastTouchPoint.set(touches[0].x, touches[0].y);
482
+ } else if (touches.length === 2) {
483
+ this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
484
+ this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
485
  }
486
  };
487
+ OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
488
+ OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
489
+ OrbitCameraInputTouch.worldDiff = new pc.Vec3();
490
+ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
491
+ var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
492
+ var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
493
+ var worldDiff = OrbitCameraInputTouch.worldDiff;
494
+ var camera = this.entity.camera;
495
+ var distance = this.orbitCamera.distance;
496
+ camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
497
+ camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
498
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
499
+ this.orbitCamera.pivotPoint.add(worldDiff);
500
+ var pitchRadians = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
501
+ var minPivotY = this.orbitCamera.distance * Math.sin(pitchRadians);
502
+ if (this.orbitCamera.pivotPoint.y < minPivotY) {
503
+ this.orbitCamera.pivotPoint.y = minPivotY;
 
 
 
 
 
 
 
 
 
 
504
  }
505
  };
 
 
 
506
  OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
507
+ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
508
+ var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
509
+ var touches = event.touches;
510
+ if (touches.length === 1) {
511
+ var touch = touches[0];
512
+ this.orbitCamera.pitch -= (touch.y - this.lastTouchPoint.y) * this.orbitSensitivity;
513
+ this.orbitCamera.yaw -= (touch.x - this.lastTouchPoint.x) * this.orbitSensitivity;
514
+ this.lastTouchPoint.set(touch.x, touch.y);
515
+ } else if (touches.length === 2) {
516
+ var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
517
+ var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
518
+ this.lastPinchDistance = currentPinchDistance;
519
+ this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
520
+ this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
521
+ this.pan(pinchMidPoint);
522
+ this.lastPinchMidPoint.copy(pinchMidPoint);
523
+ }
524
+ };