Kurz: Podrobné pokyny k vytvoření nové aplikace pro Android pomocí Azure Spatial Anchors

V tomto kurzu si ukážeme, jak vytvořit novou aplikaci pro Android, která integruje funkce ARCore s Azure Spatial Anchors.

Požadavky

Abyste mohli absolvovat tento kurz, ujistěte se, že máte následující:

Začínáme

Spusťte Android Studio. V okně Vítá vás Android Studio klikněte na Spustit nový projekt Android Studio projektu.

  1. Vyberte Soubor -> Nový Project.
  2. V okně Create New Project (Vytvořit novou aktivitu) v části Telefon a Tablet zvolte Empty Activity (Prázdná aktivita) a klikněte na Next (Další).
  3. V okně New Project – Empty Activity (Nová aktivita – prázdná aktivita) změňte následující hodnoty:
    • Změňte Název, Název balíčku a Uložit umístění na požadované hodnoty.
    • Nastavte jazyk na . Java
    • Nastavte minimální úroveň rozhraní API na . API 26: Android 8.0 (Oreo)
    • Ostatní možnosti ponechte tak, jak jsou.
    • Klikněte na Finish (Dokončit).
  4. Spustí se Instalační program komponent. Po nějakém zpracování Android Studio integrované vývojové prostředí (IDE).

Android Studio – Nový Project

Zkouším to

Pokud chcete novou aplikaci otestovat, připojte zařízení s podporou vývojáře k vývojovému počítači pomocí kabelu USB. V pravém horním rohu Android Studio připojené zařízení a klikněte na ikonu Spustit aplikaci. Android Studio nainstaluje aplikaci do připojeného zařízení a spustí ji. Teď byste měli vidět "Hello World!" v aplikaci spuštěné na vašem zařízení. Klikněte na Run Stop -> 'app' (Zastavit aplikaci). Android Studio – Spustit

Integrace ARCore

ARCore je platforma Společnosti Google pro vytváření prostředí rozšířené reality, která umožňuje zařízení sledovat jeho pozici při pohybu a budování vlastních principů skutečného světa.

Upravte app\manifests\AndroidManifest.xml soubor tak, aby zahrnoval následující položky v kořenovém <manifest> uzlu. Tento fragment kódu dělá několik věcí:

  • Umožní aplikaci přístup k fotoaparátu zařízení.
  • Zajistí také, že vaše aplikace bude viditelná jenom v Obchod Google Play pro zařízení, která podporují ARCore.
  • Nakonfiguruje virtuální Obchod Google Play pro stažení a instalaci arCore, pokud ještě není nainstalovaná, když je vaše aplikace nainstalovaná.
<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>

Upravte Gradle Scripts\build.gradle (Module: app) soubor tak, aby zahrnoval následující položku. Tento kód zajistí, že vaše aplikace cílí na ARCore verze 1.25. Po této změně se od Gradlu může zobrazit oznámení s žádostí o synchronizaci: klikněte na Synchronizovat.

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

Integrace sceneformu

Sceneform usnadňuje vykreslování realistických 3D scén v aplikacích rozšířené reality, aniž byste se museli učit OpenGL.

Upravte Gradle Scripts\build.gradle (Module: app) tak, aby zahrnoval následující položky. Tento kód umožní vaší aplikaci používat jazykové konstrukce z Javy 8, což Sceneform vyžaduje. Zajistí také, že vaše aplikace cílí Sceneform na verzi 1.15. Po této změně se od Gradlu může zobrazit oznámení s žádostí o synchronizaci: klikněte na Synchronizovat.

android {
    ...

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

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

Otevřete soubor app\res\layout\activity_main.xml a nahraďte existující element Hello Přird <TextView ... /> následujícím kódem ArFragment. Tento kód způsobí, že se na obrazovce zobrazí kanál fotoaparátu, který umožní arCore sledovat polohu zařízení při jeho pohybu.

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

Poznámka

Pokud chcete zobrazit nezpracovaný xml hlavní aktivity, klikněte na tlačítko Code (Kód) nebo Split (Rozdělit) v pravém horním rohu Android Studio.

Aplikaci znovu nasaďte do zařízení, abyste ji ještě jednou ověřili. Tentokrát byste měli být požádáni o oprávnění fotoaparátu. Po schválení by se na obrazovce mělo zobrazit vykreslení kanálu fotoaparátu.

Umístění objektu do reálného světa

Pojďme vytvořit objekt & pomocí aplikace. Nejprve do souboru přidejte následující app\java\<PackageName>\MainActivity importy:

package com.example.myfirstapp;

import android.support.v7.app.AppCompatActivity;
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;

Tady je postup, jak `MainActivity` by měl soubor celé třídy vypadat, až budou všechny různé prvky vloženy dohromady. Můžete ji použít jako referenci pro porovnání s vlastním souborem a na místě, kde můžete mít nějaké jiné rozdíly.

Pak do třídy přidejte následující členské MainActivity proměnné:

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

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

Dále do metody přidejte následující app\java\<PackageName>\MainActivity onCreate() kód. Tento kód propne naslouchací proces s názvem , který zjistí, když uživatel klepne na obrazovku handleTap() na vašem zařízení. Pokud se klepnutí stane na skutečném povrchu, který už sledování ARCore rozpoznal, spustí se naslouchací proces.

private CloudSpatialAnchorSession cloudSession;

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

    Scene scene = sceneView.getScene();

Nakonec přidejte následující handleTap() metodu, která vše spojuje dohromady. Vytvoří kouli a umístě ji do umístění, na které jste klepli. Zpočátku bude černá, protože je teď nastavená na this.recommendedSessionProgress nulu. Tato hodnota se upraví později.


    this.tapExecuted = true;
}

