Zelfstudie: Stapsgewijze instructies voor het maken van een nieuwe Android-app met behulp van Azure Spatial Anchors

In deze zelfstudie ontdekt u hoe u een nieuwe Android-app kunt maken die ARCore-functies combineert met Azure Spatial Anchors.

Vereisten

Het volgende moet zijn geïnstalleerd om deze zelfstudie te voltooien:

Aan de slag

Start Android Studio. Klik op Een nieuw Android Studio-project starten in het venster Welkom bij Android Studio.

  1. Selecteer Bestand-nieuw> project.
  2. Kies Lege activiteit in het venster Nieuw project maken in de sectie Telefoon en tablet en klik op Volgende.
  3. Wijzig in het venster Nieuw project - Lege activiteit de volgende waarden:
    • De naam, pakketnaam en opslaglocatie wijzigen in de gewenste waarden
    • Taal instellen is opJava
    • Minimum-API-niveau instellen opAPI 26: Android 8.0 (Oreo)
    • Laat de andere opties staan zoals ze zijn
    • Klik op Voltooien.
  4. Het installatieprogramma wordt uitgevoerd. Na enkele bewerkingen opent Android Studio de IDE.

Android Studio - New Project

Het uitproberen

Om uw nieuwe app uit te testen, verbindt u uw apparaat in ontwikkelaarsmodus met uw ontwikkelcomputer via een USB-kabel. Selecteer in de rechterbovenhoek van Android Studio uw verbonden apparaat en klik op het pictogram App uitvoeren. Android Studio installeert de app op uw verbonden apparaat en start het op. U ziet nu dat 'Hallo wereld!' wordt weergegeven in de app die op uw apparaat wordt uitgevoerd. Klik op 'App uitvoeren'.> Android Studio - Run

ARCore integreren

ARCore is het platform van Google om Augmented Reality-ervaringen te ontwikkelen. Het stelt uw apparaat in staat om de eigen positie op te volgen tijdens het bewegen om zo een eigen begrip te ontwikkelen van de echte wereld.

Wijzig app\manifests\AndroidManifest.xml zodat de volgende vermeldingen worden opgenomen in het hoofd<manifest>knooppunt. Dit codefragment doet een aantal dingen:

  • Het geeft uw app toegang tot de camera van uw apparaat.
  • Het zorgt er ook voor dat uw app in de Google Play Store enkel zichtbaar is voor apparaten die ondersteuning bieden voor ARCore.
  • Het configureert de Google Play Store om ARCore te downloaden en installeren, indien dat nog niet gebeurd is, zodra uw app is geïnstalleerd.
<manifest ...>

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.ar" />

    <application>
        ...
        <meta-data android:name="com.google.ar.core" android:value="required" />
        ...
    </application>

</manifest>

Wijzig Gradle Scripts\build.gradle (Module: app) zodat het de volgende vermelding bevat. Deze code zorgt ervoor dat uw app is gericht op ARCore versie 1.25. Na deze wijziging is het mogelijk dat u een melding ontvangt van Gradle met de vraag om te synchroniseren: klik op Nu synchroniseren.

dependencies {
    ...
    implementation 'com.google.ar:core:1.25.0'
    ...
}

Sceneform integreren

Sceneform maakt het gemakkelijker om realistische 3D-scènes weer te geven in Augmented Reality-apps, zonder dat u OpenGL moet leren.

Wijzig Gradle Scripts\build.gradle (Module: app) zodat het de volgende vermeldingen bevat. Deze code zorgt ervoor dat uw app taalconstructs van Java 8 gebruikt, wat vereist is voor Sceneform. Het zorgt er ook voor dat uw app is gericht op Sceneform versie 1.15. Na deze wijziging is het mogelijk dat u een melding ontvangt van Gradle met de vraag om te synchroniseren: klik op Nu synchroniseren.

android {
    ...

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.15.0'
    ...
}

Open uw app\res\layout\activity_main.xml en vervang het bestaande Hallo wereld-<TextView ... />element door het volgende ArFragment. Deze code zorgt ervoor dat de camerafeed wordt weergegeven op uw scherm, zodat ARCore de positie van uw apparaat kan volgen wanneer het beweegt.

<fragment android:name="com.google.ar.sceneform.ux.ArFragment"
    android:id="@+id/ux_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Notitie

Als u de onbewerkte XML van uw hoofdactiviteit wilt zien, klikt u op de knop Code of Split in de rechterbovenhoek van Android Studio.

Implementeer uw app opnieuw naar uw apparaat om het nogmaals te valideren. Ditmaal wordt u gevraagd om cameramachtigingen. Zodra u instemt, ziet u dat uw camerafeed wordt weergegeven op uw scherm.

Een object in het echt plaatsen

We gaan een object maken en plaatsen met behulp van uw app. Voeg eerst de volgende importen toe aan uw app\java\<PackageName>\MainActivity:

import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;

import android.view.MotionEvent;

Voeg vervolgens de volgende lidvariabelen toe aan uw MainActivity-klasse:

private boolean tapExecuted = false;
private final Object syncTaps = new Object();
private ArFragment arFragment;
private AnchorNode anchorNode;
private Renderable nodeRenderable = null;
private float recommendedSessionProgress = 0f;

Voeg vervolgens de volgende code toe aan uw app\java\<PackageName>\MainActivityonCreate() methode. Deze code koppelt een listener, genaamd handleTap(), die detecteert wanneer de gebruiker op het scherm van uw apparaat tikt. Als er wordt getikt op een oppervlak in de echte wereld dat al is erkend door ARCore, dan wordt de listener uitgevoerd.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
    this.arFragment.setOnTapArPlaneListener(this::handleTap);
}

Voeg ten slotte de volgende handleTap()-methode toe, dit zal alles met elkaar verbinden. Het creëert een bol en plaatst deze op de aangetikte locatie. De bol is in eerste instantie zwart, aangezien this.recommendedSessionProgress nu op zwart is ingesteld. Deze waarde wordt later aangepast.

protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());
            });
}

Implementeer uw app opnieuw naar uw apparaat om het nogmaals te valideren. Ditmaal kunt u uw apparaat bewegen zodat ARCore uw omgeving kan beginnen herkennen. Tik vervolgens op het scherm om uw zwarte bol op het gewenste oppervlak te maken en te plaatsen.

Een lokaal Azure Spatial Anchor koppelen

Wijzig Gradle Scripts\build.gradle (Module: app) zodat het de volgende vermelding bevat. Dit voorbeeldcodefragment is gericht op Azure Spatial Anchors SDK versie 2.10.2. Sdk-versie 2.7.0 is momenteel de minimaal ondersteunde versie en verwijst ook naar een recentere versie van Azure Spatial Anchors. U wordt aangeraden de nieuwste versie van de Azure Spatial Anchors SDK te gebruiken. Hier vindt u de releaseopmerkingen voor de SDK.

dependencies {
    ...
    implementation 'com.microsoft.azure.spatialanchors:spatialanchors_jni:[2.10.2]'
    implementation 'com.microsoft.azure.spatialanchors:spatialanchors_java:[2.10.2]'
    ...
}

Als u zich richt op Azure Spatial Anchors SDK 2.10.0 of hoger, neemt u de volgende vermelding op in de sectie opslagplaatsen van het settings.gradle bestand van uw project. Dit omvat de URL naar de Maven-pakketfeed die als host fungeert voor Azure Spatial Anchors Android-pakketten voor SDK 2.10.0 of hoger:

dependencyResolutionManagement {
    ...
    repositories {
        ...
        maven {
            url 'https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Maven-packages/maven/v1'
        }
        ...
    }
}

Klik met de rechtermuisknop op app\java\<PackageName>->New-Java> Class. Stel De naam in op MyFirstApp en selecteer Klasse. Er wordt een bestand met de naam MyFirstApp.java gemaakt. Voeg de volgende import toe:

import com.microsoft.CloudServices;

Definiëren android.app.Application als superklasse.

