blob: 3c88af44fae5a41c0b5242cd4ff5449652970857 [file] [log] [blame]
// Copyright 2017 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.
#include "third_party/blink/renderer/modules/xr/xr_view.h"
#include "base/numerics/ranges.h"
#include "third_party/blink/renderer/modules/xr/xr_frame.h"
#include "third_party/blink/renderer/modules/xr/xr_utils.h"
#include "third_party/blink/renderer/platform/geometry/float_point_3d.h"
namespace blink {
namespace {
// Arbitrary minimum size multiplier for dynamic viewport scaling,
// where 1.0 is full framebuffer size (which may in turn be adjusted
// by framebufferScaleFactor). TODO(klausw): a value around 0.2 would
// be more reasonable. Intentionally allow extreme viewport scaling
// to make the effect more obvious in initial testing.
constexpr double kMinViewportScale = 0.05;
} // namespace
XRView::XRView(XRFrame* frame, XRViewData* view_data)
: eye_(view_data->Eye()), frame_(frame), view_data_(view_data) {
switch (eye_) {
case kEyeLeft:
eye_string_ = "left";
break;
case kEyeRight:
eye_string_ = "right";
break;
default:
eye_string_ = "none";
}
ref_space_from_eye_ =
MakeGarbageCollected<XRRigidTransform>(view_data->Transform());
projection_matrix_ =
transformationMatrixToDOMFloat32Array(view_data->ProjectionMatrix());
}
XRFrame* XRView::frame() const {
return frame_;
}
XRSession* XRView::session() const {
return frame_->session();
}
DOMFloat32Array* XRView::projectionMatrix() const {
if (!projection_matrix_ || !projection_matrix_->Data()) {
// A page may take the projection matrix value and detach it so
// projection_matrix_ is a detached array buffer. This breaks the
// inspector, so return null instead.
return nullptr;
}
return projection_matrix_;
}
void XRViewData::UpdateProjectionMatrixFromFoV(float up_rad,
float down_rad,
float left_rad,
float right_rad,
float near_depth,
float far_depth) {
float up_tan = tanf(up_rad);
float down_tan = tanf(down_rad);
float left_tan = tanf(left_rad);
float right_tan = tanf(right_rad);
float x_scale = 2.0f / (left_tan + right_tan);
float y_scale = 2.0f / (up_tan + down_tan);
float inv_nf = 1.0f / (near_depth - far_depth);
projection_matrix_ = TransformationMatrix(
x_scale, 0.0f, 0.0f, 0.0f, 0.0f, y_scale, 0.0f, 0.0f,
-((left_tan - right_tan) * x_scale * 0.5),
((up_tan - down_tan) * y_scale * 0.5), (near_depth + far_depth) * inv_nf,
-1.0f, 0.0f, 0.0f, (2.0f * far_depth * near_depth) * inv_nf, 0.0f);
}
void XRViewData::UpdateProjectionMatrixFromAspect(float fovy,
float aspect,
float near_depth,
float far_depth) {
float f = 1.0f / tanf(fovy / 2);
float inv_nf = 1.0f / (near_depth - far_depth);
projection_matrix_ = TransformationMatrix(
f / aspect, 0.0f, 0.0f, 0.0f, 0.0f, f, 0.0f, 0.0f, 0.0f, 0.0f,
(far_depth + near_depth) * inv_nf, -1.0f, 0.0f, 0.0f,
(2.0f * far_depth * near_depth) * inv_nf, 0.0f);
inv_projection_dirty_ = true;
}
TransformationMatrix XRViewData::UnprojectPointer(double x,
double y,
double canvas_width,
double canvas_height) {
// Recompute the inverse projection matrix if needed.
if (inv_projection_dirty_) {
inv_projection_ = projection_matrix_.Inverse();
inv_projection_dirty_ = false;
}
// Transform the x/y coordinate into WebGL normalized device coordinates.
// Z coordinate of -1 means the point will be projected onto the projection
// matrix near plane.
FloatPoint3D point_in_projection_space(
x / canvas_width * 2.0 - 1.0,
(canvas_height - y) / canvas_height * 2.0 - 1.0, -1.0);
FloatPoint3D point_in_view_space =
inv_projection_.MapPoint(point_in_projection_space);
const FloatPoint3D kOrigin(0.0, 0.0, 0.0);
const FloatPoint3D kUp(0.0, 1.0, 0.0);
// Generate a "Look At" matrix
FloatPoint3D z_axis = kOrigin - point_in_view_space;
z_axis.Normalize();
FloatPoint3D x_axis = kUp.Cross(z_axis);
x_axis.Normalize();
FloatPoint3D y_axis = z_axis.Cross(x_axis);
y_axis.Normalize();
// TODO(bajones): There's probably a more efficent way to do this?
TransformationMatrix inv_pointer(x_axis.X(), y_axis.X(), z_axis.X(), 0.0,
x_axis.Y(), y_axis.Y(), z_axis.Y(), 0.0,
x_axis.Z(), y_axis.Z(), z_axis.Z(), 0.0, 0.0,
0.0, 0.0, 1.0);
inv_pointer.Translate3d(-point_in_view_space.X(), -point_in_view_space.Y(),
-point_in_view_space.Z());
// LookAt matrices are view matrices (inverted), so invert before returning.
return inv_pointer.Inverse();
}
void XRViewData::SetHeadFromEyeTransform(
const TransformationMatrix& head_from_eye) {
head_from_eye_ = head_from_eye;
}
// ref_space_from_eye_ = ref_space_from_head * head_from_eye_
void XRViewData::UpdatePoseMatrix(
const TransformationMatrix& ref_space_from_head) {
ref_space_from_eye_ = ref_space_from_head;
ref_space_from_eye_.Multiply(head_from_eye_);
}
XRRigidTransform* XRView::transform() const {
return ref_space_from_eye_;
}
base::Optional<double> XRView::recommendedViewportScale() const {
return view_data_->recommendedViewportScale();
}
void XRView::requestViewportScale(base::Optional<double> scale) {
view_data_->requestViewportScale(scale);
}
void XRView::Trace(Visitor* visitor) const {
visitor->Trace(frame_);
visitor->Trace(projection_matrix_);
visitor->Trace(ref_space_from_eye_);
visitor->Trace(view_data_);
ScriptWrappable::Trace(visitor);
}
base::Optional<double> XRViewData::recommendedViewportScale() const {
return recommended_viewport_scale_;
}
void XRViewData::requestViewportScale(base::Optional<double> scale) {
if (!scale)
return;
requested_viewport_scale_ =
base::ClampToRange(*scale, kMinViewportScale, 1.0);
}
} // namespace blink