if (this.anchorId != null) {
    this.anchorNode.getAnchor().detach();
    this.anchorNode.setParent(null);
    this.anchorNode = null;
    initializeSession();
MaterialFactory.makeOpaqueWithColor(this, new Color(
        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(() -> {
    this.scanningForUpload = true;

Aplikaci znovu nasaďte do zařízení, abyste ji ještě jednou ověřili. Tentokrát se můžete pohybovat po svém zařízení a získat arCore, abyste mohli začít rozpoznáovat vaše prostředí. Potom klepněte na obrazovku a & černou kouli umístěte na plochu podle svého výběru.

Připojení místní služby Azure Spatial Anchor

Upravte Gradle Scripts\build.gradle (Module: app) soubor tak, aby zahrnoval následující položku. Tento ukázkový fragment kódu cílí na Azure Spatial Anchors SDK verze 2.10.2. Všimněte si, že sdk verze 2.7.0 je aktuálně minimální podporovaná verze a odkazování na novější verzi Azure Spatial Anchors by také mělo fungovat. Doporučujeme používat nejnovější verzi sady Azure Spatial Anchors SDK. Poznámky k verzi sady SDK najdete tady.

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

Pokud cílíte na Azure Spatial Anchors SDK 2.10.0 nebo novější, do části úložiště v souboru projektu zahrpište následující settings.gradle položku. Bude obsahovat adresu URL informačního kanálu balíčků Maven, který hostuje balíčky Azure Spatial Anchors Android pro sadu SDK 2.10.0 nebo novější:

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

Klikněte pravým tlačítkem app\java\<PackageName> -> na New -> Java Class (Nová třída Javy). Nastavte Název na MyFirstApp a vyberte Třída. Vytvoří se MyFirstApp.java soubor s názvem . Přidejte do něj následující import:

import com.microsoft.CloudServices;

android.app.ApplicationDefinujte jako nadtřídu.

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

Potom do nové třídy přidejte následující kód, který zajistí inicializaci azure Spatial Anchors s kontextem MyFirstApp vaší aplikace.

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

Teď upravte app\manifests\AndroidManifest.xml soubor tak, aby v kořenovém uzlu zahrnoval následující <application> položku. Tento kód zaháhne třídu Application, kterou jste vytvořili, do vaší aplikace.

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

Vraťte app\java\<PackageName>\MainActivity se do souboru a přidejte do něj následující importy:


## <a name="putting-everything-together"></a>Vložení všech objektů dohromady
Tady je postup, jak `MainActivity` by měl soubor celé třídy vypadat, až budou všechny různé prvky vloženy dohromady. Můžete ji použít jako referenci pro porovnání s vlastním souborem a na místě, kde můžete mít nějaké jiné rozdíly.
import android.os.Bundle;
import com.google.ar.core.HitResult;

import android.view.MotionEvent;

Pak do třídy přidejte následující členské MainActivity proměnné:


public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();

Teď do třídy přidáme následující initializeSession() mainActivity metodu. Po zavolána se zajistí, že se Spatial Anchors aplikace Azure vytvoří a správně inicializuje během spuštění aplikace. Tento kód zajišťuje, že relace sceneview předaná do relace ASA prostřednictvím volání nebude cloudSession.setSession mít hodnotu null díky včasném návratu.

private void initializeSession() {
    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())));

    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)
            {
    synchronized (this.syncTaps) {

Vzhledem k tomu, že by mohlo dojít k dřívějšímu vrácení, pokud relace sceneView ještě není nastavená (tj. pokud má hodnotu null), přidáme volání onUpdate, které se po vytvoření relace initializeSession() sceneView.getSession() sceneView inicializuje.

Teď zahápněme initializeSession() metodu scene_OnUpdate(...) a k onCreate() metodě . Také zajistíme, aby se snímky z vašeho kanálu fotoaparátu odesílané do Azure Spatial Anchors SDK ke zpracování.

private CloudSpatialAnchorSession cloudSession;

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

// <onCreate>
@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();

Nakonec do metody přidejte následující handleTap() kód. Připojí místní Azure Spatial Anchor k černé kouli, kterou umístíme do reálného světa.


    this.tapExecuted = true;
}

if (this.anchorId != null) {
    this.anchorNode.getAnchor().detach();
    this.anchorNode.setParent(null);
    this.anchorNode = null;
    initializeSession();
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(() -> {
    this.scanningForUpload = true;

Znovu nasaďte aplikaci znovu. Pohybujte se zařízením, klepněte na obrazovku a umístěte černou kouli. Tentokrát ale váš kód vytvoří a připojí k vaší kouli místní Azure Spatial Anchor.

Než budete pokračovat, budete muset vytvořit účet Azure Spatial Anchors, abyste získali identifikátor účtu, klíč a doménu, pokud ho ještě nemáte. Pokud je chcete získat, postupujte podle následující části.

Vytvoření Spatial Anchors prostředků

Přejděte na Azure Portal.

V levém podokně vyberte Vytvořit prostředek.

Pomocí vyhledávacího pole vyhledejte Spatial Anchors.

Snímek obrazovky zobrazující výsledky hledání Spatial Anchors

Vyberte Spatial Anchors a pak vyberte Vytvořit.

V Spatial Anchors Účet proveďte následující:

  • Zadejte jedinečný název prostředku pomocí běžných alfanumerických znaků.

  • Vyberte předplatné, ke které chcete prostředek připojit.

  • Vytvořte skupinu prostředků výběrem možnosti Vytvořit novou. Pojmechte ji myResourceGroup a pak vyberte OK.

    Skupina prostředků je logický kontejner, ve kterém se nasazují a spravují prostředky Azure, jako jsou webové aplikace, databáze a účty úložiště. Později se například můžete rozhodnout odstranit celou skupinu prostředků v jednom jednoduchém kroku.

  • Vyberte umístění (oblast), do kterého chcete prostředek umístit.

  • Výběrem možnosti Vytvořit zahajte vytváření prostředku.

Snímek obrazovky Spatial Anchors pro vytvoření prostředku

Po vytvoření prostředku se v Azure Portal zobrazí, že je vaše nasazení dokončené.

Snímek obrazovky znázorňující dokončení nasazení prostředku

Vyberte Přejít k prostředku. Teď můžete zobrazit vlastnosti prostředku.

Zkopírujte hodnotu ID účtu prostředku do textového editoru pro pozdější použití.

Snímek obrazovky s podoknem vlastností prostředku

Zkopírujte také hodnotu Domény účtu prostředku do textového editoru pro pozdější použití.

Snímek obrazovky zobrazující hodnotu domény účtu prostředku

V části Nastavení vyberte Přístupový klíč. Zkopírujte hodnotu Primární klíč, Klíč účtu, do textového editoru pro pozdější použití.

Snímek obrazovky s podoknem Klíče pro účet

Upload místního ukotvení do cloudu

Jakmile budete mít identifikátor účtu azure Spatial Anchors, klíč a doménu, můžeme se vrátit do souboru a přidat do něj app\java\<PackageName>\MainActivity následující importy:


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;

Pak do třídy přidejte následující členské MainActivity proměnné:

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

Nyní do své metody přidejte následující kód initializeSession() . Nejprve tento kód umožní vaší aplikaci monitorovat průběh, který sada SDK prostorových ukotvení vytvoří při shromažďování snímků z kanálu kamery. V takovém případě se barva vaší koule začne měnit z původní černé na šedá. Pak se v případě, že se shromáždí dostatek snímků pro odeslání kotvy do cloudu, zapíná bíle. Za druhé tento kód poskytne přihlašovací údaje potřebné ke komunikaci s back-end cloudu. Tady můžete nakonfigurovat aplikaci tak, aby používala identifikátor účtu, klíč a doménu. Při nastavování prostředku prostorových ukotveníjste je zkopírovali do textového editoru.

private void initializeSession() {
    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())));

    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());
// </initializeSession>

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

Dále do uploadCloudAnchorAsync() vaší třídy přidejte následující metodu mainActivity . Po zavolání bude tato metoda asynchronně čekat, dokud nebudou ze zařízení shromažďovány dostatečné snímky. Jakmile k tomu dojde, změní se barva vaší koule na žlutou a potom se začne nahrávat vaše místní prostorové ukotvení Azure do cloudu. Po dokončení nahrávání kód vrátí identifikátor kotvy.

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());
}

Nakonec připojovat všechno dohromady. Do handleTap() metody přidejte následující kód. Vyvolá vaši uploadCloudAnchorAsync() metodu, jakmile bude vaše koule vytvořena. Jakmile se metoda vrátí, kód níže provede jednu poslední aktualizaci vaší koule, přičemž změna barvy na modrou.


        this.tapExecuted = true;
    }

    if (this.anchorId != null) {
        this.anchorNode.getAnchor().detach();
        this.anchorNode.setParent(null);
        this.anchorNode = null;
        initializeSession();
    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;

Znovu nasaďte aplikaci ještě jednou. Pohybujte na svém zařízení, klepněte na obrazovku a umístěte svoji koule. Tentokrát ale vaše koule změní barvu z černé na bílou, protože se shromažďují snímky kamery. Jakmile máme dostatek snímků, koule se změní na žlutou a spustí se nahrávání do cloudu. Ujistěte se, že je váš telefon připojený k Internetu. Až se nahrávání dokončí, vaše koule se změní na modrou. Volitelně můžete monitorovat Logcat okno v Android Studio a zobrazit tak zprávy protokolu, které vaše aplikace posílá. Příklady zpráv, které se mají protokolovat, zahrnují průběh relace během zachycení snímků a identifikátor kotvy, které Cloud vrátí po dokončení nahrávání.

Poznámka

Pokud se vám nezobrazují hodnota recommendedSessionProgress (v protokolech pro ladění, které se označují jako), ujistěte se, že jste v Session progress oblasti, kterou jste umístili, nastavili pohyb i otočení svého telefonu.

Najděte své cloudové kotvy

Po nahrání kotvy do cloudu jsme připraveni pokusit se o jeho vyhledání. Nejprve přidáme následující importy do kódu.

import com.microsoft.azure.spatialanchors.SessionLogLevel;

import com.google.ar.sceneform.ux.ArFragment;
import android.util.Log;

Pak přidejte do vaší metody následující kód handleTap() . Tento kód bude:

  • Z obrazovky odeberte existující modrou koule.
  • Znovu inicializujte naši relaci prostorové kotvy Azure. Tato akce zajistí, že kotva, kterou vyhledáme, pochází z cloudu namísto místního ukotvení, které jsme vytvořili.
  • Vydejte dotaz na kotvu, kterou jsme nahráli do cloudu.
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;
                                }
                            });
                    });
                });
        });
}

