bilca commited on
Commit
428618a
·
verified ·
1 Parent(s): 55768bf

Update orbit-camera.js

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