public class MyFirstApp extends android.app.Application {...

Voeg vervolgens deze code toe aan de nieuwe MyFirstApp-klasse. Dit zal ervoor zorgen dat Azure Spatial Anchors wordt geïnitialiseerd met de context van uw toepassing.

    @Override
    public void onCreate() {
        super.onCreate();
        CloudServices.initialize(this);
    }

Wijzig nu app\manifests\AndroidManifest.xml zodat de volgende vermelding wordt opgenomen in het hoofd<application>knooppunt. Deze code koppelt de Toepassingsklasse die u hebt gemaakt aan uw app.

    <application
        android:name=".MyFirstApp"
        ...
    </application>

Voeg in app\java\<PackageName>\MainActivity de volgende importen toe:

import android.view.MotionEvent;
import android.util.Log;

import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.Scene;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

Voeg vervolgens de volgende lidvariabelen toe aan uw MainActivity-klasse:

private float recommendedSessionProgress = 0f;

private ArSceneView sceneView;
private CloudSpatialAnchorSession cloudSession;
private boolean sessionInitialized = false;

Daarna voegen we de volgende initializeSession()-methode toe in uw mainActivity-klasse. Zodra deze is aangeroepen, zorgt deze ervoor dat er een Azure Spatial Anchors-sessie wordt gemaakt en op de juiste wijze wordt geïnitialiseerd tijdens het opstarten van uw app. Deze code zorgt ervoor dat de scèneview-sessie die via de cloudSession.setSession aanroep wordt doorgegeven, niet null is door vroegtijdig terug te keren.

private void initializeSession() {
    if (sceneView.getSession() == null) {
        //Early return if the ARCore Session is still being set up
        return;
    }

    if (this.cloudSession != null) {
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    sessionInitialized = true;
}

Aangezien initializeSession() dit vroeg kan worden geretourneerd als de sceneView-sessie nog niet is ingesteld (dat wil weten als sceneView.getSession() null is), voegen we een onUpdate-aanroep toe om ervoor te zorgen dat de ASA-sessie wordt geïnitialiseerd zodra de sceneView-sessie is gemaakt.

private void scene_OnUpdate(FrameTime frameTime) {
    if (!sessionInitialized) {
        //retry if initializeSession did an early return due to ARCore Session not yet available (i.e. sceneView.getSession() == null)
        initializeSession();
    }
}

Nu gaan we uw initializeSession() en scene_OnUpdate(...) methode aan uw onCreate() methode koppelen. We zorgen er ook voor dat de frames van uw camerafeed naar Azure Spacial Anchors SDK worden verzonden voor verwerking.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
    this.arFragment.setOnTapArPlaneListener(this::handleTap);

    this.sceneView = arFragment.getArSceneView();
    Scene scene = sceneView.getScene();
    scene.addOnUpdateListener(frameTime -> {
        if (this.cloudSession != null) {
            this.cloudSession.processFrame(sceneView.getArFrame());
        }
    });
    scene.addOnUpdateListener(this::scene_OnUpdate);
    initializeSession();
}

Voeg tot slot de volgende code toe aan uw handleTap()-methode. Hiermee wordt een lokale Azure Spatial Anchor gekoppeld aan de zwarte bol die we in het echt gaan plaatsen.

protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());
            });
}

Implementeer uw app nogmaals. Beweeg uw apparaat, tik op het scherm en plaats een zwarte bol. Ditmaal zal uw code een lokale Azure Spatial Anchor maken en aan uw bol koppelen.

Voordat u verder gaat, moet u een Azure Spatial Anchors-account maken om het account-id, de sleutel en het domein te krijgen, als u deze nog niet hebt. Zie de volgende sectie om deze te verkrijgen.

Een Spatial Anchors-resource maken

Ga naar de Azure-portal.

Selecteer Een resource maken in het linkerdeelvenster.

Gebruik het zoekvak om te zoeken naar Spatial Anchors.

Screenshot showing the results of a search for Spatial Anchors.

Selecteer Spatial Anchors en vervolgens Maken.

Doe in het deelvenster Spatial Anchors-account het volgende:

  • Voer een unieke resourcenaam in met gewone alfanumerieke tekens.

  • Selecteer het abonnement waaraan u de resource wilt koppelen.

  • Maak een resourcegroep door Nieuwe maken te selecteren. Noem deze myResourceGroup en selecteer OK.

    Een resourcegroep is een logische container waarin Azure-resources, zoals web-apps, databases en opslagaccounts, worden geïmplementeerd en beheerd. U kunt bijvoorbeeld later de hele resourcegroep in één stap verwijderen.

  • Selecteer de locatie (regio) waarin u de resource wilt plaatsen.

  • Selecteer Maken om de resource te maken.

Screenshot of the Spatial Anchors pane for creating a resource.

Nadat de resource is gemaakt, ziet u in de Azure-portal dat uw implementatie is voltooid.

Screenshot showing that the resource deployment is complete.

Selecteer Naar resource. Nu kunt u de resource-eigenschappen bekijken.

Kopieer de waarde bij Account-id van de resource naar een teksteditor om later te gebruiken.

Screenshot of the resource properties pane.

Kopieer ook de waarde bij Accountdomein van de resource naar een teksteditor om later te gebruiken.

Screenshot showing the resource's account domain value.

Selecteer Toegangssleutel onder Instellingen. Kopieer de waarde bij Primaire sleutel, Accountsleutel, naar een teksteditor om later te gebruiken.

Screenshot of the Keys pane for the account.

Uw lokale anker uploaden naar de cloud

Zodra u uw account-id, -sleutel en -domein voor Azure Spatial Anchors hebt kunnen we terug in app\java\<PackageName>\MainActivity gaan om de volgende importen toe te voegen:

import com.microsoft.azure.spatialanchors.SessionLogLevel;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

Voeg vervolgens de volgende lidvariabelen toe aan uw MainActivity-klasse:

private boolean sessionInitialized = false;

private String anchorId = null;
private boolean scanningForUpload = false;
private final Object syncSessionProgress = new Object();
private ExecutorService executorService = Executors.newSingleThreadExecutor();

Voeg nu de volgende code toe aan de methode initializeSession(). Deze code stelt uw app in staat om de vooruitgang die Azure Spatial Anchors SDK boekt op te volgen terwijl het frames van uw camerafeed verzamelt. Tijdens dat proces verandert de kleur van uw bol van de oorspronkelijke zwarte kleur naar grijs. Zodra er voldoende frames zijn verzameld om uw anker naar de cloud te sturen, wordt de bol wit. Vervolgens voorziet deze code de aanmeldingsgegevens die u nodig heeft om te communiceren met de back-end van de cloud. Hier kunt u uw app configureren om uw account-id, -sleutel en -domein te gebruiken. U hebt ze naar een teksteditor gekopieerd bij het instellen van de Spatial Anchors-resource.

private void initializeSession() {
    if (sceneView.getSession() == null) {
        //Early return if the ARCore Session is still being set up
        return;
    }

    if (this.cloudSession != null) {
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    sessionInitialized = true;

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload) {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                        .thenAccept(material -> {
                            this.nodeRenderable.setMaterial(material);
                        });
            }
        });
    });

    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
    this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
    this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
    this.cloudSession.start();
}

Voeg vervolgens de volgende uploadCloudAnchorAsync()-methode toe in uw mainActivity-klasse. Zodra deze is aangeroepen wacht deze methode asynchroon tot er genoeg frames verzameld zijn van uw apparaat. Zodra dat gebeurd is, verandert de kleur van uw bol naar geel en begint het uw lokale Azure Spatial Anchor te uploaden naar de cloud. Wanneer de upload voltooid is, retourneert de code een anker-id.

private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
    synchronized (this.syncSessionProgress) {
        this.scanningForUpload = true;
    }


    return CompletableFuture.runAsync(() -> {
        try {
            float currentSessionProgress;
            do {
                synchronized (this.syncSessionProgress) {
                    currentSessionProgress = this.recommendedSessionProgress;
                }
                if (currentSessionProgress < 1.0) {
                    Thread.sleep(500);
                }
            }
            while (currentSessionProgress < 1.0);

            synchronized (this.syncSessionProgress) {
                this.scanningForUpload = false;
            }
            runOnUiThread(() -> {
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                        .thenAccept(yellowMaterial -> {
                            this.nodeRenderable.setMaterial(yellowMaterial);
                        });
            });

            this.cloudSession.createAnchorAsync(anchor).get();
        } catch (InterruptedException | ExecutionException e) {
            Log.e("ASAError", e.toString());
            throw new RuntimeException(e);
        }
    }, executorService).thenApply(ignore -> anchor.getIdentifier());
}

Ten slotte gaan we alles aan elkaar koppelen. Voeg de volgende code toe aan de methode handleTap(). Hiermee wordt uw uploadCloudAnchorAsync()-methode aangeroepen zodra uw bol is gemaakt. Zodra de methode is geretourneerd, voert de onderstaande code een laatste update uit op uw bol, waardoor de kleur wordt gewijzigd in blauw.

protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());
            });


    uploadCloudAnchorAsync(cloudAnchor)
            .thenAccept(id -> {
                this.anchorId = id;
                Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                runOnUiThread(() -> {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                });
            });
}

Implementeer uw app nogmaals. Beweeg uw apparaat, tik op het scherm en plaats uw bol. Deze keer verandert uw bol echter van zwart naar wit, omdat cameraframes worden verzameld. Zodra we voldoende frames hebben, wordt de bol geel en wordt de cloudupload gestart. Zorg ervoor dat uw telefoon is verbonden met internet. Zodra het uploaden is voltooid, wordt de bol blauw. U kunt desgewenst het Logcat venster in Android Studio bewaken om de logboekberichten weer te geven die uw app verzendt. Voorbeelden van berichten die worden geregistreerd, zijn de sessievoortgang tijdens het vastleggen van frames en de anker-id die de cloud retourneert zodra het uploaden is voltooid.

Notitie

Als u de waarde van recommendedSessionProgress (in uw foutopsporingslogboeken waarnaar wordt verwezen) Session progressniet ziet, moet u ervoor zorgen dat u zowel uw telefoon verplaatst als draait rond de bol die u hebt geplaatst.

Het ruimtelijke anker in de cloud opzoeken

Zodra uw anker is geüpload naar de cloud, kunt u proberen het opnieuw te vinden. Laten we eerst de volgende importen toevoegen aan uw code.

import java.util.concurrent.Executors;

import com.microsoft.azure.spatialanchors.AnchorLocateCriteria;
import com.microsoft.azure.spatialanchors.LocateAnchorStatus;

Vervolgens voegen we de volgende code toe aan de methode handleTap(). Met deze code:

  • Verwijder onze bestaande blauwe bol van het scherm.
  • Initialiseer onze Azure Spatial Anchors-sessie opnieuw. Deze actie zorgt ervoor dat het anker waar we naar op zoek gaan afkomstig is uit de cloud en niet het lokale anker dat we gemaakt hebben.
  • Voer een query uit voor het anker dat we hebben geüpload naar de cloud.
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    if (this.anchorId != null) {
        this.anchorNode.getAnchor().detach();
        this.anchorNode.setParent(null);
        this.anchorNode = null;
        initializeSession();
        AnchorLocateCriteria criteria = new AnchorLocateCriteria();
        criteria.setIdentifiers(new String[]{this.anchorId});
        cloudSession.createWatcher(criteria);
        return;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());
            });


    uploadCloudAnchorAsync(cloudAnchor)
            .thenAccept(id -> {
                this.anchorId = id;
                Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                runOnUiThread(() -> {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                });
            });
}