Nyní připravujeme kód, který bude vyvolán, když se na něj nachází kotva, pro kterou se dotazuje. V rámci initializeSession() metody přidejte následující kód. Tento fragment kódu vytvoří & umístit zelenou koule po umístění cloudové kotvy. Také se znovu aktivuje obrazovka, takže můžete celý scénář zopakovat, a to ještě jednou: vytvořte další místní kotvu, nahrajte ho a znovu ho vyhledejte.

private void initializeSession() {
    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())));

    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();
}

A to je vše! Opětovným nasazením aplikace z poslední doby můžete vyzkoušet celý scénář a ukončit ho. Pohybujte kolem zařízení a umístěte černou koule. Pak můžete pokračovat ve svém zařízení a zachytit snímky z kamery, dokud se koule nezmění žlutě. Vaše místní kotva se nahraje a vaše koule se zachová modře. Nakonec klepněte na obrazovku ještě jednou, aby se vaše místní kotva odebrala, a pak se podíváme na svůj cloudový protějšek. Pokračujte v přesouvání zařízení, dokud se neumístí cloudové ukotvení. Zelená koule by se měla zobrazit ve správném umístění a můžete ji vypláchněte & opakujte celý scénář.

Vložení všech objektů dohromady

Zde je MainActivity uvedeno, jak by měl soubor celé třídy vypadat po sobě všech různých prvků. Můžete ji použít jako referenci pro porovnání s vlastním souborem a na místě, kde můžete mít nějaké jiné rozdíly.

Vložení všech objektů dohromady

Tady je postup, jak MainActivity by měl soubor celé třídy vypadat, až budou všechny různé prvky vloženy dohromady. Můžete ji použít jako referenci pro porovnání s vlastním souborem a na místě, kde můžete mít nějaké jiné rozdíly.

package com.example.myfirstapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

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;
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;

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

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

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 String anchorId = null;
    private boolean scanningForUpload = false;
    private final Object syncSessionProgress = new Object();
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    // <onCreate>
    @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());
            }
        });

        initializeSession();
    }
    // </onCreate>

    // <initializeSession>
    private void initializeSession() {
        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())));

        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>
}

Další kroky

V tomto kurzu jste viděli, jak vytvořit novou aplikaci pro Android, která integruje funkce ARCore s prostorovými kotvami Azure. Další informace o knihovně prostorových kotev Azure najdete v naší příručce k vytváření a hledání kotev.

Další kroky

V tomto kurzu jste viděli, jak vytvořit novou aplikaci pro Android, která integruje funkce ARCore s prostorovými kotvami Azure. Další informace o knihovně prostorových kotev Azure najdete v naší příručce k vytváření a hledání kotev.