bilca commited on
Commit
55768bf
·
verified ·
1 Parent(s): 7f10211

Upload orbit-camera.js

Browse files
Files changed (1) hide show
  1. orbit-camera.js +652 -652
orbit-camera.js CHANGED
@@ -1,652 +1,652 @@
1
- ////////////////////////////////////////////////////////////////////////////////
2
- // Orbit Camera Script //
3
- ////////////////////////////////////////////////////////////////////////////////
4
- var OrbitCamera = pc.createScript('orbitCamera');
5
-
6
- OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 0, title: 'Distance Max', description: 'Setting this at 0 will give an infinite distance limit' });
7
- OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 0, title: 'Distance Min' });
8
- OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 10, title: 'Pitch Angle Max (degrees)' });
9
- OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: -10, title: 'Pitch Angle Min (degrees)' });
10
- OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 90, title: 'Yaw Angle Max (degrees)' });
11
- OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -90, title: 'Yaw Angle Min (degrees)' });
12
-
13
-
14
- OrbitCamera.attributes.add('inertiaFactor', {
15
- type: 'number',
16
- default: 0,
17
- title: 'Inertia Factor',
18
- description: 'Higher value means that the camera will continue moving after the user has stopped dragging. 0 is fully responsive.'
19
- });
20
-
21
- OrbitCamera.attributes.add('focusEntity', {
22
- type: 'entity',
23
- title: 'Focus Entity',
24
- description: 'Entity for the camera to focus on. If blank, then the camera will use the whole scene'
25
- });
26
-
27
- OrbitCamera.attributes.add('frameOnStart', {
28
- type: 'boolean',
29
- default: true,
30
- title: 'Frame on Start',
31
- description: 'Frames the entity or scene at the start of the application."'
32
- });
33
-
34
-
35
- // Property to get and set the distance between the pivot point and camera
36
- // Clamped between this.distanceMin and this.distanceMax
37
- Object.defineProperty(OrbitCamera.prototype, 'distance', {
38
- get: function () {
39
- return this._targetDistance;
40
- },
41
-
42
- set: function (value) {
43
- this._targetDistance = this._clampDistance(value);
44
- }
45
- });
46
-
47
- // Property to get and set the camera orthoHeight
48
- // Clamped above 0
49
- Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
50
- get: function () {
51
- return this.entity.camera.orthoHeight;
52
- },
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 of the camera around the pivot point (degrees)
61
- // Clamped between this.pitchAngleMin and this.pitchAngleMax
62
- // When set at 0, the camera angle is flat, looking along the horizon
63
- Object.defineProperty(OrbitCamera.prototype, 'pitch', {
64
- get: function () {
65
- return this._targetPitch;
66
- },
67
-
68
- set: function (value) {
69
- this._targetPitch = this._clampPitchAngle(value);
70
- }
71
- });
72
-
73
-
74
- // Property to get and set the yaw of the camera around the pivot point (degrees)
75
- Object.defineProperty(OrbitCamera.prototype, 'yaw', {
76
- get: function () {
77
- return this._targetYaw;
78
- },
79
-
80
- set: function (value) {
81
- this._targetYaw = this._clampYawAngle(value);
82
-
83
- // Ensure that the yaw takes the shortest route by making sure that
84
- // the difference between the targetYaw and the actual is 180 degrees
85
- // in either direction
86
- /*var diff = this._targetYaw - this._yaw;
87
- var reminder = diff % 360;
88
- if (reminder > 180) {
89
- this._targetYaw = this._yaw - (360 - reminder);
90
- } else if (reminder < -180) {
91
- this._targetYaw = this._yaw + (360 + reminder);
92
- } else {
93
- this._targetYaw = this._yaw + reminder;
94
- }*/
95
- }
96
- });
97
-
98
-
99
- // Property to get and set the world position of the pivot point that the camera orbits around
100
- Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
101
- get: function () {
102
- return this._pivotPoint;
103
- },
104
-
105
- set: function (value) {
106
- this._pivotPoint.copy(value);
107
- }
108
- });
109
-
110
-
111
- // Moves the camera to look at an entity and all its children so they are all in the view
112
- OrbitCamera.prototype.focus = function (focusEntity) {
113
- // Calculate an bounding box that encompasses all the models to frame in the camera view
114
- this._buildAabb(focusEntity);
115
-
116
- var halfExtents = this._modelsAabb.halfExtents;
117
- var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
118
-
119
- this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
120
-
121
- this._removeInertia();
122
-
123
- this._pivotPoint.copy(this._modelsAabb.center);
124
- };
125
-
126
-
127
- OrbitCamera.distanceBetween = new pc.Vec3();
128
-
129
- // Set the camera position to a world position and look at a world position
130
- // Useful if you have multiple viewing angles to swap between in a scene
131
- OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
132
- this.pivotPoint.copy(lookAtPoint);
133
- this.entity.setPosition(resetPoint);
134
-
135
- this.entity.lookAt(lookAtPoint);
136
-
137
- var distance = OrbitCamera.distanceBetween;
138
- distance.sub2(lookAtPoint, resetPoint);
139
- this.distance = distance.length();
140
-
141
- this.pivotPoint.copy(lookAtPoint);
142
-
143
- var cameraQuat = this.entity.getRotation();
144
- this.yaw = this._calcYaw(cameraQuat);
145
- this.pitch = this._calcPitch(cameraQuat, this.yaw);
146
-
147
- this._removeInertia();
148
- this._updatePosition();
149
- };
150
-
151
-
152
- // Set camera position to a world position and look at an entity in the scene
153
- // Useful if you have multiple models to swap between in a scene
154
- OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
155
- this._buildAabb(entity);
156
- this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
157
- };
158
-
159
-
160
- // Set the camera at a specific, yaw, pitch and distance without inertia (instant cut)
161
- OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
162
- this.pitch = pitch;
163
- this.yaw = yaw;
164
- this.distance = distance;
165
-
166
- this._removeInertia();
167
- };
168
-
169
- /////////////////////////////////////////////////////////////////////////////////////////////
170
- // Private methods
171
-
172
- OrbitCamera.prototype.initialize = function () {
173
- var self = this;
174
- var onWindowResize = function () {
175
- self._checkAspectRatio();
176
- };
177
-
178
- window.addEventListener('resize', onWindowResize, false);
179
-
180
- this._checkAspectRatio();
181
-
182
- // Find all the models in the scene that are under the focused entity
183
- this._modelsAabb = new pc.BoundingBox();
184
- this._buildAabb(this.focusEntity || this.app.root);
185
-
186
- this.entity.lookAt(this._modelsAabb.center);
187
-
188
- this._pivotPoint = new pc.Vec3();
189
- this._pivotPoint.copy(this._modelsAabb.center);
190
-
191
- // Calculate the camera euler angle rotation around x and y axes
192
- // This allows us to place the camera at a particular rotation to begin with in the scene
193
- var cameraQuat = this.entity.getRotation();
194
-
195
- // Preset the camera
196
- this._yaw = this._calcYaw(cameraQuat);
197
- this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
198
- this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
199
-
200
- this._distance = 0;
201
-
202
- this._targetYaw = this._yaw;
203
- this._targetPitch = this._pitch;
204
-
205
- // If we have ticked focus on start, then attempt to position the camera where it frames
206
- // the focused entity and move the pivot point to entity's position otherwise, set the distance
207
- // to be between the camera position in the scene and the pivot point
208
- if (this.frameOnStart) {
209
- this.focus(this.focusEntity || this.app.root);
210
- } else {
211
- var distanceBetween = new pc.Vec3();
212
- distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
213
- this._distance = this._clampDistance(distanceBetween.length());
214
- }
215
-
216
- this._targetDistance = this._distance;
217
-
218
- // Reapply the clamps if they are changed in the editor
219
- this.on('attr:distanceMin', function (value, prev) {
220
- this._distance = this._clampDistance(this._distance);
221
- });
222
-
223
- this.on('attr:distanceMax', function (value, prev) {
224
- this._distance = this._clampDistance(this._distance);
225
- });
226
-
227
- this.on('attr:pitchAngleMin', function (value, prev) {
228
- this._pitch = this._clampPitchAngle(this._pitch);
229
- });
230
-
231
- this.on('attr:pitchAngleMax', function (value, prev) {
232
- this._pitch = this._clampPitchAngle(this._pitch);
233
- });
234
-
235
- // Focus on the entity if we change the focus entity
236
- this.on('attr:focusEntity', function (value, prev) {
237
- if (this.frameOnStart) {
238
- this.focus(value || this.app.root);
239
- } else {
240
- this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);
241
- }
242
- });
243
-
244
- this.on('attr:frameOnStart', function (value, prev) {
245
- if (value) {
246
- this.focus(this.focusEntity || this.app.root);
247
- }
248
- });
249
-
250
- this.on('destroy', () => {
251
- window.removeEventListener('resize', onWindowResize, false);
252
- });
253
- };
254
-
255
-
256
- OrbitCamera.prototype.update = function (dt) {
257
- // Add inertia, if any
258
- var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
259
- this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
260
- this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
261
- this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
262
-
263
- this._updatePosition();
264
- };
265
-
266
-
267
- OrbitCamera.prototype._updatePosition = function () {
268
- // Work out the camera position based on the pivot point, pitch, yaw and distance
269
- this.entity.setLocalPosition(0, 0, 0);
270
- this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
271
-
272
- var position = this.entity.getPosition();
273
- position.copy(this.entity.forward);
274
- position.mulScalar(-this._distance);
275
- position.add(this.pivotPoint);
276
- this.entity.setPosition(position);
277
- };
278
-
279
-
280
- OrbitCamera.prototype._removeInertia = function () {
281
- this._yaw = this._targetYaw;
282
- this._pitch = this._targetPitch;
283
- this._distance = this._targetDistance;
284
- };
285
-
286
-
287
- OrbitCamera.prototype._checkAspectRatio = function () {
288
- var height = this.app.graphicsDevice.height;
289
- var width = this.app.graphicsDevice.width;
290
-
291
- // Match the axis of FOV to match the aspect ratio of the canvas so
292
- // the focused entities is always in frame
293
- this.entity.camera.horizontalFov = height > width;
294
- };
295
-
296
-
297
- OrbitCamera.prototype._buildAabb = function (entity) {
298
- var i, m, meshInstances = [];
299
-
300
- var renders = entity.findComponents('render');
301
- for (i = 0; i < renders.length; i++) {
302
- var render = renders[i];
303
- for (m = 0; m < render.meshInstances.length; m++) {
304
- meshInstances.push(render.meshInstances[m]);
305
- }
306
- }
307
-
308
- var models = entity.findComponents('model');
309
- for (i = 0; i < models.length; i++) {
310
- var model = models[i];
311
- for (m = 0; m < model.meshInstances.length; m++) {
312
- meshInstances.push(model.meshInstances[m]);
313
- }
314
- }
315
-
316
- var gsplats = entity.findComponents('gsplat');
317
- for (i = 0; i < gsplats.length; i++) {
318
- var gsplat = gsplats[i];
319
- var instance = gsplat.instance;
320
- if (instance?.meshInstance) {
321
- meshInstances.push(instance.meshInstance);
322
- }
323
- }
324
-
325
- for (i = 0; i < meshInstances.length; i++) {
326
- if (i === 0) {
327
- this._modelsAabb.copy(meshInstances[i].aabb);
328
- } else {
329
- this._modelsAabb.add(meshInstances[i].aabb);
330
- }
331
- }
332
- };
333
-
334
-
335
- OrbitCamera.prototype._calcYaw = function (quat) {
336
- var transformedForward = new pc.Vec3();
337
- quat.transformVector(pc.Vec3.FORWARD, transformedForward);
338
-
339
- return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
340
- };
341
-
342
-
343
- OrbitCamera.prototype._clampDistance = function (distance) {
344
- if (this.distanceMax > 0) {
345
- return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
346
- }
347
- return Math.max(distance, this.distanceMin);
348
-
349
- };
350
-
351
-
352
- OrbitCamera.prototype._clampPitchAngle = function (pitch) {
353
- // Negative due as the pitch is inversed since the camera is orbiting the entity
354
- return pc.math.clamp(pitch, -this.pitchAngleMax, -this.pitchAngleMin);
355
- };
356
-
357
- OrbitCamera.prototype._clampYawAngle = function (yaw) {
358
- // Negative due as the pitch is inversed since the camera is orbiting the entity
359
- return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
360
- };
361
-
362
-
363
- OrbitCamera.quatWithoutYaw = new pc.Quat();
364
- OrbitCamera.yawOffset = new pc.Quat();
365
-
366
- OrbitCamera.prototype._calcPitch = function (quat, yaw) {
367
- var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
368
- var yawOffset = OrbitCamera.yawOffset;
369
-
370
- yawOffset.setFromEulerAngles(0, -yaw, 0);
371
- quatWithoutYaw.mul2(yawOffset, quat);
372
-
373
- var transformedForward = new pc.Vec3();
374
-
375
- quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
376
-
377
- return Math.atan2(transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
378
- };
379
-
380
-
381
- ////////////////////////////////////////////////////////////////////////////////
382
- // Orbit Camera Mouse Input Script //
383
- ////////////////////////////////////////////////////////////////////////////////
384
- var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
385
-
386
- OrbitCameraInputMouse.attributes.add('orbitSensitivity', {
387
- type: 'number',
388
- default: 0.5,
389
- title: 'Orbit Sensitivity',
390
- description: 'How fast the camera moves around the orbit. Higher is faster'
391
- });
392
-
393
- OrbitCameraInputMouse.attributes.add('distanceSensitivity', {
394
- type: 'number',
395
- default: 0.5,
396
- title: 'Distance Sensitivity',
397
- description: 'How fast the camera moves in and out. Higher is faster'
398
- });
399
-
400
- // initialize code called once per entity
401
- OrbitCameraInputMouse.prototype.initialize = function () {
402
- this.orbitCamera = this.entity.script.orbitCamera;
403
-
404
- if (this.orbitCamera) {
405
- var self = this;
406
-
407
- var onMouseOut = function (e) {
408
- self.onMouseOut(e);
409
- };
410
-
411
- this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
412
- this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
413
- this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
414
- this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
415
-
416
- // Listen to when the mouse travels out of the window
417
- window.addEventListener('mouseout', onMouseOut, false);
418
-
419
- // Remove the listeners so if this entity is destroyed
420
- this.on('destroy', function () {
421
- this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
422
- this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
423
- this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
424
- this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
425
-
426
- window.removeEventListener('mouseout', onMouseOut, false);
427
- });
428
- }
429
-
430
- // Disabling the context menu stops the browser displaying a menu when
431
- // you right-click the page
432
- this.app.mouse.disableContextMenu();
433
-
434
- this.lookButtonDown = false;
435
- this.panButtonDown = false;
436
- this.lastPoint = new pc.Vec2();
437
- };
438
-
439
-
440
- OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
441
- OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
442
- OrbitCameraInputMouse.worldDiff = new pc.Vec3();
443
-
444
-
445
- OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
446
- var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
447
- var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
448
- var worldDiff = OrbitCameraInputMouse.worldDiff;
449
-
450
- // For panning to work at any zoom level, we use screen point to world projection
451
- // to work out how far we need to pan the pivotEntity in world space
452
- var camera = this.entity.camera;
453
- var distance = this.orbitCamera.distance;
454
-
455
- camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
456
- camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
457
-
458
- worldDiff.sub2(toWorldPoint, fromWorldPoint);
459
-
460
- this.orbitCamera.pivotPoint.add(worldDiff);
461
- };
462
-
463
-
464
- OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
465
- switch (event.button) {
466
- case pc.MOUSEBUTTON_LEFT:
467
- this.lookButtonDown = true;
468
- break;
469
- case pc.MOUSEBUTTON_MIDDLE:
470
- case pc.MOUSEBUTTON_RIGHT:
471
- this.panButtonDown = true;
472
- break;
473
- }
474
- };
475
-
476
-
477
- OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
478
- switch (event.button) {
479
- case pc.MOUSEBUTTON_LEFT:
480
- this.lookButtonDown = false;
481
- break;
482
- case pc.MOUSEBUTTON_MIDDLE:
483
- case pc.MOUSEBUTTON_RIGHT:
484
- this.panButtonDown = false;
485
- break;
486
- }
487
- };
488
-
489
-
490
- OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
491
- if (this.lookButtonDown) {
492
- this.orbitCamera.pitch -= event.dy * this.orbitSensitivity;
493
- this.orbitCamera.yaw -= event.dx * this.orbitSensitivity;
494
-
495
- } else if (this.panButtonDown) {
496
- this.pan(event);
497
- }
498
-
499
- this.lastPoint.set(event.x, event.y);
500
- };
501
-
502
-
503
- OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
504
- if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
505
- this.orbitCamera.distance -= event.wheelDelta * -2 * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
506
- } else {
507
- this.orbitCamera.orthoHeight -= event.wheelDelta * -2 * this.distanceSensitivity;
508
- }
509
- event.event.preventDefault();
510
- };
511
-
512
-
513
- OrbitCameraInputMouse.prototype.onMouseOut = function (event) {
514
- this.lookButtonDown = false;
515
- this.panButtonDown = false;
516
- };
517
-
518
-
519
- ////////////////////////////////////////////////////////////////////////////////
520
- // Orbit Camera Touch Input Script //
521
- ////////////////////////////////////////////////////////////////////////////////
522
- var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
523
-
524
- OrbitCameraInputTouch.attributes.add('orbitSensitivity', {
525
- type: 'number',
526
- default: 0.6,
527
- title: 'Orbit Sensitivity',
528
- description: 'How fast the camera moves around the orbit. Higher is faster'
529
- });
530
-
531
- OrbitCameraInputTouch.attributes.add('distanceSensitivity', {
532
- type: 'number',
533
- default: 0.5,
534
- title: 'Distance Sensitivity',
535
- description: 'How fast the camera moves in and out. Higher is faster'
536
- });
537
-
538
- // initialize code called once per entity
539
- OrbitCameraInputTouch.prototype.initialize = function () {
540
- this.orbitCamera = this.entity.script.orbitCamera;
541
-
542
- // Store the position of the touch so we can calculate the distance moved
543
- this.lastTouchPoint = new pc.Vec2();
544
- this.lastPinchMidPoint = new pc.Vec2();
545
- this.lastPinchDistance = 0;
546
-
547
- if (this.orbitCamera && this.app.touch) {
548
- // Use the same callback for the touchStart, touchEnd and touchCancel events as they
549
- // all do the same thing which is to deal the possible multiple touches to the screen
550
- this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
551
- this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
552
- this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
553
-
554
- this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
555
-
556
- this.on('destroy', function () {
557
- this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
558
- this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
559
- this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
560
-
561
- this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
562
- });
563
- }
564
- };
565
-
566
-
567
- OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
568
- // Return the distance between the two points
569
- var dx = pointA.x - pointB.x;
570
- var dy = pointA.y - pointB.y;
571
-
572
- return Math.sqrt((dx * dx) + (dy * dy));
573
- };
574
-
575
-
576
- OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
577
- result.set(pointB.x - pointA.x, pointB.y - pointA.y);
578
- result.mulScalar(0.5);
579
- result.x += pointA.x;
580
- result.y += pointA.y;
581
- };
582
-
583
-
584
- OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
585
- // We only care about the first touch for camera rotation. As the user touches the screen,
586
- // we stored the current touch position
587
- var touches = event.touches;
588
- if (touches.length === 1) {
589
- this.lastTouchPoint.set(touches[0].x, touches[0].y);
590
-
591
- } else if (touches.length === 2) {
592
- // If there are 2 touches on the screen, then set the pinch distance
593
- this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
594
- this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
595
- }
596
- };
597
-
598
-
599
- OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
600
- OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
601
- OrbitCameraInputTouch.worldDiff = new pc.Vec3();
602
-
603
-
604
- OrbitCameraInputTouch.prototype.pan = function (midPoint) {
605
- var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
606
- var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
607
- var worldDiff = OrbitCameraInputTouch.worldDiff;
608
-
609
- // For panning to work at any zoom level, we use screen point to world projection
610
- // to work out how far we need to pan the pivotEntity in world space
611
- var camera = this.entity.camera;
612
- var distance = this.orbitCamera.distance;
613
-
614
- camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
615
- camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
616
-
617
- worldDiff.sub2(toWorldPoint, fromWorldPoint);
618
-
619
- this.orbitCamera.pivotPoint.add(worldDiff);
620
- };
621
-
622
-
623
- OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
624
-
625
- OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
626
- var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
627
-
628
- // We only care about the first touch for camera rotation. Work out the difference moved since the last event
629
- // and use that to update the camera target position
630
- var touches = event.touches;
631
- if (touches.length === 1) {
632
- var touch = touches[0];
633
-
634
- this.orbitCamera.pitch -= (touch.y - this.lastTouchPoint.y) * this.orbitSensitivity;
635
- this.orbitCamera.yaw -= (touch.x - this.lastTouchPoint.x) * this.orbitSensitivity;
636
-
637
- this.lastTouchPoint.set(touch.x, touch.y);
638
-
639
- } else if (touches.length === 2) {
640
- // Calculate the difference in pinch distance since the last event
641
- var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
642
- var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
643
- this.lastPinchDistance = currentPinchDistance;
644
-
645
- this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
646
-
647
- // Calculate pan difference
648
- this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
649
- this.pan(pinchMidPoint);
650
- this.lastPinchMidPoint.copy(pinchMidPoint);
651
- }
652
- };
 
1
+ ////////////////////////////////////////////////////////////////////////////////
2
+ // Orbit Camera Script //
3
+ ////////////////////////////////////////////////////////////////////////////////
4
+ var OrbitCamera = pc.createScript('orbitCamera');
5
+
6
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 0, title: 'Distance Max', description: 'Setting this at 0 will give an infinite distance limit' });
7
+ OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 0, title: 'Distance Min' });
8
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 45, title: 'Pitch Angle Max (degrees)' });
9
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: -45, title: 'Pitch Angle Min (degrees)' });
10
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 70, title: 'Yaw Angle Max (degrees)' });
11
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -70, title: 'Yaw Angle Min (degrees)' });
12
+
13
+
14
+ OrbitCamera.attributes.add('inertiaFactor', {
15
+ type: 'number',
16
+ default: 0,
17
+ title: 'Inertia Factor',
18
+ description: 'Higher value means that the camera will continue moving after the user has stopped dragging. 0 is fully responsive.'
19
+ });
20
+
21
+ OrbitCamera.attributes.add('focusEntity', {
22
+ type: 'entity',
23
+ title: 'Focus Entity',
24
+ description: 'Entity for the camera to focus on. If blank, then the camera will use the whole scene'
25
+ });
26
+
27
+ OrbitCamera.attributes.add('frameOnStart', {
28
+ type: 'boolean',
29
+ default: true,
30
+ title: 'Frame on Start',
31
+ description: 'Frames the entity or scene at the start of the application."'
32
+ });
33
+
34
+
35
+ // Property to get and set the distance between the pivot point and camera
36
+ // Clamped between this.distanceMin and this.distanceMax
37
+ Object.defineProperty(OrbitCamera.prototype, 'distance', {
38
+ get: function () {
39
+ return this._targetDistance;
40
+ },
41
+
42
+ set: function (value) {
43
+ this._targetDistance = this._clampDistance(value);
44
+ }
45
+ });
46
+
47
+ // Property to get and set the camera orthoHeight
48
+ // Clamped above 0
49
+ Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
50
+ get: function () {
51
+ return this.entity.camera.orthoHeight;
52
+ },
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 of the camera around the pivot point (degrees)
61
+ // Clamped between this.pitchAngleMin and this.pitchAngleMax
62
+ // When set at 0, the camera angle is flat, looking along the horizon
63
+ Object.defineProperty(OrbitCamera.prototype, 'pitch', {
64
+ get: function () {
65
+ return this._targetPitch;
66
+ },
67
+
68
+ set: function (value) {
69
+ this._targetPitch = this._clampPitchAngle(value);
70
+ }
71
+ });
72
+
73
+
74
+ // Property to get and set the yaw of the camera around the pivot point (degrees)
75
+ Object.defineProperty(OrbitCamera.prototype, 'yaw', {
76
+ get: function () {
77
+ return this._targetYaw;
78
+ },
79
+
80
+ set: function (value) {
81
+ this._targetYaw = this._clampYawAngle(value);
82
+
83
+ // Ensure that the yaw takes the shortest route by making sure that
84
+ // the difference between the targetYaw and the actual is 180 degrees
85
+ // in either direction
86
+ /*var diff = this._targetYaw - this._yaw;
87
+ var reminder = diff % 360;
88
+ if (reminder > 180) {
89
+ this._targetYaw = this._yaw - (360 - reminder);
90
+ } else if (reminder < -180) {
91
+ this._targetYaw = this._yaw + (360 + reminder);
92
+ } else {
93
+ this._targetYaw = this._yaw + reminder;
94
+ }*/
95
+ }
96
+ });
97
+
98
+
99
+ // Property to get and set the world position of the pivot point that the camera orbits around
100
+ Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
101
+ get: function () {
102
+ return this._pivotPoint;
103
+ },
104
+
105
+ set: function (value) {
106
+ this._pivotPoint.copy(value);
107
+ }
108
+ });
109
+
110
+
111
+ // Moves the camera to look at an entity and all its children so they are all in the view
112
+ OrbitCamera.prototype.focus = function (focusEntity) {
113
+ // Calculate an bounding box that encompasses all the models to frame in the camera view
114
+ this._buildAabb(focusEntity);
115
+
116
+ var halfExtents = this._modelsAabb.halfExtents;
117
+ var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
118
+
119
+ this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
120
+
121
+ this._removeInertia();
122
+
123
+ this._pivotPoint.copy(this._modelsAabb.center);
124
+ };
125
+
126
+
127
+ OrbitCamera.distanceBetween = new pc.Vec3();
128
+
129
+ // Set the camera position to a world position and look at a world position
130
+ // Useful if you have multiple viewing angles to swap between in a scene
131
+ OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
132
+ this.pivotPoint.copy(lookAtPoint);
133
+ this.entity.setPosition(resetPoint);
134
+
135
+ this.entity.lookAt(lookAtPoint);
136
+
137
+ var distance = OrbitCamera.distanceBetween;
138
+ distance.sub2(lookAtPoint, resetPoint);
139
+ this.distance = distance.length();
140
+
141
+ this.pivotPoint.copy(lookAtPoint);
142
+
143
+ var cameraQuat = this.entity.getRotation();
144
+ this.yaw = this._calcYaw(cameraQuat);
145
+ this.pitch = this._calcPitch(cameraQuat, this.yaw);
146
+
147
+ this._removeInertia();
148
+ this._updatePosition();
149
+ };
150
+
151
+
152
+ // Set camera position to a world position and look at an entity in the scene
153
+ // Useful if you have multiple models to swap between in a scene
154
+ OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
155
+ this._buildAabb(entity);
156
+ this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
157
+ };
158
+
159
+
160
+ // Set the camera at a specific, yaw, pitch and distance without inertia (instant cut)
161
+ OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
162
+ this.pitch = pitch;
163
+ this.yaw = yaw;
164
+ this.distance = distance;
165
+
166
+ this._removeInertia();
167
+ };
168
+
169
+ /////////////////////////////////////////////////////////////////////////////////////////////
170
+ // Private methods
171
+
172
+ OrbitCamera.prototype.initialize = function () {
173
+ var self = this;
174
+ var onWindowResize = function () {
175
+ self._checkAspectRatio();
176
+ };
177
+
178
+ window.addEventListener('resize', onWindowResize, false);
179
+
180
+ this._checkAspectRatio();
181
+
182
+ // Find all the models in the scene that are under the focused entity
183
+ this._modelsAabb = new pc.BoundingBox();
184
+ this._buildAabb(this.focusEntity || this.app.root);
185
+
186
+ this.entity.lookAt(this._modelsAabb.center);
187
+
188
+ this._pivotPoint = new pc.Vec3();
189
+ this._pivotPoint.copy(this._modelsAabb.center);
190
+
191
+ // Calculate the camera euler angle rotation around x and y axes
192
+ // This allows us to place the camera at a particular rotation to begin with in the scene
193
+ var cameraQuat = this.entity.getRotation();
194
+
195
+ // Preset the camera
196
+ this._yaw = this._calcYaw(cameraQuat);
197
+ this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
198
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
199
+
200
+ this._distance = 0;
201
+
202
+ this._targetYaw = this._yaw;
203
+ this._targetPitch = this._pitch;
204
+
205
+ // If we have ticked focus on start, then attempt to position the camera where it frames
206
+ // the focused entity and move the pivot point to entity's position otherwise, set the distance
207
+ // to be between the camera position in the scene and the pivot point
208
+ if (this.frameOnStart) {
209
+ this.focus(this.focusEntity || this.app.root);
210
+ } else {
211
+ var distanceBetween = new pc.Vec3();
212
+ distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
213
+ this._distance = this._clampDistance(distanceBetween.length());
214
+ }
215
+
216
+ this._targetDistance = this._distance;
217
+
218
+ // Reapply the clamps if they are changed in the editor
219
+ this.on('attr:distanceMin', function (value, prev) {
220
+ this._distance = this._clampDistance(this._distance);
221
+ });
222
+
223
+ this.on('attr:distanceMax', function (value, prev) {
224
+ this._distance = this._clampDistance(this._distance);
225
+ });
226
+
227
+ this.on('attr:pitchAngleMin', function (value, prev) {
228
+ this._pitch = this._clampPitchAngle(this._pitch);
229
+ });
230
+
231
+ this.on('attr:pitchAngleMax', function (value, prev) {
232
+ this._pitch = this._clampPitchAngle(this._pitch);
233
+ });
234
+
235
+ // Focus on the entity if we change the focus entity
236
+ this.on('attr:focusEntity', function (value, prev) {
237
+ if (this.frameOnStart) {
238
+ this.focus(value || this.app.root);
239
+ } else {
240
+ this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);
241
+ }
242
+ });
243
+
244
+ this.on('attr:frameOnStart', function (value, prev) {
245
+ if (value) {
246
+ this.focus(this.focusEntity || this.app.root);
247
+ }
248
+ });
249
+
250
+ this.on('destroy', () => {
251
+ window.removeEventListener('resize', onWindowResize, false);
252
+ });
253
+ };
254
+
255
+
256
+ OrbitCamera.prototype.update = function (dt) {
257
+ // Add inertia, if any
258
+ var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
259
+ this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
260
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
261
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
262
+
263
+ this._updatePosition();
264
+ };
265
+
266
+
267
+ OrbitCamera.prototype._updatePosition = function () {
268
+ // Work out the camera position based on the pivot point, pitch, yaw and distance
269
+ this.entity.setLocalPosition(0, 0, 0);
270
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
271
+
272
+ var position = this.entity.getPosition();
273
+ position.copy(this.entity.forward);
274
+ position.mulScalar(-this._distance);
275
+ position.add(this.pivotPoint);
276
+ this.entity.setPosition(position);
277
+ };
278
+
279
+
280
+ OrbitCamera.prototype._removeInertia = function () {
281
+ this._yaw = this._targetYaw;
282
+ this._pitch = this._targetPitch;
283
+ this._distance = this._targetDistance;
284
+ };
285
+
286
+
287
+ OrbitCamera.prototype._checkAspectRatio = function () {
288
+ var height = this.app.graphicsDevice.height;
289
+ var width = this.app.graphicsDevice.width;
290
+
291
+ // Match the axis of FOV to match the aspect ratio of the canvas so
292
+ // the focused entities is always in frame
293
+ this.entity.camera.horizontalFov = height > width;
294
+ };
295
+
296
+
297
+ OrbitCamera.prototype._buildAabb = function (entity) {
298
+ var i, m, meshInstances = [];
299
+
300
+ var renders = entity.findComponents('render');
301
+ for (i = 0; i < renders.length; i++) {
302
+ var render = renders[i];
303
+ for (m = 0; m < render.meshInstances.length; m++) {
304
+ meshInstances.push(render.meshInstances[m]);
305
+ }
306
+ }
307
+
308
+ var models = entity.findComponents('model');
309
+ for (i = 0; i < models.length; i++) {
310
+ var model = models[i];
311
+ for (m = 0; m < model.meshInstances.length; m++) {
312
+ meshInstances.push(model.meshInstances[m]);
313
+ }
314
+ }
315
+
316
+ var gsplats = entity.findComponents('gsplat');
317
+ for (i = 0; i < gsplats.length; i++) {
318
+ var gsplat = gsplats[i];
319
+ var instance = gsplat.instance;
320
+ if (instance?.meshInstance) {
321
+ meshInstances.push(instance.meshInstance);
322
+ }
323
+ }
324
+
325
+ for (i = 0; i < meshInstances.length; i++) {
326
+ if (i === 0) {
327
+ this._modelsAabb.copy(meshInstances[i].aabb);
328
+ } else {
329
+ this._modelsAabb.add(meshInstances[i].aabb);
330
+ }
331
+ }
332
+ };
333
+
334
+
335
+ OrbitCamera.prototype._calcYaw = function (quat) {
336
+ var transformedForward = new pc.Vec3();
337
+ quat.transformVector(pc.Vec3.FORWARD, transformedForward);
338
+
339
+ return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
340
+ };
341
+
342
+
343
+ OrbitCamera.prototype._clampDistance = function (distance) {
344
+ if (this.distanceMax > 0) {
345
+ return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
346
+ }
347
+ return Math.max(distance, this.distanceMin);
348
+
349
+ };
350
+
351
+
352
+ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
353
+ // Negative due as the pitch is inversed since the camera is orbiting the entity
354
+ return pc.math.clamp(pitch, -this.pitchAngleMax, -this.pitchAngleMin);
355
+ };
356
+
357
+ OrbitCamera.prototype._clampYawAngle = function (yaw) {
358
+ // Negative due as the pitch is inversed since the camera is orbiting the entity
359
+ return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
360
+ };
361
+
362
+
363
+ OrbitCamera.quatWithoutYaw = new pc.Quat();
364
+ OrbitCamera.yawOffset = new pc.Quat();
365
+
366
+ OrbitCamera.prototype._calcPitch = function (quat, yaw) {
367
+ var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
368
+ var yawOffset = OrbitCamera.yawOffset;
369
+
370
+ yawOffset.setFromEulerAngles(0, -yaw, 0);
371
+ quatWithoutYaw.mul2(yawOffset, quat);
372
+
373
+ var transformedForward = new pc.Vec3();
374
+
375
+ quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
376
+
377
+ return Math.atan2(transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
378
+ };
379
+
380
+
381
+ ////////////////////////////////////////////////////////////////////////////////
382
+ // Orbit Camera Mouse Input Script //
383
+ ////////////////////////////////////////////////////////////////////////////////
384
+ var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
385
+
386
+ OrbitCameraInputMouse.attributes.add('orbitSensitivity', {
387
+ type: 'number',
388
+ default: 0.5,
389
+ title: 'Orbit Sensitivity',
390
+ description: 'How fast the camera moves around the orbit. Higher is faster'
391
+ });
392
+
393
+ OrbitCameraInputMouse.attributes.add('distanceSensitivity', {
394
+ type: 'number',
395
+ default: 0.5,
396
+ title: 'Distance Sensitivity',
397
+ description: 'How fast the camera moves in and out. Higher is faster'
398
+ });
399
+
400
+ // initialize code called once per entity
401
+ OrbitCameraInputMouse.prototype.initialize = function () {
402
+ this.orbitCamera = this.entity.script.orbitCamera;
403
+
404
+ if (this.orbitCamera) {
405
+ var self = this;
406
+
407
+ var onMouseOut = function (e) {
408
+ self.onMouseOut(e);
409
+ };
410
+
411
+ this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
412
+ this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
413
+ this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
414
+ this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
415
+
416
+ // Listen to when the mouse travels out of the window
417
+ window.addEventListener('mouseout', onMouseOut, false);
418
+
419
+ // Remove the listeners so if this entity is destroyed
420
+ this.on('destroy', function () {
421
+ this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
422
+ this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
423
+ this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
424
+ this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
425
+
426
+ window.removeEventListener('mouseout', onMouseOut, false);
427
+ });
428
+ }
429
+
430
+ // Disabling the context menu stops the browser displaying a menu when
431
+ // you right-click the page
432
+ this.app.mouse.disableContextMenu();
433
+
434
+ this.lookButtonDown = false;
435
+ this.panButtonDown = false;
436
+ this.lastPoint = new pc.Vec2();
437
+ };
438
+
439
+
440
+ OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
441
+ OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
442
+ OrbitCameraInputMouse.worldDiff = new pc.Vec3();
443
+
444
+
445
+ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
446
+ var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
447
+ var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
448
+ var worldDiff = OrbitCameraInputMouse.worldDiff;
449
+
450
+ // For panning to work at any zoom level, we use screen point to world projection
451
+ // to work out how far we need to pan the pivotEntity in world space
452
+ var camera = this.entity.camera;
453
+ var distance = this.orbitCamera.distance;
454
+
455
+ camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
456
+ camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
457
+
458
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
459
+
460
+ this.orbitCamera.pivotPoint.add(worldDiff);
461
+ };
462
+
463
+
464
+ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
465
+ switch (event.button) {
466
+ case pc.MOUSEBUTTON_LEFT:
467
+ this.lookButtonDown = true;
468
+ break;
469
+ case pc.MOUSEBUTTON_MIDDLE:
470
+ case pc.MOUSEBUTTON_RIGHT:
471
+ this.panButtonDown = true;
472
+ break;
473
+ }
474
+ };
475
+
476
+
477
+ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
478
+ switch (event.button) {
479
+ case pc.MOUSEBUTTON_LEFT:
480
+ this.lookButtonDown = false;
481
+ break;
482
+ case pc.MOUSEBUTTON_MIDDLE:
483
+ case pc.MOUSEBUTTON_RIGHT:
484
+ this.panButtonDown = false;
485
+ break;
486
+ }
487
+ };
488
+
489
+
490
+ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
491
+ if (this.lookButtonDown) {
492
+ this.orbitCamera.pitch -= event.dy * this.orbitSensitivity;
493
+ this.orbitCamera.yaw -= event.dx * this.orbitSensitivity;
494
+
495
+ } else if (this.panButtonDown) {
496
+ this.pan(event);
497
+ }
498
+
499
+ this.lastPoint.set(event.x, event.y);
500
+ };
501
+
502
+
503
+ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
504
+ if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
505
+ this.orbitCamera.distance -= event.wheelDelta * -2 * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
506
+ } else {
507
+ this.orbitCamera.orthoHeight -= event.wheelDelta * -2 * this.distanceSensitivity;
508
+ }
509
+ event.event.preventDefault();
510
+ };
511
+
512
+
513
+ OrbitCameraInputMouse.prototype.onMouseOut = function (event) {
514
+ this.lookButtonDown = false;
515
+ this.panButtonDown = false;
516
+ };
517
+
518
+
519
+ ////////////////////////////////////////////////////////////////////////////////
520
+ // Orbit Camera Touch Input Script //
521
+ ////////////////////////////////////////////////////////////////////////////////
522
+ var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
523
+
524
+ OrbitCameraInputTouch.attributes.add('orbitSensitivity', {
525
+ type: 'number',
526
+ default: 0.6,
527
+ title: 'Orbit Sensitivity',
528
+ description: 'How fast the camera moves around the orbit. Higher is faster'
529
+ });
530
+
531
+ OrbitCameraInputTouch.attributes.add('distanceSensitivity', {
532
+ type: 'number',
533
+ default: 0.5,
534
+ title: 'Distance Sensitivity',
535
+ description: 'How fast the camera moves in and out. Higher is faster'
536
+ });
537
+
538
+ // initialize code called once per entity
539
+ OrbitCameraInputTouch.prototype.initialize = function () {
540
+ this.orbitCamera = this.entity.script.orbitCamera;
541
+
542
+ // Store the position of the touch so we can calculate the distance moved
543
+ this.lastTouchPoint = new pc.Vec2();
544
+ this.lastPinchMidPoint = new pc.Vec2();
545
+ this.lastPinchDistance = 0;
546
+
547
+ if (this.orbitCamera && this.app.touch) {
548
+ // Use the same callback for the touchStart, touchEnd and touchCancel events as they
549
+ // all do the same thing which is to deal the possible multiple touches to the screen
550
+ this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
551
+ this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
552
+ this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
553
+
554
+ this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
555
+
556
+ this.on('destroy', function () {
557
+ this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
558
+ this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
559
+ this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
560
+
561
+ this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
562
+ });
563
+ }
564
+ };
565
+
566
+
567
+ OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
568
+ // Return the distance between the two points
569
+ var dx = pointA.x - pointB.x;
570
+ var dy = pointA.y - pointB.y;
571
+
572
+ return Math.sqrt((dx * dx) + (dy * dy));
573
+ };
574
+
575
+
576
+ OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
577
+ result.set(pointB.x - pointA.x, pointB.y - pointA.y);
578
+ result.mulScalar(0.5);
579
+ result.x += pointA.x;
580
+ result.y += pointA.y;
581
+ };
582
+
583
+
584
+ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
585
+ // We only care about the first touch for camera rotation. As the user touches the screen,
586
+ // we stored the current touch position
587
+ var touches = event.touches;
588
+ if (touches.length === 1) {
589
+ this.lastTouchPoint.set(touches[0].x, touches[0].y);
590
+
591
+ } else if (touches.length === 2) {
592
+ // If there are 2 touches on the screen, then set the pinch distance
593
+ this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
594
+ this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
595
+ }
596
+ };
597
+
598
+
599
+ OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
600
+ OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
601
+ OrbitCameraInputTouch.worldDiff = new pc.Vec3();
602
+
603
+
604
+ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
605
+ var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
606
+ var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
607
+ var worldDiff = OrbitCameraInputTouch.worldDiff;
608
+
609
+ // For panning to work at any zoom level, we use screen point to world projection
610
+ // to work out how far we need to pan the pivotEntity in world space
611
+ var camera = this.entity.camera;
612
+ var distance = this.orbitCamera.distance;
613
+
614
+ camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
615
+ camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
616
+
617
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
618
+
619
+ this.orbitCamera.pivotPoint.add(worldDiff);
620
+ };
621
+
622
+
623
+ OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
624
+
625
+ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
626
+ var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
627
+
628
+ // We only care about the first touch for camera rotation. Work out the difference moved since the last event
629
+ // and use that to update the camera target position
630
+ var touches = event.touches;
631
+ if (touches.length === 1) {
632
+ var touch = touches[0];
633
+
634
+ this.orbitCamera.pitch -= (touch.y - this.lastTouchPoint.y) * this.orbitSensitivity;
635
+ this.orbitCamera.yaw -= (touch.x - this.lastTouchPoint.x) * this.orbitSensitivity;
636
+
637
+ this.lastTouchPoint.set(touch.x, touch.y);
638
+
639
+ } else if (touches.length === 2) {
640
+ // Calculate the difference in pinch distance since the last event
641
+ var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
642
+ var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
643
+ this.lastPinchDistance = currentPinchDistance;
644
+
645
+ this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
646
+
647
+ // Calculate pan difference
648
+ this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
649
+ this.pan(pinchMidPoint);
650
+ this.lastPinchMidPoint.copy(pinchMidPoint);
651
+ }
652
+ };