bilca commited on
Commit
9554ecc
·
verified ·
1 Parent(s): 9c6c6bc

Create camera-controls/orbit-camera.js

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