|
5 | 5 | * This source code is licensed under the MIT license found in the
|
6 | 6 | * LICENSE file in the root directory of this source tree.
|
7 | 7 | */
|
8 |
| - |
9 |
| -'use strict'; |
10 |
| - |
11 |
| -module.exports = createCamera; |
12 |
| - |
13 |
| -var now = require('right-now'); |
14 |
| -var createView = require('3d-view'); |
15 |
| -var mouseChange = require('mouse-change'); |
16 |
| -var mouseWheel = require('mouse-wheel'); |
17 |
| -var mouseOffset = require('mouse-event-offset'); |
18 |
| -var supportsPassive = require('has-passive-events'); |
19 |
| - |
20 |
| -function createCamera(element, options) { |
21 |
| - element = element || document.body; |
22 |
| - options = options || {}; |
23 |
| - |
24 |
| - var limits = [ 0.01, Infinity ]; |
25 |
| - if('distanceLimits' in options) { |
26 |
| - limits[0] = options.distanceLimits[0]; |
27 |
| - limits[1] = options.distanceLimits[1]; |
28 |
| - } |
29 |
| - if('zoomMin' in options) { |
30 |
| - limits[0] = options.zoomMin; |
31 |
| - } |
32 |
| - if('zoomMax' in options) { |
33 |
| - limits[1] = options.zoomMax; |
34 |
| - } |
35 |
| - |
36 |
| - var view = createView({ |
37 |
| - center: options.center || [0, 0, 0], |
38 |
| - up: options.up || [0, 1, 0], |
39 |
| - eye: options.eye || [0, 0, 10], |
40 |
| - mode: options.mode || 'orbit', |
41 |
| - distanceLimits: limits |
42 |
| - }); |
43 |
| - |
44 |
| - var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; |
45 |
| - var distance = 0.0; |
46 |
| - var width = element.clientWidth; |
47 |
| - var height = element.clientHeight; |
48 |
| - |
49 |
| - var camera = { |
50 |
| - keyBindingMode: 'rotate', |
51 |
| - enableWheel: true, |
52 |
| - view: view, |
53 |
| - element: element, |
54 |
| - delay: options.delay || 16, |
55 |
| - rotateSpeed: options.rotateSpeed || 1, |
56 |
| - zoomSpeed: options.zoomSpeed || 1, |
57 |
| - translateSpeed: options.translateSpeed || 1, |
58 |
| - flipX: !!options.flipX, |
59 |
| - flipY: !!options.flipY, |
60 |
| - modes: view.modes, |
61 |
| - tick: function() { |
62 |
| - var t = now(); |
63 |
| - var delay = this.delay; |
64 |
| - var ctime = t - 2 * delay; |
65 |
| - view.idle(t - delay); |
66 |
| - view.recalcMatrix(ctime); |
67 |
| - view.flush(t - (100 + delay * 2)); |
68 |
| - var allEqual = true; |
69 |
| - var matrix = view.computedMatrix; |
70 |
| - for(var i = 0; i < 16; ++i) { |
71 |
| - allEqual = allEqual && (pmatrix[i] === matrix[i]); |
72 |
| - pmatrix[i] = matrix[i]; |
73 |
| - } |
74 |
| - var sizeChanged = |
75 |
| - element.clientWidth === width && |
76 |
| - element.clientHeight === height; |
77 |
| - width = element.clientWidth; |
78 |
| - height = element.clientHeight; |
79 |
| - if(allEqual) return !sizeChanged; |
80 |
| - distance = Math.exp(view.computedRadius[0]); |
81 |
| - return true; |
82 |
| - }, |
83 |
| - lookAt: function(center, eye, up) { |
84 |
| - view.lookAt(view.lastT(), center, eye, up); |
85 |
| - }, |
86 |
| - rotate: function(pitch, yaw, roll) { |
87 |
| - view.rotate(view.lastT(), pitch, yaw, roll); |
88 |
| - }, |
89 |
| - pan: function(dx, dy, dz) { |
90 |
| - view.pan(view.lastT(), dx, dy, dz); |
91 |
| - }, |
92 |
| - translate: function(dx, dy, dz) { |
93 |
| - view.translate(view.lastT(), dx, dy, dz); |
94 |
| - } |
95 |
| - }; |
96 |
| - |
97 |
| - Object.defineProperties(camera, { |
98 |
| - matrix: { |
99 |
| - get: function() { |
100 |
| - return view.computedMatrix; |
101 |
| - }, |
102 |
| - set: function(mat) { |
103 |
| - view.setMatrix(view.lastT(), mat); |
104 |
| - return view.computedMatrix; |
105 |
| - }, |
106 |
| - enumerable: true |
107 |
| - }, |
108 |
| - mode: { |
109 |
| - get: function() { |
110 |
| - return view.getMode(); |
111 |
| - }, |
112 |
| - set: function(mode) { |
113 |
| - var curUp = view.computedUp.slice(); |
114 |
| - var curEye = view.computedEye.slice(); |
115 |
| - var curCenter = view.computedCenter.slice(); |
116 |
| - view.setMode(mode); |
117 |
| - if(mode === 'turntable') { |
118 |
| - // Hacky time warping stuff to generate smooth animation |
119 |
| - var t0 = now(); |
120 |
| - view._active.lookAt(t0, curEye, curCenter, curUp); |
121 |
| - view._active.lookAt(t0 + 500, curEye, curCenter, [0, 0, 1]); |
122 |
| - view._active.flush(t0); |
123 |
| - } |
124 |
| - return view.getMode(); |
125 |
| - }, |
126 |
| - enumerable: true |
127 |
| - }, |
128 |
| - center: { |
129 |
| - get: function() { |
130 |
| - return view.computedCenter; |
131 |
| - }, |
132 |
| - set: function(ncenter) { |
133 |
| - view.lookAt(view.lastT(), null, ncenter); |
134 |
| - return view.computedCenter; |
135 |
| - }, |
136 |
| - enumerable: true |
137 |
| - }, |
138 |
| - eye: { |
139 |
| - get: function() { |
140 |
| - return view.computedEye; |
141 |
| - }, |
142 |
| - set: function(neye) { |
143 |
| - view.lookAt(view.lastT(), neye); |
144 |
| - return view.computedEye; |
145 |
| - }, |
146 |
| - enumerable: true |
147 |
| - }, |
148 |
| - up: { |
149 |
| - get: function() { |
150 |
| - return view.computedUp; |
151 |
| - }, |
152 |
| - set: function(nup) { |
153 |
| - view.lookAt(view.lastT(), null, null, nup); |
154 |
| - return view.computedUp; |
155 |
| - }, |
156 |
| - enumerable: true |
157 |
| - }, |
158 |
| - distance: { |
159 |
| - get: function() { |
160 |
| - return distance; |
161 |
| - }, |
162 |
| - set: function(d) { |
163 |
| - view.setDistance(view.lastT(), d); |
164 |
| - return d; |
165 |
| - }, |
166 |
| - enumerable: true |
167 |
| - }, |
168 |
| - distanceLimits: { |
169 |
| - get: function() { |
170 |
| - return view.getDistanceLimits(limits); |
171 |
| - }, |
172 |
| - set: function(v) { |
173 |
| - view.setDistanceLimits(v); |
174 |
| - return v; |
175 |
| - }, |
176 |
| - enumerable: true |
177 |
| - } |
178 |
| - }); |
179 |
| - |
180 |
| - element.addEventListener('contextmenu', function(ev) { |
181 |
| - ev.preventDefault(); |
182 |
| - return false; |
183 |
| - }); |
184 |
| - |
185 |
| - var lastX = 0; |
186 |
| - var lastY = 0; |
187 |
| - var lastMods = {shift: false, control: false, alt: false, meta: false}; |
188 |
| - camera.mouseListener = mouseChange(element, handleInteraction); |
189 |
| - |
190 |
| - // enable simple touch interactions |
191 |
| - element.addEventListener('touchstart', function(ev) { |
192 |
| - var xy = mouseOffset(ev.changedTouches[0], element); |
193 |
| - handleInteraction(0, xy[0], xy[1], lastMods); |
194 |
| - handleInteraction(1, xy[0], xy[1], lastMods); |
195 |
| - |
196 |
| - ev.preventDefault(); |
197 |
| - }, supportsPassive ? {passive: false} : false); |
198 |
| - element.addEventListener('touchmove', function(ev) { |
199 |
| - var xy = mouseOffset(ev.changedTouches[0], element); |
200 |
| - handleInteraction(1, xy[0], xy[1], lastMods); |
201 |
| - |
202 |
| - ev.preventDefault(); |
203 |
| - }, supportsPassive ? {passive: false} : false); |
204 |
| - element.addEventListener('touchend', function(ev) { |
205 |
| - handleInteraction(0, lastX, lastY, lastMods); |
206 |
| - |
207 |
| - ev.preventDefault(); |
208 |
| - }, supportsPassive ? {passive: false} : false); |
209 |
| - |
210 |
| - function handleInteraction(buttons, x, y, mods) { |
211 |
| - var keyBindingMode = camera.keyBindingMode; |
212 |
| - |
213 |
| - if(keyBindingMode === false) return; |
214 |
| - |
215 |
| - var rotate = keyBindingMode === 'rotate'; |
216 |
| - var pan = keyBindingMode === 'pan'; |
217 |
| - var zoom = keyBindingMode === 'zoom'; |
218 |
| - |
219 |
| - var ctrl = !!mods.control; |
220 |
| - var alt = !!mods.alt; |
221 |
| - var shift = !!mods.shift; |
222 |
| - var left = !!(buttons & 1); |
223 |
| - var right = !!(buttons & 2); |
224 |
| - var middle = !!(buttons & 4); |
225 |
| - |
226 |
| - var scale = 1.0 / element.clientHeight; |
227 |
| - var dx = scale * (x - lastX); |
228 |
| - var dy = scale * (y - lastY); |
229 |
| - |
230 |
| - var flipX = camera.flipX ? 1 : -1; |
231 |
| - var flipY = camera.flipY ? 1 : -1; |
232 |
| - |
233 |
| - var t = now(); |
234 |
| - |
235 |
| - var drot = Math.PI * camera.rotateSpeed; |
236 |
| - |
237 |
| - if((rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { |
238 |
| - // Rotate |
239 |
| - view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0); |
240 |
| - } |
241 |
| - |
242 |
| - if((pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { |
243 |
| - // Pan |
244 |
| - view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0); |
245 |
| - } |
246 |
| - |
247 |
| - if((zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { |
248 |
| - // Zoom |
249 |
| - var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100; |
250 |
| - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); |
251 |
| - } |
252 |
| - |
253 |
| - lastX = x; |
254 |
| - lastY = y; |
255 |
| - lastMods = mods; |
256 |
| - |
257 |
| - return true; |
258 |
| - } |
259 |
| - |
260 |
| - camera.wheelListener = mouseWheel(element, function(dx, dy) { |
261 |
| - // TODO remove now that we can disable scroll via scrollZoom? |
262 |
| - if(camera.keyBindingMode === false) return; |
263 |
| - if(!camera.enableWheel) return; |
264 |
| - |
265 |
| - var flipX = camera.flipX ? 1 : -1; |
266 |
| - var flipY = camera.flipY ? 1 : -1; |
267 |
| - var t = now(); |
268 |
| - if(Math.abs(dx) > Math.abs(dy)) { |
269 |
| - view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth); |
270 |
| - } else { |
271 |
| - var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 20.0; |
272 |
| - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); |
273 |
| - } |
274 |
| - }, true); |
275 |
| - |
276 |
| - return camera; |
277 |
| -} |
0 commit comments