Laten we nu de code koppelen die zal worden aangeroepen wanneer het gezochte anker gevonden wordt. Voeg de volgende code toe binnen uw initializeSession()-methode. Dit fragment maakt en plaatst een groene bol zodra het spatial anchor in de cloud is gevonden. Ook wordt tikken op het scherm weer geactiveerd, zodat u het hele scenario nog een keer kunt herhalen: nog een lokaal anker maken, het uploaden en het weer zoeken.

private void initializeSession() {
    if (sceneView.getSession() == null) {
        //Early return if the ARCore Session is still being set up
        return;
    }

    if (this.cloudSession != null) {
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    sessionInitialized = true;

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload) {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                        .thenAccept(material -> {
                            this.nodeRenderable.setMaterial(material);
                        });
            }
        });
    });

    this.cloudSession.addAnchorLocatedListener(args -> {
        if (args.getStatus() == LocateAnchorStatus.Located) {
            runOnUiThread(() -> {
                this.anchorNode = new AnchorNode();
                this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                        .thenAccept(greenMaterial -> {
                            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                            this.anchorNode.setRenderable(nodeRenderable);
                            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                            this.anchorId = null;
                            synchronized (this.syncTaps) {
                                this.tapExecuted = false;
                            }
                        });
            });
        }
    });

    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
    this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
    this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
    this.cloudSession.start();
}

Dat is het! Implementeer uw app een laatste keer opnieuw uit om het hele scenario van begin tot eind uit te proberen. Verplaats uw apparaat en plaats uw zwarte bol. Beweeg vervolgens uw apparaat om cameraframes vast te leggen totdat de bol geel wordt. Uw lokale anker wordt geüpload en uw bol wordt blauw. Tik ten slotte nog één keer op uw scherm, zodat uw lokale anker wordt verwijderd en zoek vervolgens naar het equivalent in de cloud. Blijf doorgaan met het verplaatsen van uw apparaat totdat het ruimtelijke anker in de cloud is gevonden. Er wordt een groene bol op de juiste locatie weergegeven en u kunt het hele scenario weer herhalen.

Alles bij elkaar

Hier ziet u hoe het volledige MainActivity klassebestand eruit moet zien, nadat alle verschillende elementen zijn samengesteld. U kunt deze gebruiken ter referentie om te vergelijken met uw eigen bestand, en om te controleren of er verschillen zijn opgetreden.

package com.example.myfirstapp;

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;

import androidx.appcompat.app.AppCompatActivity;

import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.Scene;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;

