Use augmented reality (AR) to pin a scene to a table or desk for easy exploration.
Use case
Tabletop scenes allow you to use your device to interact with scenes as if they are 3D-printed model models sitting on your desk. You could use this to virtually explore a proposed development without needing to create a physical model.
How to use the sample
You'll see a feed from the camera when you open the sample. Tap on any flat, horizontal surface (like a desk or table) to place the scene. With the scene placed, you can move the camera around the scene to explore. You can also pan and zoom with touch to adjust the position of the scene.
How it works
- Create an
ArcGISArView
and add it to the view.- Note: this sample uses content in the WGS 84 geographic tiling scheme, rather than the web mercator tiling scheme. Once a scene has been displayed, the scene view cannot display another scene with a non-matching tiling scheme. To avoid that, the sample starts by showing a blank scene with an invisible base surface. Touch events will not be raised for the scene view unless a scene is displayed.
- Wait for the user to tap the view, then use
ArcGISArView::setInitialTransformation(tappedPoint)
to set the initial transformation, which allows you to place the scene. This method uses ARKit's built-in plane detection. - To enable looking at the scene from below, set
scene::baseSurface::setNavigationConstraint
toNavigationConstraint::None
. - Set the clipping distance property of the AR view. This will clip the scene to the area you want to show.
- Set the origin camera to the point in the scene where it should be anchored to the real-world surface you tapped. Typically that is the point at the center of the scene, with the altitude of the lowest point in the scene.
- Set
ArcGISArView::translationFactor
such that the user can view the entire scene by moving the device around it. The translation factor defines how far the virtual camera moves when the physical camera moves.- A good formula for determining translation factor to use in a tabletop map experience is translationFactor = sceneWidth / tableTopWidth. The scene width is the width/length of the scene content you wish to display in meters. The tabletop width is the length of the area on the physical surface that you want the scene content to fill. For simplicity, the sample assumes a scene width of 800 meters and physical size of 1 meter.
Relevant API
- ArcGISArView
- Surface
Offline data
To set up the sample's offline data, see the Use offline data in the samples section of the Qt Samples repository overview.
Link | Local Location |
---|---|
Philadelphia MSPK | <userhome> /ArcGIS/Runtime/Data/mspk/philadelphia.mspk |
About the data
This sample uses the Philadelphia Mobile Scene Package. It was chosen because it is a compact scene ideal for tabletop use. Note that tabletop mapping experiences work best with small, focused scenes. The small, focused area with basemap tiles defines a clear boundary for the scene.
Additional information
Tabletop AR is one of three main patterns for working with geographic information in augmented reality. Augmented reality is made possible with the ArcGIS Runtime Toolkit. See Augmented reality in the guide for more information about augmented reality and adding it to your app.
Clone the toolkit repo - Required for AR samples
Change directory into your locally cloned samples repo and then use git clone
to get a copy of the ArcGIS Runtime Toolkit - Qt.
# Change directory to the clone of the samples repository
# Clone the toolkit repository into the current directory in terminal
$ cd /arcgis-runtime-samples-qt
$ git clone https://github.com/Esri/arcgis-runtime-toolkit-qt.git
Cloning the toolkit in this location will allow for the samples to automatically pick it up. If you wish to place the toolkit in another location, you will need to update the samples project file accordingly to locate the necessary .pri file.
Note: Filesystem permissions is required for this sample.
For more information on the Augmented Reality (AR) toolkit, including the steps required to deploy to Android and iOS, see the AR README on GitHub.
Tags
augmented reality, drop, mixed reality, model, pin, place, table-top, tabletop
Sample Code
// [WriteFile Name=DisplayScenesInTabletopAR, Category=AR]
// [Legal]
// Copyright 2020 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]
#ifdef PCH_BUILD
#include "pch.hpp"
#endif // PCH_BUILD
#include "DisplayScenesInTabletopAR.h"
#include "ArcGISSceneLayer.h"
#include "ArcGISTiledElevationSource.h"
#include "Error.h"
#include "MobileScenePackage.h"
#include "Scene.h"
#include "SceneQuickView.h"
#include <QDir>
#include <QtCore/qglobal.h>
#ifdef Q_OS_IOS
#include <QStandardPaths>
#endif // Q_OS_IOS
using namespace Esri::ArcGISRuntime;
// helper method to get cross platform data path
namespace
{
QString defaultDataPath()
{
QString dataPath;
#ifdef Q_OS_ANDROID
dataPath = "/sdcard";
#elif defined Q_OS_IOS
dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
#else
dataPath = QDir::homePath();
#endif
return dataPath;
}
} // namespace
using namespace Esri::ArcGISRuntime;
using namespace Esri::ArcGISRuntime::Toolkit;
ArcGISArView* DisplayScenesInTabletopAR::arcGISArView() const
{
return m_arcGISArView;
}
void DisplayScenesInTabletopAR::setArcGISArView(ArcGISArView* arcGISArView)
{
if (!arcGISArView || arcGISArView == m_arcGISArView)
return;
m_arcGISArView = arcGISArView;
emit arcGISArViewChanged();
}
DisplayScenesInTabletopAR::DisplayScenesInTabletopAR(QObject* parent /* = nullptr */):
QObject(parent),
m_scene(new Scene(SceneViewTilingScheme::Geographic, this)),
m_permissionsHelper(new PermissionsHelper(this))
{
const QString dataPath = defaultDataPath() + "/ArcGIS/Runtime/Data/mspk/philadelphia.mspk";
// Connect to the Mobile Scene Package instance to know when errors occur
connect(MobileScenePackage::instance(), &MobileScenePackage::errorOccurred, [](Error e)
{
if (e.isEmpty())
return;
qDebug() << QString("Error: %1 %2").arg(e.message(), e.additionalMessage());
});
// Create the Scene Package
createScenePackage(dataPath);
}
DisplayScenesInTabletopAR::~DisplayScenesInTabletopAR() = default;
void DisplayScenesInTabletopAR::init()
{
// Register classes for QML
qmlRegisterType<SceneQuickView>("Esri.Samples", 1, 0, "SceneView");
qmlRegisterType<DisplayScenesInTabletopAR>("Esri.Samples", 1, 0, "DisplayScenesInTabletopARSample");
}
SceneQuickView* DisplayScenesInTabletopAR::sceneView() const
{
return m_sceneView;
}
// Set the view (created in QML)
void DisplayScenesInTabletopAR::setSceneView(SceneQuickView* sceneView)
{
if (!sceneView || sceneView == m_sceneView)
return;
m_sceneView = sceneView;
// Connect the mouse clicked events.
connect(m_sceneView, &SceneQuickView::mouseClicked, this, &DisplayScenesInTabletopAR::onMouseClicked);
emit sceneViewChanged();
}
// Slot for handling when the package loads
void DisplayScenesInTabletopAR::packageLoaded(const Error& e)
{
if (!e.isEmpty())
{
qDebug() << QString("Package load error: %1 %2").arg(e.message(), e.additionalMessage());
return;
}
if (m_scenePackage->scenes().isEmpty())
return;
// Get the first scene
m_scene = m_scenePackage->scenes().at(0);
// Create a camera at the bottom and center of the scene.
// This camera is the point at which the scene is pinned to the real-world surface.
m_originCamera = Camera(39.95787000283599, -75.16996728256345, 8.813445091247559, 0, 90, 0);
}
// Create scene package and connect to signals
void DisplayScenesInTabletopAR::createScenePackage(const QString& path)
{
m_scenePackage = new MobileScenePackage(path, this);
connect(m_scenePackage, &MobileScenePackage::doneLoading, this, &DisplayScenesInTabletopAR::packageLoaded);
connect(m_permissionsHelper, &PermissionsHelper::requestFilesystemAccessCompleted, this, [this]()
{
m_scenePackage->load();
});
if (!m_permissionsHelper->fileSystemAccessGranted())
m_permissionsHelper->requestFilesystemAccess();
else
m_scenePackage->load();
}
void DisplayScenesInTabletopAR::onMouseClicked(QMouseEvent& event)
{
Q_CHECK_PTR(m_arcGISArView);
if (!m_scene && !m_sceneView)
return;
// Display the scene
if (m_sceneView->arcGISScene() != m_scene)
{
m_sceneView->setArcGISScene(m_scene);
m_dialogVisible = false;
emit dialogVisibleChanged();
}
// Set the clipping distance for the scene.
m_arcGISArView->setClippingDistance(400);
// Set the surface opacity to 0.
m_arcGISArView->sceneView()->arcGISScene()->baseSurface()->setOpacity(0.0f);
// Enable subsurface navigation. This allows you to look at the scene from below.
m_sceneView->arcGISScene()->baseSurface()->setNavigationConstraint(NavigationConstraint::None);
// Set the initial transformation using the point clicked on the screen
const QPoint screenPoint = event.localPos().toPoint();
m_arcGISArView->setInitialTransformation(screenPoint);
// Set the origin camera.
m_arcGISArView->setOriginCamera(m_originCamera);
// Set the translation factor based on the scene content width and desired physical size.
m_arcGISArView->setTranslationFactor(m_sceneWidth/m_tableTopWidth);
emit sceneViewChanged();
emit arcGISArViewChanged();
}