blob: 36b374d36b39282de7e3ae400e054228cc308525 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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 kCannotReportPoses[] =
"Poses cannot be given out for the current state.";
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";
} // 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) {
DVLOG(3) << __func__ << ": is_active_=" << is_active_
<< ", is_animation_frame_=" << is_animation_frame_;
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!is_animation_frame_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kNonAnimationFrame);
return nullptr;
}
if (!reference_space) {
DVLOG(1) << __func__ << ": reference space not present, returning null";
return nullptr;
}
// Must use a reference space created from the same session.
if (reference_space->session() != session_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return nullptr;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(kCannotReportPoses);
return nullptr;
}
session_->LogGetPose();
base::Optional<TransformationMatrix> 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;
}
return MakeGarbageCollected<XRViewerPose>(this, *offset_space_from_viewer);
}
XRAnchorSet* XRFrame::trackedAnchors() const {
return session_->TrackedAnchors();
}
XRPlaneSet* XRFrame::detectedPlanes(ExceptionState& exception_state) const {
DVLOG(3) << __func__;
if (!session_->IsFeatureEnabled(
device::mojom::XRSessionFeature::PLANE_DETECTION)) {
DVLOG(2) << __func__
<< ": plane detection feature not enabled on a session";
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
XRSession::kPlanesFeatureNotSupported);
return {};
}
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 (light_probe->session() != session_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
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);
}
// Return an XRPose that has a transform of basespace_from_space, while
// accounting for the base pose matrix of this frame. If computing a transform
// isn't possible, return nullptr.
XRPose* XRFrame::getPose(XRSpace* space,
XRSpace* basespace,
ExceptionState& exception_state) {
DVLOG(2) << __func__;
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (!space || !basespace) {
DVLOG(2) << __func__ << " : space or basespace is null, space =" << space
<< ", basespace = " << basespace;
return nullptr;
}
if (space->session() != session_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return nullptr;
}
if (basespace->session() != session_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return nullptr;
}
if (!session_->CanReportPoses()) {
exception_state.ThrowSecurityError(kCannotReportPoses);
return nullptr;
}
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 {};
}
base::Optional<device::mojom::blink::XRNativeOriginInformation>
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();
// 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, exception_state);
}
return CreateAnchorFromNonStationarySpace(
script_state, native_origin_from_anchor, space, exception_state);
}
ScriptPromise XRFrame::CreateAnchorFromNonStationarySpace(
ScriptState* script_state,
const blink::TransformationMatrix& native_origin_from_anchor,
XRSpace* space,
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:
base::Optional<XRSession::ReferenceSpaceInformation>
reference_space_information = session_->GetStationaryReferenceSpace();
if (!reference_space_information) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
XRSession::kUnableToRetrieveMatrix);
return {};
}
const TransformationMatrix& mojo_from_stationary_space =
reference_space_information->mojo_from_space;
DCHECK(mojo_from_stationary_space.IsInvertible());
auto stationary_space_from_mojo = mojo_from_stationary_space.Inverse();
// 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, exception_state);
}
HeapVector<Member<XRImageTrackingResult>> XRFrame::getImageTrackingResults(
ExceptionState& exception_state) {
return session_->ImageTrackingResults(exception_state);
}
XRJointPose* XRFrame::getJointPose(XRJointSpace* joint,
XRSpace* baseSpace,
ExceptionState& exception_state) {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return nullptr;
}
if (session_ != baseSpace->session() || session_ != joint->session()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
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) {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return false;
}
if (jointSpaces.size() != radii->length()) {
exception_state.ThrowTypeError(kMismatchedBufferSizes);
return false;
}
for (unsigned offset = 0; offset < jointSpaces.size(); offset++) {
const XRJointSpace* jointSpace = jointSpaces[offset];
if (session_ != jointSpace->session()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return false;
}
radii->Data()[offset] = jointSpace->radius();
}
return true;
}
bool XRFrame::fillPoses(HeapVector<Member<XRSpace>>& spaces,
XRSpace* baseSpace,
NotShared<DOMFloat32Array> transforms,
ExceptionState& exception_state) {
if (!is_active_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInactiveFrame);
return false;
}
const unsigned floats_per_transform = 16;
if (spaces.size() * floats_per_transform > transforms->length()) {
exception_state.ThrowTypeError(kSpacesSequenceTooLarge);
return false;
}
if (session_ != baseSpace->session()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return false;
}
bool allValid = true;
unsigned offset = 0;
for (const auto& space : spaces) {
if (session_ != space->session()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kSessionMismatch);
return false;
}
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