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

Update orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +128 -416
orbit-camera.js CHANGED
@@ -1,217 +1,91 @@
 
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;
177
-
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) {
@@ -225,13 +99,10 @@ OrbitCamera.prototype.update = function (dt) {
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 () {
@@ -241,284 +112,125 @@ OrbitCamera.prototype._removeInertia = function () {
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
- };
 
1
+ // orbit-camera.js
2
  ///////////////////////////////////////////////////////////////////////////////
3
+ // Orbit Camera Script //
4
  ////////////////////////////////////////////////////////////////////////////////
5
 
6
  var OrbitCamera = pc.createScript('orbitCamera');
7
 
8
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20 });
9
+ OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1 });
10
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90 });
11
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: -90 });
12
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360 });
13
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360 });
14
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0 });
15
+ OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2 });
16
+ OrbitCamera.attributes.add('focusEntity', { type: 'entity' });
17
+ OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true });
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  Object.defineProperty(OrbitCamera.prototype, 'distance', {
20
+ get: function () { return this._targetDistance; },
21
+ set: function (v) { this._targetDistance = this._clampDistance(v); }
 
 
 
 
22
  });
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
 
 
 
 
 
 
29
  Object.defineProperty(OrbitCamera.prototype, 'pitch', {
30
+ get: function () { return this._targetPitch; },
31
+ set: function (v) { this._targetPitch = this._clampPitchAngle(v); }
 
 
 
 
32
  });
33
 
 
 
34
  Object.defineProperty(OrbitCamera.prototype, 'yaw', {
35
+ get: function () { return this._targetYaw; },
36
+ set: function (v) { this._targetYaw = this._clampYawAngle(v); }
 
 
 
 
37
  });
38
 
 
 
39
  Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
40
+ get: function () { return this._pivotPoint; },
41
+ set: function (v) { this._pivotPoint.copy(v); }
 
 
 
 
42
  });
43
 
 
 
44
  OrbitCamera.prototype.focus = function (focusEntity) {
 
45
  this._buildAabb(focusEntity);
46
+ var r = Math.max(this._modelsAabb.halfExtents.x, this._modelsAabb.halfExtents.y, this._modelsAabb.halfExtents.z);
47
+ this.distance = (r * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
 
48
  this._removeInertia();
49
  this._pivotPoint.copy(this._modelsAabb.center);
50
  };
51
 
52
  OrbitCamera.distanceBetween = new pc.Vec3();
53
 
54
+ OrbitCamera.prototype.resetAndLookAtPoint = function (p, l) {
55
+ this.pivotPoint.copy(l);
56
+ this.entity.setPosition(p);
57
+ this.entity.lookAt(l);
58
+ var d = OrbitCamera.distanceBetween;
59
+ d.sub2(l, p);
60
+ this.distance = d.length();
61
+ var q = this.entity.getRotation();
62
+ this.yaw = this._calcYaw(q);
63
+ this.pitch = this._calcPitch(q, this.yaw);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  this._removeInertia();
65
  this._updatePosition();
66
  };
67
 
 
 
 
 
 
68
  OrbitCamera.prototype.initialize = function () {
69
+ window.addEventListener('resize', this._checkAspectRatio.bind(this), false);
 
 
 
 
 
70
  this._checkAspectRatio();
71
  this._modelsAabb = new pc.BoundingBox();
72
  this._buildAabb(this.focusEntity || this.app.root);
73
  this.entity.lookAt(this._modelsAabb.center);
74
+ this._pivotPoint = this._modelsAabb.center.clone();
75
+ var q = this.entity.getRotation();
76
+ this._yaw = this._calcYaw(q);
77
+ this._pitch = this._clampPitchAngle(this._calcPitch(q, this._yaw));
 
 
78
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
79
  this._distance = 0;
80
  this._targetYaw = this._yaw;
81
  this._targetPitch = this._pitch;
82
+ if (this.frameOnStart) this.focus(this.focusEntity || this.app.root);
83
+ else {
84
+ var dist = new pc.Vec3();
85
+ dist.sub2(this.entity.getPosition(), this._pivotPoint);
86
+ this._distance = this._clampDistance(dist.length());
 
 
87
  }
 
88
  this._targetDistance = this._distance;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  };
90
 
91
  OrbitCamera.prototype.update = function (dt) {
 
99
  OrbitCamera.prototype._updatePosition = function () {
100
  this.entity.setLocalPosition(0, 0, 0);
101
  this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
102
+ var pos = this.entity.getPosition();
103
+ pos.copy(this.entity.forward).mulScalar(-this._distance).add(this.pivotPoint);
104
+ pos.y = Math.max(pos.y, this.minY);
105
+ this.entity.setPosition(pos);
 
 
 
106
  };
107
 
108
  OrbitCamera.prototype._removeInertia = function () {
 
112
  };
113
 
114
  OrbitCamera.prototype._checkAspectRatio = function () {
115
+ this.entity.camera.horizontalFov = (this.app.graphicsDevice.height > this.app.graphicsDevice.width);
 
 
116
  };
117
 
118
  OrbitCamera.prototype._buildAabb = function (entity) {
119
+ var meshes = [];
120
+ var rs = entity.findComponents('render');
121
+ var ms = entity.findComponents('model');
122
+ rs.forEach(r => meshes.push(...r.meshInstances));
123
+ ms.forEach(m => meshes.push(...m.meshInstances));
124
+ var gs = entity.findComponents('gsplat');
125
+ gs.forEach(g => { if (g.instance?.meshInstance) meshes.push(g.instance.meshInstance); });
126
+ meshes.forEach((m, i) => i === 0 ? this._modelsAabb.copy(m.aabb) : this._modelsAabb.add(m.aabb));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  };
128
 
129
+ OrbitCamera.prototype._calcYaw = function (q) {
130
+ var v = new pc.Vec3();
131
+ q.transformVector(pc.Vec3.FORWARD, v);
132
+ return Math.atan2(-v.x, -v.z) * pc.math.RAD_TO_DEG;
133
  };
134
 
135
+ OrbitCamera.prototype._clampDistance = function (d) {
136
+ return this.distanceMax > 0 ? pc.math.clamp(d, this.distanceMin, this.distanceMax) : Math.max(d, this.distanceMin);
 
 
 
137
  };
138
 
139
+ OrbitCamera.prototype._clampPitchAngle = function (p) {
140
+ return pc.math.clamp(p, this.pitchAngleMin, this.pitchAngleMax);
 
 
 
 
141
  };
142
 
143
+ OrbitCamera.prototype._clampYawAngle = function (y) {
144
+ return pc.math.clamp(y, -this.yawAngleMax, -this.yawAngleMin);
145
  };
146
 
147
  OrbitCamera.quatWithoutYaw = new pc.Quat();
148
  OrbitCamera.yawOffset = new pc.Quat();
149
 
150
+ OrbitCamera.prototype._calcPitch = function (q, yaw) {
151
+ var qNoYaw = OrbitCamera.quatWithoutYaw;
152
+ var yo = OrbitCamera.yawOffset;
153
+ yo.setFromEulerAngles(0, -yaw, 0);
154
+ qNoYaw.mul2(yo, q);
155
+ var v = new pc.Vec3();
156
+ qNoYaw.transformVector(pc.Vec3.FORWARD, v);
157
+ return Math.atan2(-v.y, -v.z) * pc.math.RAD_TO_DEG;
 
 
 
 
 
 
 
158
  };
159
 
 
160
  ////////////////////////////////////////////////////////////////////////////////
161
+ // Orbit Camera Mouse Input Script //
162
  ////////////////////////////////////////////////////////////////////////////////
163
  var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
164
+ OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3 });
165
+ OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4 });
166
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  OrbitCameraInputMouse.prototype.initialize = function () {
168
  this.orbitCamera = this.entity.script.orbitCamera;
169
+ this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
170
+ this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
171
+ this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
172
+ this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  this.lastPoint = new pc.Vec2();
174
  };
