chromium/device/vr/openxr/openxr_scene_understanding_manager.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/vr/openxr/openxr_scene_understanding_manager.h"

#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "device/vr/openxr/openxr_extension_helper.h"
#include "device/vr/openxr/openxr_util.h"
#include "third_party/openxr/src/include/openxr/openxr.h"

namespace device {

OpenXRSceneUnderstandingManager::OpenXRSceneUnderstandingManager() = default;
OpenXRSceneUnderstandingManager::~OpenXRSceneUnderstandingManager() = default;

std::optional<HitTestSubscriptionId>
OpenXRSceneUnderstandingManager::SubscribeToHitTest(
    mojom::XRNativeOriginInformationPtr native_origin_information,
    const std::vector<mojom::EntityTypeForHitTest>& entity_types,
    mojom::XRRayPtr ray) {
  if (!OnNewHitTestSubscription()) {
    return std::nullopt;
  }

  auto subscription_id = hittest_id_generator_.GenerateNextId();

  hit_test_subscription_id_to_data_.emplace(
      subscription_id,
      HitTestSubscriptionData{std::move(native_origin_information),
                              entity_types, std::move(ray)});

  return subscription_id;
}

std::optional<HitTestSubscriptionId>
OpenXRSceneUnderstandingManager::SubscribeToHitTestForTransientInput(
    const std::string& profile_name,
    const std::vector<mojom::EntityTypeForHitTest>& entity_types,
    mojom::XRRayPtr ray) {
  if (!OnNewHitTestSubscription()) {
    return std::nullopt;
  }

  auto subscription_id = hittest_id_generator_.GenerateNextId();

  hit_test_subscription_id_to_transient_hit_test_data_.emplace(
      subscription_id, TransientInputHitTestSubscriptionData{
                           profile_name, entity_types, std::move(ray)});

  return subscription_id;
}

device::mojom::XRHitTestSubscriptionResultDataPtr
OpenXRSceneUnderstandingManager::GetHitTestSubscriptionResult(
    HitTestSubscriptionId id,
    const mojom::XRRay& native_origin_ray,
    const gfx::Transform& mojo_from_native_origin) {
  DVLOG(3) << __func__ << ": id=" << id;

  // Transform the ray according to the latest transform based on the XRSpace
  // used in hit test subscription.

  gfx::Point3F origin =
      mojo_from_native_origin.MapPoint(native_origin_ray.origin);

  gfx::Vector3dF direction =
      mojo_from_native_origin.MapVector(native_origin_ray.direction);

  return mojom::XRHitTestSubscriptionResultData::New(
      id.GetUnsafeValue(), RequestHitTest(origin, direction));
}

device::mojom::XRHitTestTransientInputSubscriptionResultDataPtr
OpenXRSceneUnderstandingManager::GetTransientHitTestSubscriptionResult(
    HitTestSubscriptionId id,
    const mojom::XRRay& input_source_ray,
    const std::vector<std::pair<uint32_t, gfx::Transform>>&
        input_source_ids_and_mojo_from_input_sources) {
  auto result =
      device::mojom::XRHitTestTransientInputSubscriptionResultData::New();

  result->subscription_id = id.GetUnsafeValue();

  for (const auto& input_source_id_and_mojo_from_input_source :
       input_source_ids_and_mojo_from_input_sources) {
    gfx::Point3F origin =
        input_source_id_and_mojo_from_input_source.second.MapPoint(
            input_source_ray.origin);

    gfx::Vector3dF direction =
        input_source_id_and_mojo_from_input_source.second.MapVector(
            input_source_ray.direction);

    result->input_source_id_to_hit_test_results.insert(
        {input_source_id_and_mojo_from_input_source.first,
         RequestHitTest(origin, direction)});
  }

  return result;
}

mojom::XRHitTestSubscriptionResultsDataPtr
OpenXRSceneUnderstandingManager::GetHitTestResults(
    const gfx::Transform& mojo_from_viewer,
    const std::vector<mojom::XRInputSourceStatePtr>& input_state) {
  mojom::XRHitTestSubscriptionResultsDataPtr result =
      mojom::XRHitTestSubscriptionResultsData::New();

  DVLOG(3) << __func__
           << ": calculating hit test subscription results, "
              "hit_test_subscription_id_to_data_.size()="
           << hit_test_subscription_id_to_data_.size();

  for (auto& subscription_id_and_data : hit_test_subscription_id_to_data_) {
    // First, check if we can find the current transformation for a ray. If not,
    // skip processing this subscription.
    auto maybe_mojo_from_native_origin = GetMojoFromNativeOrigin(
        *subscription_id_and_data.second.native_origin_information,
        mojo_from_viewer, input_state);

    if (!maybe_mojo_from_native_origin) {
      continue;
    }

    // Since we have a transform, let's use it to obtain hit test results.
    result->results.push_back(GetHitTestSubscriptionResult(
        HitTestSubscriptionId(subscription_id_and_data.first),
        *subscription_id_and_data.second.ray, *maybe_mojo_from_native_origin));
  }

  // Calculate results for transient input sources
  DVLOG(3)
      << __func__
      << ": calculating hit test subscription results for transient input, "
         "hit_test_subscription_id_to_transient_hit_test_data_.size()="
      << hit_test_subscription_id_to_transient_hit_test_data_.size();

  for (const auto& subscription_id_and_data :
       hit_test_subscription_id_to_transient_hit_test_data_) {
    auto input_source_ids_and_transforms = GetMojoFromInputSources(
        subscription_id_and_data.second.profile_name, input_state);

    result->transient_input_results.push_back(
        GetTransientHitTestSubscriptionResult(
            HitTestSubscriptionId(subscription_id_and_data.first),
            *subscription_id_and_data.second.ray,
            input_source_ids_and_transforms));
  }

  return result;
}

void OpenXRSceneUnderstandingManager::UnsubscribeFromHitTest(
    HitTestSubscriptionId subscription_id) {
  // Hit test subscription ID space is the same for transient and non-transient
  // hit test sources, so we can attempt to remove it from both collections (it
  // will succeed only for one of them anyway).
  hit_test_subscription_id_to_data_.erase(
      HitTestSubscriptionId(subscription_id));
  hit_test_subscription_id_to_transient_hit_test_data_.erase(
      HitTestSubscriptionId(subscription_id));
  if (hit_test_subscription_id_to_data_.empty() &&
      hit_test_subscription_id_to_transient_hit_test_data_.empty()) {
    OnAllHitTestSubscriptionsRemoved();
  }
}

std::optional<gfx::Transform>
OpenXRSceneUnderstandingManager::GetMojoFromNativeOrigin(
    const mojom::XRNativeOriginInformation& native_origin_information,
    const gfx::Transform& mojo_from_viewer,
    const std::vector<mojom::XRInputSourceStatePtr>& input_state) {
  switch (native_origin_information.which()) {
    case mojom::XRNativeOriginInformation::Tag::kInputSourceSpaceInfo:
      for (auto& input_source_state : input_state) {
        mojom::XRInputSourceSpaceInfo* input_source_space_info =
            native_origin_information.get_input_source_space_info().get();
        if (input_source_state->source_id ==
            input_source_space_info->input_source_id) {
          return GetMojoFromPointerInput(input_source_state);
        }
      }
      return std::nullopt;
    case mojom::XRNativeOriginInformation::Tag::kReferenceSpaceType:
      return GetMojoFromReferenceSpace(
          native_origin_information.get_reference_space_type(),
          mojo_from_viewer);
    case mojom::XRNativeOriginInformation::Tag::kPlaneId:
      return std::nullopt;
    case mojom::XRNativeOriginInformation::Tag::kAnchorId:
      return std::nullopt;
    case mojom::XRNativeOriginInformation::Tag::kHandJointSpaceInfo:
      return std::nullopt;
    case mojom::XRNativeOriginInformation::Tag::kImageIndex:
      return std::nullopt;
  }
}

std::optional<gfx::Transform>
OpenXRSceneUnderstandingManager::GetMojoFromReferenceSpace(
    device::mojom::XRReferenceSpaceType type,
    const gfx::Transform& mojo_from_viewer) {
  DVLOG(3) << __func__ << ": type=" << type;

  switch (type) {
    case device::mojom::XRReferenceSpaceType::kLocal:
      return gfx::Transform{};
    case device::mojom::XRReferenceSpaceType::kLocalFloor:
      return std::nullopt;
    case device::mojom::XRReferenceSpaceType::kViewer:
      return mojo_from_viewer;
    case device::mojom::XRReferenceSpaceType::kBoundedFloor:
      return std::nullopt;
    case device::mojom::XRReferenceSpaceType::kUnbounded:
      return std::nullopt;
  }
}

std::vector<std::pair<uint32_t, gfx::Transform>>
OpenXRSceneUnderstandingManager::GetMojoFromInputSources(
    const std::string& profile_name,
    const std::vector<mojom::XRInputSourceStatePtr>& input_state) {
  std::vector<std::pair<uint32_t, gfx::Transform>> result;

  for (const auto& input_source_state : input_state) {
    if (input_source_state && input_source_state->description) {
      if (base::Contains(input_source_state->description->profiles,
                         profile_name)) {
        // Input source represented by input_state matches the profile, find
        // the transform and grab input source id.
        std::optional<gfx::Transform> maybe_mojo_from_input_source =
            GetMojoFromPointerInput(input_source_state);

        if (!maybe_mojo_from_input_source)
          continue;

        result.push_back(
            {input_source_state->source_id, *maybe_mojo_from_input_source});
      }
    }
  }

  return result;
}

std::optional<gfx::Transform>
OpenXRSceneUnderstandingManager::GetMojoFromPointerInput(
    const device::mojom::XRInputSourceStatePtr& input_source_state) {
  if (!input_source_state->mojo_from_input ||
      !input_source_state->description ||
      !input_source_state->description->input_from_pointer) {
    return std::nullopt;
  }

  gfx::Transform mojo_from_input = *input_source_state->mojo_from_input;

  gfx::Transform input_from_pointer =
      *input_source_state->description->input_from_pointer;

  return mojo_from_input * input_from_pointer;
}

}  // namespace device