import com.microsoft.azure.spatialanchors.AnchorLocateCriteria;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.LocateAnchorStatus;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();
    private ArFragment arFragment;
    private AnchorNode anchorNode;
    private Renderable nodeRenderable = null;
    private float recommendedSessionProgress = 0f;

    private ArSceneView sceneView;
    private CloudSpatialAnchorSession cloudSession;
    private boolean sessionInitialized = false;

    private String anchorId = null;
    private boolean scanningForUpload = false;
    private final Object syncSessionProgress = new Object();
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
        this.arFragment.setOnTapArPlaneListener(this::handleTap);

        this.sceneView = arFragment.getArSceneView();
        Scene scene = sceneView.getScene();
        scene.addOnUpdateListener(frameTime -> {
            if (this.cloudSession != null) {
                this.cloudSession.processFrame(sceneView.getArFrame());
            }
        });
        scene.addOnUpdateListener(this::scene_OnUpdate);
        initializeSession();
    }

    // <scene_OnUpdate>
    private void scene_OnUpdate(FrameTime frameTime) {
        if (!sessionInitialized) {
            //retry if initializeSession did an early return due to ARCore Session not yet available (i.e. sceneView.getSession() == null)
            initializeSession();
        }
    }
    // </scene_OnUpdate>

    // <initializeSession>
    private void initializeSession() {
        if (sceneView.getSession() == null) {
            //Early return if the ARCore Session is still being set up
            return;
        }

        if (this.cloudSession != null) {
            this.cloudSession.close();
        }
        this.cloudSession = new CloudSpatialAnchorSession();
        this.cloudSession.setSession(sceneView.getSession());
        this.cloudSession.setLogLevel(SessionLogLevel.Information);
        this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
        this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

        sessionInitialized = true;

        this.cloudSession.addSessionUpdatedListener(args -> {
            synchronized (this.syncSessionProgress) {
                this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
                Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
                if (!this.scanningForUpload) {
                    return;
                }
            }

            runOnUiThread(() -> {
                synchronized (this.syncSessionProgress) {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress))
                            .thenAccept(material -> {
                                this.nodeRenderable.setMaterial(material);
                            });
                }
            });
        });

        this.cloudSession.addAnchorLocatedListener(args -> {
            if (args.getStatus() == LocateAnchorStatus.Located) {
                runOnUiThread(() -> {
                    this.anchorNode = new AnchorNode();
                    this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                            .thenAccept(greenMaterial -> {
                                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                                this.anchorNode.setRenderable(nodeRenderable);
                                this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                                this.anchorId = null;
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                });
            }
        });

        this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
        this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
        this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
        this.cloudSession.start();
    }
    // </initializeSession>

    // <handleTap>
    protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
        synchronized (this.syncTaps) {
            if (this.tapExecuted) {
                return;
            }

            this.tapExecuted = true;
        }

        if (this.anchorId != null) {
            this.anchorNode.getAnchor().detach();
            this.anchorNode.setParent(null);
            this.anchorNode = null;
            initializeSession();
            AnchorLocateCriteria criteria = new AnchorLocateCriteria();
            criteria.setIdentifiers(new String[]{this.anchorId});
            cloudSession.createWatcher(criteria);
            return;
        }

        this.anchorNode = new AnchorNode();
        this.anchorNode.setAnchor(hitResult.createAnchor());
        CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
        cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

        MaterialFactory.makeOpaqueWithColor(this, new Color(
                this.recommendedSessionProgress,
                this.recommendedSessionProgress,
                this.recommendedSessionProgress))
                .thenAccept(material -> {
                    this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                    this.anchorNode.setRenderable(nodeRenderable);
                    this.anchorNode.setParent(arFragment.getArSceneView().getScene());
                });


        uploadCloudAnchorAsync(cloudAnchor)
                .thenAccept(id -> {
                    this.anchorId = id;
                    Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                    runOnUiThread(() -> {
                        MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                                .thenAccept(blueMaterial -> {
                                    this.nodeRenderable.setMaterial(blueMaterial);
                                    synchronized (this.syncTaps) {
                                        this.tapExecuted = false;
                                    }
                                });
                    });
                });
    }
    // </handleTap>

    // <uploadCloudAnchorAsync>
    private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
        synchronized (this.syncSessionProgress) {
            this.scanningForUpload = true;
        }


        return CompletableFuture.runAsync(() -> {
            try {
                float currentSessionProgress;
                do {
                    synchronized (this.syncSessionProgress) {
                        currentSessionProgress = this.recommendedSessionProgress;
                    }
                    if (currentSessionProgress < 1.0) {
                        Thread.sleep(500);
                    }
                }
                while (currentSessionProgress < 1.0);

                synchronized (this.syncSessionProgress) {
                    this.scanningForUpload = false;
                }
                runOnUiThread(() -> {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                            .thenAccept(yellowMaterial -> {
                                this.nodeRenderable.setMaterial(yellowMaterial);
                            });
                });

                this.cloudSession.createAnchorAsync(anchor).get();
            } catch (InterruptedException | ExecutionException e) {
                Log.e("ASAError", e.toString());
                throw new RuntimeException(e);
            }
        }, executorService).thenApply(ignore -> anchor.getIdentifier());
    }
    // </uploadCloudAnchorAsync>

}

Volgende stappen

In deze zelfstudie hebt u gezien hoe u een nieuwe Android-app kunt maken die ARCore-functies combineert met Azure Spatial Anchors. Ga voor meer informatie over de Azure Spatial Anchors-bibliotheek door naar de handleiding over het maken en vinden van ankers.

Create and locate anchors using Azure Spatial Anchors (Ankers maken en vinden met Azure Spatial Anchors)