910e62b5创建于 1月15日历史提交
// Copyright 2024 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_hand_utils.h"

#include <iomanip>
#include <sstream>

#include "base/containers/span.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/trace_event/trace_event.h"
#include "device/vr/public/mojom/xr_hand_tracking_data.mojom.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/vector3d_f.h"

namespace device {
namespace {
// WebXR doesn't expose the palm joint, so there's not a corresponding mojom
// value to check, but validate which index we're skipping for it.
static_assert(XR_HAND_JOINT_PALM_EXT == 0u,
              "OpenXR palm joint expected to be the 0th joint");

// Because we do not expose the PALM joint (which is the first joint in OpenXR),
// we have one less joint than OpenXR.
static_assert(kNumWebXRJoints == XR_HAND_JOINT_COUNT_EXT - 1u);

// Enforce that the conversion is correct at compilation time.
// The mojom hand joints must match the WebXR spec. If these are ever out of
// sync, this mapping will need to be updated.
static_assert(mojom::XRHandJoint::kWrist ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_WRIST_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kThumbMetacarpal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_THUMB_METACARPAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kThumbPhalanxProximal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_THUMB_PROXIMAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kThumbPhalanxDistal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_THUMB_DISTAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kThumbTip ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_THUMB_TIP_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kIndexFingerMetacarpal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_INDEX_METACARPAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kIndexFingerPhalanxProximal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_INDEX_PROXIMAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kIndexFingerPhalanxIntermediate ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kIndexFingerPhalanxDistal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_INDEX_DISTAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kIndexFingerTip ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_INDEX_TIP_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kMiddleFingerMetacarpal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_MIDDLE_METACARPAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kMiddleFingerPhalanxProximal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kMiddleFingerPhalanxIntermediate ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kMiddleFingerPhalanxDistal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_MIDDLE_DISTAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kMiddleFingerTip ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_MIDDLE_TIP_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kRingFingerMetacarpal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_RING_METACARPAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kRingFingerPhalanxProximal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_RING_PROXIMAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kRingFingerPhalanxIntermediate ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_RING_INTERMEDIATE_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kRingFingerPhalanxDistal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_RING_DISTAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kRingFingerTip ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_RING_TIP_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kPinkyFingerMetacarpal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_LITTLE_METACARPAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kPinkyFingerPhalanxProximal ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_LITTLE_PROXIMAL_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(
    mojom::XRHandJoint::kPinkyFingerPhalanxIntermediate ==
        OpenXRHandJointToMojomJoint(XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT),
    "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kPinkyFingerPhalanxDistal ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_LITTLE_DISTAL_EXT),
              "WebXR - OpenXR joint enum value mismatch");
static_assert(mojom::XRHandJoint::kPinkyFingerTip ==
                  OpenXRHandJointToMojomJoint(XR_HAND_JOINT_LITTLE_TIP_EXT),
              "WebXR - OpenXR joint enum value mismatch");

constexpr size_t kThumbJointCount = 4;
constexpr size_t kIndexJointCount = 5;
constexpr size_t kMiddleJointCount = 5;
constexpr size_t kRingJointCount = 5;
constexpr size_t kPinkyJointCount = 5;

// Add one for the wrist
static_assert(1 + kThumbJointCount + kIndexJointCount + kMiddleJointCount +
                  kRingJointCount + kPinkyJointCount ==
              kNumWebXRJoints);

// Values reported by Meta Quest runtime with XrHandTrackingScaleFB extension
constexpr std::array<float, kNumWebXRJoints> kJointRadii = {
    0.01,   0.0194, .0123,  0.0098, 0.0088, 0.0212, 0.0103, 0.0085, 0.0076,
    0.0066, 0.0212, 0.0112, 0.0080, 0.0076, 0.0066, 0.0191, 0.0099, 0.0076,
    0.0072, 0.0062, 0.0181, 0.0085, 0.0068, 0.0064, 0.0054};

constexpr std::array<float, kThumbJointCount> kThumbDistances = {
    0.0486, 0.0325, 0.0338, 0.0246};
constexpr std::array<float, kIndexJointCount> kIndexDistances = {
    0.0425, 0.0597, 0.0379, 0.0243, 0.0224};
constexpr std::array<float, kMiddleJointCount> kMiddleDistances = {
    0.0353, 0.0616, 0.0429, 0.0275, 0.0250};
constexpr std::array<float, kRingJointCount> kRingDistances = {
    0.0383, 0.0540, 0.0390, 0.0266, 0.0244};
constexpr std::array<float, kPinkyJointCount> kPinkyDistances = {
    0.0422, 0.0457, 0.0307, 0.0203, 0.0220};

constexpr size_t kThumbOrigin = static_cast<size_t>(mojom::XRHandJoint::kWrist);
constexpr size_t kIndexOrigin = static_cast<size_t>(mojom::XRHandJoint::kWrist);
constexpr size_t kMiddleOrigin =
    static_cast<size_t>(mojom::XRHandJoint::kWrist);
constexpr size_t kRingOrigin = static_cast<size_t>(mojom::XRHandJoint::kWrist);
constexpr size_t kPinkyOrigin = static_cast<size_t>(mojom::XRHandJoint::kWrist);

constexpr size_t kThumbStartIndex =
    static_cast<size_t>(mojom::XRHandJoint::kThumbMetacarpal);
constexpr size_t kIndexStartIndex =
    static_cast<size_t>(mojom::XRHandJoint::kIndexFingerMetacarpal);
constexpr size_t kMiddleStartIndex =
    static_cast<size_t>(mojom::XRHandJoint::kMiddleFingerMetacarpal);
constexpr size_t kRingStartIndex =
    static_cast<size_t>(mojom::XRHandJoint::kRingFingerMetacarpal);
constexpr size_t kPinkyStartIndex =
    static_cast<size_t>(mojom::XRHandJoint::kPinkyFingerMetacarpal);

struct XRFingerMapping {
  // The index of the "origin" of the finger. This value will not be remapped.
  const size_t origin_index;
  // The first joint of the finger to be remapped.
  const size_t start_index;

  // TODO(367764863) Rewrite to base::raw_span.
  RAW_PTR_EXCLUSION const base::span<const float> standard_joint_sizes;

  size_t JointCount() const { return standard_joint_sizes.size(); }
};

constexpr std::array<XRFingerMapping, 5> kHandMapping({
    {kThumbOrigin, kThumbStartIndex, kThumbDistances},
    {kIndexOrigin, kIndexStartIndex, kIndexDistances},
    {kMiddleOrigin, kMiddleStartIndex, kMiddleDistances},
    {kRingOrigin, kRingStartIndex, kRingDistances},
    {kPinkyOrigin, kPinkyStartIndex, kPinkyDistances},
});
}  // namespace

bool AnonymizeHand(base::span<mojom::XRHandJointDataPtr> hand_data) {
  TRACE_EVENT0("xr", "AnonymizeHand");
  // First set all of the radii to their intended values.
  for (const auto& joint : hand_data) {
    joint->radius = kJointRadii[static_cast<uint32_t>(joint->joint)];
  }

  // Now work our way down each finger to set the distances appropriately. The
  // basic strategy here is that we have two points. The "base" or "origin" (e.g
  // the tip of the previous joint) and the "tip" of the current joint. These
  // two points represent a Euclidean vector in space, which has both magnitude
  // and direction. This magnitude is what needs to be anonymized to a standard
  // joint distance rather than the user's actual joint distance. We do this by
  // adjusting where the "tip" of the current joint is. This has ripple effects
  // down the rest of the finger as each joint now has a new origin. So we use
  // the original joint positions to calculate the magnitude and direction,
  // scale to the new magnitude, and then reset both the joint's magnitude and
  // origin, keeping the relative direction between the two joints the same.
  for (const auto& finger_info : kHandMapping) {
    // Find the position of the initial base joint for the finger, since this
    // base isn't moving, we also set it as the first
    // previous_joint_anonymized_position|, which the next joint will use to
    // calculate it's new position. For now, if this base is unlocatable, fail
    // anonymization.
    const auto& base_joint = hand_data[finger_info.origin_index];
    if (!base_joint->mojo_from_joint) {
      return false;
    }

    const auto& base_transform = base_joint->mojo_from_joint.value();

    // |previous_joint_real_position| is the real origin of the joint previous
    // to the one that we are currently working on, and is what we will use to
    // calculate the current vector, which needs to be scaled.
    gfx::Vector3dF previous_joint_real_position =
        base_transform.To3dTranslation();

    // |previous_joint_anonymized_position| represents the adjusted position of
    // the joint previous to the  one that we are currently working on, and is
    // the location that the new location of the tip of the current one needs to
    // be calculated from.
    gfx::Vector3dF previous_joint_anonymized_position =
        base_transform.To3dTranslation();

    // For a little bit of simplicity, extract this finger as a subspan from the
    // overall hand joint data vector. However, we'll still need to use an index
    // based iterator, as we need to index into both this array and the array
    // with the standard sizes simultaneously.
    base::span<mojom::XRHandJointDataPtr> joints =
        hand_data.subspan(finger_info.start_index, finger_info.JointCount());
    for (size_t i = 0; i < joints.size(); i++) {
      // If we cannot locate the joint fail anonymization. We could try to
      // accommodate, but that would in the best case require simply sending a
      // "double long" joint and in the worst case emulating an entire finger.
      if (!joints[i]->mojo_from_joint.has_value()) {
        return false;
      }
      gfx::Transform& current_joint_transform =
          joints[i]->mojo_from_joint.value();

      // To find the vector between two points simply subtract the origin from
      // the destination.
      auto previous_joint_to_current_joint =
          current_joint_transform.To3dTranslation() -
          previous_joint_real_position;

      // If two joints are reported as on top of each other, we have no way of
      // knowing the actual direction and must return false.
      if (previous_joint_to_current_joint.Length() <
          std::numeric_limits<float>::epsilon()) {
        return false;
      }

      // Calculate and adjust how far off from the standard length it is.
      float scale_factor = finger_info.standard_joint_sizes[i] /
                           previous_joint_to_current_joint.Length();
      previous_joint_to_current_joint.Scale(scale_factor);

      // Save the original location of this joint for the next calculation.
      previous_joint_real_position = current_joint_transform.To3dTranslation();

      // Need to move previous_joint_to_current_joint from the previous base to
      // get to the base of this joint.
      const auto current_joint_anonymized_position =
          previous_joint_anonymized_position + previous_joint_to_current_joint;

      // We essentially want to set the translation component of
      // |current_joint_transform| to |current_joint_anonymized_position|. We
      // do this by directly manipulating the matrix, because any rotation of
      // the joint shouldn't affect this calculation.
      current_joint_transform.set_rc(0, 3,
                                     current_joint_anonymized_position.x());
      current_joint_transform.set_rc(1, 3,
                                     current_joint_anonymized_position.y());
      current_joint_transform.set_rc(2, 3,
                                     current_joint_anonymized_position.z());

      // Save this position as the new "previous" position for the next joint.
      previous_joint_anonymized_position = current_joint_anonymized_position;
    }
  }

  return true;
}

}  // namespace device