├── .gitignore
├── LICENSE
├── NPM-INSTRUCTIONS.md
├── README.md
├── dist
├── .gitkeep
├── aframe-orbit-controls-component.js
└── aframe-orbit-controls-component.min.js
├── examples
├── assets
│ ├── index.html
│ └── sky.jpg
├── basic
│ └── index.html
├── build.js
├── camera-children
│ └── index.html
├── index.html
├── main.js
└── rotate-to
│ └── index.html
├── index.js
├── package-lock.json
├── package.json
└── scripts
└── unboil.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .sw[ponm]
2 | examples/node_modules/
3 | gh-pages
4 | node_modules/
5 | npm-debug.log
6 | NPM-INSTRUCTIONS.md
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Kevin Ngo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/NPM-INSTRUCTIONS.md:
--------------------------------------------------------------------------------
1 | ### Update on npm
2 |
3 | Do changes.
4 | Make sure to update README.md to next NPM version.
5 |
6 | run `npm build`
7 | run `npm dist`
8 |
9 | Push changes on git locally.
10 |
11 | bump NPM version, e.g.
12 | `npm version patch -m "Upgrade for reasons"`
13 |
14 | Push git to remote.
15 | Make sure to push tags to remote as well.
16 |
17 | Publish to NPM.
18 | `npm publish`
19 |
20 | ---
21 |
22 | ### Merging a PR
23 |
24 | Review and accept PR on GitHub.
25 |
26 | Pull locally from remote repository.
27 |
28 | Do NPM steps as outline above.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## aframe-orbit-controls-component
2 |
3 | ***
4 | Please use this more performant component instead: https://github.com/ngokevin/kframe/tree/master/components/orbit-controls
5 | ***
6 |
7 |
8 | A (almost) direct port of the ThreeJS Orbit Controls for [A-Frame](https://aframe.io).
9 | It allows users to rotate the camera around an object. Might be useful as a fallback to VR mode. Automatically switches to look-controls in VR mode.
10 |
11 | Have a look at the [examples](https://tizzle.github.io/aframe-orbit-controls-component/)
12 |
13 | ### API
14 |
15 | | Property | Description | Default Value |
16 | | ---------- | ----------- | ------------- |
17 | | enabled | Boolean – defines if the Orbit Controls are used | false
18 | | target | String – the object the camera is looking at and orbits around | '' |
19 | | distance | Number – the distance of the camera to the target | 1 |
20 | | enableRotate | Boolean – defines if the camera can be rotated | true |
21 | | rotateSpeed | Number – rotation speed | 1 |
22 | | enableZoom | Boolean – defines if the camera can be zoomed in or out | true |
23 | | invertZoom | Boolean – defines if zooming is inverted | false |
24 | | zoomSpeed | Number – zoom speed | 1 |
25 | | enablePan | Boolean – defines if the camera can be panned (using the arrow keys) | true |
26 | | keyPanSpeed | Number – panning speed | 7 |
27 | | enableDamping | Boolean – defines if the rotational movement of the camera is damped / eased | false |
28 | | dampingFactor | Number – damping factor | 0.25 |
29 | | autoRotate | Boolean – defines if the camera automatically rotates around the target | false |
30 | | autoRotateSpeed | Number – speed of the automatic rotation | 2 |
31 | | enableKeys | Boolean – defines if the keyboard can be used | true |
32 | | minAzimuthAngle | Number – minimum azimuth angle – Defines how far you can orbit horizontally, lower limit | -Infinity |
33 | | maxAzimuthAngle | Number – maximum azimuth angle – Defines how far you can orbit horizontally, upper limit | Infinity |
34 | | minPolarAngle | Number – minimum polar angle – Defines how far you can orbit vertically, lower limit | 0 |
35 | | maxPolarAngle | Number – maximum polar angle – Defines how far you can orbit vertically, upper limit | Math.PI |
36 | | minZoom | Number – minimum zoom value – Defines how far you can zoom out for Orthographic Cameras | 0 |
37 | | maxZoom | Number – maximum zoom value – Defines how far you can zoom in for Orthographic Cameras | Infinity |
38 | | minDistance | Number – minimum distance – Defines how far you can zoom in for Perspective Cameras | 0 |
39 | | maxDistance | Number – maximum distance – Defines how far you can zoom out for Perspective Cameras | Infinity |
40 | | rotateTo | Vector3 – position to rotate automatically to | {x:0,y:0,z:0} |
41 | | rotateToSpeed | Number – rotateTo speed | 0.05 |
42 | | logPosition | Boolean – prints out camera position to console.log() when rotating | true |
43 | | autoVRLookCam | Boolean - automatically switch over to look-controls in VR mode? | true |
44 |
45 | ### Installation
46 |
47 | #### Browser
48 |
49 | Install and use by directly including the [browser files](dist):
50 |
51 | ```html
52 |
53 | A-Frame using a Camera with Orbit Controls
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | ```
90 |
91 |
92 | #### npm
93 |
94 | Install via npm:
95 |
96 | ```bash
97 | npm install aframe-orbit-controls-component-2
98 | ```
99 |
100 | Then register and use.
101 |
102 | ```js
103 | require('aframe');
104 | require('aframe-orbit-controls-component-2');
105 | ```
106 |
107 | Alternatively, include as a `
110 | ```
111 | When the user enters VR mode, `orbit-controls` will pause itself and switch to the `look-controls` attached to the same camera. If no `look-controls` is specified on the current camera, one will be created with the default settings (this usually works fine). If you do not want this behaviour (probably becuase you want to control the camera juggling behaviour yourself) just specify `autoVRLookCam:false`.
112 |
--------------------------------------------------------------------------------
/dist/.gitkeep:
--------------------------------------------------------------------------------
1 | `npm run dist` to generate browser files.
2 |
--------------------------------------------------------------------------------
/dist/aframe-orbit-controls-component.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ (function(module, exports) {
46 |
47 | /* global AFRAME THREE */
48 |
49 | if (typeof AFRAME === 'undefined') {
50 | throw new Error('Component attempted to register before AFRAME was available.');
51 | }
52 |
53 | var radToDeg = THREE.Math.radToDeg;
54 |
55 | /**
56 | * Example component for A-Frame.
57 | */
58 | AFRAME.registerComponent('orbit-controls', {
59 | dependencies: ['position', 'rotation'],
60 | schema: {
61 | enabled: {
62 | default: true
63 | },
64 | target: {
65 | default: ''
66 | },
67 | distance: {
68 | default: 1
69 | },
70 | enableRotate: {
71 | default: true
72 | },
73 | rotateSpeed: {
74 | default: 1.0
75 | },
76 | enableZoom: {
77 | default: true
78 | },
79 | zoomSpeed: {
80 | default: 1.0
81 | },
82 | enablePan: {
83 | default: true
84 | },
85 | keyPanSpeed: {
86 | default: 7.0
87 | },
88 | enableDamping: {
89 | default: false
90 | },
91 | dampingFactor: {
92 | default: 0.25
93 | },
94 | autoRotate: {
95 | default: false
96 | },
97 | autoRotateSpeed: {
98 | default: 2.0
99 | },
100 | enableKeys: {
101 | default: true
102 | },
103 | minAzimuthAngle: {
104 | default: -Infinity
105 | },
106 | maxAzimuthAngle: {
107 | default: Infinity
108 | },
109 | minPolarAngle: {
110 | default: 0
111 | },
112 | maxPolarAngle: {
113 | default: Math.PI
114 | },
115 | minZoom: {
116 | default: 0
117 | },
118 | maxZoom: {
119 | default: Infinity
120 | },
121 | invertZoom: {
122 | default: false
123 | },
124 | minDistance: {
125 | default: 0
126 | },
127 | maxDistance: {
128 | default: Infinity
129 | },
130 | rotateTo: {
131 | type: 'vec3',
132 | default: {x: 0, y: 0, z: 0}
133 | },
134 | rotateToSpeed: {
135 | type: 'number',
136 | default: 0.05
137 | },
138 | logPosition: {
139 | type: 'boolean',
140 | default: false
141 | },
142 | autoVRLookCam: {
143 | type: 'boolean',
144 | default: true
145 | }
146 | },
147 |
148 | /**
149 | * Set if component needs multiple instancing.
150 | */
151 | multiple: false,
152 |
153 | /**
154 | * Called once when component is attached. Generally for initial setup.
155 | */
156 | init: function () {
157 | this.sceneEl = this.el.sceneEl;
158 | this.object = this.el.object3D;
159 | this.target = this.sceneEl.querySelector(this.data.target).object3D.position;
160 |
161 | console.log('enabled: ', this.data.enabled);
162 |
163 | // Find the look-controls component on this camera, or create if it doesn't exist.
164 | this.isRunning = false;
165 | this.lookControls = null;
166 |
167 | if (this.data.autoVRLookCam) {
168 | if (this.el.components['look-controls']) {
169 | this.lookControls = this.el.components['look-controls'];
170 | } else {
171 | this.el.setAttribute('look-controls', '');
172 | this.lookControls = this.el.components['look-controls'];
173 | }
174 | this.lookControls.pause();
175 | this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR.bind(this), false);
176 | this.el.sceneEl.addEventListener('exit-vr', this.onExitVR.bind(this), false);
177 | }
178 |
179 | this.dolly = new THREE.Object3D();
180 | this.dolly.position.copy(this.object.position);
181 |
182 | this.savedPose = null;
183 |
184 | this.STATE = {
185 | NONE: -1,
186 | ROTATE: 0,
187 | DOLLY: 1,
188 | PAN: 2,
189 | TOUCH_ROTATE: 3,
190 | TOUCH_DOLLY: 4,
191 | TOUCH_PAN: 5,
192 | ROTATE_TO: 6
193 | };
194 |
195 | this.state = this.STATE.NONE;
196 |
197 | this.EPS = 0.000001;
198 | this.lastPosition = new THREE.Vector3();
199 | this.lastQuaternion = new THREE.Quaternion();
200 |
201 | this.spherical = new THREE.Spherical();
202 | this.sphericalDelta = new THREE.Spherical();
203 |
204 | this.scale = 1.0;
205 | this.zoomChanged = false;
206 |
207 | this.rotateStart = new THREE.Vector2();
208 | this.rotateEnd = new THREE.Vector2();
209 | this.rotateDelta = new THREE.Vector2();
210 |
211 | this.panStart = new THREE.Vector2();
212 | this.panEnd = new THREE.Vector2();
213 | this.panDelta = new THREE.Vector2();
214 | this.panOffset = new THREE.Vector3();
215 |
216 | this.dollyStart = new THREE.Vector2();
217 | this.dollyEnd = new THREE.Vector2();
218 | this.dollyDelta = new THREE.Vector2();
219 |
220 | this.vector = new THREE.Vector3();
221 | this.desiredPosition = new THREE.Vector3();
222 |
223 | this.mouseButtons = {
224 | ORBIT: THREE.MOUSE.LEFT,
225 | ZOOM: THREE.MOUSE.MIDDLE,
226 | PAN: THREE.MOUSE.RIGHT
227 | };
228 |
229 | this.keys = {
230 | LEFT: 37,
231 | UP: 38,
232 | RIGHT: 39,
233 | BOTTOM: 40
234 | };
235 |
236 | this.bindMethods();
237 | },
238 |
239 | /**
240 | * Called when component is attached and when component data changes.
241 | * Generally modifies the entity based on the data.
242 | */
243 | update: function (oldData) {
244 | console.log('component update');
245 |
246 | if (this.data.rotateTo) {
247 | var rotateToVec3 = new THREE.Vector3(this.data.rotateTo.x, this.data.rotateTo.y, this.data.rotateTo.z);
248 | // Check if rotateToVec3 is already desiredPosition
249 | if (!this.desiredPosition.equals(rotateToVec3)) {
250 | this.desiredPosition.copy(rotateToVec3);
251 | this.rotateTo(this.desiredPosition);
252 | }
253 | }
254 |
255 | this.dolly.position.copy(this.object.position);
256 | this.updateView(true);
257 | },
258 |
259 | /**
260 | * Called when a component is removed (e.g., via removeAttribute).
261 | * Generally undoes all modifications to the entity.
262 | */
263 | remove: function () {
264 | // console.log("component remove");
265 | this.isRunning = false;
266 | this.removeEventListeners();
267 | this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR, false);
268 | this.el.sceneEl.removeEventListener('exit-vr', this.onExitVR, false);
269 | },
270 |
271 | /**
272 | * Called on each scene tick.
273 | */
274 | tick: function (t) {
275 | var render = this.data.enabled && this.isRunning ? this.updateView() : false;
276 | if (render === true && this.data.logPosition === true) {
277 | console.log(this.el.object3D.position);
278 | }
279 | },
280 |
281 | /*
282 | * Called when entering VR mode
283 | */
284 | onEnterVR: function (event) {
285 | // console.log('enter vr', this);
286 |
287 | this.saveCameraPose();
288 |
289 | this.el.setAttribute('position', {x: 0, y: 2, z: 5});
290 | this.el.setAttribute('rotation', {x: 0, y: 0, z: 0});
291 |
292 | this.pause();
293 | this.lookControls.play();
294 | if (this.data.autoRotate) console.warn('orbit-controls: Sorry, autoRotate is not implemented in VR mode');
295 | },
296 |
297 | /*
298 | * Called when exiting VR mode
299 | */
300 | onExitVR: function (event) {
301 | // console.log('exit vr');
302 |
303 | this.lookControls.pause();
304 | this.play();
305 |
306 | this.restoreCameraPose();
307 | this.updateView(true);
308 | },
309 |
310 | /**
311 | * Called when entity pauses.
312 | * Use to stop or remove any dynamic or background behavior such as events.
313 | */
314 | pause: function () {
315 | // console.log("component pause");
316 | this.isRunning = false;
317 | this.removeEventListeners();
318 | },
319 |
320 | /**
321 | * Called when entity resumes.
322 | * Use to continue or add any dynamic or background behavior such as events.
323 | */
324 | play: function () {
325 | // console.log("component play");
326 | this.isRunning = true;
327 |
328 | var camera, cameraType;
329 | this.object.traverse(function (child) {
330 | if (child instanceof THREE.PerspectiveCamera) {
331 | camera = child;
332 | cameraType = 'PerspectiveCamera';
333 | } else if (child instanceof THREE.OrthographicCamera) {
334 | camera = child;
335 | cameraType = 'OrthographicCamera';
336 | }
337 | });
338 |
339 | this.camera = camera;
340 | this.cameraType = cameraType;
341 |
342 | this.sceneEl.addEventListener('renderstart', this.onRenderTargetLoaded, false);
343 |
344 | if (this.lookControls) this.lookControls.pause();
345 | if (this.canvasEl) this.addEventListeners();
346 | },
347 |
348 | /*
349 | * Called when Render Target is completely loaded
350 | * Then set canvasEl and add event listeners
351 | */
352 | onRenderTargetLoaded: function () {
353 | this.sceneEl.removeEventListener('renderstart', this.onRenderTargetLoaded, false);
354 | this.canvasEl = this.sceneEl.canvas;
355 | this.addEventListeners();
356 | },
357 |
358 | /*
359 | * Bind this to all event handlera
360 | */
361 | bindMethods: function () {
362 | this.onRenderTargetLoaded = this.onRenderTargetLoaded.bind(this);
363 |
364 | this.onContextMenu = this.onContextMenu.bind(this);
365 | this.onMouseDown = this.onMouseDown.bind(this);
366 | this.onMouseWheel = this.onMouseWheel.bind(this);
367 | this.onMouseMove = this.onMouseMove.bind(this);
368 | this.onMouseUp = this.onMouseUp.bind(this);
369 | this.onTouchStart = this.onTouchStart.bind(this);
370 | this.onTouchMove = this.onTouchMove.bind(this);
371 | this.onTouchEnd = this.onTouchEnd.bind(this);
372 | this.onKeyDown = this.onKeyDown.bind(this);
373 | },
374 |
375 | /*
376 | * Add event listeners
377 | */
378 | addEventListeners: function () {
379 | this.canvasEl.addEventListener('contextmenu', this.onContextMenu, false);
380 |
381 | this.canvasEl.addEventListener('mousedown', this.onMouseDown, false);
382 | this.canvasEl.addEventListener('mousewheel', this.onMouseWheel, false);
383 | this.canvasEl.addEventListener('MozMousePixelScroll', this.onMouseWheel, false); // firefox
384 |
385 | this.canvasEl.addEventListener('touchstart', this.onTouchStart, false);
386 | this.canvasEl.addEventListener('touchend', this.onTouchEnd, false);
387 | this.canvasEl.addEventListener('touchmove', this.onTouchMove, false);
388 |
389 | window.addEventListener('keydown', this.onKeyDown, false);
390 | },
391 |
392 | /*
393 | * Remove event listeners
394 | */
395 | removeEventListeners: function () {
396 |
397 | if(this.canvasEl){
398 | this.canvasEl.removeEventListener('contextmenu', this.onContextMenu, false);
399 | this.canvasEl.removeEventListener('mousedown', this.onMouseDown, false);
400 | this.canvasEl.removeEventListener('mousewheel', this.onMouseWheel, false);
401 | this.canvasEl.removeEventListener('MozMousePixelScroll', this.onMouseWheel, false); // firefox
402 |
403 | this.canvasEl.removeEventListener('touchstart', this.onTouchStart, false);
404 | this.canvasEl.removeEventListener('touchend', this.onTouchEnd, false);
405 | this.canvasEl.removeEventListener('touchmove', this.onTouchMove, false);
406 |
407 | this.canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
408 | this.canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
409 | this.canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
410 | }
411 |
412 | window.removeEventListener('keydown', this.onKeyDown, false);
413 | },
414 |
415 | /*
416 | * EVENT LISTENERS
417 | */
418 |
419 | /*
420 | * Called when right clicking the A-Frame component
421 | */
422 |
423 | onContextMenu: function (event) {
424 | event.preventDefault();
425 | },
426 |
427 | /*
428 | * MOUSE CLICK EVENT LISTENERS
429 | */
430 |
431 | onMouseDown: function (event) {
432 | // console.log('onMouseDown');
433 |
434 | if (!this.data.enabled || !this.isRunning) return;
435 |
436 | if (event.button === this.mouseButtons.ORBIT && (event.shiftKey || event.ctrlKey)) {
437 | if (this.data.enablePan === false) return;
438 | this.handleMouseDownPan(event);
439 | this.state = this.STATE.PAN;
440 | } else if (event.button === this.mouseButtons.ORBIT) {
441 | this.panOffset.set(0, 0, 0);
442 | if (this.data.enableRotate === false) return;
443 | this.handleMouseDownRotate(event);
444 | this.state = this.STATE.ROTATE;
445 | } else if (event.button === this.mouseButtons.ZOOM) {
446 | this.panOffset.set(0, 0, 0);
447 | if (this.data.enableZoom === false) return;
448 | this.handleMouseDownDolly(event);
449 | this.state = this.STATE.DOLLY;
450 | } else if (event.button === this.mouseButtons.PAN) {
451 | if (this.data.enablePan === false) return;
452 | this.handleMouseDownPan(event);
453 | this.state = this.STATE.PAN;
454 | }
455 |
456 | if (this.state !== this.STATE.NONE) {
457 | this.canvasEl.addEventListener('mousemove', this.onMouseMove, false);
458 | this.canvasEl.addEventListener('mouseup', this.onMouseUp, false);
459 | this.canvasEl.addEventListener('mouseout', this.onMouseUp, false);
460 |
461 | this.el.emit('start-drag-orbit-controls', null, false);
462 | }
463 | },
464 |
465 | onMouseMove: function (event) {
466 | // console.log('onMouseMove');
467 |
468 | if (!this.data.enabled || !this.isRunning) return;
469 |
470 | event.preventDefault();
471 |
472 | if (this.state === this.STATE.ROTATE) {
473 | if (this.data.enableRotate === false) return;
474 | this.handleMouseMoveRotate(event);
475 | } else if (this.state === this.STATE.DOLLY) {
476 | if (this.data.enableZoom === false) return;
477 | this.handleMouseMoveDolly(event);
478 | } else if (this.state === this.STATE.PAN) {
479 | if (this.data.enablePan === false) return;
480 | this.handleMouseMovePan(event);
481 | }
482 | },
483 |
484 | onMouseUp: function (event) {
485 | // console.log('onMouseUp');
486 |
487 | if (!this.data.enabled || !this.isRunning) return;
488 |
489 | if (this.state === this.STATE.ROTATE_TO) return;
490 |
491 | event.preventDefault();
492 | event.stopPropagation();
493 |
494 | this.handleMouseUp(event);
495 |
496 | this.canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
497 | this.canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
498 | this.canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
499 |
500 | this.state = this.STATE.NONE;
501 |
502 | this.el.emit('end-drag-orbit-controls', null, false);
503 | },
504 |
505 | /*
506 | * MOUSE WHEEL EVENT LISTENERS
507 | */
508 |
509 | onMouseWheel: function (event) {
510 | // console.log('onMouseWheel');
511 |
512 | if (!this.data.enabled || !this.isRunning || this.data.enableZoom === false || (this.state !== this.STATE.NONE && this.state !== this.STATE.ROTATE)) return;
513 |
514 | event.preventDefault();
515 | event.stopPropagation();
516 | this.handleMouseWheel(event);
517 | },
518 |
519 | /*
520 | * TOUCH EVENT LISTENERS
521 | */
522 |
523 | onTouchStart: function (event) {
524 | // console.log('onTouchStart');
525 |
526 | if (!this.data.enabled || !this.isRunning) return;
527 |
528 | switch (event.touches.length) {
529 | case 1: // one-fingered touch: rotate
530 | if (this.data.enableRotate === false) return;
531 | this.handleTouchStartRotate(event);
532 | this.state = this.STATE.TOUCH_ROTATE;
533 | break;
534 | case 2: // two-fingered touch: dolly
535 | if (this.data.enableZoom === false) return;
536 | this.handleTouchStartDolly(event);
537 | this.state = this.STATE.TOUCH_DOLLY;
538 | break;
539 | case 3: // three-fingered touch: pan
540 | if (this.data.enablePan === false) return;
541 | this.handleTouchStartPan(event);
542 | this.state = this.STATE.TOUCH_PAN;
543 | break;
544 | default:
545 | this.state = this.STATE.NONE;
546 | }
547 |
548 | if (this.state !== this.STATE.NONE) {
549 | this.el.emit('start-drag-orbit-controls', null, false);
550 | }
551 | },
552 |
553 | onTouchMove: function (event) {
554 | // console.log('onTouchMove');
555 |
556 | if (!this.data.enabled || !this.isRunning) return;
557 |
558 | event.preventDefault();
559 | event.stopPropagation();
560 |
561 | switch (event.touches.length) {
562 | case 1: // one-fingered touch: rotate
563 | if (this.enableRotate === false) return;
564 | if (this.state !== this.STATE.TOUCH_ROTATE) return; // is this needed?...
565 | this.handleTouchMoveRotate(event);
566 | break;
567 |
568 | case 2: // two-fingered touch: dolly
569 | if (this.data.enableZoom === false) return;
570 | if (this.state !== this.STATE.TOUCH_DOLLY) return; // is this needed?...
571 | this.handleTouchMoveDolly(event);
572 | break;
573 |
574 | case 3: // three-fingered touch: pan
575 | if (this.data.enablePan === false) return;
576 | if (this.state !== this.STATE.TOUCH_PAN) return; // is this needed?...
577 | this.handleTouchMovePan(event);
578 | break;
579 |
580 | default:
581 | this.state = this.STATE.NONE;
582 | }
583 | },
584 |
585 | onTouchEnd: function (event) {
586 | // console.log('onTouchEnd');
587 |
588 | if (!this.data.enabled || !this.isRunning) return;
589 |
590 | this.handleTouchEnd(event);
591 |
592 | this.el.emit('end-drag-orbit-controls', null, false);
593 |
594 | this.state = this.STATE.NONE;
595 | },
596 |
597 | /*
598 | * KEYBOARD EVENT LISTENERS
599 | */
600 |
601 | onKeyDown: function (event) {
602 | // console.log('onKeyDown');
603 |
604 | if (!this.data.enabled || !this.isRunning || this.data.enableKeys === false || this.data.enablePan === false) return;
605 |
606 | this.handleKeyDown(event);
607 | },
608 |
609 | /*
610 | * EVENT HANDLERS
611 | */
612 |
613 | /*
614 | * MOUSE CLICK EVENT HANDLERS
615 | */
616 |
617 | handleMouseDownRotate: function (event) {
618 | // console.log( 'handleMouseDownRotate' );
619 | this.rotateStart.set(event.clientX, event.clientY);
620 | },
621 |
622 | handleMouseDownDolly: function (event) {
623 | // console.log( 'handleMouseDownDolly' );
624 | this.dollyStart.set(event.clientX, event.clientY);
625 | },
626 |
627 | handleMouseDownPan: function (event) {
628 | // console.log( 'handleMouseDownPan' );
629 | this.panStart.set(event.clientX, event.clientY);
630 | },
631 |
632 | handleMouseMoveRotate: function (event) {
633 | // console.log( 'handleMouseMoveRotate' );
634 |
635 | this.rotateEnd.set(event.clientX, event.clientY);
636 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
637 |
638 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
639 |
640 | // rotating across whole screen goes 360 degrees around
641 | this.rotateLeft(2 * Math.PI * this.rotateDelta.x / canvas.clientWidth * this.data.rotateSpeed);
642 |
643 | // rotating up and down along whole screen attempts to go 360, but limited to 180
644 | this.rotateUp(2 * Math.PI * this.rotateDelta.y / canvas.clientHeight * this.data.rotateSpeed);
645 |
646 | this.rotateStart.copy(this.rotateEnd);
647 |
648 | this.updateView();
649 | },
650 |
651 | handleMouseMoveDolly: function (event) {
652 | // console.log( 'handleMouseMoveDolly' );
653 |
654 | this.dollyEnd.set(event.clientX, event.clientY);
655 | this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
656 |
657 | if (this.dollyDelta.y > 0) {
658 | !this.data.invertZoom ? this.dollyIn(this.getZoomScale()) : this.dollyOut(this.getZoomScale());
659 | } else if (this.dollyDelta.y < 0) {
660 | !this.data.invertZoom ? this.dollyOut(this.getZoomScale()) : this.dollyIn(this.getZoomScale());
661 | }
662 |
663 | this.dollyStart.copy(this.dollyEnd);
664 |
665 | this.updateView();
666 | },
667 |
668 | handleMouseMovePan: function (event) {
669 | // console.log( 'handleMouseMovePan' );
670 |
671 | this.panEnd.set(event.clientX, event.clientY);
672 | this.panDelta.subVectors(this.panEnd, this.panStart);
673 | this.pan(this.panDelta.x, this.panDelta.y);
674 | this.panStart.copy(this.panEnd);
675 |
676 | this.updateView();
677 | },
678 |
679 | handleMouseUp: function (event) {
680 | // console.log( 'handleMouseUp' );
681 | },
682 |
683 | /*
684 | * MOUSE WHEEL EVENT HANDLERS
685 | */
686 |
687 | handleMouseWheel: function (event) {
688 | // console.log( 'handleMouseWheel' );
689 |
690 | var delta = 0;
691 | if (event.wheelDelta !== undefined) {
692 | // WebKit / Opera / Explorer 9
693 | delta = event.wheelDelta;
694 | } else if (event.detail !== undefined) {
695 | // Firefox
696 | delta = -event.detail;
697 | }
698 |
699 | if (delta > 0) {
700 | !this.data.invertZoom ? this.dollyOut(this.getZoomScale()) : this.dollyIn(this.getZoomScale());
701 | } else if (delta < 0) {
702 | !this.data.invertZoom ? this.dollyIn(this.getZoomScale()) : this.dollyOut(this.getZoomScale());
703 | }
704 |
705 | this.updateView();
706 | },
707 |
708 | /*
709 | * TOUCH EVENT HANDLERS
710 | */
711 |
712 | handleTouchStartRotate: function (event) {
713 | // console.log( 'handleTouchStartRotate' );
714 |
715 | this.rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
716 | },
717 |
718 | handleTouchStartDolly: function (event) {
719 | // console.log( 'handleTouchStartDolly' );
720 |
721 | var dx = event.touches[0].pageX - event.touches[1].pageX;
722 | var dy = event.touches[0].pageY - event.touches[1].pageY;
723 | var distance = Math.sqrt(dx * dx + dy * dy);
724 | this.dollyStart.set(0, distance);
725 | },
726 |
727 | handleTouchStartPan: function (event) {
728 | // console.log( 'handleTouchStartPan' );
729 |
730 | this.panStart.set(event.touches[0].pageX, event.touches[0].pageY);
731 | },
732 |
733 | handleTouchMoveRotate: function (event) {
734 | // console.log( 'handleTouchMoveRotate' );
735 |
736 | this.rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
737 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
738 |
739 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
740 | // rotating across whole screen goes 360 degrees around
741 | this.rotateLeft(2 * Math.PI * this.rotateDelta.x / canvas.clientWidth * this.data.rotateSpeed);
742 | // rotating up and down along whole screen attempts to go 360, but limited to 180
743 | this.rotateUp(2 * Math.PI * this.rotateDelta.y / canvas.clientHeight * this.data.rotateSpeed);
744 | this.rotateStart.copy(this.rotateEnd);
745 | this.updateView();
746 | },
747 |
748 | handleTouchMoveDolly: function (event) {
749 | // console.log( 'handleTouchMoveDolly' );
750 |
751 | var dx = event.touches[0].pageX - event.touches[1].pageX;
752 | var dy = event.touches[0].pageY - event.touches[1].pageY;
753 |
754 | var distance = Math.sqrt(dx * dx + dy * dy);
755 |
756 | this.dollyEnd.set(0, distance);
757 | this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
758 | if (this.dollyDelta.y > 0) {
759 | this.dollyIn(this.getZoomScale());
760 | } else if (this.dollyDelta.y < 0) {
761 | this.dollyOut(this.getZoomScale());
762 | }
763 |
764 | this.dollyStart.copy(this.dollyEnd);
765 | this.updateView();
766 | },
767 |
768 | handleTouchMovePan: function (event) {
769 | // console.log( 'handleTouchMovePan' );
770 |
771 | this.panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
772 | this.panDelta.subVectors(this.panEnd, this.panStart);
773 | this.pan(this.panDelta.x, this.panDelta.y);
774 | this.panStart.copy(this.panEnd);
775 | this.updateView();
776 | },
777 |
778 | handleTouchEnd: function (event) {
779 | // console.log( 'handleTouchEnd' );
780 | },
781 |
782 | /*
783 | * KEYBOARD EVENT HANDLERS
784 | */
785 |
786 | handleKeyDown: function (event) {
787 | // console.log( 'handleKeyDown' );
788 |
789 | switch (event.keyCode) {
790 | case this.keys.UP:
791 | this.pan(0, this.data.keyPanSpeed);
792 | this.updateView();
793 | break;
794 | case this.keys.BOTTOM:
795 | this.pan(0, -this.data.keyPanSpeed);
796 | this.updateView();
797 | break;
798 | case this.keys.LEFT:
799 | this.pan(this.data.keyPanSpeed, 0);
800 | this.updateView();
801 | break;
802 | case this.keys.RIGHT:
803 | this.pan(-this.data.keyPanSpeed, 0);
804 | this.updateView();
805 | break;
806 | }
807 | },
808 |
809 | /*
810 | * HELPER FUNCTIONS
811 | */
812 |
813 | getAutoRotationAngle: function () {
814 | return 2 * Math.PI / 60 / 60 * this.data.autoRotateSpeed;
815 | },
816 |
817 | getZoomScale: function () {
818 | return Math.pow(0.95, this.data.zoomSpeed);
819 | },
820 |
821 | rotateLeft: function (angle) {
822 | this.sphericalDelta.theta -= angle;
823 | },
824 |
825 | rotateUp: function (angle) {
826 | this.sphericalDelta.phi -= angle;
827 | },
828 |
829 | rotateTo: function (vec3) {
830 | this.state = this.STATE.ROTATE_TO;
831 | this.desiredPosition.copy(vec3);
832 | },
833 |
834 | panHorizontally: function (distance, objectMatrix) {
835 | // console.log('pan horizontally', distance, objectMatrix);
836 | var v = new THREE.Vector3();
837 | v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
838 | v.multiplyScalar(-distance);
839 | this.panOffset.add(v);
840 | },
841 |
842 | panVertically: function (distance, objectMatrix) {
843 | // console.log('pan vertically', distance, objectMatrix);
844 | var v = new THREE.Vector3();
845 | v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
846 | v.multiplyScalar(distance);
847 | this.panOffset.add(v);
848 | },
849 |
850 | pan: function (deltaX, deltaY) { // deltaX and deltaY are in pixels; right and down are positive
851 | // console.log('panning', deltaX, deltaY );
852 | var offset = new THREE.Vector3();
853 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
854 |
855 | if (this.cameraType === 'PerspectiveCamera') {
856 | // perspective
857 | var position = this.dolly.position;
858 | offset.copy(position).sub(this.target);
859 | var targetDistance = offset.length();
860 | targetDistance *= Math.tan((this.camera.fov / 2) * Math.PI / 180.0); // half of the fov is center to top of screen
861 | this.panHorizontally(2 * deltaX * targetDistance / canvas.clientHeight, this.object.matrix); // we actually don't use screenWidth, since perspective camera is fixed to screen height
862 | this.panVertically(2 * deltaY * targetDistance / canvas.clientHeight, this.object.matrix);
863 | } else if (this.cameraType === 'OrthographicCamera') {
864 | // orthographic
865 | this.panHorizontally(deltaX * (this.dolly.right - this.dolly.left) / this.camera.zoom / canvas.clientWidth, this.object.matrix);
866 | this.panVertically(deltaY * (this.dolly.top - this.dolly.bottom) / this.camera.zoom / canvas.clientHeight, this.object.matrix);
867 | } else {
868 | // camera neither orthographic nor perspective
869 | console.warn('Trying to pan: WARNING: Orbit Controls encountered an unknown camera type - pan disabled.');
870 | this.data.enablePan = false;
871 | }
872 | },
873 |
874 | dollyIn: function (dollyScale) {
875 | // console.log( "dollyIn camera" );
876 | if (this.cameraType === 'PerspectiveCamera') {
877 | this.scale *= dollyScale;
878 | } else if (this.cameraType === 'OrthographicCamera') {
879 | this.camera.zoom = Math.max(this.data.minZoom, Math.min(this.data.maxZoom, this.camera.zoom * dollyScale));
880 | this.camera.updateProjectionMatrix();
881 | this.zoomChanged = true;
882 | } else {
883 | console.warn('Trying to dolly in: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled.');
884 | this.data.enableZoom = false;
885 | }
886 | },
887 |
888 | dollyOut: function (dollyScale) {
889 | // console.log( "dollyOut camera" );
890 | if (this.cameraType === 'PerspectiveCamera') {
891 | this.scale /= dollyScale;
892 | } else if (this.cameraType === 'OrthographicCamera') {
893 | this.camera.zoom = Math.max(this.data.minZoom, Math.min(this.data.maxZoom, this.camera.zoom / dollyScale));
894 | this.camera.updateProjectionMatrix();
895 | this.zoomChanged = true;
896 | } else {
897 | console.warn('Trying to dolly out: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled.');
898 | this.data.enableZoom = false;
899 | }
900 | },
901 |
902 | lookAtTarget: function (object, target) {
903 | var v = new THREE.Vector3();
904 | v.subVectors(object.position, target).add(object.position);
905 | object.lookAt(v);
906 | },
907 |
908 | /*
909 | * SAVES CAMERA POSE (WHEN ENTERING VR)
910 | */
911 |
912 | saveCameraPose: function () {
913 | if (this.savedPose) { return; }
914 | this.savedPose = {
915 | position: this.dolly.position,
916 | rotation: this.dolly.rotation
917 | };
918 | },
919 |
920 | /*
921 | * RESTORE CAMERA POSE (WHEN EXITING VR)
922 | */
923 |
924 | restoreCameraPose: function () {
925 | if (!this.savedPose) { return; }
926 | this.dolly.position.copy(this.savedPose.position);
927 | this.dolly.rotation.copy(this.savedPose.rotation);
928 | this.savedPose = null;
929 | },
930 |
931 | /*
932 | * VIEW UPDATE
933 | */
934 |
935 | updateView: function (forceUpdate) {
936 | if (this.desiredPosition && this.state === this.STATE.ROTATE_TO) {
937 | var desiredSpherical = new THREE.Spherical();
938 | desiredSpherical.setFromVector3(this.desiredPosition);
939 | var phiDiff = desiredSpherical.phi - this.spherical.phi;
940 | var thetaDiff = desiredSpherical.theta - this.spherical.theta;
941 | this.sphericalDelta.set(this.spherical.radius, phiDiff * this.data.rotateToSpeed, thetaDiff * this.data.rotateToSpeed);
942 | }
943 |
944 | var offset = new THREE.Vector3();
945 |
946 | var quat = new THREE.Quaternion().setFromUnitVectors(this.dolly.up, new THREE.Vector3(0, 1, 0)); // so camera.up is the orbit axis
947 | var quatInverse = quat.clone().inverse();
948 |
949 | offset.copy(this.dolly.position).sub(this.target);
950 | offset.applyQuaternion(quat); // rotate offset to "y-axis-is-up" space
951 | this.spherical.setFromVector3(offset); // angle from z-axis around y-axis
952 |
953 | if (this.data.autoRotate && this.state === this.STATE.NONE) this.rotateLeft(this.getAutoRotationAngle());
954 |
955 | this.spherical.theta += this.sphericalDelta.theta;
956 | this.spherical.phi += this.sphericalDelta.phi;
957 | this.spherical.theta = Math.max(this.data.minAzimuthAngle, Math.min(this.data.maxAzimuthAngle, this.spherical.theta)); // restrict theta to be inside desired limits
958 | this.spherical.phi = Math.max(this.data.minPolarAngle, Math.min(this.data.maxPolarAngle, this.spherical.phi)); // restrict phi to be inside desired limits
959 | this.spherical.makeSafe();
960 | this.spherical.radius *= this.scale;
961 | this.spherical.radius = Math.max(this.data.minDistance, Math.min(this.data.maxDistance, this.spherical.radius)); // restrict radius to be inside desired limits
962 |
963 | this.target.add(this.panOffset); // move target to panned location
964 |
965 | offset.setFromSpherical(this.spherical);
966 | offset.applyQuaternion(quatInverse); // rotate offset back to "camera-up-vector-is-up" space
967 |
968 | this.dolly.position.copy(this.target).add(offset);
969 |
970 | if (this.target) {
971 | this.lookAtTarget(this.dolly, this.target);
972 | }
973 |
974 | if (this.data.enableDamping === true) {
975 | this.sphericalDelta.theta *= (1 - this.data.dampingFactor);
976 | this.sphericalDelta.phi *= (1 - this.data.dampingFactor);
977 | } else {
978 | this.sphericalDelta.set(0, 0, 0);
979 | }
980 |
981 | this.scale = 1;
982 | this.panOffset.set(0, 0, 0);
983 |
984 | // update condition is:
985 | // min(camera displacement, camera rotation in radians)^2 > EPS
986 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
987 |
988 | if (forceUpdate === true ||
989 | this.zoomChanged ||
990 | this.lastPosition.distanceToSquared(this.dolly.position) > this.EPS ||
991 | 8 * (1 - this.lastQuaternion.dot(this.dolly.quaternion)) > this.EPS) {
992 | // this.el.emit('change-drag-orbit-controls', null, false);
993 |
994 | var hmdQuaternion = this.calculateHMDQuaternion();
995 | var hmdEuler = new THREE.Euler();
996 | hmdEuler.setFromQuaternion(hmdQuaternion, 'YXZ');
997 |
998 | this.el.setAttribute('position', {
999 | x: this.dolly.position.x,
1000 | y: this.dolly.position.y,
1001 | z: this.dolly.position.z
1002 | });
1003 |
1004 | this.el.setAttribute('rotation', {
1005 | x: radToDeg(hmdEuler.x),
1006 | y: radToDeg(hmdEuler.y),
1007 | z: radToDeg(hmdEuler.z)
1008 | });
1009 |
1010 | this.lastPosition.copy(this.dolly.position);
1011 | this.lastQuaternion.copy(this.dolly.quaternion);
1012 |
1013 | this.zoomChanged = false;
1014 |
1015 | return true;
1016 | }
1017 |
1018 | return false;
1019 | },
1020 |
1021 | calculateHMDQuaternion: (function () {
1022 | var hmdQuaternion = new THREE.Quaternion();
1023 | return function () {
1024 | hmdQuaternion.copy(this.dolly.quaternion);
1025 | return hmdQuaternion;
1026 | };
1027 | })()
1028 |
1029 | });
1030 |
1031 |
1032 | /***/ })
1033 | /******/ ]);
--------------------------------------------------------------------------------
/dist/aframe-orbit-controls-component.min.js:
--------------------------------------------------------------------------------
1 | !function(t){function e(i){if(s[i])return s[i].exports;var a=s[i]={exports:{},id:i,loaded:!1};return t[i].call(a.exports,a,a.exports,e),a.loaded=!0,a.exports}var s={};return e.m=t,e.c=s,e.p="",e(0)}([function(t,e){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");var s=THREE.Math.radToDeg;AFRAME.registerComponent("orbit-controls",{dependencies:["position","rotation"],schema:{enabled:{default:!0},target:{default:""},distance:{default:1},enableRotate:{default:!0},rotateSpeed:{default:1},enableZoom:{default:!0},zoomSpeed:{default:1},enablePan:{default:!0},keyPanSpeed:{default:7},enableDamping:{default:!1},dampingFactor:{default:.25},autoRotate:{default:!1},autoRotateSpeed:{default:2},enableKeys:{default:!0},minAzimuthAngle:{default:-(1/0)},maxAzimuthAngle:{default:1/0},minPolarAngle:{default:0},maxPolarAngle:{default:Math.PI},minZoom:{default:0},maxZoom:{default:1/0},invertZoom:{default:!1},minDistance:{default:0},maxDistance:{default:1/0},rotateTo:{type:"vec3",default:{x:0,y:0,z:0}},rotateToSpeed:{type:"number",default:.05},logPosition:{type:"boolean",default:!1},autoVRLookCam:{type:"boolean",default:!0}},multiple:!1,init:function(){this.sceneEl=this.el.sceneEl,this.object=this.el.object3D,this.target=this.sceneEl.querySelector(this.data.target).object3D.position,console.log("enabled: ",this.data.enabled),this.isRunning=!1,this.lookControls=null,this.data.autoVRLookCam&&(this.el.components["look-controls"]?this.lookControls=this.el.components["look-controls"]:(this.el.setAttribute("look-controls",""),this.lookControls=this.el.components["look-controls"]),this.lookControls.pause(),this.el.sceneEl.addEventListener("enter-vr",this.onEnterVR.bind(this),!1),this.el.sceneEl.addEventListener("exit-vr",this.onExitVR.bind(this),!1)),this.dolly=new THREE.Object3D,this.dolly.position.copy(this.object.position),this.savedPose=null,this.STATE={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_DOLLY:4,TOUCH_PAN:5,ROTATE_TO:6},this.state=this.STATE.NONE,this.EPS=1e-6,this.lastPosition=new THREE.Vector3,this.lastQuaternion=new THREE.Quaternion,this.spherical=new THREE.Spherical,this.sphericalDelta=new THREE.Spherical,this.scale=1,this.zoomChanged=!1,this.rotateStart=new THREE.Vector2,this.rotateEnd=new THREE.Vector2,this.rotateDelta=new THREE.Vector2,this.panStart=new THREE.Vector2,this.panEnd=new THREE.Vector2,this.panDelta=new THREE.Vector2,this.panOffset=new THREE.Vector3,this.dollyStart=new THREE.Vector2,this.dollyEnd=new THREE.Vector2,this.dollyDelta=new THREE.Vector2,this.vector=new THREE.Vector3,this.desiredPosition=new THREE.Vector3,this.mouseButtons={ORBIT:THREE.MOUSE.LEFT,ZOOM:THREE.MOUSE.MIDDLE,PAN:THREE.MOUSE.RIGHT},this.keys={LEFT:37,UP:38,RIGHT:39,BOTTOM:40},this.bindMethods()},update:function(t){if(console.log("component update"),this.data.rotateTo){var e=new THREE.Vector3(this.data.rotateTo.x,this.data.rotateTo.y,this.data.rotateTo.z);this.desiredPosition.equals(e)||(this.desiredPosition.copy(e),this.rotateTo(this.desiredPosition))}this.dolly.position.copy(this.object.position),this.updateView(!0)},remove:function(){this.isRunning=!1,this.removeEventListeners(),this.el.sceneEl.removeEventListener("enter-vr",this.onEnterVR,!1),this.el.sceneEl.removeEventListener("exit-vr",this.onExitVR,!1)},tick:function(t){var e=!(!this.data.enabled||!this.isRunning)&&this.updateView();e===!0&&this.data.logPosition===!0&&console.log(this.el.object3D.position)},onEnterVR:function(t){this.saveCameraPose(),this.el.setAttribute("position",{x:0,y:2,z:5}),this.el.setAttribute("rotation",{x:0,y:0,z:0}),this.pause(),this.lookControls.play(),this.data.autoRotate&&console.warn("orbit-controls: Sorry, autoRotate is not implemented in VR mode")},onExitVR:function(t){this.lookControls.pause(),this.play(),this.restoreCameraPose(),this.updateView(!0)},pause:function(){this.isRunning=!1,this.removeEventListeners()},play:function(){this.isRunning=!0;var t,e;this.object.traverse(function(s){s instanceof THREE.PerspectiveCamera?(t=s,e="PerspectiveCamera"):s instanceof THREE.OrthographicCamera&&(t=s,e="OrthographicCamera")}),this.camera=t,this.cameraType=e,this.sceneEl.addEventListener("renderstart",this.onRenderTargetLoaded,!1),this.lookControls&&this.lookControls.pause(),this.canvasEl&&this.addEventListeners()},onRenderTargetLoaded:function(){this.sceneEl.removeEventListener("renderstart",this.onRenderTargetLoaded,!1),this.canvasEl=this.sceneEl.canvas,this.addEventListeners()},bindMethods:function(){this.onRenderTargetLoaded=this.onRenderTargetLoaded.bind(this),this.onContextMenu=this.onContextMenu.bind(this),this.onMouseDown=this.onMouseDown.bind(this),this.onMouseWheel=this.onMouseWheel.bind(this),this.onMouseMove=this.onMouseMove.bind(this),this.onMouseUp=this.onMouseUp.bind(this),this.onTouchStart=this.onTouchStart.bind(this),this.onTouchMove=this.onTouchMove.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this),this.onKeyDown=this.onKeyDown.bind(this)},addEventListeners:function(){this.canvasEl.addEventListener("contextmenu",this.onContextMenu,!1),this.canvasEl.addEventListener("mousedown",this.onMouseDown,!1),this.canvasEl.addEventListener("mousewheel",this.onMouseWheel,!1),this.canvasEl.addEventListener("MozMousePixelScroll",this.onMouseWheel,!1),this.canvasEl.addEventListener("touchstart",this.onTouchStart,!1),this.canvasEl.addEventListener("touchend",this.onTouchEnd,!1),this.canvasEl.addEventListener("touchmove",this.onTouchMove,!1),window.addEventListener("keydown",this.onKeyDown,!1)},removeEventListeners:function(){this.canvasEl&&(this.canvasEl.removeEventListener("contextmenu",this.onContextMenu,!1),this.canvasEl.removeEventListener("mousedown",this.onMouseDown,!1),this.canvasEl.removeEventListener("mousewheel",this.onMouseWheel,!1),this.canvasEl.removeEventListener("MozMousePixelScroll",this.onMouseWheel,!1),this.canvasEl.removeEventListener("touchstart",this.onTouchStart,!1),this.canvasEl.removeEventListener("touchend",this.onTouchEnd,!1),this.canvasEl.removeEventListener("touchmove",this.onTouchMove,!1),this.canvasEl.removeEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.removeEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.removeEventListener("mouseout",this.onMouseUp,!1)),window.removeEventListener("keydown",this.onKeyDown,!1)},onContextMenu:function(t){t.preventDefault()},onMouseDown:function(t){if(this.data.enabled&&this.isRunning){if(t.button===this.mouseButtons.ORBIT&&(t.shiftKey||t.ctrlKey)){if(this.data.enablePan===!1)return;this.handleMouseDownPan(t),this.state=this.STATE.PAN}else if(t.button===this.mouseButtons.ORBIT){if(this.panOffset.set(0,0,0),this.data.enableRotate===!1)return;this.handleMouseDownRotate(t),this.state=this.STATE.ROTATE}else if(t.button===this.mouseButtons.ZOOM){if(this.panOffset.set(0,0,0),this.data.enableZoom===!1)return;this.handleMouseDownDolly(t),this.state=this.STATE.DOLLY}else if(t.button===this.mouseButtons.PAN){if(this.data.enablePan===!1)return;this.handleMouseDownPan(t),this.state=this.STATE.PAN}this.state!==this.STATE.NONE&&(this.canvasEl.addEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.addEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.addEventListener("mouseout",this.onMouseUp,!1),this.el.emit("start-drag-orbit-controls",null,!1))}},onMouseMove:function(t){if(this.data.enabled&&this.isRunning)if(t.preventDefault(),this.state===this.STATE.ROTATE){if(this.data.enableRotate===!1)return;this.handleMouseMoveRotate(t)}else if(this.state===this.STATE.DOLLY){if(this.data.enableZoom===!1)return;this.handleMouseMoveDolly(t)}else if(this.state===this.STATE.PAN){if(this.data.enablePan===!1)return;this.handleMouseMovePan(t)}},onMouseUp:function(t){this.data.enabled&&this.isRunning&&this.state!==this.STATE.ROTATE_TO&&(t.preventDefault(),t.stopPropagation(),this.handleMouseUp(t),this.canvasEl.removeEventListener("mousemove",this.onMouseMove,!1),this.canvasEl.removeEventListener("mouseup",this.onMouseUp,!1),this.canvasEl.removeEventListener("mouseout",this.onMouseUp,!1),this.state=this.STATE.NONE,this.el.emit("end-drag-orbit-controls",null,!1))},onMouseWheel:function(t){this.data.enabled&&this.isRunning&&this.data.enableZoom!==!1&&(this.state===this.STATE.NONE||this.state===this.STATE.ROTATE)&&(t.preventDefault(),t.stopPropagation(),this.handleMouseWheel(t))},onTouchStart:function(t){if(this.data.enabled&&this.isRunning){switch(t.touches.length){case 1:if(this.data.enableRotate===!1)return;this.handleTouchStartRotate(t),this.state=this.STATE.TOUCH_ROTATE;break;case 2:if(this.data.enableZoom===!1)return;this.handleTouchStartDolly(t),this.state=this.STATE.TOUCH_DOLLY;break;case 3:if(this.data.enablePan===!1)return;this.handleTouchStartPan(t),this.state=this.STATE.TOUCH_PAN;break;default:this.state=this.STATE.NONE}this.state!==this.STATE.NONE&&this.el.emit("start-drag-orbit-controls",null,!1)}},onTouchMove:function(t){if(this.data.enabled&&this.isRunning)switch(t.preventDefault(),t.stopPropagation(),t.touches.length){case 1:if(this.enableRotate===!1)return;if(this.state!==this.STATE.TOUCH_ROTATE)return;this.handleTouchMoveRotate(t);break;case 2:if(this.data.enableZoom===!1)return;if(this.state!==this.STATE.TOUCH_DOLLY)return;this.handleTouchMoveDolly(t);break;case 3:if(this.data.enablePan===!1)return;if(this.state!==this.STATE.TOUCH_PAN)return;this.handleTouchMovePan(t);break;default:this.state=this.STATE.NONE}},onTouchEnd:function(t){this.data.enabled&&this.isRunning&&(this.handleTouchEnd(t),this.el.emit("end-drag-orbit-controls",null,!1),this.state=this.STATE.NONE)},onKeyDown:function(t){this.data.enabled&&this.isRunning&&this.data.enableKeys!==!1&&this.data.enablePan!==!1&&this.handleKeyDown(t)},handleMouseDownRotate:function(t){this.rotateStart.set(t.clientX,t.clientY)},handleMouseDownDolly:function(t){this.dollyStart.set(t.clientX,t.clientY)},handleMouseDownPan:function(t){this.panStart.set(t.clientX,t.clientY)},handleMouseMoveRotate:function(t){this.rotateEnd.set(t.clientX,t.clientY),this.rotateDelta.subVectors(this.rotateEnd,this.rotateStart);var e=this.canvasEl===document?this.canvasEl.body:this.canvasEl;this.rotateLeft(2*Math.PI*this.rotateDelta.x/e.clientWidth*this.data.rotateSpeed),this.rotateUp(2*Math.PI*this.rotateDelta.y/e.clientHeight*this.data.rotateSpeed),this.rotateStart.copy(this.rotateEnd),this.updateView()},handleMouseMoveDolly:function(t){this.dollyEnd.set(t.clientX,t.clientY),this.dollyDelta.subVectors(this.dollyEnd,this.dollyStart),this.dollyDelta.y>0?this.data.invertZoom?this.dollyOut(this.getZoomScale()):this.dollyIn(this.getZoomScale()):this.dollyDelta.y<0&&(this.data.invertZoom?this.dollyIn(this.getZoomScale()):this.dollyOut(this.getZoomScale())),this.dollyStart.copy(this.dollyEnd),this.updateView()},handleMouseMovePan:function(t){this.panEnd.set(t.clientX,t.clientY),this.panDelta.subVectors(this.panEnd,this.panStart),this.pan(this.panDelta.x,this.panDelta.y),this.panStart.copy(this.panEnd),this.updateView()},handleMouseUp:function(t){},handleMouseWheel:function(t){var e=0;void 0!==t.wheelDelta?e=t.wheelDelta:void 0!==t.detail&&(e=-t.detail),e>0?this.data.invertZoom?this.dollyIn(this.getZoomScale()):this.dollyOut(this.getZoomScale()):e<0&&(this.data.invertZoom?this.dollyOut(this.getZoomScale()):this.dollyIn(this.getZoomScale())),this.updateView()},handleTouchStartRotate:function(t){this.rotateStart.set(t.touches[0].pageX,t.touches[0].pageY)},handleTouchStartDolly:function(t){var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this.dollyStart.set(0,i)},handleTouchStartPan:function(t){this.panStart.set(t.touches[0].pageX,t.touches[0].pageY)},handleTouchMoveRotate:function(t){this.rotateEnd.set(t.touches[0].pageX,t.touches[0].pageY),this.rotateDelta.subVectors(this.rotateEnd,this.rotateStart);var e=this.canvasEl===document?this.canvasEl.body:this.canvasEl;this.rotateLeft(2*Math.PI*this.rotateDelta.x/e.clientWidth*this.data.rotateSpeed),this.rotateUp(2*Math.PI*this.rotateDelta.y/e.clientHeight*this.data.rotateSpeed),this.rotateStart.copy(this.rotateEnd),this.updateView()},handleTouchMoveDolly:function(t){var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this.dollyEnd.set(0,i),this.dollyDelta.subVectors(this.dollyEnd,this.dollyStart),this.dollyDelta.y>0?this.dollyIn(this.getZoomScale()):this.dollyDelta.y<0&&this.dollyOut(this.getZoomScale()),this.dollyStart.copy(this.dollyEnd),this.updateView()},handleTouchMovePan:function(t){this.panEnd.set(t.touches[0].pageX,t.touches[0].pageY),this.panDelta.subVectors(this.panEnd,this.panStart),this.pan(this.panDelta.x,this.panDelta.y),this.panStart.copy(this.panEnd),this.updateView()},handleTouchEnd:function(t){},handleKeyDown:function(t){switch(t.keyCode){case this.keys.UP:this.pan(0,this.data.keyPanSpeed),this.updateView();break;case this.keys.BOTTOM:this.pan(0,-this.data.keyPanSpeed),this.updateView();break;case this.keys.LEFT:this.pan(this.data.keyPanSpeed,0),this.updateView();break;case this.keys.RIGHT:this.pan(-this.data.keyPanSpeed,0),this.updateView()}},getAutoRotationAngle:function(){return 2*Math.PI/60/60*this.data.autoRotateSpeed},getZoomScale:function(){return Math.pow(.95,this.data.zoomSpeed)},rotateLeft:function(t){this.sphericalDelta.theta-=t},rotateUp:function(t){this.sphericalDelta.phi-=t},rotateTo:function(t){this.state=this.STATE.ROTATE_TO,this.desiredPosition.copy(t)},panHorizontally:function(t,e){var s=new THREE.Vector3;s.setFromMatrixColumn(e,0),s.multiplyScalar(-t),this.panOffset.add(s)},panVertically:function(t,e){var s=new THREE.Vector3;s.setFromMatrixColumn(e,1),s.multiplyScalar(t),this.panOffset.add(s)},pan:function(t,e){var s=new THREE.Vector3,i=this.canvasEl===document?this.canvasEl.body:this.canvasEl;if("PerspectiveCamera"===this.cameraType){var a=this.dolly.position;s.copy(a).sub(this.target);var o=s.length();o*=Math.tan(this.camera.fov/2*Math.PI/180),this.panHorizontally(2*t*o/i.clientHeight,this.object.matrix),this.panVertically(2*e*o/i.clientHeight,this.object.matrix)}else"OrthographicCamera"===this.cameraType?(this.panHorizontally(t*(this.dolly.right-this.dolly.left)/this.camera.zoom/i.clientWidth,this.object.matrix),this.panVertically(e*(this.dolly.top-this.dolly.bottom)/this.camera.zoom/i.clientHeight,this.object.matrix)):(console.warn("Trying to pan: WARNING: Orbit Controls encountered an unknown camera type - pan disabled."),this.data.enablePan=!1)},dollyIn:function(t){"PerspectiveCamera"===this.cameraType?this.scale*=t:"OrthographicCamera"===this.cameraType?(this.camera.zoom=Math.max(this.data.minZoom,Math.min(this.data.maxZoom,this.camera.zoom*t)),this.camera.updateProjectionMatrix(),this.zoomChanged=!0):(console.warn("Trying to dolly in: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled."),this.data.enableZoom=!1)},dollyOut:function(t){"PerspectiveCamera"===this.cameraType?this.scale/=t:"OrthographicCamera"===this.cameraType?(this.camera.zoom=Math.max(this.data.minZoom,Math.min(this.data.maxZoom,this.camera.zoom/t)),this.camera.updateProjectionMatrix(),this.zoomChanged=!0):(console.warn("Trying to dolly out: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled."),this.data.enableZoom=!1)},lookAtTarget:function(t,e){var s=new THREE.Vector3;s.subVectors(t.position,e).add(t.position),t.lookAt(s)},saveCameraPose:function(){this.savedPose||(this.savedPose={position:this.dolly.position,rotation:this.dolly.rotation})},restoreCameraPose:function(){this.savedPose&&(this.dolly.position.copy(this.savedPose.position),this.dolly.rotation.copy(this.savedPose.rotation),this.savedPose=null)},updateView:function(t){if(this.desiredPosition&&this.state===this.STATE.ROTATE_TO){var e=new THREE.Spherical;e.setFromVector3(this.desiredPosition);var i=e.phi-this.spherical.phi,a=e.theta-this.spherical.theta;this.sphericalDelta.set(this.spherical.radius,i*this.data.rotateToSpeed,a*this.data.rotateToSpeed)}var o=new THREE.Vector3,n=(new THREE.Quaternion).setFromUnitVectors(this.dolly.up,new THREE.Vector3(0,1,0)),h=n.clone().inverse();if(o.copy(this.dolly.position).sub(this.target),o.applyQuaternion(n),this.spherical.setFromVector3(o),this.data.autoRotate&&this.state===this.STATE.NONE&&this.rotateLeft(this.getAutoRotationAngle()),this.spherical.theta+=this.sphericalDelta.theta,this.spherical.phi+=this.sphericalDelta.phi,this.spherical.theta=Math.max(this.data.minAzimuthAngle,Math.min(this.data.maxAzimuthAngle,this.spherical.theta)),this.spherical.phi=Math.max(this.data.minPolarAngle,Math.min(this.data.maxPolarAngle,this.spherical.phi)),this.spherical.makeSafe(),this.spherical.radius*=this.scale,this.spherical.radius=Math.max(this.data.minDistance,Math.min(this.data.maxDistance,this.spherical.radius)),this.target.add(this.panOffset),o.setFromSpherical(this.spherical),o.applyQuaternion(h),this.dolly.position.copy(this.target).add(o),this.target&&this.lookAtTarget(this.dolly,this.target),this.data.enableDamping===!0?(this.sphericalDelta.theta*=1-this.data.dampingFactor,this.sphericalDelta.phi*=1-this.data.dampingFactor):this.sphericalDelta.set(0,0,0),this.scale=1,this.panOffset.set(0,0,0),t===!0||this.zoomChanged||this.lastPosition.distanceToSquared(this.dolly.position)>this.EPS||8*(1-this.lastQuaternion.dot(this.dolly.quaternion))>this.EPS){var l=this.calculateHMDQuaternion(),r=new THREE.Euler;return r.setFromQuaternion(l,"YXZ"),this.el.setAttribute("position",{x:this.dolly.position.x,y:this.dolly.position.y,z:this.dolly.position.z}),this.el.setAttribute("rotation",{x:s(r.x),y:s(r.y),z:s(r.z)}),this.lastPosition.copy(this.dolly.position),this.lastQuaternion.copy(this.dolly.quaternion),this.zoomChanged=!1,!0}return!1},calculateHMDQuaternion:function(){var t=new THREE.Quaternion;return function(){return t.copy(this.dolly.quaternion),t}}()})}]);
--------------------------------------------------------------------------------
/examples/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A-Frame Example Component - Basic
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/examples/assets/sky.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tizzle/aframe-orbit-controls-component/870d7357ebc0100ab7234875d93ccb334cdc12bf/examples/assets/sky.jpg
--------------------------------------------------------------------------------
/examples/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A-Frame Example Component - Basic
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Change Camera
47 |
48 |
49 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/examples/camera-children/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A-Frame Example Component - Basic
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A-Frame Example Component
5 |
6 |
25 |
26 |
27 |
28 | A-Frame Example Component
29 | Basic
30 | This is an example showing the basic functions of orbit controls.
31 |
32 | Rotate-To
33 | This is an example showing a rotation to a manual position.
34 |
35 | Camera-Children
36 | This is an example showing a camera with a light as a child entity that always follows the camera.
37 |
38 | Assets
39 | This is an example showing the use of an a-asset as a sky.
40 |
41 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | require('aframe');
2 | require('../index.js');
3 |
--------------------------------------------------------------------------------
/examples/rotate-to/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A-Frame Example Component - Rotate-To
5 |
6 |
7 |
8 |
9 | Position 1
10 | Position 2
11 | Position 3
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* global AFRAME THREE */
2 |
3 | if (typeof AFRAME === 'undefined') {
4 | throw new Error('Component attempted to register before AFRAME was available.');
5 | }
6 |
7 | var radToDeg = THREE.Math.radToDeg;
8 |
9 | /**
10 | * Example component for A-Frame.
11 | */
12 | AFRAME.registerComponent('orbit-controls', {
13 | dependencies: ['position', 'rotation'],
14 | schema: {
15 | enabled: {
16 | default: true
17 | },
18 | target: {
19 | default: ''
20 | },
21 | distance: {
22 | default: 1
23 | },
24 | enableRotate: {
25 | default: true
26 | },
27 | rotateSpeed: {
28 | default: 1.0
29 | },
30 | enableZoom: {
31 | default: true
32 | },
33 | zoomSpeed: {
34 | default: 1.0
35 | },
36 | enablePan: {
37 | default: true
38 | },
39 | keyPanSpeed: {
40 | default: 7.0
41 | },
42 | enableDamping: {
43 | default: false
44 | },
45 | dampingFactor: {
46 | default: 0.25
47 | },
48 | autoRotate: {
49 | default: false
50 | },
51 | autoRotateSpeed: {
52 | default: 2.0
53 | },
54 | enableKeys: {
55 | default: true
56 | },
57 | minAzimuthAngle: {
58 | default: -Infinity
59 | },
60 | maxAzimuthAngle: {
61 | default: Infinity
62 | },
63 | minPolarAngle: {
64 | default: 0
65 | },
66 | maxPolarAngle: {
67 | default: Math.PI
68 | },
69 | minZoom: {
70 | default: 0
71 | },
72 | maxZoom: {
73 | default: Infinity
74 | },
75 | invertZoom: {
76 | default: false
77 | },
78 | minDistance: {
79 | default: 0
80 | },
81 | maxDistance: {
82 | default: Infinity
83 | },
84 | rotateTo: {
85 | type: 'vec3',
86 | default: {x: 0, y: 0, z: 0}
87 | },
88 | rotateToSpeed: {
89 | type: 'number',
90 | default: 0.05
91 | },
92 | logPosition: {
93 | type: 'boolean',
94 | default: false
95 | },
96 | autoVRLookCam: {
97 | type: 'boolean',
98 | default: true
99 | }
100 | },
101 |
102 | /**
103 | * Set if component needs multiple instancing.
104 | */
105 | multiple: false,
106 |
107 | /**
108 | * Called once when component is attached. Generally for initial setup.
109 | */
110 | init: function () {
111 | this.sceneEl = this.el.sceneEl;
112 | this.object = this.el.object3D;
113 | this.target = this.sceneEl.querySelector(this.data.target).object3D.position;
114 |
115 | console.log('enabled: ', this.data.enabled);
116 |
117 | // Find the look-controls component on this camera, or create if it doesn't exist.
118 | this.isRunning = false;
119 | this.lookControls = null;
120 |
121 | if (this.data.autoVRLookCam) {
122 | if (this.el.components['look-controls']) {
123 | this.lookControls = this.el.components['look-controls'];
124 | } else {
125 | this.el.setAttribute('look-controls', '');
126 | this.lookControls = this.el.components['look-controls'];
127 | }
128 | this.lookControls.pause();
129 | this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR.bind(this), false);
130 | this.el.sceneEl.addEventListener('exit-vr', this.onExitVR.bind(this), false);
131 | }
132 |
133 | this.dolly = new THREE.Object3D();
134 | this.dolly.position.copy(this.object.position);
135 |
136 | this.savedPose = null;
137 |
138 | this.STATE = {
139 | NONE: -1,
140 | ROTATE: 0,
141 | DOLLY: 1,
142 | PAN: 2,
143 | TOUCH_ROTATE: 3,
144 | TOUCH_DOLLY: 4,
145 | TOUCH_PAN: 5,
146 | ROTATE_TO: 6
147 | };
148 |
149 | this.state = this.STATE.NONE;
150 |
151 | this.EPS = 0.000001;
152 | this.lastPosition = new THREE.Vector3();
153 | this.lastQuaternion = new THREE.Quaternion();
154 |
155 | this.spherical = new THREE.Spherical();
156 | this.sphericalDelta = new THREE.Spherical();
157 |
158 | this.scale = 1.0;
159 | this.zoomChanged = false;
160 |
161 | this.rotateStart = new THREE.Vector2();
162 | this.rotateEnd = new THREE.Vector2();
163 | this.rotateDelta = new THREE.Vector2();
164 |
165 | this.panStart = new THREE.Vector2();
166 | this.panEnd = new THREE.Vector2();
167 | this.panDelta = new THREE.Vector2();
168 | this.panOffset = new THREE.Vector3();
169 |
170 | this.dollyStart = new THREE.Vector2();
171 | this.dollyEnd = new THREE.Vector2();
172 | this.dollyDelta = new THREE.Vector2();
173 |
174 | this.vector = new THREE.Vector3();
175 | this.desiredPosition = new THREE.Vector3();
176 |
177 | this.mouseButtons = {
178 | ORBIT: THREE.MOUSE.LEFT,
179 | ZOOM: THREE.MOUSE.MIDDLE,
180 | PAN: THREE.MOUSE.RIGHT
181 | };
182 |
183 | this.keys = {
184 | LEFT: 37,
185 | UP: 38,
186 | RIGHT: 39,
187 | BOTTOM: 40
188 | };
189 |
190 | this.bindMethods();
191 | },
192 |
193 | /**
194 | * Called when component is attached and when component data changes.
195 | * Generally modifies the entity based on the data.
196 | */
197 | update: function (oldData) {
198 | console.log('component update');
199 |
200 | if (this.data.rotateTo) {
201 | var rotateToVec3 = new THREE.Vector3(this.data.rotateTo.x, this.data.rotateTo.y, this.data.rotateTo.z);
202 | // Check if rotateToVec3 is already desiredPosition
203 | if (!this.desiredPosition.equals(rotateToVec3)) {
204 | this.desiredPosition.copy(rotateToVec3);
205 | this.rotateTo(this.desiredPosition);
206 | }
207 | }
208 |
209 | this.dolly.position.copy(this.object.position);
210 | this.updateView(true);
211 | },
212 |
213 | /**
214 | * Called when a component is removed (e.g., via removeAttribute).
215 | * Generally undoes all modifications to the entity.
216 | */
217 | remove: function () {
218 | // console.log("component remove");
219 | this.isRunning = false;
220 | this.removeEventListeners();
221 | this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR, false);
222 | this.el.sceneEl.removeEventListener('exit-vr', this.onExitVR, false);
223 | },
224 |
225 | /**
226 | * Called on each scene tick.
227 | */
228 | tick: function (t) {
229 | var render = this.data.enabled && this.isRunning ? this.updateView() : false;
230 | if (render === true && this.data.logPosition === true) {
231 | console.log(this.el.object3D.position);
232 | }
233 | },
234 |
235 | /*
236 | * Called when entering VR mode
237 | */
238 | onEnterVR: function (event) {
239 | // console.log('enter vr', this);
240 |
241 | this.saveCameraPose();
242 |
243 | this.el.setAttribute('position', {x: 0, y: 2, z: 5});
244 | this.el.setAttribute('rotation', {x: 0, y: 0, z: 0});
245 |
246 | this.pause();
247 | this.lookControls.play();
248 | if (this.data.autoRotate) console.warn('orbit-controls: Sorry, autoRotate is not implemented in VR mode');
249 | },
250 |
251 | /*
252 | * Called when exiting VR mode
253 | */
254 | onExitVR: function (event) {
255 | // console.log('exit vr');
256 |
257 | this.lookControls.pause();
258 | this.play();
259 |
260 | this.restoreCameraPose();
261 | this.updateView(true);
262 | },
263 |
264 | /**
265 | * Called when entity pauses.
266 | * Use to stop or remove any dynamic or background behavior such as events.
267 | */
268 | pause: function () {
269 | // console.log("component pause");
270 | this.isRunning = false;
271 | this.removeEventListeners();
272 | },
273 |
274 | /**
275 | * Called when entity resumes.
276 | * Use to continue or add any dynamic or background behavior such as events.
277 | */
278 | play: function () {
279 | // console.log("component play");
280 | this.isRunning = true;
281 |
282 | var camera, cameraType;
283 | this.object.traverse(function (child) {
284 | if (child instanceof THREE.PerspectiveCamera) {
285 | camera = child;
286 | cameraType = 'PerspectiveCamera';
287 | } else if (child instanceof THREE.OrthographicCamera) {
288 | camera = child;
289 | cameraType = 'OrthographicCamera';
290 | }
291 | });
292 |
293 | this.camera = camera;
294 | this.cameraType = cameraType;
295 |
296 | this.sceneEl.addEventListener('renderstart', this.onRenderTargetLoaded, false);
297 |
298 | if (this.lookControls) this.lookControls.pause();
299 | if (this.canvasEl) this.addEventListeners();
300 | },
301 |
302 | /*
303 | * Called when Render Target is completely loaded
304 | * Then set canvasEl and add event listeners
305 | */
306 | onRenderTargetLoaded: function () {
307 | this.sceneEl.removeEventListener('renderstart', this.onRenderTargetLoaded, false);
308 | this.canvasEl = this.sceneEl.canvas;
309 | this.addEventListeners();
310 | },
311 |
312 | /*
313 | * Bind this to all event handlera
314 | */
315 | bindMethods: function () {
316 | this.onRenderTargetLoaded = this.onRenderTargetLoaded.bind(this);
317 |
318 | this.onContextMenu = this.onContextMenu.bind(this);
319 | this.onMouseDown = this.onMouseDown.bind(this);
320 | this.onMouseWheel = this.onMouseWheel.bind(this);
321 | this.onMouseMove = this.onMouseMove.bind(this);
322 | this.onMouseUp = this.onMouseUp.bind(this);
323 | this.onTouchStart = this.onTouchStart.bind(this);
324 | this.onTouchMove = this.onTouchMove.bind(this);
325 | this.onTouchEnd = this.onTouchEnd.bind(this);
326 | this.onKeyDown = this.onKeyDown.bind(this);
327 | },
328 |
329 | /*
330 | * Add event listeners
331 | */
332 | addEventListeners: function () {
333 | this.canvasEl.addEventListener('contextmenu', this.onContextMenu, false);
334 |
335 | this.canvasEl.addEventListener('mousedown', this.onMouseDown, false);
336 | this.canvasEl.addEventListener('mousewheel', this.onMouseWheel, false);
337 | this.canvasEl.addEventListener('MozMousePixelScroll', this.onMouseWheel, false); // firefox
338 |
339 | this.canvasEl.addEventListener('touchstart', this.onTouchStart, false);
340 | this.canvasEl.addEventListener('touchend', this.onTouchEnd, false);
341 | this.canvasEl.addEventListener('touchmove', this.onTouchMove, false);
342 |
343 | window.addEventListener('keydown', this.onKeyDown, false);
344 | },
345 |
346 | /*
347 | * Remove event listeners
348 | */
349 | removeEventListeners: function () {
350 |
351 | if(this.canvasEl){
352 | this.canvasEl.removeEventListener('contextmenu', this.onContextMenu, false);
353 | this.canvasEl.removeEventListener('mousedown', this.onMouseDown, false);
354 | this.canvasEl.removeEventListener('mousewheel', this.onMouseWheel, false);
355 | this.canvasEl.removeEventListener('MozMousePixelScroll', this.onMouseWheel, false); // firefox
356 |
357 | this.canvasEl.removeEventListener('touchstart', this.onTouchStart, false);
358 | this.canvasEl.removeEventListener('touchend', this.onTouchEnd, false);
359 | this.canvasEl.removeEventListener('touchmove', this.onTouchMove, false);
360 |
361 | this.canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
362 | this.canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
363 | this.canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
364 | }
365 |
366 | window.removeEventListener('keydown', this.onKeyDown, false);
367 | },
368 |
369 | /*
370 | * EVENT LISTENERS
371 | */
372 |
373 | /*
374 | * Called when right clicking the A-Frame component
375 | */
376 |
377 | onContextMenu: function (event) {
378 | event.preventDefault();
379 | },
380 |
381 | /*
382 | * MOUSE CLICK EVENT LISTENERS
383 | */
384 |
385 | onMouseDown: function (event) {
386 | // console.log('onMouseDown');
387 |
388 | if (!this.data.enabled || !this.isRunning) return;
389 |
390 | if (event.button === this.mouseButtons.ORBIT && (event.shiftKey || event.ctrlKey)) {
391 | if (this.data.enablePan === false) return;
392 | this.handleMouseDownPan(event);
393 | this.state = this.STATE.PAN;
394 | } else if (event.button === this.mouseButtons.ORBIT) {
395 | this.panOffset.set(0, 0, 0);
396 | if (this.data.enableRotate === false) return;
397 | this.handleMouseDownRotate(event);
398 | this.state = this.STATE.ROTATE;
399 | } else if (event.button === this.mouseButtons.ZOOM) {
400 | this.panOffset.set(0, 0, 0);
401 | if (this.data.enableZoom === false) return;
402 | this.handleMouseDownDolly(event);
403 | this.state = this.STATE.DOLLY;
404 | } else if (event.button === this.mouseButtons.PAN) {
405 | if (this.data.enablePan === false) return;
406 | this.handleMouseDownPan(event);
407 | this.state = this.STATE.PAN;
408 | }
409 |
410 | if (this.state !== this.STATE.NONE) {
411 | this.canvasEl.addEventListener('mousemove', this.onMouseMove, false);
412 | this.canvasEl.addEventListener('mouseup', this.onMouseUp, false);
413 | this.canvasEl.addEventListener('mouseout', this.onMouseUp, false);
414 |
415 | this.el.emit('start-drag-orbit-controls', null, false);
416 | }
417 | },
418 |
419 | onMouseMove: function (event) {
420 | // console.log('onMouseMove');
421 |
422 | if (!this.data.enabled || !this.isRunning) return;
423 |
424 | event.preventDefault();
425 |
426 | if (this.state === this.STATE.ROTATE) {
427 | if (this.data.enableRotate === false) return;
428 | this.handleMouseMoveRotate(event);
429 | } else if (this.state === this.STATE.DOLLY) {
430 | if (this.data.enableZoom === false) return;
431 | this.handleMouseMoveDolly(event);
432 | } else if (this.state === this.STATE.PAN) {
433 | if (this.data.enablePan === false) return;
434 | this.handleMouseMovePan(event);
435 | }
436 | },
437 |
438 | onMouseUp: function (event) {
439 | // console.log('onMouseUp');
440 |
441 | if (!this.data.enabled || !this.isRunning) return;
442 |
443 | if (this.state === this.STATE.ROTATE_TO) return;
444 |
445 | event.preventDefault();
446 | event.stopPropagation();
447 |
448 | this.handleMouseUp(event);
449 |
450 | this.canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
451 | this.canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
452 | this.canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
453 |
454 | this.state = this.STATE.NONE;
455 |
456 | this.el.emit('end-drag-orbit-controls', null, false);
457 | },
458 |
459 | /*
460 | * MOUSE WHEEL EVENT LISTENERS
461 | */
462 |
463 | onMouseWheel: function (event) {
464 | // console.log('onMouseWheel');
465 |
466 | if (!this.data.enabled || !this.isRunning || this.data.enableZoom === false || (this.state !== this.STATE.NONE && this.state !== this.STATE.ROTATE)) return;
467 |
468 | event.preventDefault();
469 | event.stopPropagation();
470 | this.handleMouseWheel(event);
471 | },
472 |
473 | /*
474 | * TOUCH EVENT LISTENERS
475 | */
476 |
477 | onTouchStart: function (event) {
478 | // console.log('onTouchStart');
479 |
480 | if (!this.data.enabled || !this.isRunning) return;
481 |
482 | switch (event.touches.length) {
483 | case 1: // one-fingered touch: rotate
484 | if (this.data.enableRotate === false) return;
485 | this.handleTouchStartRotate(event);
486 | this.state = this.STATE.TOUCH_ROTATE;
487 | break;
488 | case 2: // two-fingered touch: dolly
489 | if (this.data.enableZoom === false) return;
490 | this.handleTouchStartDolly(event);
491 | this.state = this.STATE.TOUCH_DOLLY;
492 | break;
493 | case 3: // three-fingered touch: pan
494 | if (this.data.enablePan === false) return;
495 | this.handleTouchStartPan(event);
496 | this.state = this.STATE.TOUCH_PAN;
497 | break;
498 | default:
499 | this.state = this.STATE.NONE;
500 | }
501 |
502 | if (this.state !== this.STATE.NONE) {
503 | this.el.emit('start-drag-orbit-controls', null, false);
504 | }
505 | },
506 |
507 | onTouchMove: function (event) {
508 | // console.log('onTouchMove');
509 |
510 | if (!this.data.enabled || !this.isRunning) return;
511 |
512 | event.preventDefault();
513 | event.stopPropagation();
514 |
515 | switch (event.touches.length) {
516 | case 1: // one-fingered touch: rotate
517 | if (this.enableRotate === false) return;
518 | if (this.state !== this.STATE.TOUCH_ROTATE) return; // is this needed?...
519 | this.handleTouchMoveRotate(event);
520 | break;
521 |
522 | case 2: // two-fingered touch: dolly
523 | if (this.data.enableZoom === false) return;
524 | if (this.state !== this.STATE.TOUCH_DOLLY) return; // is this needed?...
525 | this.handleTouchMoveDolly(event);
526 | break;
527 |
528 | case 3: // three-fingered touch: pan
529 | if (this.data.enablePan === false) return;
530 | if (this.state !== this.STATE.TOUCH_PAN) return; // is this needed?...
531 | this.handleTouchMovePan(event);
532 | break;
533 |
534 | default:
535 | this.state = this.STATE.NONE;
536 | }
537 | },
538 |
539 | onTouchEnd: function (event) {
540 | // console.log('onTouchEnd');
541 |
542 | if (!this.data.enabled || !this.isRunning) return;
543 |
544 | this.handleTouchEnd(event);
545 |
546 | this.el.emit('end-drag-orbit-controls', null, false);
547 |
548 | this.state = this.STATE.NONE;
549 | },
550 |
551 | /*
552 | * KEYBOARD EVENT LISTENERS
553 | */
554 |
555 | onKeyDown: function (event) {
556 | // console.log('onKeyDown');
557 |
558 | if (!this.data.enabled || !this.isRunning || this.data.enableKeys === false || this.data.enablePan === false) return;
559 |
560 | this.handleKeyDown(event);
561 | },
562 |
563 | /*
564 | * EVENT HANDLERS
565 | */
566 |
567 | /*
568 | * MOUSE CLICK EVENT HANDLERS
569 | */
570 |
571 | handleMouseDownRotate: function (event) {
572 | // console.log( 'handleMouseDownRotate' );
573 | this.rotateStart.set(event.clientX, event.clientY);
574 | },
575 |
576 | handleMouseDownDolly: function (event) {
577 | // console.log( 'handleMouseDownDolly' );
578 | this.dollyStart.set(event.clientX, event.clientY);
579 | },
580 |
581 | handleMouseDownPan: function (event) {
582 | // console.log( 'handleMouseDownPan' );
583 | this.panStart.set(event.clientX, event.clientY);
584 | },
585 |
586 | handleMouseMoveRotate: function (event) {
587 | // console.log( 'handleMouseMoveRotate' );
588 |
589 | this.rotateEnd.set(event.clientX, event.clientY);
590 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
591 |
592 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
593 |
594 | // rotating across whole screen goes 360 degrees around
595 | this.rotateLeft(2 * Math.PI * this.rotateDelta.x / canvas.clientWidth * this.data.rotateSpeed);
596 |
597 | // rotating up and down along whole screen attempts to go 360, but limited to 180
598 | this.rotateUp(2 * Math.PI * this.rotateDelta.y / canvas.clientHeight * this.data.rotateSpeed);
599 |
600 | this.rotateStart.copy(this.rotateEnd);
601 |
602 | this.updateView();
603 | },
604 |
605 | handleMouseMoveDolly: function (event) {
606 | // console.log( 'handleMouseMoveDolly' );
607 |
608 | this.dollyEnd.set(event.clientX, event.clientY);
609 | this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
610 |
611 | if (this.dollyDelta.y > 0) {
612 | !this.data.invertZoom ? this.dollyIn(this.getZoomScale()) : this.dollyOut(this.getZoomScale());
613 | } else if (this.dollyDelta.y < 0) {
614 | !this.data.invertZoom ? this.dollyOut(this.getZoomScale()) : this.dollyIn(this.getZoomScale());
615 | }
616 |
617 | this.dollyStart.copy(this.dollyEnd);
618 |
619 | this.updateView();
620 | },
621 |
622 | handleMouseMovePan: function (event) {
623 | // console.log( 'handleMouseMovePan' );
624 |
625 | this.panEnd.set(event.clientX, event.clientY);
626 | this.panDelta.subVectors(this.panEnd, this.panStart);
627 | this.pan(this.panDelta.x, this.panDelta.y);
628 | this.panStart.copy(this.panEnd);
629 |
630 | this.updateView();
631 | },
632 |
633 | handleMouseUp: function (event) {
634 | // console.log( 'handleMouseUp' );
635 | },
636 |
637 | /*
638 | * MOUSE WHEEL EVENT HANDLERS
639 | */
640 |
641 | handleMouseWheel: function (event) {
642 | // console.log( 'handleMouseWheel' );
643 |
644 | var delta = 0;
645 | if (event.wheelDelta !== undefined) {
646 | // WebKit / Opera / Explorer 9
647 | delta = event.wheelDelta;
648 | } else if (event.detail !== undefined) {
649 | // Firefox
650 | delta = -event.detail;
651 | }
652 |
653 | if (delta > 0) {
654 | !this.data.invertZoom ? this.dollyOut(this.getZoomScale()) : this.dollyIn(this.getZoomScale());
655 | } else if (delta < 0) {
656 | !this.data.invertZoom ? this.dollyIn(this.getZoomScale()) : this.dollyOut(this.getZoomScale());
657 | }
658 |
659 | this.updateView();
660 | },
661 |
662 | /*
663 | * TOUCH EVENT HANDLERS
664 | */
665 |
666 | handleTouchStartRotate: function (event) {
667 | // console.log( 'handleTouchStartRotate' );
668 |
669 | this.rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
670 | },
671 |
672 | handleTouchStartDolly: function (event) {
673 | // console.log( 'handleTouchStartDolly' );
674 |
675 | var dx = event.touches[0].pageX - event.touches[1].pageX;
676 | var dy = event.touches[0].pageY - event.touches[1].pageY;
677 | var distance = Math.sqrt(dx * dx + dy * dy);
678 | this.dollyStart.set(0, distance);
679 | },
680 |
681 | handleTouchStartPan: function (event) {
682 | // console.log( 'handleTouchStartPan' );
683 |
684 | this.panStart.set(event.touches[0].pageX, event.touches[0].pageY);
685 | },
686 |
687 | handleTouchMoveRotate: function (event) {
688 | // console.log( 'handleTouchMoveRotate' );
689 |
690 | this.rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
691 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
692 |
693 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
694 | // rotating across whole screen goes 360 degrees around
695 | this.rotateLeft(2 * Math.PI * this.rotateDelta.x / canvas.clientWidth * this.data.rotateSpeed);
696 | // rotating up and down along whole screen attempts to go 360, but limited to 180
697 | this.rotateUp(2 * Math.PI * this.rotateDelta.y / canvas.clientHeight * this.data.rotateSpeed);
698 | this.rotateStart.copy(this.rotateEnd);
699 | this.updateView();
700 | },
701 |
702 | handleTouchMoveDolly: function (event) {
703 | // console.log( 'handleTouchMoveDolly' );
704 |
705 | var dx = event.touches[0].pageX - event.touches[1].pageX;
706 | var dy = event.touches[0].pageY - event.touches[1].pageY;
707 |
708 | var distance = Math.sqrt(dx * dx + dy * dy);
709 |
710 | this.dollyEnd.set(0, distance);
711 | this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
712 | if (this.dollyDelta.y > 0) {
713 | this.dollyIn(this.getZoomScale());
714 | } else if (this.dollyDelta.y < 0) {
715 | this.dollyOut(this.getZoomScale());
716 | }
717 |
718 | this.dollyStart.copy(this.dollyEnd);
719 | this.updateView();
720 | },
721 |
722 | handleTouchMovePan: function (event) {
723 | // console.log( 'handleTouchMovePan' );
724 |
725 | this.panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
726 | this.panDelta.subVectors(this.panEnd, this.panStart);
727 | this.pan(this.panDelta.x, this.panDelta.y);
728 | this.panStart.copy(this.panEnd);
729 | this.updateView();
730 | },
731 |
732 | handleTouchEnd: function (event) {
733 | // console.log( 'handleTouchEnd' );
734 | },
735 |
736 | /*
737 | * KEYBOARD EVENT HANDLERS
738 | */
739 |
740 | handleKeyDown: function (event) {
741 | // console.log( 'handleKeyDown' );
742 |
743 | switch (event.keyCode) {
744 | case this.keys.UP:
745 | this.pan(0, this.data.keyPanSpeed);
746 | this.updateView();
747 | break;
748 | case this.keys.BOTTOM:
749 | this.pan(0, -this.data.keyPanSpeed);
750 | this.updateView();
751 | break;
752 | case this.keys.LEFT:
753 | this.pan(this.data.keyPanSpeed, 0);
754 | this.updateView();
755 | break;
756 | case this.keys.RIGHT:
757 | this.pan(-this.data.keyPanSpeed, 0);
758 | this.updateView();
759 | break;
760 | }
761 | },
762 |
763 | /*
764 | * HELPER FUNCTIONS
765 | */
766 |
767 | getAutoRotationAngle: function () {
768 | return 2 * Math.PI / 60 / 60 * this.data.autoRotateSpeed;
769 | },
770 |
771 | getZoomScale: function () {
772 | return Math.pow(0.95, this.data.zoomSpeed);
773 | },
774 |
775 | rotateLeft: function (angle) {
776 | this.sphericalDelta.theta -= angle;
777 | },
778 |
779 | rotateUp: function (angle) {
780 | this.sphericalDelta.phi -= angle;
781 | },
782 |
783 | rotateTo: function (vec3) {
784 | this.state = this.STATE.ROTATE_TO;
785 | this.desiredPosition.copy(vec3);
786 | },
787 |
788 | panHorizontally: function (distance, objectMatrix) {
789 | // console.log('pan horizontally', distance, objectMatrix);
790 | var v = new THREE.Vector3();
791 | v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
792 | v.multiplyScalar(-distance);
793 | this.panOffset.add(v);
794 | },
795 |
796 | panVertically: function (distance, objectMatrix) {
797 | // console.log('pan vertically', distance, objectMatrix);
798 | var v = new THREE.Vector3();
799 | v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
800 | v.multiplyScalar(distance);
801 | this.panOffset.add(v);
802 | },
803 |
804 | pan: function (deltaX, deltaY) { // deltaX and deltaY are in pixels; right and down are positive
805 | // console.log('panning', deltaX, deltaY );
806 | var offset = new THREE.Vector3();
807 | var canvas = this.canvasEl === document ? this.canvasEl.body : this.canvasEl;
808 |
809 | if (this.cameraType === 'PerspectiveCamera') {
810 | // perspective
811 | var position = this.dolly.position;
812 | offset.copy(position).sub(this.target);
813 | var targetDistance = offset.length();
814 | targetDistance *= Math.tan((this.camera.fov / 2) * Math.PI / 180.0); // half of the fov is center to top of screen
815 | this.panHorizontally(2 * deltaX * targetDistance / canvas.clientHeight, this.object.matrix); // we actually don't use screenWidth, since perspective camera is fixed to screen height
816 | this.panVertically(2 * deltaY * targetDistance / canvas.clientHeight, this.object.matrix);
817 | } else if (this.cameraType === 'OrthographicCamera') {
818 | // orthographic
819 | this.panHorizontally(deltaX * (this.dolly.right - this.dolly.left) / this.camera.zoom / canvas.clientWidth, this.object.matrix);
820 | this.panVertically(deltaY * (this.dolly.top - this.dolly.bottom) / this.camera.zoom / canvas.clientHeight, this.object.matrix);
821 | } else {
822 | // camera neither orthographic nor perspective
823 | console.warn('Trying to pan: WARNING: Orbit Controls encountered an unknown camera type - pan disabled.');
824 | this.data.enablePan = false;
825 | }
826 | },
827 |
828 | dollyIn: function (dollyScale) {
829 | // console.log( "dollyIn camera" );
830 | if (this.cameraType === 'PerspectiveCamera') {
831 | this.scale *= dollyScale;
832 | } else if (this.cameraType === 'OrthographicCamera') {
833 | this.camera.zoom = Math.max(this.data.minZoom, Math.min(this.data.maxZoom, this.camera.zoom * dollyScale));
834 | this.camera.updateProjectionMatrix();
835 | this.zoomChanged = true;
836 | } else {
837 | console.warn('Trying to dolly in: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled.');
838 | this.data.enableZoom = false;
839 | }
840 | },
841 |
842 | dollyOut: function (dollyScale) {
843 | // console.log( "dollyOut camera" );
844 | if (this.cameraType === 'PerspectiveCamera') {
845 | this.scale /= dollyScale;
846 | } else if (this.cameraType === 'OrthographicCamera') {
847 | this.camera.zoom = Math.max(this.data.minZoom, Math.min(this.data.maxZoom, this.camera.zoom / dollyScale));
848 | this.camera.updateProjectionMatrix();
849 | this.zoomChanged = true;
850 | } else {
851 | console.warn('Trying to dolly out: WARNING: Orbit Controls encountered an unknown camera type - dolly/zoom disabled.');
852 | this.data.enableZoom = false;
853 | }
854 | },
855 |
856 | lookAtTarget: function (object, target) {
857 | var v = new THREE.Vector3();
858 | v.subVectors(object.position, target).add(object.position);
859 | object.lookAt(v);
860 | },
861 |
862 | /*
863 | * SAVES CAMERA POSE (WHEN ENTERING VR)
864 | */
865 |
866 | saveCameraPose: function () {
867 | if (this.savedPose) { return; }
868 | this.savedPose = {
869 | position: this.dolly.position,
870 | rotation: this.dolly.rotation
871 | };
872 | },
873 |
874 | /*
875 | * RESTORE CAMERA POSE (WHEN EXITING VR)
876 | */
877 |
878 | restoreCameraPose: function () {
879 | if (!this.savedPose) { return; }
880 | this.dolly.position.copy(this.savedPose.position);
881 | this.dolly.rotation.copy(this.savedPose.rotation);
882 | this.savedPose = null;
883 | },
884 |
885 | /*
886 | * VIEW UPDATE
887 | */
888 |
889 | updateView: function (forceUpdate) {
890 | if (this.desiredPosition && this.state === this.STATE.ROTATE_TO) {
891 | var desiredSpherical = new THREE.Spherical();
892 | desiredSpherical.setFromVector3(this.desiredPosition);
893 | var phiDiff = desiredSpherical.phi - this.spherical.phi;
894 | var thetaDiff = desiredSpherical.theta - this.spherical.theta;
895 | this.sphericalDelta.set(this.spherical.radius, phiDiff * this.data.rotateToSpeed, thetaDiff * this.data.rotateToSpeed);
896 | }
897 |
898 | var offset = new THREE.Vector3();
899 |
900 | var quat = new THREE.Quaternion().setFromUnitVectors(this.dolly.up, new THREE.Vector3(0, 1, 0)); // so camera.up is the orbit axis
901 | var quatInverse = quat.clone().inverse();
902 |
903 | offset.copy(this.dolly.position).sub(this.target);
904 | offset.applyQuaternion(quat); // rotate offset to "y-axis-is-up" space
905 | this.spherical.setFromVector3(offset); // angle from z-axis around y-axis
906 |
907 | if (this.data.autoRotate && this.state === this.STATE.NONE) this.rotateLeft(this.getAutoRotationAngle());
908 |
909 | this.spherical.theta += this.sphericalDelta.theta;
910 | this.spherical.phi += this.sphericalDelta.phi;
911 | this.spherical.theta = Math.max(this.data.minAzimuthAngle, Math.min(this.data.maxAzimuthAngle, this.spherical.theta)); // restrict theta to be inside desired limits
912 | this.spherical.phi = Math.max(this.data.minPolarAngle, Math.min(this.data.maxPolarAngle, this.spherical.phi)); // restrict phi to be inside desired limits
913 | this.spherical.makeSafe();
914 | this.spherical.radius *= this.scale;
915 | this.spherical.radius = Math.max(this.data.minDistance, Math.min(this.data.maxDistance, this.spherical.radius)); // restrict radius to be inside desired limits
916 |
917 | this.target.add(this.panOffset); // move target to panned location
918 |
919 | offset.setFromSpherical(this.spherical);
920 | offset.applyQuaternion(quatInverse); // rotate offset back to "camera-up-vector-is-up" space
921 |
922 | this.dolly.position.copy(this.target).add(offset);
923 |
924 | if (this.target) {
925 | this.lookAtTarget(this.dolly, this.target);
926 | }
927 |
928 | if (this.data.enableDamping === true) {
929 | this.sphericalDelta.theta *= (1 - this.data.dampingFactor);
930 | this.sphericalDelta.phi *= (1 - this.data.dampingFactor);
931 | } else {
932 | this.sphericalDelta.set(0, 0, 0);
933 | }
934 |
935 | this.scale = 1;
936 | this.panOffset.set(0, 0, 0);
937 |
938 | // update condition is:
939 | // min(camera displacement, camera rotation in radians)^2 > EPS
940 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
941 |
942 | if (forceUpdate === true ||
943 | this.zoomChanged ||
944 | this.lastPosition.distanceToSquared(this.dolly.position) > this.EPS ||
945 | 8 * (1 - this.lastQuaternion.dot(this.dolly.quaternion)) > this.EPS) {
946 | // this.el.emit('change-drag-orbit-controls', null, false);
947 |
948 | var hmdQuaternion = this.calculateHMDQuaternion();
949 | var hmdEuler = new THREE.Euler();
950 | hmdEuler.setFromQuaternion(hmdQuaternion, 'YXZ');
951 |
952 | this.el.setAttribute('position', {
953 | x: this.dolly.position.x,
954 | y: this.dolly.position.y,
955 | z: this.dolly.position.z
956 | });
957 |
958 | this.el.setAttribute('rotation', {
959 | x: radToDeg(hmdEuler.x),
960 | y: radToDeg(hmdEuler.y),
961 | z: radToDeg(hmdEuler.z)
962 | });
963 |
964 | this.lastPosition.copy(this.dolly.position);
965 | this.lastQuaternion.copy(this.dolly.quaternion);
966 |
967 | this.zoomChanged = false;
968 |
969 | return true;
970 | }
971 |
972 | return false;
973 | },
974 |
975 | calculateHMDQuaternion: (function () {
976 | var hmdQuaternion = new THREE.Quaternion();
977 | return function () {
978 | hmdQuaternion.copy(this.dolly.quaternion);
979 | return hmdQuaternion;
980 | };
981 | })()
982 |
983 | });
984 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aframe-orbit-controls-component-2",
3 | "version": "0.1.14",
4 | "description": "A (almost) direct port of the ThreeJS OrbitControls as a component for A-Frame.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "browserify examples/main.js -o examples/build.js",
8 | "dev": "budo examples/main.js:build.js --dir examples --port 8000 --live --open",
9 | "dist": "webpack index.js dist/aframe-orbit-controls-component.js && webpack -p index.js dist/aframe-orbit-controls-component.min.js",
10 | "lint": "semistandard -v | snazzy",
11 | "postpublish": "npm run dist",
12 | "preghpages": "npm run build && shx rm -rf gh-pages && shx mkdir gh-pages && shx cp -r examples/* gh-pages",
13 | "ghpages": "npm run preghpages && ghpages -p gh-pages",
14 | "unboil": "node scripts/unboil.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/tizzle/aframe-orbit-controls-component.git"
19 | },
20 | "keywords": [
21 | "aframe",
22 | "aframe-component",
23 | "aframe-vr",
24 | "vr",
25 | "mozvr",
26 | "webvr"
27 | ],
28 | "author": "Till Hinrichs ",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/tizzle/aframe-orbit-controls-component/issues"
32 | },
33 | "homepage": "https://github.com/tizzle/aframe-orbit-controls-component#readme",
34 | "devDependencies": {
35 | "aframe": "^0.7.0",
36 | "browserify": "^13.0.0",
37 | "browserify-css": "^0.9.1",
38 | "budo": "^8.2.2",
39 | "ghpages": "^0.0.8",
40 | "inquirer": "^1.0.2",
41 | "randomcolor": "^0.4.4",
42 | "semistandard": "^8.0.0",
43 | "shelljs": "^0.7.0",
44 | "shx": "^0.1.1",
45 | "snazzy": "^4.0.0",
46 | "webpack": "^1.13.0"
47 | },
48 | "semistandard": {
49 | "ignore": [
50 | "examples/build.js",
51 | "dist/**"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/scripts/unboil.js:
--------------------------------------------------------------------------------
1 | /* global find, ls, sed */
2 | require('shelljs/global');
3 | var exec = require('child_process').exec;
4 | var inquirer = require('inquirer');
5 |
6 | // You can manually add configuration options here if you don't want to go through the
7 | // interactive script or if the interactive script is not working.
8 | var CONFIG = {
9 | // What is your component's short-name? (e.g., `rick-roll` for aframe-rick-roll-component).
10 | shortname: '',
11 | // What is your component's long-name? (e.g., `Rick Roll` for A-Frame Rick Roll Component).
12 | longname: '',
13 | // Where is your component on GitHub (e.g., yourusername/aframe-rick-roll-component).
14 | repo: '',
15 | // Who are you? (e.g., Jane John ).
16 | author: ''
17 | };
18 |
19 | // ---
20 |
21 | exec("sed '1,/--trim--/d' README.md | tee README.md");
22 |
23 | if (CONFIG.shortname && CONFIG.longname && CONFIG.repo) {
24 | run(CONFIG);
25 | process.exit(0);
26 | }
27 |
28 | var q1 = {
29 | name: 'shortname',
30 | message: 'What is your component\'s short-name? (e.g., `rick-roll` for aframe-rick-roll-component, ``)',
31 | type: 'input'
32 | };
33 |
34 | var q2 = {
35 | name: 'longname',
36 | message: 'What is your component\'s long-name? (e.g., `Rick Roll` for A-Frame Rick Roll Component)',
37 | type: 'input'
38 | };
39 |
40 | var q3 = {
41 | name: 'repo',
42 | message: 'Where is your component on Github? (e.g., yourusername/aframe-rick-roll-component)',
43 | type: 'input'
44 | };
45 |
46 | var q4 = {
47 | name: 'author',
48 | message: 'Who are you? (e.g., Jane John )',
49 | type: 'input'
50 | };
51 |
52 | inquirer.prompt([q1, q2, q3, q4], run);
53 |
54 | function run (ans) {
55 | ls(['index.js', 'package.json', 'README.md']).forEach(function (file) {
56 | sed('-i', 'aframe-example-component', 'aframe-' + ans.shortname + '-component', file);
57 | sed('-i', 'Example Component', ans.longname + ' Component', file);
58 | sed('-i', 'Example component', ans.longname + ' component', file);
59 | sed('-i', "'example'", "'" + ans.shortname + "'", file);
60 | });
61 |
62 | ls('README.md').forEach(function (file) {
63 | sed('-i', 'example component', ans.longname + ' component', file);
64 | sed('-i', 'example=', ans.shortname + '=', file);
65 | });
66 |
67 | find('examples').filter(function (file) { return file.match(/\.html/); }).forEach(function (file) {
68 | sed('-i', 'Example Component', ans.longname + ' Component', file);
69 | sed('-i', 'ngokevin/aframe-component-boilerplate', ans.repo, file);
70 | });
71 |
72 | ls(['package.json', 'README.md']).forEach(function (file) {
73 | sed('-i', 'aframe-example-component', 'aframe-' + ans.shortname + '-component', file);
74 | sed('-i', 'ngokevin/aframe-component-boilerplate', ans.repo, file);
75 | sed('-i', 'Kevin Ngo ', ans.author, file);
76 | });
77 | }
78 |
--------------------------------------------------------------------------------