Persistent anchors
This page is a supplement to the AR Foundation Persistent Anchors manual. The following sections only contain information about APIs where ARCore exhibits unique platform-specific behavior.
Tip
When developing an AR app, refer to both the AR Foundation documentation as well as the required packages for each platform you support.
Persistent anchors for ARCore utilizes Google Cloud. To use persistent anchor functionality, you must first set up a Google Cloud project to store persistent anchor data. ARCore persistent anchors are subject to Usage quotas (Google's ARCore documentation).
When working with persistent anchors, refer to ARCore's Best practices.
Check for persistent anchor support
You can use the following code to check whether your system supports persistent anchors:
void CheckPersistentAnchorsSupport(ARAnchorManager manager)
{
var descriptor = manager.descriptor;
if (descriptor.supportsSaveAnchor && descriptor.supportsLoadAnchor)
{
// Save and load anchors are both supported and enabled.
}
}
Configure your project to use persistent anchors
To use persistent anchors on ARCore, you must first authorize your application so that it can save anchor data to Google Cloud. Refer to the ARCore Use the ARCore API on Google Cloud documentation to understand how to set up authorization for your project.
Once you have authorized your application, you can then set up your project to use persistent anchors. To configure your project to use persistent anchors:
- Follow Google's instructions to Create a Google Cloud project and enable ARCore APIs.
- In the Google ARCore settings of XR Plug-in Management, select Enable Cloud Anchors.
- Select the Authorization Type that matches the authorization you set up for your project on Google Cloud.
- If you used the API Key method, enter the API Key.
- If you used the Keyless method, follow the additional steps in Configure Keyless authentication.
ARCore Cloud Anchor settings.
Configure Keyless authentication
To configure your project to use persistent anchors using Keyless authentication, follow these additional steps:
- Navigate to the Player Settings (menu: Edit > Project Settings > Player) and in the Android tab, expand Publishing Settings.
- Under Publishing Settings, enable Custom Keystore.
- Use the Keystore Manager to create a new keystore as outlined in Create a new keystore.
- Establish appropriate OAuth2 credentials as outlined in the Create OAuth 2.0 client IDs section of Google's ARCore documentation. Ensure that the package name, and the keystore SHA1 fingerprint are exact, with no extra spaces or characters in the OAuth credentials setup fields.
- Use the command line tool to determine the fingerprint for OAuth credentials, as outlined in the How to obtain a signing fingerprint from a keystore section of Google's ARCore documentation.
- In Publishing Settings of Player Settings (menu: Edit > Project Settings > Player), select Custom Main Gradle Template.
- Modify the custom main gradle asset to add the dependent libraries from the Include required libraries section of Google's ARCore documentation into the appropriate dependencies block within gradle file.
- If Minify is selected, follow the steps within Google's ARCore documentation to create a proguard file, and add the ARCore auth-related packages to be kept in that file. In Publishing Settings of Player Settings, select Custom Proguard File and modify the asset with the keep lines.
Feature map quality
Feature map quality describes the quality of the visual features surrounding the anchor before it's saved. A higher quality indicates an anchor will yield greater success when trying to load it later.
Check feature map quality
Before saving an anchor, you should check the ArFeatureMapQuality is sufficient. To check the feature map quality, you can call EstimateFeatureMapQualityForHosting.
Use the following code sample to check the ArFeatureMapQuality
:
void CheckQualityAndSaveAnchor(ARAnchorManager manager, ARAnchor anchor)
{
if (manager.subsystem is ARCoreAnchorSubsystem arCoreAnchorSubsystem)
{
var quality = ArFeatureMapQuality.AR_FEATURE_MAP_QUALITY_SUFFICIENT;
XRResultStatus resultStatus = arCoreAnchorSubsystem.EstimateFeatureMapQualityForHosting(anchor.trackableId, ref quality);
if (!resultStatus.IsSuccess())
{
// An error occurred while attempting to check the feature map quality.
return;
}
if (quality == ArFeatureMapQuality.AR_FEATURE_MAP_QUALITY_INSUFFICIENT)
{
// Anchor map quality is insufficient. Save the anchor when the quality improves.
return;
}
}
// Proceed with saving the anchor
}
Improve feature map quality
If the quality of the feature map is insufficient, prompt the user to move their device around to capture the anchor from different angles, and then try again.
Anchor lifespan
Persistent anchors stored on Google Cloud have an expiration. The anchors will only persist for as long as the provided lifespan. You can specify the lifespan of the anchor in TrySaveAnchorWithLifespanAsync. If you call TrySaveAnchorAsync, AR Foundation uses a default lifespan, as this method doesn't take a lifespan parameter. The default lifespans used are 1
day when using an API Key and 365
days when using keyless authorization.
The following code demonstrates how to use TrySaveAnchorWithLifeSpanAsync
to set the anchor's lifespan:
async void TrySaveAnchorWithLifespanAsync(ARAnchorManager manager, ARAnchor anchor)
{
if (manager.subsystem is ARCoreAnchorSubsystem arCoreAnchorSubsystem)
{
// Save the anchor for 180 days
var result = await arCoreAnchorSubsystem.TrySaveAnchorWithLifespanAsync(anchor.trackableId, 180);
if (result.status.IsError())
{
// handle error
return;
}
// Save this value, then use it as an input parameter
// to TryLoadAnchorAsync or TryEraseAnchorAsync
SerializableGuid guid = result.value;
}
}
Native status code
The native status code for a save or load operation should be interpreted as an ArCloudAnchorState, unless if the operation is cancelled, in which case the native status code is an ArFutureState.
The following code demonstrates how to check the native status code when the native status code is an ArCloudAnchorState
:
async void LoadAndCheckNativeStatusCode(ARAnchorManager manager, SerializableGuid anchorId)
{
var result = await manager.TryLoadAnchorAsync(anchorId);
// Interpreting the status code
var cloudAnchorState = (ArCloudAnchorState)result.status.nativeStatusCode;
switch (cloudAnchorState)
{
case ArCloudAnchorState.AR_CLOUD_ANCHOR_STATE_SUCCESS:
// Load was successful
break;
case ArCloudAnchorState.AR_CLOUD_ANCHOR_STATE_ERROR_NOT_AUTHORIZED:
// Authorization to Google Cloud failed.
// As a developer, ensure that you have a Google Cloud project and that you have
// authorized your application with an API Key or with Keyless authorization.
break;
case ArCloudAnchorState.AR_CLOUD_ANCHOR_STATE_ERROR_RESOURCE_EXHAUSTED:
// Google Cloud resource exhausted. Ensure that your Google Cloud project has enough
// resources to support your application's needs.
break;
case ArCloudAnchorState.AR_CLOUD_ANCHOR_STATE_ERROR_CLOUD_ID_NOT_FOUND:
// Anchor was not found. You may have specified the wrong anchor ID, or the anchor
// may have expired on the server.
break;
default:
break;
}
}
The following code demonstrates how to check the native status code when the native status code is an ArFutureState
:
async void CancelAndCheckNativeStatusCode(ARAnchorManager manager, SerializableGuid anchorId)
{
// Create a CancellationTokenSource to serve our CancellationToken
var cts = new CancellationTokenSource();
// Try to load an anchor
var awaitable = manager.TryLoadAnchorAsync(anchorId, cts.Token);
// Cancel the async operation before it completes
cts.Cancel();
// Wait for and obtain the result from TryLoadAnchorAsync
var result = await awaitable;
// Interpreting the status code.
// The nativeStatusCode is an ArFutureState because the operation was cancelled.
var futureState = (ArFutureState)result.status.nativeStatusCode;
switch (futureState)
{
case ArFutureState.AR_FUTURE_STATE_DONE:
// The operation is complete and the result is available.
break;
case ArFutureState.AR_FUTURE_STATE_CANCELLED:
// The operation has been cancelled.
break;
case ArFutureState.AR_FUTURE_STATE_PENDING:
// The operation is still pending.
break;
default:
break;
}
}