MikaFil commited on
Commit
2a56e9e
·
verified ·
1 Parent(s): 41e4282

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +306 -85
orbit-camera.js CHANGED
@@ -1,63 +1,205 @@
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,88 +207,133 @@ 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.add(forward.clone().scale(-this._distance)); // <-- Fixed line
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.add(forward.clone().scale(-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,6 +354,11 @@ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
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,7 +370,6 @@ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
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,6 +382,8 @@ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
191
  break;
192
  }
193
  };
 
 
194
  OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
195
  if (this.lookButtonDown) {
196
  var sens = this.orbitSensitivity;
@@ -202,14 +395,14 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
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,21 +411,27 @@ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
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,19 +440,27 @@ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
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,9 +513,15 @@ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
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,12 +536,14 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
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,14 +552,19 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
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,3 +576,4 @@ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
356
  this.lastPinchMidPoint.copy(pinchMidPoint);
357
  }
358
  };
 
 
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
  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
  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
  this.lookButtonDown = true;
371
  break;
372
  }
 
373
  };
374
  OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
375
  switch (event.button) {
 
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
  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
  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
  }
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
  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
  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
  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
  this.lastPinchMidPoint.copy(pinchMidPoint);
577
  }
578
  };
579
+ // ---------------------------------------------------------------------