blob: 118c00cb42473dd7ca641f53fd421bb568c414ae [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 "third_party/blink/renderer/modules/xr/xr_frame.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/modules/xr/xr_hit_test_source.h"
#include "third_party/blink/renderer/modules/xr/xr_input_source.h"
#include "third_party/blink/renderer/modules/xr/xr_joint_space.h"
#include "third_party/blink/renderer/modules/xr/xr_light_estimate.h"
#include "third_party/blink/renderer/modules/xr/xr_light_probe.h"
#include "third_party/blink/renderer/modules/xr/xr_plane_set.h"
#include "third_party/blink/renderer/modules/xr/xr_reference_space.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
#include "third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_source.h"
#include "third_party/blink/renderer/modules/xr/xr_view.h"
#include "third_party/blink/renderer/modules/xr/xr_viewer_pose.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
namespace {
const char kInvalidView[] =
"XRView passed in to the method did not originate from current XRFrame.";
const char kSessionMismatch[] = "XRSpace and XRFrame sessions do not match.";
const char kHitTestSourceUnavailable[] =
"Unable to obtain hit test results for specified hit test source. Ensure "
"that it was not already canceled.";
const char kCannotObtainNativeOrigin[] =
"The operation was unable to obtain necessary information and could not be "
"completed.";
const char kSpacesSequenceTooLarge[] =
"Insufficient buffer capacity for pose results.";
const char kMismatchedBufferSizes[] = "Buffer sizes must be equal";
absl::optional<uint64_t> GetPlaneId(
const device::mojom::blink::XRNativeOriginInformation& native_origin) {
if (native_origin.is_plane_id()) {
return native_origin.get_plane_id();
}
return absl::nullopt;
}
} // namespace
constexpr char XRFrame::kInactiveFrame[];
constexpr char XRFrame::kNonAnimationFrame[];
XRFrame::XRFrame(XRSession* session, bool is_animation_frame)
: session_(session), is_animation_frame_(is_animation_frame) {}
XRViewerPose* XRFrame::getViewerPose(XRReferenceSpace* reference_space,
ExceptionState& exception_state) {
DCHECK(reference_space);
DVLOG(3) << __func__ << ": is_active_=" << is_active_
<< ", is_animation_frame_=" << is_animation_frame_
<< ", reference_space->ToString()=" << reference_space->ToString();
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!is_animation_frame_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kNonAnimationFrame);
return nullptr;
}
// Must use a reference space created from the same session.
if (!IsSameSession(reference_space->session(), exception_state)) {
return nullptr;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
return nullptr;
}
session_->LogGetPose();
absl::optional<gfx::Transform> native_from_mojo =
reference_space->NativeFromMojo();
if (!native_from_mojo) {
DVLOG(1) << __func__ << ": native_from_mojo is invalid";
return nullptr;
}
gfx::Transform ref_space_from_mojo =
reference_space->OffsetFromNativeMatrix();
ref_space_from_mojo.PreConcat(*native_from_mojo);
// Can only update an XRViewerPose's views with an invertible matrix.
if (!ref_space_from_mojo.IsInvertible()) {
DVLOG(1) << __func__ << ": ref_space_from_mojo is not invertible";
return nullptr;
}
absl::optional<gfx::Transform> offset_space_from_viewer =
reference_space->OffsetFromViewer();
// Can only update an XRViewerPose's views with an invertible matrix.
if (!(offset_space_from_viewer && offset_space_from_viewer->IsInvertible())) {
DVLOG(1) << __func__
<< ": offset_space_from_viewer is invalid or not invertible - "
"returning nullptr, offset_space_from_viewer valid? "
<< (offset_space_from_viewer ? true : false);
return nullptr;
}
device::mojom::blink::XRReferenceSpaceType type = reference_space->GetType();
// If the |reference_space| type is kViewer, we know that the pose is not
// emulated. Otherwise, ask the session if the poses are emulated or not.
return MakeGarbageCollected<XRViewerPose>(
this, ref_space_from_mojo, *offset_space_from_viewer,
(type == device::mojom::blink::XRReferenceSpaceType::kViewer)
? false
: session_->EmulatedPosition());
}
XRAnchorSet* XRFrame::trackedAnchors() const {
return session_->TrackedAnchors();
}
XRPlaneSet* XRFrame::detectedPlanes(ExceptionState& exception_state) const {
DVLOG(3) << __func__;
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
return session_->GetDetectedPlanes();
}
XRLightEstimate* XRFrame::getLightEstimate(
XRLightProbe* light_probe,
ExceptionState& exception_state) const {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!is_animation_frame_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kNonAnimationFrame);
return nullptr;
}
if (!light_probe) {
return nullptr;
}
// Must use a light probe created from the same session.
if (!IsSameSession(light_probe->session(), exception_state)) {
return nullptr;
}
return light_probe->getLightEstimate();
}
XRCPUDepthInformation* XRFrame::getDepthInformation(
XRView* view,
ExceptionState& exception_state) const {
DVLOG(2) << __func__;
if (!session_->IsFeatureEnabled(device::mojom::XRSessionFeature::DEPTH)) {
DVLOG(2) << __func__ << ": depth sensing is not enabled on a session";
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
XRSession::kDepthSensingFeatureNotSupported);
return nullptr;
}
if (!is_active_) {
DVLOG(2) << __func__ << ": frame is not active";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!is_animation_frame_) {
DVLOG(2) << __func__ << ": frame is not animating";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kNonAnimationFrame);
return nullptr;
}
if (this != view->frame()) {
DVLOG(2) << __func__ << ": view did not originate from the frame";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInvalidView);
return nullptr;
}
return session_->GetCpuDepthInformation(this, exception_state);
}
XRPose* XRFrame::getPose(XRSpace* space,
XRSpace* basespace,
ExceptionState& exception_state) {
DCHECK(space);
DCHECK(basespace);
DVLOG(2) << __func__ << ": is_active=" << is_active_
<< ", space->ToString()=" << space->ToString()
<< ", basespace->ToString()=" << basespace->ToString();
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!IsSameSession(space->session(), exception_state) ||
!IsSameSession(basespace->session(), exception_state)) {
return nullptr;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
return nullptr;
}
// If the addresses match, the pose between the spaces is definitely an
// identity & we can skip the rest of the logic. The pose is not emulated.
if (space == basespace) {
DVLOG(3) << __func__ << ": addresses match, returning identity";
return MakeGarbageCollected<XRPose>(gfx::Transform{}, false);
}
// If the native origins match, the pose between the spaces is fixed and
// depends only on their offsets from the same native origin - we can compute
// it here and skip the rest of the logic. The pose is not emulated.
if (space->NativeOrigin() == basespace->NativeOrigin()) {
DVLOG(3) << __func__
<< ": native origins match, returning a pose based on offesets";
auto basespace_from_native_origin = basespace->OffsetFromNativeMatrix();
auto native_origin_from_space = space->NativeFromOffsetMatrix();
return MakeGarbageCollected<XRPose>(
basespace_from_native_origin * native_origin_from_space, false);
}
return space->getPose(basespace);
}
void XRFrame::Deactivate() {
is_active_ = false;
is_animation_frame_ = false;
}
bool XRFrame::IsActive() const {
return is_active_;
}
HeapVector<Member<XRHitTestResult>> XRFrame::getHitTestResults(
XRHitTestSource* hit_test_source,
ExceptionState& exception_state) {
if (!hit_test_source ||
!session_->ValidateHitTestSourceExists(hit_test_source)) {
// This should only happen when hit test source was already canceled.
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kHitTestSourceUnavailable);
return {};
}
return hit_test_source->Results();
}
HeapVector<Member<XRTransientInputHitTestResult>>
XRFrame::getHitTestResultsForTransientInput(
XRTransientInputHitTestSource* hit_test_source,
ExceptionState& exception_state) {
if (!hit_test_source ||
!session_->ValidateHitTestSourceExists(hit_test_source)) {
// This should only happen when hit test source was already canceled.
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kHitTestSourceUnavailable);
return {};
}
return hit_test_source->Results();
}
ScriptPromise XRFrame::createAnchor(ScriptState* script_state,
XRRigidTransform* offset_space_from_anchor,
XRSpace* space,
ExceptionState& exception_state) {
DVLOG(2) << __func__;
if (!session_->IsFeatureEnabled(device::mojom::XRSessionFeature::ANCHORS)) {
DVLOG(2) << __func__
<< ": feature not enabled on a session, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
XRSession::kAnchorsFeatureNotSupported);
return {};
}
if (!is_active_) {
DVLOG(2) << __func__ << ": frame not active, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return {};
}
if (!offset_space_from_anchor) {
DVLOG(2) << __func__
<< ": offset_space_from_anchor not set, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
XRSession::kNoRigidTransformSpecified);
return {};
}
if (!space) {
DVLOG(2) << __func__ << ": space not set, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
XRSession::kNoSpaceSpecified);
return {};
}
device::mojom::blink::XRNativeOriginInformationPtr maybe_native_origin =
space->NativeOrigin();
if (!maybe_native_origin) {
DVLOG(2) << __func__ << ": native origin not set, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kCannotObtainNativeOrigin);
return {};
}
DVLOG(3) << __func__ << ": space->ToString()=" << space->ToString();
auto maybe_plane_id = GetPlaneId(*maybe_native_origin);
// The passed in space may be an offset space, we need to transform the pose
// to account for origin-offset:
auto native_origin_from_offset_space = space->NativeFromOffsetMatrix();
auto native_origin_from_anchor = native_origin_from_offset_space *
offset_space_from_anchor->TransformMatrix();
// We should strive to create an anchor whose location aligns with the pose
// |offset_space_from_anchor| relative to |space|. For spaces that are
// dynamically changing, this means we need to convert the pose to be relative
// to stationary space, using data valid in the current frame, and change the
// native origin relative to which the pose is expressed when communicating
// with the device. For spaces that are classified as stationary, this
// adjustment is not needed.
if (space->IsStationary()) {
// Space is considered stationary, no adjustments are needed.
return session_->CreateAnchorHelper(script_state, native_origin_from_anchor,
maybe_native_origin, maybe_plane_id,
exception_state);
}
return CreateAnchorFromNonStationarySpace(script_state,
native_origin_from_anchor, space,
maybe_plane_id, exception_state);
}
ScriptPromise XRFrame::CreateAnchorFromNonStationarySpace(
ScriptState* script_state,
const gfx::Transform& native_origin_from_anchor,
XRSpace* space,
absl::optional<uint64_t> maybe_plane_id,
ExceptionState& exception_state) {
DVLOG(2) << __func__;
// Space is not considered stationary - need to adjust the app-provided pose.
// Let's ask the session about the appropriate stationary reference space:
absl::optional<XRSession::ReferenceSpaceInformation>
reference_space_information = session_->GetStationaryReferenceSpace();
if (!reference_space_information) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
XRSession::kUnableToRetrieveMatrix);
return {};
}
auto stationary_space_from_mojo =
reference_space_information->mojo_from_space.GetCheckedInverse();
// We now have 2 spaces - the dynamic one passed in to create anchor
// call, and the stationary one. We also have a rigid transform
// expressed relative to the dynamic space. Time to convert it so that it's
// expressed relative to stationary space.
auto mojo_from_native_origin = space->MojoFromNative();
if (!mojo_from_native_origin) {
DVLOG(2) << __func__ << ": native_origin not set, failing anchor creation";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
XRSession::kUnableToRetrieveMatrix);
return {};
}
auto mojo_from_anchor = *mojo_from_native_origin * native_origin_from_anchor;
auto stationary_space_from_anchor =
stationary_space_from_mojo * mojo_from_anchor;
// Conversion done, make the adjusted call:
return session_->CreateAnchorHelper(
script_state, stationary_space_from_anchor,
reference_space_information->native_origin, maybe_plane_id,
exception_state);
}
bool XRFrame::IsSameSession(XRSession* space_session,
ExceptionState& exception_state) const {
if (space_session != session_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return false;
}
return true;
}
HeapVector<Member<XRImageTrackingResult>> XRFrame::getImageTrackingResults(
ExceptionState& exception_state) {
return session_->ImageTrackingResults(exception_state);
}
XRJointPose* XRFrame::getJointPose(XRJointSpace* joint,
XRSpace* baseSpace,
ExceptionState& exception_state) const {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!IsSameSession(baseSpace->session(), exception_state) ||
!IsSameSession(joint->session(), exception_state)) {
return nullptr;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
return nullptr;
}
const XRPose* pose = joint->getPose(baseSpace);
if (!pose) {
return nullptr;
}
const float radius = joint->radius();
return MakeGarbageCollected<XRJointPose>(pose->transform()->TransformMatrix(),
radius);
}
bool XRFrame::fillJointRadii(HeapVector<Member<XRJointSpace>>& jointSpaces,
NotShared<DOMFloat32Array> radii,
ExceptionState& exception_state) const {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return false;
}
for (const auto& joint_space : jointSpaces) {
if (!IsSameSession(joint_space->session(), exception_state)) {
return false;
}
}
if (jointSpaces.size() != radii->length()) {
exception_state.ThrowTypeError(kMismatchedBufferSizes);
return false;
}
bool all_valid = true;
for (unsigned offset = 0; offset < jointSpaces.size(); offset++) {
const XRJointSpace* joint_space = jointSpaces[offset];
if (joint_space->handHasMissingPoses()) {
radii->Data()[offset] = NAN;
all_valid = false;
} else {
radii->Data()[offset] = joint_space->radius();
}
}
return all_valid;
}
bool XRFrame::fillPoses(HeapVector<Member<XRSpace>>& spaces,
XRSpace* baseSpace,
NotShared<DOMFloat32Array> transforms,
ExceptionState& exception_state) const {
const unsigned floats_per_transform = 16;
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return false;
}
for (const auto& space : spaces) {
if (!IsSameSession(space->session(), exception_state)) {
return false;
}
}
if (!IsSameSession(baseSpace->session(), exception_state)) {
return false;
}
if (spaces.size() * floats_per_transform > transforms->length()) {
exception_state.ThrowTypeError(kSpacesSequenceTooLarge);
return false;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
return false;
}
bool allValid = true;
unsigned offset = 0;
for (const auto& space : spaces) {
const XRPose* pose = space->getPose(baseSpace);
if (!pose) {
for (unsigned i = 0; i < floats_per_transform; i++) {
transforms->Data()[offset + i] = NAN;
}
allValid = false;
} else {
const float* const poseMatrix = pose->transform()->matrix()->Data();
for (unsigned i = 0; i < floats_per_transform; i++) {
transforms->Data()[offset + i] = poseMatrix[i];
}
}
offset += floats_per_transform;
}
return allValid;
}
void XRFrame::Trace(Visitor* visitor) const {
visitor->Trace(session_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink