1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 base.require('base.settings');
10 base.exportTo('ui', function() {
14 DEFAULT_EYE_DISTANCE: 10000,
15 MINIMUM_DISTANCE: 1000,
16 MAXIMUM_DISTANCE: 100000,
18 RESCALE_TIMEOUT_MS: 200,
20 SETTINGS_NAMESPACE: 'ui_camera'
24 var Camera = ui.define('camera');
27 __proto__: HTMLUnknownElement.prototype,
29 decorate: function(eventSource) {
30 this.eventSource_ = eventSource;
32 this.eventSource_.addEventListener('beginpan',
33 this.onPanBegin_.bind(this));
34 this.eventSource_.addEventListener('updatepan',
35 this.onPanUpdate_.bind(this));
36 this.eventSource_.addEventListener('endpan',
37 this.onPanEnd_.bind(this));
39 this.eventSource_.addEventListener('beginzoom',
40 this.onZoomBegin_.bind(this));
41 this.eventSource_.addEventListener('updatezoom',
42 this.onZoomUpdate_.bind(this));
43 this.eventSource_.addEventListener('endzoom',
44 this.onZoomEnd_.bind(this));
46 this.eventSource_.addEventListener('beginrotate',
47 this.onRotateBegin_.bind(this));
48 this.eventSource_.addEventListener('updaterotate',
49 this.onRotateUpdate_.bind(this));
50 this.eventSource_.addEventListener('endrotate',
51 this.onRotateEnd_.bind(this));
53 this.eye_ = [0, 0, constants.DEFAULT_EYE_DISTANCE];
54 this.gazeTarget_ = [0, 0, 0];
55 this.rotation_ = [0, 0];
57 this.pixelRatio_ = window.devicePixelRatio || 1;
61 get modelViewMatrix() {
62 var mvMatrix = mat4.create();
64 mat4.lookAt(mvMatrix, this.eye_, this.gazeTarget_, [0, 1, 0]);
68 get projectionMatrix() {
70 base.windowRectForElement(this.canvas_).scaleSize(this.pixelRatio_);
72 var aspectRatio = rect.width / rect.height;
73 var matrix = mat4.create();
75 matrix, base.deg2rad(constants.FOV), aspectRatio, 1, 100000);
84 set deviceRect(rect) {
85 this.deviceRect_ = rect;
88 get stackingDistanceDampening() {
90 this.gazeTarget_[0] - this.eye_[0],
91 this.gazeTarget_[1] - this.eye_[1],
92 this.gazeTarget_[2] - this.eye_[2]];
93 vec3.normalize(gazeVector, gazeVector);
94 return 1 + gazeVector[2];
97 loadCameraFromSettings: function(settings) {
98 this.eye_ = settings.get(
99 'eye', this.eye_, constants.SETTINGS_NAMESPACE);
100 this.gazeTarget_ = settings.get(
101 'gaze_target', this.gazeTarget_, constants.SETTINGS_NAMESPACE);
102 this.rotation_ = settings.get(
103 'rotation', this.rotation_, constants.SETTINGS_NAMESPACE);
105 this.dispatchRenderEvent_();
108 saveCameraToSettings: function(settings) {
110 'eye', this.eye_, constants.SETTINGS_NAMESPACE);
112 'gaze_target', this.gazeTarget_, constants.SETTINGS_NAMESPACE);
114 'rotation', this.rotation_, constants.SETTINGS_NAMESPACE);
117 resetCamera: function() {
118 this.eye_ = [0, 0, constants.DEFAULT_EYE_DISTANCE];
119 this.gazeTarget_ = [0, 0, 0];
120 this.rotation_ = [0, 0];
122 var settings = base.SessionSettings();
123 var keys = settings.keys(constants.SETTINGS_NAMESPACE);
124 if (keys.length !== 0) {
125 this.loadCameraFromSettings(settings);
129 if (this.deviceRect_) {
131 base.windowRectForElement(this.canvas_).scaleSize(this.pixelRatio_);
133 this.eye_[0] = this.deviceRect_.width / 2;
134 this.eye_[1] = this.deviceRect_.height / 2;
136 this.gazeTarget_[0] = this.deviceRect_.width / 2;
137 this.gazeTarget_[1] = this.deviceRect_.height / 2;
140 this.saveCameraToSettings(settings);
141 this.dispatchRenderEvent_();
144 updatePanByDelta: function(delta) {
146 base.windowRectForElement(this.canvas_).scaleSize(this.pixelRatio_);
148 // Get the eye vector, since we'll be adjusting gazeTarget.
150 this.eye_[0] - this.gazeTarget_[0],
151 this.eye_[1] - this.gazeTarget_[1],
152 this.eye_[2] - this.gazeTarget_[2]];
153 var length = vec3.length(eyeVector);
154 vec3.normalize(eyeVector, eyeVector);
156 var halfFov = constants.FOV / 2;
158 2.0 * length * Math.tan(base.deg2rad(halfFov)) / rect.height;
160 // Get the up and right vectors.
162 var rotMatrix = mat4.create();
164 rotMatrix, rotMatrix, base.deg2rad(this.rotation_[1]), [0, 1, 0]);
166 rotMatrix, rotMatrix, base.deg2rad(this.rotation_[0]), [1, 0, 0]);
167 vec3.transformMat4(up, up, rotMatrix);
169 var right = [0, 0, 0];
170 vec3.cross(right, eyeVector, up);
171 vec3.normalize(right, right);
173 // Update the gaze target.
174 for (var i = 0; i < 3; ++i) {
175 this.gazeTarget_[i] +=
176 delta[0] * multiplier * right[i] - delta[1] * multiplier * up[i];
178 this.eye_[i] = this.gazeTarget_[i] + length * eyeVector[i];
181 // If we have some z offset, we need to reposition gazeTarget
182 // to be on the plane z = 0 with normal [0, 0, 1].
183 if (Math.abs(this.gazeTarget_[2]) > 1e-6) {
184 var gazeVector = [-eyeVector[0], -eyeVector[1], -eyeVector[2]];
185 var newLength = base.clamp(
186 -this.eye_[2] / gazeVector[2],
187 constants.MINIMUM_DISTANCE,
188 constants.MAXIMUM_DISTANCE);
190 for (var i = 0; i < 3; ++i)
191 this.gazeTarget_[i] = this.eye_[i] + newLength * gazeVector[i];
194 this.saveCameraToSettings(base.SessionSettings());
195 this.dispatchRenderEvent_();
198 updateZoomByDelta: function(delta) {
199 var deltaY = delta[1];
200 deltaY = base.clamp(deltaY, -50, 50);
201 var scale = 1.0 - deltaY / 100.0;
203 var eyeVector = [0, 0, 0];
204 vec3.subtract(eyeVector, this.eye_, this.gazeTarget_);
206 var length = vec3.length(eyeVector);
208 // Clamp the length to allowed values by changing the scale.
209 if (length * scale < constants.MINIMUM_DISTANCE)
210 scale = constants.MINIMUM_DISTANCE / length;
211 else if (length * scale > constants.MAXIMUM_DISTANCE)
212 scale = constants.MAXIMUM_DISTANCE / length;
214 vec3.scale(eyeVector, eyeVector, scale);
215 vec3.add(this.eye_, this.gazeTarget_, eyeVector);
217 this.saveCameraToSettings(base.SessionSettings());
218 this.dispatchRenderEvent_();
221 updateRotateByDelta: function(delta) {
225 if (Math.abs(this.rotation_[0] + delta[1]) > constants.MAXIMUM_TILT)
227 if (Math.abs(this.rotation_[1] - delta[0]) > constants.MAXIMUM_TILT)
230 var eyeVector = [0, 0, 0, 0];
231 vec3.subtract(eyeVector, this.eye_, this.gazeTarget_);
233 // Undo the current rotation.
234 var rotMatrix = mat4.create();
236 rotMatrix, rotMatrix, -base.deg2rad(this.rotation_[0]), [1, 0, 0]);
238 rotMatrix, rotMatrix, -base.deg2rad(this.rotation_[1]), [0, 1, 0]);
239 vec4.transformMat4(eyeVector, eyeVector, rotMatrix);
241 // Update rotation values.
242 this.rotation_[0] += delta[1];
243 this.rotation_[1] -= delta[0];
245 // Redo the new rotation.
246 mat4.identity(rotMatrix);
248 rotMatrix, rotMatrix, base.deg2rad(this.rotation_[1]), [0, 1, 0]);
250 rotMatrix, rotMatrix, base.deg2rad(this.rotation_[0]), [1, 0, 0]);
251 vec4.transformMat4(eyeVector, eyeVector, rotMatrix);
253 vec3.add(this.eye_, this.gazeTarget_, eyeVector);
255 this.saveCameraToSettings(base.SessionSettings());
256 this.dispatchRenderEvent_();
261 onPanBegin_: function(e) {
262 this.panning_ = true;
263 this.lastMousePosition_ = this.getMousePosition_(e);
266 onPanUpdate_: function(e) {
270 var delta = this.getMouseDelta_(e, this.lastMousePosition_);
271 this.lastMousePosition_ = this.getMousePosition_(e);
272 this.updatePanByDelta(delta);
275 onPanEnd_: function(e) {
276 this.panning_ = false;
279 onZoomBegin_: function(e) {
280 this.zooming_ = true;
282 var p = this.getMousePosition_(e);
284 this.lastMousePosition_ = p;
288 onZoomUpdate_: function(e) {
292 var delta = this.getMouseDelta_(e, this.lastMousePosition_);
293 this.lastMousePosition_ = this.getMousePosition_(e);
294 this.updateZoomByDelta(delta);
297 onZoomEnd_: function(e) {
298 this.zooming_ = false;
299 this.zoomPoint_ = undefined;
302 onRotateBegin_: function(e) {
303 this.rotating_ = true;
304 this.lastMousePosition_ = this.getMousePosition_(e);
307 onRotateUpdate_: function(e) {
311 var delta = this.getMouseDelta_(e, this.lastMousePosition_);
312 this.lastMousePosition_ = this.getMousePosition_(e);
313 this.updateRotateByDelta(delta);
316 onRotateEnd_: function(e) {
317 this.rotating_ = false;
321 // Misc helper functions.
322 getMousePosition_: function(e) {
323 var rect = base.windowRectForElement(this.canvas_);
324 return [(e.clientX - rect.x) * this.pixelRatio_,
325 (e.clientY - rect.y) * this.pixelRatio_];
328 getMouseDelta_: function(e, p) {
329 var newP = this.getMousePosition_(e);
330 return [newP[0] - p[0], newP[1] - p[1]];
333 dispatchRenderEvent_: function() {
334 base.dispatchSimpleEvent(this, 'renderrequired', false, false);