175
+
176
+ OrbitCameraInputMouse.prototype.onMouseDown = function (e) {
177
+ if (e.button === pc.MOUSEBUTTON_LEFT) this.panButtonDown = true;
178
+ if (e.button === pc.MOUSEBUTTON_RIGHT) this.lookButtonDown = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  };
180
+
181
+ OrbitCameraInputMouse.prototype.onMouseUp = function (e) {
182
+ if (e.button === pc.MOUSEBUTTON_LEFT) this.panButtonDown = false;
183
+ if (e.button === pc.MOUSEBUTTON_RIGHT) this.lookButtonDown = false;
 
 
 
 
 
 
184
  };
185
+
186
+ OrbitCameraInputMouse.prototype.onMouseMove = function (e) {
187
  if (this.lookButtonDown) {
188
+ this.orbitCamera.pitch -= e.dy * this.orbitSensitivity;
189
+ const r = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
190
+ const candY = this.orbitCamera.pivotPoint.y + Math.sin(r) * this.orbitCamera.distance;
191
+ if (candY < this.orbitCamera.minY) {
192
+ const maxRad = Math.asin(pc.math.clamp((this.orbitCamera.minY - this.orbitCamera.pivotPoint.y) / this.orbitCamera.distance, -1, 1));
193
+ const clampedPitch = maxRad * pc.math.RAD_TO_DEG;
194
+ this.orbitCamera.pitch = clampedPitch;
195
+ this.orbitCamera._targetPitch = clampedPitch;
196
+ }
197
+ this.orbitCamera.yaw -= e.dx * this.orbitSensitivity;
 
 
198
  }
199
+ this.lastPoint.set(e.x, e.y);
 
 
 
 
200
  };
201
 
202
+ OrbitCameraInputMouse.prototype.onMouseWheel = function (e) {
203
+ const factor = this.orbitCamera.distance * 0.1;
204
+ this.orbitCamera.distance -= e.wheelDelta * this.distanceSensitivity * factor;
205
+ e.event.preventDefault();
206
+ };
207
 
208
  ////////////////////////////////////////////////////////////////////////////////
209
+ // Orbit Camera Touch Input Script //
210
  ////////////////////////////////////////////////////////////////////////////////
211
  var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
212
+ OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6 });
213
+ OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5 });
214
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  OrbitCameraInputTouch.prototype.initialize = function () {
216
  this.orbitCamera = this.entity.script.orbitCamera;
217
  this.lastTouchPoint = new pc.Vec2();
218
+ this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
219
+ };
220
+
221
+ OrbitCameraInputTouch.prototype.onTouchMove = function (e) {
222
+ if (e.touches.length === 1) {
223
+ const t = e.touches[0];
224
+ this.orbitCamera.pitch -= (t.y - this.lastTouchPoint.y) * this.orbitSensitivity;
225
+ const r = this.orbitCamera.pitch * pc.math.DEG_TO_RAD;
226
+ const candY = this.orbitCamera.pivotPoint.y + Math.sin(r) * this.orbitCamera.distance;
227
+ if (candY < this.orbitCamera.minY) {
228
+ const maxRad = Math.asin(pc.math.clamp((this.orbitCamera.minY - this.orbitCamera.pivotPoint.y) / this.orbitCamera.distance, -1, 1));
229
+ const clampedPitch = maxRad * pc.math.RAD_TO_DEG;
230
+ this.orbitCamera.pitch = clampedPitch;
231
+ this.orbitCamera._targetPitch = clampedPitch;
232
+ }
233
+ this.orbitCamera.yaw -= (t.x - this.lastTouchPoint.x) * this.orbitSensitivity;
234
+ this.lastTouchPoint.set(t.x, t.y);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
  };