blob: 66154cdf7a8c2f8a793b8ec649cf0f44f9a167c0 [file] [log] [blame]
// Copyright 2018 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_input_source.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/xr/xr.h"
#include "third_party/blink/renderer/modules/xr/xr_grip_space.h"
#include "third_party/blink/renderer/modules/xr/xr_input_source_event.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
#include "third_party/blink/renderer/modules/xr/xr_space.h"
#include "third_party/blink/renderer/modules/xr/xr_target_ray_space.h"
#include "third_party/blink/renderer/modules/xr/xr_utils.h"
namespace blink {
namespace {
std::unique_ptr<TransformationMatrix> TryGetTransformationMatrix(
const base::Optional<gfx::Transform>& transform) {
if (transform) {
return std::make_unique<TransformationMatrix>(transform->matrix());
}
return nullptr;
}
std::unique_ptr<TransformationMatrix> TryGetTransformationMatrix(
const TransformationMatrix* other) {
if (other) {
return std::make_unique<TransformationMatrix>(*other);
}
return nullptr;
}
} // namespace
XRInputSource::InternalState::InternalState(
uint32_t source_id,
device::mojom::XRTargetRayMode target_ray_mode,
base::TimeTicks base_timestamp)
: source_id(source_id),
target_ray_mode(target_ray_mode),
base_timestamp(base_timestamp) {}
XRInputSource::InternalState::InternalState(const InternalState& other) =
default;
XRInputSource::InternalState::~InternalState() = default;
XRInputSource* XRInputSource::CreateOrUpdateFrom(
XRInputSource* other,
XRSession* session,
const device::mojom::blink::XRInputSourceStatePtr& state) {
if (!state)
return other;
XRInputSource* updated_source = other;
// Check if we have an existing object, and if we do, if it can be re-used.
if (!other) {
auto source_id = state->source_id;
updated_source = MakeGarbageCollected<XRInputSource>(session, source_id);
} else if (other->InvalidatesSameObject(state)) {
// Something in the state has changed which requires us to re-create the
// object. Create a copy now, and we will blindly update any state later,
// knowing that we now have a new object if needed.
updated_source = MakeGarbageCollected<XRInputSource>(*other);
}
updated_source->UpdateGamepad(state->gamepad);
// Update the input source's description if this state update includes them.
if (state->description) {
const device::mojom::blink::XRInputSourceDescriptionPtr& desc =
state->description;
updated_source->state_.target_ray_mode = desc->target_ray_mode;
updated_source->state_.handedness = desc->handedness;
updated_source->input_from_pointer_ =
TryGetTransformationMatrix(desc->pointer_offset);
updated_source->state_.profiles.clear();
for (const auto& name : state->description->profiles) {
updated_source->state_.profiles.push_back(name);
}
}
updated_source->mojo_from_input_ = TryGetTransformationMatrix(state->grip);
updated_source->state_.emulated_position = state->emulated_position;
return updated_source;
}
XRInputSource::XRInputSource(XRSession* session,
uint32_t source_id,
device::mojom::XRTargetRayMode target_ray_mode)
: state_(source_id, target_ray_mode, session->xr()->NavigationStart()),
session_(session),
target_ray_space_(MakeGarbageCollected<XRTargetRaySpace>(session, this)),
grip_space_(MakeGarbageCollected<XRGripSpace>(session, this)) {}
// Must make new target_ray_space_ and grip_space_ to ensure that they point to
// the correct XRInputSource object. Otherwise, the controller position gets
// stuck when an XRInputSource gets re-created. Also need to make a deep copy of
// the matrices since they use unique_ptrs.
XRInputSource::XRInputSource(const XRInputSource& other)
: state_(other.state_),
session_(other.session_),
target_ray_space_(
MakeGarbageCollected<XRTargetRaySpace>(other.session_, this)),
grip_space_(MakeGarbageCollected<XRGripSpace>(other.session_, this)),
gamepad_(other.gamepad_),
mojo_from_input_(
TryGetTransformationMatrix(other.mojo_from_input_.get())),
input_from_pointer_(
TryGetTransformationMatrix(other.input_from_pointer_.get())) {}
const String XRInputSource::handedness() const {
switch (state_.handedness) {
case device::mojom::XRHandedness::NONE:
return "none";
case device::mojom::XRHandedness::LEFT:
return "left";
case device::mojom::XRHandedness::RIGHT:
return "right";
}
NOTREACHED() << "Unknown handedness: " << state_.handedness;
}
const String XRInputSource::targetRayMode() const {
switch (state_.target_ray_mode) {
case device::mojom::XRTargetRayMode::GAZING:
return "gaze";
case device::mojom::XRTargetRayMode::POINTING:
return "tracked-pointer";
case device::mojom::XRTargetRayMode::TAPPING:
return "screen";
}
NOTREACHED() << "Unknown target ray mode: " << state_.target_ray_mode;
}
XRSpace* XRInputSource::targetRaySpace() const {
return target_ray_space_;
}
XRSpace* XRInputSource::gripSpace() const {
if (state_.target_ray_mode == device::mojom::XRTargetRayMode::POINTING) {
return grip_space_;
}
return nullptr;
}
bool XRInputSource::InvalidatesSameObject(
const device::mojom::blink::XRInputSourceStatePtr& state) {
if ((state->gamepad && !gamepad_) || (!state->gamepad && gamepad_)) {
return true;
}
if (state->description) {
if (state->description->handedness != state_.handedness) {
return true;
}
if (state->description->target_ray_mode != state_.target_ray_mode) {
return true;
}
if (state->description->profiles.size() != state_.profiles.size()) {
return true;
}
for (wtf_size_t i = 0; i < state_.profiles.size(); ++i) {
if (state->description->profiles[i] != state_.profiles[i]) {
return true;
}
}
}
return false;
}
void XRInputSource::SetInputFromPointer(
const TransformationMatrix* input_from_pointer) {
input_from_pointer_ = TryGetTransformationMatrix(input_from_pointer);
}
void XRInputSource::SetGamepadConnected(bool state) {
if (gamepad_)
gamepad_->SetConnected(state);
}
void XRInputSource::UpdateGamepad(
const base::Optional<device::Gamepad>& gamepad) {
if (gamepad) {
if (!gamepad_) {
gamepad_ = MakeGarbageCollected<Gamepad>(this, -1, state_.base_timestamp,
base::TimeTicks::Now());
}
gamepad_->UpdateFromDeviceState(*gamepad);
} else {
gamepad_ = nullptr;
}
}
base::Optional<XRNativeOriginInformation> XRInputSource::nativeOrigin() const {
return XRNativeOriginInformation::Create(this);
}
void XRInputSource::OnSelectStart() {
// Discard duplicate events and ones after the session has ended.
if (state_.primary_input_pressed || session_->ended())
return;
state_.primary_input_pressed = true;
state_.selection_cancelled = false;
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelectstart);
session_->DispatchEvent(*event);
if (event->defaultPrevented())
state_.selection_cancelled = true;
// Ensure the frame cannot be used outside of the event handler.
event->frame()->Deactivate();
}
void XRInputSource::OnSelectEnd(UserActivation user_activation) {
// Discard duplicate events and ones after the session has ended.
if (!state_.primary_input_pressed || session_->ended())
return;
state_.primary_input_pressed = false;
LocalFrame* frame = session_->xr()->GetFrame();
if (!frame)
return;
std::unique_ptr<UserGestureIndicator> gesture_indicator =
user_activation == UserActivation::kEnabled
? LocalFrame::NotifyUserActivation(frame)
: nullptr;
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelectend);
session_->DispatchEvent(*event);
if (event->defaultPrevented())
state_.selection_cancelled = true;
// Ensure the frame cannot be used outside of the event handler.
event->frame()->Deactivate();
}
void XRInputSource::OnSelect() {
// If a select was fired but we had not previously started the selection it
// indicates a sub-frame or instantaneous select event, and we should fire a
// selectstart prior to the selectend.
if (!state_.primary_input_pressed) {
OnSelectStart();
}
// If SelectStart caused the session to end, we shouldn't try to fire the
// select event.
if (!state_.selection_cancelled && !session_->ended()) {
LocalFrame* frame = session_->xr()->GetFrame();
if (!frame)
return;
std::unique_ptr<UserGestureIndicator> gesture_indicator =
LocalFrame::NotifyUserActivation(frame);
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelect);
session_->DispatchEvent(*event);
// Ensure the frame cannot be used outside of the event handler.
event->frame()->Deactivate();
}
OnSelectEnd(UserActivation::kEnabled);
}
void XRInputSource::UpdateSelectState(
const device::mojom::blink::XRInputSourceStatePtr& state) {
if (!state)
return;
// Handle state change of the primary input, which may fire events
if (state->primary_input_clicked)
OnSelect();
if (state->primary_input_pressed) {
OnSelectStart();
} else if (state_.primary_input_pressed) {
// May get here if the input source was previously pressed but now isn't,
// but the input source did not set primary_input_clicked to true. We will
// treat this as a cancelled selection, firing the selectend event so the
// page stays in sync with the controller state but won't fire the
// usual select event.
OnSelectEnd(UserActivation::kDisabled);
}
}
void XRInputSource::OnRemoved() {
if (state_.primary_input_pressed) {
state_.primary_input_pressed = false;
XRInputSourceEvent* event =
CreateInputSourceEvent(event_type_names::kSelectend);
session_->DispatchEvent(*event);
if (event->defaultPrevented())
state_.selection_cancelled = true;
// Ensure the frame cannot be used outside of the event handler.
event->frame()->Deactivate();
}
SetGamepadConnected(false);
}
XRInputSourceEvent* XRInputSource::CreateInputSourceEvent(
const AtomicString& type) {
XRFrame* presentation_frame = session_->CreatePresentationFrame();
return XRInputSourceEvent::Create(type, presentation_frame, this);
}
void XRInputSource::Trace(blink::Visitor* visitor) {
visitor->Trace(session_);
visitor->Trace(target_ray_space_);
visitor->Trace(grip_space_);
visitor->Trace(gamepad_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink