Commit 0c8f47ee authored by Piotr Bialecki's avatar Piotr Bialecki Committed by Commit Bot

WebXR - anchor and plane pose can be null coming from the device

Other changes:
- rename anchor and plane pose in mojo to mojo_from_xxxxxx to match convention
- adjust samples to account for the possibility that anchors and planes
  can have their poses unknown
- send null poses from device if tracking is paused
- ARCore device now does not treat planes and anchors whose tracking is
  paused as removed
- add DVLOGs all around to help with issue investigation
- add test page for experiments with local space stability & drift

Note: the test mentioned above is not exposed via proposals/index.html.
Change-Id: I786e86d69b69badbace57ce62585e3d5fd1ae756
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2130778Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarKlaus Weidner <klausw@chromium.org>
Commit-Queue: Piotr Bialecki <bialpio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757560}
parent c2bd3712
...@@ -28,9 +28,13 @@ ArCoreAnchorManager::ArCoreAnchorManager(util::PassKey<ArCoreImpl> pass_key, ...@@ -28,9 +28,13 @@ ArCoreAnchorManager::ArCoreAnchorManager(util::PassKey<ArCoreImpl> pass_key,
ArCoreAnchorManager::~ArCoreAnchorManager() = default; ArCoreAnchorManager::~ArCoreAnchorManager() = default;
mojom::XRAnchorsDataPtr ArCoreAnchorManager::GetAnchorsData() const { mojom::XRAnchorsDataPtr ArCoreAnchorManager::GetAnchorsData() const {
DVLOG(3) << __func__ << ": anchor_id_to_anchor_info_.size()="
<< anchor_id_to_anchor_info_.size()
<< ", updated_anchor_ids_.size()=" << updated_anchor_ids_.size();
std::vector<uint64_t> all_anchors_ids; std::vector<uint64_t> all_anchors_ids;
all_anchors_ids.reserve(anchor_id_to_anchor_object_.size()); all_anchors_ids.reserve(anchor_id_to_anchor_info_.size());
for (const auto& anchor_id_and_object : anchor_id_to_anchor_object_) { for (const auto& anchor_id_and_object : anchor_id_to_anchor_info_) {
all_anchors_ids.push_back(anchor_id_and_object.first.GetUnsafeValue()); all_anchors_ids.push_back(anchor_id_and_object.first.GetUnsafeValue());
} }
...@@ -38,18 +42,28 @@ mojom::XRAnchorsDataPtr ArCoreAnchorManager::GetAnchorsData() const { ...@@ -38,18 +42,28 @@ mojom::XRAnchorsDataPtr ArCoreAnchorManager::GetAnchorsData() const {
updated_anchors.reserve(updated_anchor_ids_.size()); updated_anchors.reserve(updated_anchor_ids_.size());
for (const auto& anchor_id : updated_anchor_ids_) { for (const auto& anchor_id : updated_anchor_ids_) {
const device::internal::ScopedArCoreObject<ArAnchor*>& anchor = const device::internal::ScopedArCoreObject<ArAnchor*>& anchor =
anchor_id_to_anchor_object_.at(anchor_id); anchor_id_to_anchor_info_.at(anchor_id).anchor;
if (anchor_id_to_anchor_info_.at(anchor_id).tracking_state ==
AR_TRACKING_STATE_TRACKING) {
// pose // pose
ArAnchor_getPose(arcore_session_, anchor.get(), ar_pose_.get()); ArAnchor_getPose(arcore_session_, anchor.get(), ar_pose_.get());
mojom::Pose pose = GetMojomPoseFromArPose(arcore_session_, ar_pose_.get()); mojom::Pose pose =
GetMojomPoseFromArPose(arcore_session_, ar_pose_.get());
DVLOG(3) << __func__ << ": anchor id: " << anchor_id.GetUnsafeValue() DVLOG(3) << __func__ << ": anchor_id: " << anchor_id.GetUnsafeValue()
<< ", position=" << pose.position.ToString() << ", position=" << pose.position.ToString()
<< ", orientation=" << pose.orientation.ToString(); << ", orientation=" << pose.orientation.ToString();
updated_anchors.push_back(mojom::XRAnchorData::New( updated_anchors.push_back(mojom::XRAnchorData::New(
anchor_id.GetUnsafeValue(), device::mojom::Pose::New(pose))); anchor_id.GetUnsafeValue(), device::mojom::Pose::New(pose)));
} else {
DVLOG(3) << __func__ << ": anchor_id: " << anchor_id.GetUnsafeValue()
<< ", position=untracked, orientation=untracked";
updated_anchors.push_back(
mojom::XRAnchorData::New(anchor_id.GetUnsafeValue(), nullptr));
}
} }
return mojom::XRAnchorsData::New(std::move(all_anchors_ids), return mojom::XRAnchorsData::New(std::move(all_anchors_ids),
...@@ -73,7 +87,7 @@ void ArCoreAnchorManager::ForEachArCoreAnchor(ArAnchorList* arcore_anchors, ...@@ -73,7 +87,7 @@ void ArCoreAnchorManager::ForEachArCoreAnchor(ArAnchorList* arcore_anchors,
ArTrackingState tracking_state; ArTrackingState tracking_state;
ArAnchor_getTrackingState(arcore_session_, anchor.get(), &tracking_state); ArAnchor_getTrackingState(arcore_session_, anchor.get(), &tracking_state);
if (tracking_state != ArTrackingState::AR_TRACKING_STATE_TRACKING) { if (tracking_state == ArTrackingState::AR_TRACKING_STATE_STOPPED) {
// Skip all anchors that are not currently tracked. // Skip all anchors that are not currently tracked.
continue; continue;
} }
...@@ -100,7 +114,7 @@ void ArCoreAnchorManager::Update(ArFrame* ar_frame) { ...@@ -100,7 +114,7 @@ void ArCoreAnchorManager::Update(ArFrame* ar_frame) {
std::tie(anchor_id, created) = CreateOrGetAnchorId(ar_anchor.get()); std::tie(anchor_id, created) = CreateOrGetAnchorId(ar_anchor.get());
DVLOG(3) << __func__ DVLOG(3) << __func__
<< ": anchor updated, anchor id=" << anchor_id.GetUnsafeValue() << ": anchor updated, anchor_id=" << anchor_id.GetUnsafeValue()
<< ", tracking_state=" << tracking_state; << ", tracking_state=" << tracking_state;
DCHECK(!created) DCHECK(!created)
...@@ -118,44 +132,54 @@ void ArCoreAnchorManager::Update(ArFrame* ar_frame) { ...@@ -118,44 +132,54 @@ void ArCoreAnchorManager::Update(ArFrame* ar_frame) {
// Collect the objects of all currently tracked anchors. // Collect the objects of all currently tracked anchors.
// |ar_plane_address_to_id_| should *not* grow. // |ar_plane_address_to_id_| should *not* grow.
std::map<AnchorId, device::internal::ScopedArCoreObject<ArAnchor*>> std::map<AnchorId, AnchorInfo> new_anchor_id_to_anchor_info;
anchor_id_to_anchor_object;
ForEachArCoreAnchor(arcore_anchors_.get(), [this, ForEachArCoreAnchor(arcore_anchors_.get(), [this,
&anchor_id_to_anchor_object]( &new_anchor_id_to_anchor_info,
&updated_anchor_ids](
device::internal:: device::internal::
ScopedArCoreObject< ScopedArCoreObject<
ArAnchor*> ar_anchor, ArAnchor*> anchor,
ArTrackingState ArTrackingState
tracking_state) { tracking_state) {
// ID // ID
AnchorId anchor_id; AnchorId anchor_id;
bool created; bool created;
std::tie(anchor_id, created) = CreateOrGetAnchorId(ar_anchor.get()); std::tie(anchor_id, created) = CreateOrGetAnchorId(anchor.get());
DVLOG(3) << __func__ DVLOG(3) << __func__
<< ": anchor present, anchor id=" << anchor_id.GetUnsafeValue() << ": anchor present, anchor_id=" << anchor_id.GetUnsafeValue()
<< ", tracking state=" << tracking_state; << ", tracking state=" << tracking_state;
DCHECK(!created) DCHECK(!created)
<< "Anchor creation is app-initiated - we should never encounter an " << "Anchor creation is app-initiated - we should never encounter an "
"anchor that was created outside of `ArCoreImpl::CreateAnchor()`."; "anchor that was created outside of `ArCoreImpl::CreateAnchor()`.";
anchor_id_to_anchor_object[anchor_id] = std::move(ar_anchor); // Inspect the tracking state of this anchor in the previous frame. If it
// changed, mark the anchor as updated.
if (base::Contains(anchor_id_to_anchor_info_, anchor_id) &&
anchor_id_to_anchor_info_.at(anchor_id).tracking_state !=
tracking_state) {
updated_anchor_ids.insert(anchor_id);
}
AnchorInfo new_anchor_info = AnchorInfo(std::move(anchor), tracking_state);
new_anchor_id_to_anchor_info.emplace(anchor_id, std::move(new_anchor_info));
}); });
DVLOG(3) << __func__ << ": anchor_id_to_anchor_object.size()=" DVLOG(3) << __func__ << ": new_anchor_id_to_anchor_info.size()="
<< anchor_id_to_anchor_object.size(); << new_anchor_id_to_anchor_info.size();
// Shrink |ar_plane_address_to_id_|, removing all planes that are no longer // Shrink |ar_plane_address_to_id_|, removing all planes that are no longer
// tracked or were subsumed - if they do not show up in // tracked or were subsumed - if they do not show up in
// |plane_id_to_plane_object| map, they are no longer tracked. // |plane_id_to_plane_object| map, they are no longer tracked.
base::EraseIf( base::EraseIf(
ar_anchor_address_to_id_, ar_anchor_address_to_id_,
[&anchor_id_to_anchor_object](const auto& anchor_address_and_id) { [&new_anchor_id_to_anchor_info](const auto& anchor_address_and_id) {
return !base::Contains(anchor_id_to_anchor_object, return !base::Contains(new_anchor_id_to_anchor_info,
anchor_address_and_id.second); anchor_address_and_id.second);
}); });
anchor_id_to_anchor_object_.swap(anchor_id_to_anchor_object); anchor_id_to_anchor_info_.swap(new_anchor_id_to_anchor_info);
updated_anchor_ids_.swap(updated_anchor_ids); updated_anchor_ids_.swap(updated_anchor_ids);
} }
...@@ -196,7 +220,8 @@ base::Optional<AnchorId> ArCoreAnchorManager::CreateAnchor( ...@@ -196,7 +220,8 @@ base::Optional<AnchorId> ArCoreAnchorManager::CreateAnchor(
DCHECK(created) << "This should always be a new anchor, not something we've " DCHECK(created) << "This should always be a new anchor, not something we've "
"seen previously."; "seen previously.";
anchor_id_to_anchor_object_[anchor_id] = std::move(ar_anchor); anchor_id_to_anchor_info_.emplace(
anchor_id, AnchorInfo(std::move(ar_anchor), AR_TRACKING_STATE_TRACKING));
return anchor_id; return anchor_id;
} }
...@@ -222,38 +247,47 @@ base::Optional<AnchorId> ArCoreAnchorManager::CreateAnchor( ...@@ -222,38 +247,47 @@ base::Optional<AnchorId> ArCoreAnchorManager::CreateAnchor(
DCHECK(created) << "This should always be a new anchor, not something we've " DCHECK(created) << "This should always be a new anchor, not something we've "
"seen previously."; "seen previously.";
anchor_id_to_anchor_object_[anchor_id] = std::move(ar_anchor); anchor_id_to_anchor_info_.emplace(
anchor_id, AnchorInfo(std::move(ar_anchor), AR_TRACKING_STATE_TRACKING));
return anchor_id; return anchor_id;
} }
void ArCoreAnchorManager::DetachAnchor(AnchorId anchor_id) { void ArCoreAnchorManager::DetachAnchor(AnchorId anchor_id) {
auto it = anchor_id_to_anchor_object_.find(anchor_id); auto it = anchor_id_to_anchor_info_.find(anchor_id);
if (it == anchor_id_to_anchor_object_.end()) { if (it == anchor_id_to_anchor_info_.end()) {
return; return;
} }
ArAnchor_detach(arcore_session_, it->second.get()); ArAnchor_detach(arcore_session_, it->second.anchor.get());
anchor_id_to_anchor_object_.erase(it); anchor_id_to_anchor_info_.erase(it);
} }
bool ArCoreAnchorManager::AnchorExists(AnchorId id) const { bool ArCoreAnchorManager::AnchorExists(AnchorId id) const {
return base::Contains(anchor_id_to_anchor_object_, id); return base::Contains(anchor_id_to_anchor_info_, id);
} }
base::Optional<gfx::Transform> ArCoreAnchorManager::GetMojoFromAnchor( base::Optional<gfx::Transform> ArCoreAnchorManager::GetMojoFromAnchor(
AnchorId id) const { AnchorId id) const {
auto it = anchor_id_to_anchor_object_.find(id); auto it = anchor_id_to_anchor_info_.find(id);
if (it == anchor_id_to_anchor_object_.end()) { if (it == anchor_id_to_anchor_info_.end()) {
return base::nullopt; return base::nullopt;
} }
ArAnchor_getPose(arcore_session_, it->second.get(), ar_pose_.get()); ArAnchor_getPose(arcore_session_, it->second.anchor.get(), ar_pose_.get());
mojom::Pose mojo_pose = mojom::Pose mojo_pose =
GetMojomPoseFromArPose(arcore_session_, ar_pose_.get()); GetMojomPoseFromArPose(arcore_session_, ar_pose_.get());
return mojo::ConvertTo<gfx::Transform>(mojo_pose); return mojo::ConvertTo<gfx::Transform>(mojo_pose);
} }
ArCoreAnchorManager::AnchorInfo::AnchorInfo(
device::internal::ScopedArCoreObject<ArAnchor*> anchor,
ArTrackingState tracking_state)
: anchor(std::move(anchor)), tracking_state(tracking_state) {}
ArCoreAnchorManager::AnchorInfo::AnchorInfo(AnchorInfo&& other) = default;
ArCoreAnchorManager::AnchorInfo::~AnchorInfo() = default;
} // namespace device } // namespace device
...@@ -49,9 +49,20 @@ class ArCoreAnchorManager { ...@@ -49,9 +49,20 @@ class ArCoreAnchorManager {
void DetachAnchor(AnchorId anchor_id); void DetachAnchor(AnchorId anchor_id);
private: private:
struct AnchorInfo {
device::internal::ScopedArCoreObject<ArAnchor*> anchor;
ArTrackingState tracking_state;
AnchorInfo(device::internal::ScopedArCoreObject<ArAnchor*> anchor,
ArTrackingState tracking_state);
AnchorInfo(AnchorInfo&& other);
~AnchorInfo();
};
// Executes |fn| for each still tracked, anchor present in |arcore_anchors|. // Executes |fn| for each still tracked, anchor present in |arcore_anchors|.
// |fn| will receive a `device::internal::ScopedArCoreObject<ArAnchor*>` that // |fn| will receive a `device::internal::ScopedArCoreObject<ArAnchor*>` that
// can be stored, as well as ArTrackingState of the passed in anchor. // can be stored, as well as ArTrackingState of the passed in anchor. An
// anchor is tracked if its state is not AR_TRACKING_STATE_STOPPED.
template <typename FunctionType> template <typename FunctionType>
void ForEachArCoreAnchor(ArAnchorList* arcore_anchors, FunctionType fn); void ForEachArCoreAnchor(ArAnchorList* arcore_anchors, FunctionType fn);
...@@ -72,10 +83,9 @@ class ArCoreAnchorManager { ...@@ -72,10 +83,9 @@ class ArCoreAnchorManager {
// Mapping from anchor address to anchor ID. It should be modified only during // Mapping from anchor address to anchor ID. It should be modified only during
// calls to |Update()| and anchor creation. // calls to |Update()| and anchor creation.
std::map<void*, AnchorId> ar_anchor_address_to_id_; std::map<void*, AnchorId> ar_anchor_address_to_id_;
// Mapping from anchor ID to ARCore anchor object. It should be modified only // Mapping from anchor ID to ARCore anchor information. It should be modified
// during calls to |Update()|. // only during calls to |Update()|.
std::map<AnchorId, device::internal::ScopedArCoreObject<ArAnchor*>> std::map<AnchorId, AnchorInfo> anchor_id_to_anchor_info_;
anchor_id_to_anchor_object_;
// Set containing IDs of anchors updated in the last frame. It should be // Set containing IDs of anchors updated in the last frame. It should be
// modified only during calls to |Update()|. // modified only during calls to |Update()|.
std::set<AnchorId> updated_anchor_ids_; std::set<AnchorId> updated_anchor_ids_;
......
...@@ -90,7 +90,7 @@ void ArCorePlaneManager::ForEachArCorePlane(ArTrackableList* arcore_planes, ...@@ -90,7 +90,7 @@ void ArCorePlaneManager::ForEachArCorePlane(ArTrackableList* arcore_planes,
ArTrackable_getTrackingState(arcore_session_, trackable.get(), ArTrackable_getTrackingState(arcore_session_, trackable.get(),
&tracking_state); &tracking_state);
if (tracking_state != ArTrackingState::AR_TRACKING_STATE_TRACKING) { if (tracking_state == ArTrackingState::AR_TRACKING_STATE_STOPPED) {
// Skip all planes that are not currently tracked. // Skip all planes that are not currently tracked.
continue; continue;
} }
...@@ -146,7 +146,7 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) { ...@@ -146,7 +146,7 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) {
bool created; bool created;
std::tie(plane_id, created) = CreateOrGetPlaneId(ar_plane); std::tie(plane_id, created) = CreateOrGetPlaneId(ar_plane);
DVLOG(3) << "Previously detected plane found, id=" << plane_id DVLOG(3) << "Previously detected plane found, plane_id=" << plane_id
<< ", created?=" << created << ", created?=" << created
<< ", tracking_state=" << tracking_state; << ", tracking_state=" << tracking_state;
...@@ -163,11 +163,10 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) { ...@@ -163,11 +163,10 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) {
// Collect the objects of all currently tracked planes. // Collect the objects of all currently tracked planes.
// |ar_plane_address_to_id_| should *not* grow. // |ar_plane_address_to_id_| should *not* grow.
std::map<PlaneId, device::internal::ScopedArCoreObject<ArTrackable*>> std::map<PlaneId, PlaneInfo> new_plane_id_to_plane_info;
plane_id_to_plane_object;
ForEachArCorePlane( ForEachArCorePlane(
arcore_planes_.get(), arcore_planes_.get(),
[this, &plane_id_to_plane_object]( [this, &new_plane_id_to_plane_info, &updated_plane_ids](
internal::ScopedArCoreObject<ArTrackable*> trackable, internal::ScopedArCoreObject<ArTrackable*> trackable,
ArPlane* ar_plane, ArTrackingState tracking_state) { ArPlane* ar_plane, ArTrackingState tracking_state) {
// ID // ID
...@@ -176,32 +175,43 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) { ...@@ -176,32 +175,43 @@ void ArCorePlaneManager::Update(ArFrame* ar_frame) {
std::tie(plane_id, created) = CreateOrGetPlaneId(ar_plane); std::tie(plane_id, created) = CreateOrGetPlaneId(ar_plane);
DCHECK(!created) DCHECK(!created)
<< "Newly detected planes should already be handled - new plane id=" << "Newly detected planes should already be handled - new plane_id="
<< plane_id; << plane_id;
plane_id_to_plane_object[plane_id] = std::move(trackable); // Inspect the tracking state of this plane in the previous frame. If it
// changed, mark the plane as updated.
if (base::Contains(plane_id_to_plane_info_, plane_id) &&
plane_id_to_plane_info_.at(plane_id).tracking_state !=
tracking_state) {
updated_plane_ids.insert(plane_id);
}
PlaneInfo new_plane_info =
PlaneInfo(std::move(trackable), tracking_state);
new_plane_id_to_plane_info.emplace(plane_id, std::move(new_plane_info));
}); });
DVLOG(3) << __func__ << ": plane_id_to_plane_object.size()=" DVLOG(3) << __func__ << ": new_plane_id_to_plane_info.size()="
<< plane_id_to_plane_object.size(); << new_plane_id_to_plane_info.size();
// Shrink |ar_plane_address_to_id_|, removing all planes that are no longer // Shrink |ar_plane_address_to_id_|, removing all planes that are no longer
// tracked or were subsumed - if they do not show up in // tracked or were subsumed - if they do not show up in
// |plane_id_to_plane_object| map, they are no longer tracked. // |new_plane_id_to_plane_info| map, they are no longer tracked.
base::EraseIf(ar_plane_address_to_id_, base::EraseIf(ar_plane_address_to_id_, [&new_plane_id_to_plane_info](
[&plane_id_to_plane_object](const auto& plane_address_and_id) { const auto& plane_address_and_id) {
return !base::Contains(plane_id_to_plane_object, return !base::Contains(new_plane_id_to_plane_info,
plane_address_and_id.second); plane_address_and_id.second);
}); });
plane_id_to_plane_object_.swap(plane_id_to_plane_object); plane_id_to_plane_info_.swap(new_plane_id_to_plane_info);
updated_plane_ids_.swap(updated_plane_ids); updated_plane_ids_.swap(updated_plane_ids);
} }
mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData() mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData()
const { const {
std::vector<uint64_t> all_plane_ids; std::vector<uint64_t> all_plane_ids;
all_plane_ids.reserve(plane_id_to_plane_object_.size()); all_plane_ids.reserve(plane_id_to_plane_info_.size());
for (const auto& plane_id_and_object : plane_id_to_plane_object_) { for (const auto& plane_id_and_object : plane_id_to_plane_info_) {
all_plane_ids.push_back(plane_id_and_object.first.GetUnsafeValue()); all_plane_ids.push_back(plane_id_and_object.first.GetUnsafeValue());
} }
...@@ -209,17 +219,20 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData() ...@@ -209,17 +219,20 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData()
updated_planes.reserve(updated_plane_ids_.size()); updated_planes.reserve(updated_plane_ids_.size());
for (const auto& plane_id : updated_plane_ids_) { for (const auto& plane_id : updated_plane_ids_) {
const device::internal::ScopedArCoreObject<ArTrackable*>& trackable = const device::internal::ScopedArCoreObject<ArTrackable*>& trackable =
plane_id_to_plane_object_.at(plane_id); plane_id_to_plane_info_.at(plane_id).plane;
const ArPlane* ar_plane = ArAsPlane(trackable.get()); const ArPlane* ar_plane = ArAsPlane(trackable.get());
if (plane_id_to_plane_info_.at(plane_id).tracking_state ==
AR_TRACKING_STATE_TRACKING) {
// orientation // orientation
ArPlaneType plane_type; ArPlaneType plane_type;
ArPlane_getType(arcore_session_, ar_plane, &plane_type); ArPlane_getType(arcore_session_, ar_plane, &plane_type);
// pose // pose
ArPlane_getCenterPose(arcore_session_, ar_plane, ar_pose_.get()); ArPlane_getCenterPose(arcore_session_, ar_plane, ar_pose_.get());
mojom::Pose pose = GetMojomPoseFromArPose(arcore_session_, ar_pose_.get()); mojom::Pose pose =
GetMojomPoseFromArPose(arcore_session_, ar_pose_.get());
// polygon // polygon
int32_t polygon_size; int32_t polygon_size;
...@@ -238,7 +251,7 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData() ...@@ -238,7 +251,7 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData()
mojom::XRPlanePointData::New(vertices_raw[i], vertices_raw[i + 1])); mojom::XRPlanePointData::New(vertices_raw[i], vertices_raw[i + 1]));
} }
DVLOG(3) << __func__ << ": plane id: " << plane_id.GetUnsafeValue() DVLOG(3) << __func__ << ": plane_id: " << plane_id.GetUnsafeValue()
<< ", position=" << pose.position.ToString() << ", position=" << pose.position.ToString()
<< ", orientation=" << pose.orientation.ToString(); << ", orientation=" << pose.orientation.ToString();
...@@ -246,6 +259,14 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData() ...@@ -246,6 +259,14 @@ mojom::XRPlaneDetectionDataPtr ArCorePlaneManager::GetDetectedPlanesData()
plane_id.GetUnsafeValue(), plane_id.GetUnsafeValue(),
mojo::ConvertTo<device::mojom::XRPlaneOrientation>(plane_type), mojo::ConvertTo<device::mojom::XRPlaneOrientation>(plane_type),
device::mojom::Pose::New(pose), std::move(vertices))); device::mojom::Pose::New(pose), std::move(vertices)));
} else {
DVLOG(3) << __func__ << ": plane_id: " << plane_id.GetUnsafeValue()
<< ", position=untracked, orientation=untracked";
updated_planes.push_back(mojom::XRPlaneData::New(
plane_id.GetUnsafeValue(), device::mojom::XRPlaneOrientation::UNKNOWN,
nullptr, std::vector<mojom::XRPlanePointDataPtr>{}));
}
} }
return mojom::XRPlaneDetectionData::New(std::move(all_plane_ids), return mojom::XRPlaneDetectionData::New(std::move(all_plane_ids),
...@@ -279,19 +300,19 @@ base::Optional<PlaneId> ArCorePlaneManager::GetPlaneId( ...@@ -279,19 +300,19 @@ base::Optional<PlaneId> ArCorePlaneManager::GetPlaneId(
} }
bool ArCorePlaneManager::PlaneExists(PlaneId id) const { bool ArCorePlaneManager::PlaneExists(PlaneId id) const {
return base::Contains(plane_id_to_plane_object_, id); return base::Contains(plane_id_to_plane_info_, id);
} }
base::Optional<gfx::Transform> ArCorePlaneManager::GetMojoFromPlane( base::Optional<gfx::Transform> ArCorePlaneManager::GetMojoFromPlane(
PlaneId id) const { PlaneId id) const {
auto it = plane_id_to_plane_object_.find(id); auto it = plane_id_to_plane_info_.find(id);
if (it == plane_id_to_plane_object_.end()) { if (it == plane_id_to_plane_info_.end()) {
return base::nullopt; return base::nullopt;
} }
const ArPlane* plane = // Naked pointer is fine here, ArAsPlane does not increase the internal
ArAsPlane(it->second.get()); // Naked pointer is fine here, ArAsPlane // refcount:
// does not increase the internal refcount. const ArPlane* plane = ArAsPlane(it->second.plane.get());
ArPlane_getCenterPose(arcore_session_, plane, ar_pose_.get()); ArPlane_getCenterPose(arcore_session_, plane, ar_pose_.get());
mojom::Pose mojo_pose = mojom::Pose mojo_pose =
...@@ -304,8 +325,8 @@ device::internal::ScopedArCoreObject<ArAnchor*> ...@@ -304,8 +325,8 @@ device::internal::ScopedArCoreObject<ArAnchor*>
ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key, ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key,
PlaneId id, PlaneId id,
const device::mojom::Pose& pose) const { const device::mojom::Pose& pose) const {
auto it = plane_id_to_plane_object_.find(id); auto it = plane_id_to_plane_info_.find(id);
if (it == plane_id_to_plane_object_.end()) { if (it == plane_id_to_plane_info_.end()) {
return {}; return {};
} }
...@@ -313,7 +334,7 @@ ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key, ...@@ -313,7 +334,7 @@ ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key,
device::internal::ScopedArCoreObject<ArAnchor*> ar_anchor; device::internal::ScopedArCoreObject<ArAnchor*> ar_anchor;
ArStatus status = ArTrackable_acquireNewAnchor( ArStatus status = ArTrackable_acquireNewAnchor(
arcore_session_, it->second.get(), ar_pose.get(), arcore_session_, it->second.plane.get(), ar_pose.get(),
device::internal::ScopedArCoreObject<ArAnchor*>::Receiver(ar_anchor) device::internal::ScopedArCoreObject<ArAnchor*>::Receiver(ar_anchor)
.get()); .get());
...@@ -324,4 +345,12 @@ ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key, ...@@ -324,4 +345,12 @@ ArCorePlaneManager::CreateAnchor(util::PassKey<ArCoreAnchorManager> pass_key,
return ar_anchor; return ar_anchor;
} }
ArCorePlaneManager::PlaneInfo::PlaneInfo(
device::internal::ScopedArCoreObject<ArTrackable*> plane,
ArTrackingState tracking_state)
: plane(std::move(plane)), tracking_state(tracking_state) {}
ArCorePlaneManager::PlaneInfo::PlaneInfo(PlaneInfo&& other) = default;
ArCorePlaneManager::PlaneInfo::~PlaneInfo() = default;
} // namespace device } // namespace device
...@@ -63,10 +63,21 @@ class ArCorePlaneManager { ...@@ -63,10 +63,21 @@ class ArCorePlaneManager {
const device::mojom::Pose& pose) const; const device::mojom::Pose& pose) const;
private: private:
struct PlaneInfo {
device::internal::ScopedArCoreObject<ArTrackable*> plane;
ArTrackingState tracking_state;
PlaneInfo(device::internal::ScopedArCoreObject<ArTrackable*> plane,
ArTrackingState tracking_state);
PlaneInfo(PlaneInfo&& other);
~PlaneInfo();
};
// Executes |fn| for each still tracked, non-subsumed plane present in // Executes |fn| for each still tracked, non-subsumed plane present in
// |arcore_planes|. |fn| will receive 3 parameters - a // |arcore_planes|. |fn| will receive 3 parameters - a
// `ScopedArCoreObject<ArAnchor*>` that can be stored, the non-owning ArPlane* // `ScopedArCoreObject<ArAnchor*>` that can be stored, the non-owning ArPlane*
// typecast from the first parameter, and ArTrackingState. // typecast from the first parameter, and ArTrackingState. A plane is tracked
// if its state is not AR_TRACKING_STATE_STOPPED.
template <typename FunctionType> template <typename FunctionType>
void ForEachArCorePlane(ArTrackableList* arcore_planes, FunctionType fn); void ForEachArCorePlane(ArTrackableList* arcore_planes, FunctionType fn);
...@@ -89,10 +100,9 @@ class ArCorePlaneManager { ...@@ -89,10 +100,9 @@ class ArCorePlaneManager {
// Mapping from plane address to plane ID. It should be modified only during // Mapping from plane address to plane ID. It should be modified only during
// calls to |Update()|. // calls to |Update()|.
std::map<void*, PlaneId> ar_plane_address_to_id_; std::map<void*, PlaneId> ar_plane_address_to_id_;
// Mapping from plane ID to ARCore plane object. It should be modified only // Mapping from plane ID to ARCore plane information. It should be modified
// during calls to |Update()|. // only during calls to |Update()|.
std::map<PlaneId, device::internal::ScopedArCoreObject<ArTrackable*>> std::map<PlaneId, PlaneInfo> plane_id_to_plane_info_;
plane_id_to_plane_object_;
// Set containing IDs of planes updated in the last frame. It should be // Set containing IDs of planes updated in the last frame. It should be
// modified only during calls to |Update()|. // modified only during calls to |Update()|.
std::set<PlaneId> updated_plane_ids_; std::set<PlaneId> updated_plane_ids_;
......
...@@ -18,7 +18,7 @@ import "ui/gfx/mojom/transform.mojom"; ...@@ -18,7 +18,7 @@ import "ui/gfx/mojom/transform.mojom";
// //
// Note on terminology: unless otherwise noted, all poses passed across mojo are // Note on terminology: unless otherwise noted, all poses passed across mojo are
// expressed in device space, aka mojo space. // expressed in device space, aka mojo space, aka world space.
// TODO(https://crbug.com/966099): Use EnableIf to only define values on // TODO(https://crbug.com/966099): Use EnableIf to only define values on
// platforms that have implementations. // platforms that have implementations.
...@@ -376,7 +376,9 @@ struct XRPlaneData { ...@@ -376,7 +376,9 @@ struct XRPlaneData {
// Pose of the plane's center. Defines new coordinate space. // Pose of the plane's center. Defines new coordinate space.
// Y axis of the coordinate space describes plane's normal, the rotation of // Y axis of the coordinate space describes plane's normal, the rotation of
// X and Z around the Y axis is arbitrary. // X and Z around the Y axis is arbitrary.
Pose pose; // Null if the device does not know where the plane is located in the world
// space (tracking loss), but the tracking can still be recovered.
Pose? mojo_from_plane;
// Vertices of 2D convex polygon approximating the plane. // Vertices of 2D convex polygon approximating the plane.
array<XRPlanePointData> polygon; array<XRPlanePointData> polygon;
...@@ -405,8 +407,10 @@ struct XRAnchorData { ...@@ -405,8 +407,10 @@ struct XRAnchorData {
// Unique (within a session) identifier of the anchor. // Unique (within a session) identifier of the anchor.
uint64 id; uint64 id;
// Pose of the anchor. // Pose of the anchor. Null if the device does not know where the anchor is
Pose pose; // located in the world space (tracking loss), but the tracking can still be
// recovered.
Pose? mojo_from_anchor;
}; };
// Struct containing information about all tracked & updated anchors in a given // Struct containing information about all tracked & updated anchors in a given
......
...@@ -14,18 +14,20 @@ XRAnchor::XRAnchor(uint64_t id, ...@@ -14,18 +14,20 @@ XRAnchor::XRAnchor(uint64_t id,
XRSession* session, XRSession* session,
const device::mojom::blink::XRAnchorData& anchor_data) const device::mojom::blink::XRAnchorData& anchor_data)
: id_(id), session_(session) { : id_(id), session_(session) {
// No need for else - if pose is not present, the default-constructed unique // No need for else - if mojo_from_anchor is not present, the
// ptr is fine. // default-constructed unique ptr is fine. It would signify that the anchor
if (anchor_data.pose) { // exists and is tracked by the underlying system, but its current location is
SetMojoFromAnchor( // unknown.
mojo::ConvertTo<blink::TransformationMatrix>(anchor_data.pose)); if (anchor_data.mojo_from_anchor) {
SetMojoFromAnchor(mojo::ConvertTo<blink::TransformationMatrix>(
anchor_data.mojo_from_anchor));
} }
} }
void XRAnchor::Update(const device::mojom::blink::XRAnchorData& anchor_data) { void XRAnchor::Update(const device::mojom::blink::XRAnchorData& anchor_data) {
if (anchor_data.pose) { if (anchor_data.mojo_from_anchor) {
SetMojoFromAnchor( SetMojoFromAnchor(mojo::ConvertTo<blink::TransformationMatrix>(
mojo::ConvertTo<blink::TransformationMatrix>(anchor_data.pose)); anchor_data.mojo_from_anchor));
} else { } else {
mojo_from_anchor_ = nullptr; mojo_from_anchor_ = nullptr;
} }
...@@ -36,8 +38,6 @@ uint64_t XRAnchor::id() const { ...@@ -36,8 +38,6 @@ uint64_t XRAnchor::id() const {
} }
XRSpace* XRAnchor::anchorSpace() const { XRSpace* XRAnchor::anchorSpace() const {
DCHECK(mojo_from_anchor_);
if (!anchor_space_) { if (!anchor_space_) {
anchor_space_ = anchor_space_ =
MakeGarbageCollected<XRObjectSpace<XRAnchor>>(session_, this); MakeGarbageCollected<XRObjectSpace<XRAnchor>>(session_, this);
......
...@@ -44,12 +44,8 @@ class XRAnchor : public ScriptWrappable { ...@@ -44,12 +44,8 @@ class XRAnchor : public ScriptWrappable {
Member<XRSession> session_; Member<XRSession> session_;
// |mojo_from_anchor_| will be non-null in an XRAnchor after the anchor was // Anchor's pose in device (mojo) space. Nullptr if the pose of the anchor is
// updated for the first time - this *must* happen in the same frame in which // unknown in the current frame.
// the anchor was created for the anchor to be fully usable. It is currently
// ensured by XRSession - anchors that got created prior to receiving the
// result from mojo call to GetFrameData are not returned to the application
// until their poses are known.
std::unique_ptr<TransformationMatrix> mojo_from_anchor_; std::unique_ptr<TransformationMatrix> mojo_from_anchor_;
// Cached anchor space - it will be created by `anchorSpace()` if it's not // Cached anchor space - it will be created by `anchorSpace()` if it's not
......
...@@ -13,8 +13,14 @@ namespace blink { ...@@ -13,8 +13,14 @@ namespace blink {
class XRSession; class XRSession;
// Helper class that returns an XRSpace that tracks the position of object of // Helper class that returns an XRSpace that tracks the position of object of
// type T (for example XRPlane, XRAnchor). The type T has to have a poseMatrix() // type T (for example XRPlane, XRAnchor). The type T has to have a
// method. // MojoFromObject() method, returning a base::Optional<TransformationMatrix>.
//
// If the object's MojoFromObject() method returns a base::nullopt, it means
// that the object is not localizable in the current frame (i.e. its pose is
// unknown) - the `frame.getPose(objectSpace, otherSpace)` will return null.
// That does not necessarily mean that object tracking is lost - it may be that
// the object's location will become known in subsequent frames.
template <typename T> template <typename T>
class XRObjectSpace : public XRSpace { class XRObjectSpace : public XRSpace {
public: public:
......
...@@ -24,11 +24,13 @@ XRPlane::XRPlane(uint64_t id, ...@@ -24,11 +24,13 @@ XRPlane::XRPlane(uint64_t id,
mojo::ConvertTo<HeapVector<Member<DOMPointReadOnly>>>( mojo::ConvertTo<HeapVector<Member<DOMPointReadOnly>>>(
plane_data.polygon), plane_data.polygon),
timestamp) { timestamp) {
// No need for else - if pose is not present, the default-constructed unique // No need for else - if mojo_from_plane is not present, the
// ptr is fine. // default-constructed unique ptr is fine. It would signify that the plane
if (plane_data.pose) { // exists and is tracked by the underlying system, but its current location is
SetMojoFromPlane( // unknown.
mojo::ConvertTo<blink::TransformationMatrix>(plane_data.pose)); if (plane_data.mojo_from_plane) {
SetMojoFromPlane(mojo::ConvertTo<blink::TransformationMatrix>(
plane_data.mojo_from_plane));
} }
} }
...@@ -125,9 +127,9 @@ void XRPlane::Update(const device::mojom::blink::XRPlaneData& plane_data, ...@@ -125,9 +127,9 @@ void XRPlane::Update(const device::mojom::blink::XRPlaneData& plane_data,
orientation_ = mojo::ConvertTo<base::Optional<blink::XRPlane::Orientation>>( orientation_ = mojo::ConvertTo<base::Optional<blink::XRPlane::Orientation>>(
plane_data.orientation); plane_data.orientation);
if (plane_data.pose) { if (plane_data.mojo_from_plane) {
SetMojoFromPlane( SetMojoFromPlane(mojo::ConvertTo<blink::TransformationMatrix>(
mojo::ConvertTo<blink::TransformationMatrix>(plane_data.pose)); plane_data.mojo_from_plane));
} else { } else {
mojo_from_plane_ = nullptr; mojo_from_plane_ = nullptr;
} }
......
...@@ -68,7 +68,8 @@ class XRPlane : public ScriptWrappable { ...@@ -68,7 +68,8 @@ class XRPlane : public ScriptWrappable {
HeapVector<Member<DOMPointReadOnly>> polygon_; HeapVector<Member<DOMPointReadOnly>> polygon_;
base::Optional<Orientation> orientation_; base::Optional<Orientation> orientation_;
// Plane center's pose in device (mojo) space. // Plane center's pose in device (mojo) space. Nullptr if the pose of the
// anchor is unknown in the current frame.
std::unique_ptr<TransformationMatrix> mojo_from_plane_; std::unique_ptr<TransformationMatrix> mojo_from_plane_;
Member<XRSession> session_; Member<XRSession> session_;
......
...@@ -188,6 +188,10 @@ export class PlaneNode extends Node { ...@@ -188,6 +188,10 @@ export class PlaneNode extends Node {
if(this.polygon) if(this.polygon)
throw new Error(`Polygon is set on a plane where it shouldn't be!`); throw new Error(`Polygon is set on a plane where it shouldn't be!`);
if(polygon.length === 0) {
return Promise.resolve();
}
this.createPlanePrimitive(polygon); this.createPlanePrimitive(polygon);
// eagerly clean up render primitive's VAO // eagerly clean up render primitive's VAO
......
...@@ -119,6 +119,10 @@ let calculateHitMatrix = function(ray_vector, plane_normal, point) { ...@@ -119,6 +119,10 @@ let calculateHitMatrix = function(ray_vector, plane_normal, point) {
// single plane hit test - doesn't take into account the plane's polygon // single plane hit test - doesn't take into account the plane's polygon
function hitTestPlane(frame, ray, plane, frameOfReference) { function hitTestPlane(frame, ray, plane, frameOfReference) {
const plane_pose = frame.getPose(plane.planeSpace, frameOfReference); const plane_pose = frame.getPose(plane.planeSpace, frameOfReference);
if(!plane_pose) {
return null;
}
const plane_normal = transform_point_by_matrix( const plane_normal = transform_point_by_matrix(
plane_pose.transform.matrix, {x : 0, y : 1.0, z : 0, w : 0}); plane_pose.transform.matrix, {x : 0, y : 1.0, z : 0, w : 0});
const plane_center = normalize_perspective( const plane_center = normalize_perspective(
......
...@@ -431,8 +431,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -431,8 +431,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}); });
tracked_anchors.forEach(anchor => { tracked_anchors.forEach(anchor => {
anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix; const anchorPose = frame.getPose(anchor.anchorSpace, xrRefSpace);
if(anchorPose) {
anchor.context.sceneObject.matrix = anchorPose.transform.matrix;
anchor.context.sceneObject.visible = true; anchor.context.sceneObject.visible = true;
} else {
anchor.context.sceneObject.visible = false;
}
}); });
all_previous_anchors = tracked_anchors; all_previous_anchors = tracked_anchors;
......
...@@ -261,8 +261,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -261,8 +261,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}); });
tracked_anchors.forEach(anchor => { tracked_anchors.forEach(anchor => {
anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix; const anchorPose = frame.getPose(anchor.anchorSpace, xrRefSpace);
if(anchorPose) {
anchor.context.sceneObject.matrix = anchorPose.transform.matrix;
anchor.context.sceneObject.visible = true; anchor.context.sceneObject.visible = true;
} else {
anchor.context.sceneObject.visible = false;
}
}); });
all_previous_anchors = tracked_anchors; all_previous_anchors = tracked_anchors;
...@@ -274,8 +279,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -274,8 +279,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
all_previous_anchors = new Set(); all_previous_anchors = new Set();
} }
if(xrAnchor) { if (xrAnchor) {
const anchorPose = frame.getPose(xrAnchor.anchorSpace, xrRefSpace); const anchorPose = frame.getPose(xrAnchor.anchorSpace, xrRefSpace);
if (anchorPose) {
const pos = anchorPose.transform.position; const pos = anchorPose.transform.position;
const rot = anchorPose.transform.orientation; const rot = anchorPose.transform.orientation;
...@@ -293,6 +299,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -293,6 +299,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
anchorDriftElement.innerText = drift.toFixed(3) + "m"; anchorDriftElement.innerText = drift.toFixed(3) + "m";
console.debug(pos, rot, drift); console.debug(pos, rot, drift);
} else {
anchorPoseElement.innerText = "Anchor pose null.";
anchorDriftElement.innerText = "Anchor pose null.";
}
} else if(anchorRemoved) { } else if(anchorRemoved) {
anchorPoseElement.innerText = "Anchor already gone."; anchorPoseElement.innerText = "Anchor already gone.";
anchorDriftElement.innerText = "Anchor already gone."; anchorDriftElement.innerText = "Anchor already gone.";
......
...@@ -445,10 +445,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -445,10 +445,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
planeFrameOfReference.addNode(zRay); planeFrameOfReference.addNode(zRay);
plane_node.addNode(planeFrameOfReference); plane_node.addNode(planeFrameOfReference);
plane.scene_node = plane_node; plane.scene_node = plane_node;
plane.scene_node.matrix = frame.getPose(plane.planeSpace, xrRefSpace).transform.matrix;
const planePose = frame.getPose(plane.planeSpace, xrRefSpace);
if (planePose) {
plane.scene_node.matrix = planePose.transform.matrix;
plane.scene_node.visible = true;
} else {
plane.scene_node.visible = false;
}
plane.extended_polygon = extendPolygon(plane.polygon); plane.extended_polygon = extendPolygon(plane.polygon);
plane.extended_polygon_node = new PlaneNode({ plane.extended_polygon_node = new PlaneNode({
polygon : plane.extended_polygon, polygon : plane.extended_polygon,
...@@ -464,7 +469,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -464,7 +469,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{ {
// old plane that was updated in current frame // old plane that was updated in current frame
plane.scene_node.onPlaneChanged(plane.polygon); plane.scene_node.onPlaneChanged(plane.polygon);
plane.scene_node.matrix = frame.getPose(plane.planeSpace, xrRefSpace).transform.matrix; const planePose = frame.getPose(plane.planeSpace, xrRefSpace);
if (planePose) {
plane.scene_node.matrix = planePose.transform.matrix;
plane.scene_node.visible = true;
} else {
plane.scene_node.visible = false;
}
plane.extended_polygon = extendPolygon(plane.polygon); plane.extended_polygon = extendPolygon(plane.polygon);
plane.extended_polygon_node.onPlaneChanged(plane.extended_polygon); plane.extended_polygon_node.onPlaneChanged(plane.extended_polygon);
} }
...@@ -526,8 +537,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -526,8 +537,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}); });
tracked_anchors.forEach(anchor => { tracked_anchors.forEach(anchor => {
anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix; const anchorPose = frame.getPose(anchor.anchorSpace, xrRefSpace);
if (anchorPose) {
anchor.context.sceneObject.matrix = anchorPose.transform.matrix;
anchor.context.sceneObject.visible = true; anchor.context.sceneObject.visible = true;
} else {
anchor.context.sceneObject.visible = false;
}
}); });
all_previous_anchors = tracked_anchors; all_previous_anchors = tracked_anchors;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment