blob: d8a565d1deebab92f1ebae24ea6b5ac3d0d00b3f [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <iomanip>
#include <queue>
#include <sstream>
#include <string>
#include <utility>
#include "chrome/browser/vr/ui.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/numerics/angle_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/vr/model/model.h"
#include "chrome/browser/vr/skia_surface_provider_factory.h"
#include "chrome/browser/vr/ui_element_renderer.h"
#include "chrome/browser/vr/ui_renderer.h"
#include "chrome/browser/vr/ui_scene.h"
#include "chrome/browser/vr/ui_scene_constants.h"
#include "chrome/browser/vr/ui_scene_creator.h"
#include "chrome/browser/vr/ui_test_input.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace vr {
namespace {
constexpr float kMargin = base::DegToRad(1.0f);
UiElementName UserFriendlyElementNameToUiElementName(
UserFriendlyElementName name) {
switch (name) {
case UserFriendlyElementName::kWebXrAudioIndicator:
return kWebVrAudioCaptureIndicator;
case UserFriendlyElementName::kMicrophonePermissionIndicator:
return kAudioCaptureIndicator;
case UserFriendlyElementName::kWebXrExternalPromptNotification:
return kWebXrExternalPromptNotification;
case UserFriendlyElementName::kCameraPermissionIndicator:
return kVideoCaptureIndicator;
case UserFriendlyElementName::kLocationPermissionIndicator:
return kLocationAccessIndicator;
case UserFriendlyElementName::kWebXrLocationPermissionIndicator:
return kWebVrLocationAccessIndicator;
case UserFriendlyElementName::kWebXrVideoPermissionIndicator:
return kWebVrVideoCaptureIndicator;
default:
NOTREACHED();
}
}
} // namespace
Ui::Ui()
: scene_(std::make_unique<UiScene>()), model_(std::make_unique<Model>()) {
model_->web_vr.has_received_permissions = false;
model_->web_vr.state = kWebVrAwaitingFirstFrame;
UiSceneCreator(scene_.get(), this, model_.get()).CreateScene();
}
Ui::~Ui() = default;
base::WeakPtr<BrowserUiInterface> Ui::GetBrowserUiWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
SchedulerUiInterface* Ui::GetSchedulerUiPtr() {
return this;
}
void Ui::SetCapturingState(const CapturingStateModel& active_capturing,
const CapturingStateModel& background_capturing,
const CapturingStateModel& potential_capturing) {
model_->active_capturing = active_capturing;
model_->background_capturing = background_capturing;
model_->potential_capturing = potential_capturing;
model_->web_vr.has_received_permissions = true;
}
void Ui::OnGlInitialized() {
ui_element_renderer_ = std::make_unique<UiElementRenderer>();
ui_renderer_ =
std::make_unique<UiRenderer>(scene_.get(), ui_element_renderer_.get());
provider_ = SkiaSurfaceProviderFactory::Create();
scene_->OnGlInitialized(provider_.get());
}
void Ui::OnWebXrFrameAvailable() {
model_->web_vr.state = kWebVrPresenting;
}
void Ui::OnWebXrTimeoutImminent() {
model_->web_vr.state = kWebVrTimeoutImminent;
}
void Ui::OnWebXrTimedOut() {
model_->web_vr.state = kWebVrTimedOut;
}
bool Ui::GetElementVisibility(UserFriendlyElementName element_name) {
auto* target_element = scene()->GetUiElementByName(
UserFriendlyElementNameToUiElementName(element_name));
DCHECK(target_element) << "Unsupported test element";
return target_element->IsVisible();
}
gfx::Point3F Ui::GetTargetPointForTesting(UserFriendlyElementName element_name,
const gfx::PointF& position) {
auto* target_element = scene()->GetUiElementByName(
UserFriendlyElementNameToUiElementName(element_name));
DCHECK(target_element) << "Unsupported test element";
// The position to click is provided for a unit square, so scale it to match
// the actual element.
auto scaled_position = ScalePoint(position, target_element->size().width(),
target_element->size().height());
gfx::Point3F target =
target_element->ComputeTargetWorldSpaceTransform().MapPoint(
gfx::Point3F(scaled_position));
// We do hit testing with respect to the eye position (world origin), so we
// need to project the target point into the background.
gfx::Vector3dF direction = target - kOrigin;
direction.GetNormalized(&direction);
return kOrigin +
gfx::ScaleVector3d(direction, scene()->background_distance());
}
void Ui::SetVisibleExternalPromptNotification(
ExternalPromptNotificationType prompt) {
model_->web_vr.external_prompt_notification = prompt;
}
bool Ui::OnBeginFrame(base::TimeTicks current_time,
const gfx::Transform& head_pose) {
return scene_->OnBeginFrame(current_time, head_pose);
}
bool Ui::SceneHasDirtyTextures() const {
return scene_->HasDirtyTextures();
}
void Ui::UpdateSceneTextures() {
scene_->UpdateTextures();
}
void Ui::Draw(const vr::RenderInfo& info) {
ui_renderer_->Draw(info);
}
void Ui::DrawWebVrOverlayForeground(const vr::RenderInfo& info) {
ui_renderer_->DrawWebVrOverlayForeground(info);
}
bool Ui::HasWebXrOverlayElementsToDraw() {
return scene_->HasWebXrOverlayElementsToDraw();
}
std::pair<FovRectangle, FovRectangle> Ui::GetMinimalFovForWebXrOverlayElements(
const gfx::Transform& left_view,
const FovRectangle& fov_recommended_left,
const gfx::Transform& right_view,
const FovRectangle& fov_recommended_right,
float z_near) {
auto elements = scene_->GetWebVrOverlayElementsToDraw();
return {GetMinimalFov(left_view, elements, fov_recommended_left, z_near),
GetMinimalFov(right_view, elements, fov_recommended_right, z_near)};
}
FovRectangle Ui::GetMinimalFov(const gfx::Transform& view_matrix,
const std::vector<const UiElement*>& elements,
const FovRectangle& fov_recommended,
float z_near) {
// Calculate boundary of Z near plane in view space.
float z_near_left = -z_near * std::tan(base::DegToRad(fov_recommended.left));
float z_near_right = z_near * std::tan(base::DegToRad(fov_recommended.right));
float z_near_bottom =
-z_near * std::tan(base::DegToRad(fov_recommended.bottom));
float z_near_top = z_near * std::tan(base::DegToRad(fov_recommended.top));
float left = z_near_right;
float right = z_near_left;
float bottom = z_near_top;
float top = z_near_bottom;
bool has_visible_element = false;
for (const auto* element : elements) {
gfx::Transform transform = element->world_space_transform();
transform.PostConcat(view_matrix);
// Transform to view space.
gfx::Point3F left_bottom = transform.MapPoint(gfx::Point3F(-0.5, -0.5, 0));
gfx::Point3F left_top = transform.MapPoint(gfx::Point3F(-0.5, 0.5, 0));
gfx::Point3F right_bottom = transform.MapPoint(gfx::Point3F(0.5, -0.5, 0));
gfx::Point3F right_top = transform.MapPoint(gfx::Point3F(0.5, 0.5, 0));
// Project point to Z near plane in view space.
left_bottom.Scale(-z_near / left_bottom.z());
left_top.Scale(-z_near / left_top.z());
right_bottom.Scale(-z_near / right_bottom.z());
right_top.Scale(-z_near / right_top.z());
// Find bounding box on z near plane.
float bounds_left = std::min(
{left_bottom.x(), left_top.x(), right_bottom.x(), right_top.x()});
float bounds_right = std::max(
{left_bottom.x(), left_top.x(), right_bottom.x(), right_top.x()});
float bounds_bottom = std::min(
{left_bottom.y(), left_top.y(), right_bottom.y(), right_top.y()});
float bounds_top = std::max(
{left_bottom.y(), left_top.y(), right_bottom.y(), right_top.y()});
// Ignore non visible elements.
if (bounds_left >= z_near_right || bounds_right <= z_near_left ||
bounds_bottom >= z_near_top || bounds_top <= z_near_bottom ||
bounds_left == bounds_right || bounds_bottom == bounds_top) {
continue;
}
// Clamp to Z near plane's boundary.
bounds_left = std::clamp(bounds_left, z_near_left, z_near_right);
bounds_right = std::clamp(bounds_right, z_near_left, z_near_right);
bounds_bottom = std::clamp(bounds_bottom, z_near_bottom, z_near_top);
bounds_top = std::clamp(bounds_top, z_near_bottom, z_near_top);
left = std::min(bounds_left, left);
right = std::max(bounds_right, right);
bottom = std::min(bounds_bottom, bottom);
top = std::max(bounds_top, top);
has_visible_element = true;
}
if (!has_visible_element) {
return FovRectangle{0.f, 0.f, 0.f, 0.f};
}
// Add a small margin to fix occasional border clipping due to precision.
const float margin = std::tan(kMargin) * z_near;
left = std::max(left - margin, z_near_left);
right = std::min(right + margin, z_near_right);
bottom = std::max(bottom - margin, z_near_bottom);
top = std::min(top + margin, z_near_top);
float left_degrees = base::RadToDeg(std::atan(-left / z_near));
float right_degrees = base::RadToDeg(std::atan(right / z_near));
float bottom_degrees = base::RadToDeg(std::atan(-bottom / z_near));
float top_degrees = base::RadToDeg(std::atan(top / z_near));
return FovRectangle{left_degrees, right_degrees, bottom_degrees, top_degrees};
}
} // namespace vr