910e62b5创建于 1月15日历史提交
// 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