blob: 005d09a1472e81d73fddadb9ee9ccc262e77c050 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/settings.html">
<link rel="import" href="/ui/base/utils.html">
<link rel="import" href="/ui/base/ui.html">
<script>
'use strict';
tr.exportTo('tr.ui.b', function() {
var constants = {
DEFAULT_SCALE: 0.5,
DEFAULT_EYE_DISTANCE: 10000,
MINIMUM_DISTANCE: 1000,
MAXIMUM_DISTANCE: 100000,
FOV: 15,
RESCALE_TIMEOUT_MS: 200,
MAXIMUM_TILT: 80,
SETTINGS_NAMESPACE: 'tr.ui_camera'
};
var Camera = tr.ui.b.define('camera');
Camera.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function(eventSource) {
this.eventSource_ = eventSource;
this.eventSource_.addEventListener('beginpan',
this.onPanBegin_.bind(this));
this.eventSource_.addEventListener('updatepan',
this.onPanUpdate_.bind(this));
this.eventSource_.addEventListener('endpan',
this.onPanEnd_.bind(this));
this.eventSource_.addEventListener('beginzoom',
this.onZoomBegin_.bind(this));
this.eventSource_.addEventListener('updatezoom',
this.onZoomUpdate_.bind(this));
this.eventSource_.addEventListener('endzoom',
this.onZoomEnd_.bind(this));
this.eventSource_.addEventListener('beginrotate',
this.onRotateBegin_.bind(this));
this.eventSource_.addEventListener('updaterotate',
this.onRotateUpdate_.bind(this));
this.eventSource_.addEventListener('endrotate',
this.onRotateEnd_.bind(this));
this.eye_ = [0, 0, constants.DEFAULT_EYE_DISTANCE];
this.gazeTarget_ = [0, 0, 0];
this.rotation_ = [0, 0];
this.pixelRatio_ = window.devicePixelRatio || 1;
},
get modelViewMatrix() {
var mvMatrix = mat4.create();
mat4.lookAt(mvMatrix, this.eye_, this.gazeTarget_, [0, 1, 0]);
return mvMatrix;
},
get projectionMatrix() {
var rect =
tr.ui.b.windowRectForElement(this.canvas_).
scaleSize(this.pixelRatio_);
var aspectRatio = rect.width / rect.height;
var matrix = mat4.create();
mat4.perspective(
matrix, tr.b.deg2rad(constants.FOV), aspectRatio, 1, 100000);
return matrix;
},
set canvas(c) {
this.canvas_ = c;
},
set deviceRect(rect) {
this.deviceRect_ = rect;
},
get stackingDistanceDampening() {
var gazeVector = [
this.gazeTarget_[0] - this.eye_[0],
this.gazeTarget_[1] - this.eye_[1],
this.gazeTarget_[2] - this.eye_[2]];
vec3.normalize(gazeVector, gazeVector);
return 1 + gazeVector[2];
},
loadCameraFromSettings: function(settings) {
this.eye_ = settings.get(
'eye', this.eye_, constants.SETTINGS_NAMESPACE);
this.gazeTarget_ = settings.get(
'gaze_target', this.gazeTarget_, constants.SETTINGS_NAMESPACE);
this.rotation_ = settings.get(
'rotation', this.rotation_, constants.SETTINGS_NAMESPACE);
this.dispatchRenderEvent_();
},
saveCameraToSettings: function(settings) {
settings.set(
'eye', this.eye_, constants.SETTINGS_NAMESPACE);
settings.set(
'gaze_target', this.gazeTarget_, constants.SETTINGS_NAMESPACE);
settings.set(
'rotation', this.rotation_, constants.SETTINGS_NAMESPACE);
},
resetCamera: function() {
this.eye_ = [0, 0, constants.DEFAULT_EYE_DISTANCE];
this.gazeTarget_ = [0, 0, 0];
this.rotation_ = [0, 0];
var settings = tr.b.SessionSettings();
var keys = settings.keys(constants.SETTINGS_NAMESPACE);
if (keys.length !== 0) {
this.loadCameraFromSettings(settings);
return;
}
if (this.deviceRect_) {
var rect = tr.ui.b.windowRectForElement(this.canvas_).
scaleSize(this.pixelRatio_);
this.eye_[0] = this.deviceRect_.width / 2;
this.eye_[1] = this.deviceRect_.height / 2;
this.gazeTarget_[0] = this.deviceRect_.width / 2;
this.gazeTarget_[1] = this.deviceRect_.height / 2;
}
this.saveCameraToSettings(settings);
this.dispatchRenderEvent_();
},
updatePanByDelta: function(delta) {
var rect =
tr.ui.b.windowRectForElement(this.canvas_).
scaleSize(this.pixelRatio_);
// Get the eye vector, since we'll be adjusting gazeTarget.
var eyeVector = [
this.eye_[0] - this.gazeTarget_[0],
this.eye_[1] - this.gazeTarget_[1],
this.eye_[2] - this.gazeTarget_[2]];
var length = vec3.length(eyeVector);
vec3.normalize(eyeVector, eyeVector);
var halfFov = constants.FOV / 2;
var multiplier =
2.0 * length * Math.tan(tr.b.deg2rad(halfFov)) / rect.height;
// Get the up and right vectors.
var up = [0, 1, 0];
var rotMatrix = mat4.create();
mat4.rotate(
rotMatrix, rotMatrix, tr.b.deg2rad(this.rotation_[1]), [0, 1, 0]);
mat4.rotate(
rotMatrix, rotMatrix, tr.b.deg2rad(this.rotation_[0]), [1, 0, 0]);
vec3.transformMat4(up, up, rotMatrix);
var right = [0, 0, 0];
vec3.cross(right, eyeVector, up);
vec3.normalize(right, right);
// Update the gaze target.
for (var i = 0; i < 3; ++i) {
this.gazeTarget_[i] +=
delta[0] * multiplier * right[i] - delta[1] * multiplier * up[i];
this.eye_[i] = this.gazeTarget_[i] + length * eyeVector[i];
}
// If we have some z offset, we need to reposition gazeTarget
// to be on the plane z = 0 with normal [0, 0, 1].
if (Math.abs(this.gazeTarget_[2]) > 1e-6) {
var gazeVector = [-eyeVector[0], -eyeVector[1], -eyeVector[2]];
var newLength = tr.b.clamp(
-this.eye_[2] / gazeVector[2],
constants.MINIMUM_DISTANCE,
constants.MAXIMUM_DISTANCE);
for (var i = 0; i < 3; ++i)
this.gazeTarget_[i] = this.eye_[i] + newLength * gazeVector[i];
}
this.saveCameraToSettings(tr.b.SessionSettings());
this.dispatchRenderEvent_();
},
updateZoomByDelta: function(delta) {
var deltaY = delta[1];
deltaY = tr.b.clamp(deltaY, -50, 50);
var scale = 1.0 - deltaY / 100.0;
var eyeVector = [0, 0, 0];
vec3.subtract(eyeVector, this.eye_, this.gazeTarget_);
var length = vec3.length(eyeVector);
// Clamp the length to allowed values by changing the scale.
if (length * scale < constants.MINIMUM_DISTANCE)
scale = constants.MINIMUM_DISTANCE / length;
else if (length * scale > constants.MAXIMUM_DISTANCE)
scale = constants.MAXIMUM_DISTANCE / length;
vec3.scale(eyeVector, eyeVector, scale);
vec3.add(this.eye_, this.gazeTarget_, eyeVector);
this.saveCameraToSettings(tr.b.SessionSettings());
this.dispatchRenderEvent_();
},
updateRotateByDelta: function(delta) {
delta[0] *= 0.5;
delta[1] *= 0.5;
if (Math.abs(this.rotation_[0] + delta[1]) > constants.MAXIMUM_TILT)
return;
if (Math.abs(this.rotation_[1] - delta[0]) > constants.MAXIMUM_TILT)
return;
var eyeVector = [0, 0, 0, 0];
vec3.subtract(eyeVector, this.eye_, this.gazeTarget_);
// Undo the current rotation.
var rotMatrix = mat4.create();
mat4.rotate(
rotMatrix, rotMatrix, -tr.b.deg2rad(this.rotation_[0]), [1, 0, 0]);
mat4.rotate(
rotMatrix, rotMatrix, -tr.b.deg2rad(this.rotation_[1]), [0, 1, 0]);
vec4.transformMat4(eyeVector, eyeVector, rotMatrix);
// Update rotation values.
this.rotation_[0] += delta[1];
this.rotation_[1] -= delta[0];
// Redo the new rotation.
mat4.identity(rotMatrix);
mat4.rotate(
rotMatrix, rotMatrix, tr.b.deg2rad(this.rotation_[1]), [0, 1, 0]);
mat4.rotate(
rotMatrix, rotMatrix, tr.b.deg2rad(this.rotation_[0]), [1, 0, 0]);
vec4.transformMat4(eyeVector, eyeVector, rotMatrix);
vec3.add(this.eye_, this.gazeTarget_, eyeVector);
this.saveCameraToSettings(tr.b.SessionSettings());
this.dispatchRenderEvent_();
},
// Event callbacks.
onPanBegin_: function(e) {
this.panning_ = true;
this.lastMousePosition_ = this.getMousePosition_(e);
},
onPanUpdate_: function(e) {
if (!this.panning_)
return;
var delta = this.getMouseDelta_(e, this.lastMousePosition_);
this.lastMousePosition_ = this.getMousePosition_(e);
this.updatePanByDelta(delta);
},
onPanEnd_: function(e) {
this.panning_ = false;
},
onZoomBegin_: function(e) {
this.zooming_ = true;
var p = this.getMousePosition_(e);
this.lastMousePosition_ = p;
this.zoomPoint_ = p;
},
onZoomUpdate_: function(e) {
if (!this.zooming_)
return;
var delta = this.getMouseDelta_(e, this.lastMousePosition_);
this.lastMousePosition_ = this.getMousePosition_(e);
this.updateZoomByDelta(delta);
},
onZoomEnd_: function(e) {
this.zooming_ = false;
this.zoomPoint_ = undefined;
},
onRotateBegin_: function(e) {
this.rotating_ = true;
this.lastMousePosition_ = this.getMousePosition_(e);
},
onRotateUpdate_: function(e) {
if (!this.rotating_)
return;
var delta = this.getMouseDelta_(e, this.lastMousePosition_);
this.lastMousePosition_ = this.getMousePosition_(e);
this.updateRotateByDelta(delta);
},
onRotateEnd_: function(e) {
this.rotating_ = false;
},
// Misc helper functions.
getMousePosition_: function(e) {
var rect = tr.ui.b.windowRectForElement(this.canvas_);
return [(e.clientX - rect.x) * this.pixelRatio_,
(e.clientY - rect.y) * this.pixelRatio_];
},
getMouseDelta_: function(e, p) {
var newP = this.getMousePosition_(e);
return [newP[0] - p[0], newP[1] - p[1]];
},
dispatchRenderEvent_: function() {
tr.b.dispatchSimpleEvent(this, 'renderrequired', false, false);
}
};
return {
Camera: Camera
};
});
</script>