Megosztás a következőn keresztül:


Oktatóanyag: Részletes útmutató új Android-alkalmazás létrehozásához az Azure Spatial Anchors használatával

Ez az oktatóanyag bemutatja, hogyan hozhat létre egy új Android-alkalmazást, amely integrálja az ARCore funkcióit az Azure Spatial Anchors szolgáltatással.

Előfeltételek

Az oktatóanyag elvégzéséhez győződjön meg arról, hogy rendelkezik a következőkkel:

Első lépések

Indítsa el az Android Studiót. Az Üdvözli az Android Studio ablakában kattintson az Új Android Studio-projekt indítása elemre.

  1. Válassza a Fájl–>Új projekt lehetőséget.
  2. Az Új projekt létrehozása ablak Telefon és táblagép szakaszában válassza a Tevékenység ürítése lehetőséget, majd kattintson a Tovább gombra.
  3. Az Új projekt – Üres tevékenység ablakban módosítsa a következő értékeket:
    • Módosítsa a nevet, a csomagnevet és a mentési helyet a kívánt értékekre
    • A nyelv beállítása a következő:Java
    • Minimális API-szint beállítása a következőre:API 26: Android 8.0 (Oreo)
    • Hagyja meg a többi lehetőséget
    • Kattintson a Befejezés gombra.
  4. A komponens-telepítő futni fog. Némi feldolgozás után az Android Studio megnyitja az IDE-t.

Android Studio – Új projekt

Kipróbálás

Az új alkalmazás teszteléséhez csatlakoztassa a fejlesztőeszközt egy USB-kábellel a fejlesztői géphez. Az Android Studio jobb felső sarkában válassza ki a csatlakoztatott eszközt, és kattintson az Alkalmazás futtatása ikonra. Az Android Studio telepíti az alkalmazást a csatlakoztatott eszközre, és elindítja azt. Ekkor megjelenik a "„Helló világ!” alkalmazás!" az eszközön futó alkalmazásban. Kattintson az >Alkalmazás leállítása parancsra. Android Studio – Futtatás

Az ARCore integrálása

Az ARCore a Google platformja, amellyel kiterjesztett valósági élményeket hozhat létre, lehetővé téve, hogy az eszköz nyomon kövesse a pozícióját, miközben mozog, és felépítse saját tudását a valós világról.

Módosítsa app\manifests\AndroidManifest.xml úgy, hogy a gyökércsomóponton <manifest> belül a következő bejegyzések szerepeljenek. Ez a kódrészlet néhány dolgot tesz:

  • Ez lehetővé teszi, hogy az alkalmazás hozzáférjen az eszköz kamerájához.
  • Azt is biztosítja, hogy az alkalmazás csak a Google Play Áruházban látható legyen az ARCore-t támogató eszközök számára.
  • Konfigurálja a Google Play Áruházat az ARCore letöltésére és telepítésére, ha még nincs telepítve, az alkalmazás telepítésekor.
<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>

Módosítsa Gradle Scripts\build.gradle (Module: app) úgy, hogy tartalmazza a következő bejegyzést. Ez a kód biztosítja, hogy az alkalmazás az ARCore 1.25-ös verzióját célozza meg. A módosítás után előfordulhat, hogy értesítést kap a Gradle-től, amely a szinkronizálást kéri: kattintson most a Szinkronizálás gombra.

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

Sceneform integrálása

A Sceneform egyszerűvé teszi a valósághű 3D-s jelenetek megjelenítését a Kiterjesztett valóság alkalmazásokban anélkül, hogy meg kellene tanulnia az OpenGL-t.

Módosítsa Gradle Scripts\build.gradle (Module: app) úgy, hogy a következő bejegyzéseket is tartalmazza. Ez a kód lehetővé teszi, hogy az alkalmazás nyelvi szerkezeteket használjon a Java 8-ból, amelyhez Sceneform szükség van. Emellett biztosítja, hogy az alkalmazás az 1.15-ös verziót célozza Sceneform meg. A módosítás után előfordulhat, hogy értesítést kap a Gradle-től, amely a szinkronizálást kéri: kattintson most a Szinkronizálás gombra.

android {
    ...

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

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

Nyissa meg a app\res\layout\activity_main.xmlmeglévő Hello Wolrd-elemet <TextView ... /> a következő ArFragmentre. Ez a kód hatására a kameracsatorna megjelenik a képernyőn, így az ARCore nyomon követheti az eszköz helyzetét, miközben mozog.

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

Feljegyzés

A fő tevékenység nyers xml-fájljának megtekintéséhez kattintson az Android Studio jobb felső sarkában található "Kód" vagy "Felosztás" gombra.

Telepítse újra az alkalmazást az eszközére, hogy még egyszer ellenőrizze. Ezúttal kameraengedélyeket kell kérnie. A jóváhagyást követően a kameracsatornának a képernyőn kell megjelennie.

Objektum elhelyezése a valós világban

Hozzunk létre és helyezzünk el egy objektumot az alkalmazással. Először adja hozzá a következő importálásokat a következő app\java\<PackageName>\MainActivityadatokhoz:

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;

Ezután adja hozzá a következő tagváltozókat az MainActivity osztályhoz:

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

Ezután adja hozzá a következő kódot a app\java\<PackageName>\MainActivityonCreate() metódushoz. Ez a kód csatlakoztat egy figyelőt, amely handleTap()észleli, ha a felhasználó az eszközön a képernyőre koppint. Ha a koppintás olyan valós felületen történik, amelyet az ARCore nyomon követése már felismert, a figyelő futni fog.

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

Végül adja hozzá a következő handleTap() módszert, amely mindent összekapcsol. Létrehoz egy gömböt, és a megfeleltetett helyre helyezi. A gömb kezdetben fekete lesz, mivel this.recommendedSessionProgress jelenleg nulla értékre van állítva. Ez az érték később módosul.

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

Telepítse újra az alkalmazást az eszközére, hogy még egyszer ellenőrizze. Ezúttal az eszköz körül mozogva ráveheti az ARCore-t a környezet felismerésére. Ezután a képernyőre koppintva hozza létre a fekete gömböt a választott felület fölé.

Helyi Azure Spatial Anchor csatolása

Módosítsa Gradle Scripts\build.gradle (Module: app) úgy, hogy tartalmazza a következő bejegyzést. Ez a mintakódrészlet az Azure Spatial Anchors SDK 2.10.2-es verzióját célozza meg. Vegye figyelembe, hogy az SDK 2.7.0-s verziója jelenleg a minimálisan támogatott verzió, és az Azure Spatial Anchors újabb verzióira való hivatkozásnak is működnie kell. Javasoljuk, hogy az Azure Spatial Anchors SDK legújabb verzióját használja. Az SDK kibocsátási megjegyzéseit itt találja.

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

Ha az Azure Spatial Anchors SDK 2.10.0-s vagy újabb verzióját célozza, a projekt fájljának settings.gradle adattárak szakaszában adja meg az alábbi bejegyzést. Ez tartalmazza az SDK 2.10.0-s vagy újabb verziójához készült Azure Spatial Anchors Android-csomagokat üzemeltető Maven-csomag hírcsatornájának URL-címét:

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

Kattintson a jobb gombbal a app\java\<PackageName>->New-Java> osztályra. Állítsa a Nevet a MyFirstAppra, és válassza az Osztály lehetőséget. Létrejön egy meghívott MyFirstApp.java fájl. Adja hozzá a következő importálást:

import com.microsoft.CloudServices;

Definiálja android.app.Application a szuperosztályt.

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

Ezután adja hozzá a következő kódot az új MyFirstApp osztályhoz, amely biztosítja, hogy az Azure Spatial Anchors inicializálva legyen az alkalmazás környezetével.

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

Most módosítsa app\manifests\AndroidManifest.xml a következő bejegyzést a gyökércsomóponton <application> belülre. Ez a kód csatlakoztatja a létrehozott alkalmazásosztályt az alkalmazáshoz.

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

A visszalépéshez app\java\<PackageName>\MainActivityadja hozzá a következő importálásokat:

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;

Ezután adja hozzá a következő tagváltozókat az MainActivity osztályhoz:

private float recommendedSessionProgress = 0f;

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

Ezután adja hozzá a következő initializeSession() metódust az osztályhoz mainActivity . A hívás után az alkalmazás indításakor létrejön és megfelelően inicializálódik egy Azure Spatial Anchors-munkamenet. Ez a kód gondoskodik arról, hogy a híváson keresztül cloudSession.setSession az ASA-munkamenetnek átadott sceneview-munkamenet ne legyen null értékű, ha korai visszatérést végez.

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

Mivel initializeSession() a sceneView-munkamenet még nincs beállítva (azaz sceneView.getSession() null), akkor egy onUpdate hívással biztosítjuk, hogy az ASA-munkamenet inicializálva legyen a sceneView-munkamenet létrehozása után.

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

Most kapcsoljuk be a metódust és scene_OnUpdate(...) a initializeSession() metódustonCreate(). Emellett biztosítjuk, hogy a kameracsatornából származó képkockák az Azure Spatial Anchors SDK-ba kerülnek feldolgozásra.

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

Végül adja hozzá a következő kódot a handleTap() metódushoz. A rendszer egy helyi Azure Spatial Anchort csatol a fekete gömbhöz, amelyet a valós világban helyezünk el.

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

Ismét üzembe helyezheti az alkalmazást. Mozogjon az eszközön, koppintson a képernyőre, és helyezzen el egy fekete gömböt. Ezúttal azonban a kód egy helyi Azure Spatial Anchor létrehozása és csatolása lesz a szférához.

Mielőtt továbblép, létre kell hoznia egy Azure Spatial Anchors-fiókot a fiókazonosító, a kulcs és a tartomány lekéréséhez, ha még nem rendelkezik velük. A beszerzésükhöz kövesse az alábbi szakaszt.

Térbeli horgonyerőforrás létrehozása

Nyissa meg az Azure Portalt.

A bal oldali panelen válassza az Erőforrás létrehozása lehetőséget.

A keresőmezővel keresse meg a térbeli horgonyokat.

Képernyőkép a térbeli horgonyok keresésének eredményeiről.

Válassza a Térbeli horgonyok, majd a Létrehozás lehetőséget.

A Térbeli horgonyok fiók panelen tegye a következőket:

  • Adjon meg egy egyedi erőforrásnevet normál alfanumerikus karakterek használatával.

  • Válassza ki azt az előfizetést, amelyhez csatolni szeretné az erőforrást.

  • Hozzon létre egy erőforráscsoportot az Új létrehozása gombra kattintva. Nevezze el a myResourceGroup nevet, majd kattintson az OK gombra.

    Az erőforráscsoport egy logikai tároló, amelybe az Azure-erőforrásokat, például webalkalmazásokat, adatbázisokat és tárfiókokat helyezik üzembe és felügyelik. Dönthet úgy is például, hogy később egyetlen egyszerű lépésben törli a teljes erőforráscsoportot.

  • Válassza ki azt a helyet (régiót), ahol az erőforrást el szeretné helyezni.

  • Válassza a Létrehozás lehetőséget az erőforrás létrehozásának megkezdéséhez.

Képernyőkép az erőforrás létrehozásához használt Térbeli horgonyok panelről.

Az erőforrás létrehozása után az Azure Portalon látható, hogy az üzembe helyezés befejeződött.

Képernyőkép az erőforrás üzembe helyezésének befejezéséről.

Válassza az Erőforrás megnyitása lehetőséget. Most már megtekintheti az erőforrás tulajdonságait.

Másolja az erőforrás Fiókazonosító értékét egy szövegszerkesztőbe későbbi használatra.

Képernyőkép az erőforrás tulajdonságai panelről.

Az erőforrás Fióktartomány értékét is másolja egy szövegszerkesztőbe későbbi használatra.

Képernyőkép az erőforrás fióktartomány-értékéről.

A Beállítások területen válassza az Access-kulcsot. Másolja az elsődleges kulcs értékét ( Fiókkulcs) egy szövegszerkesztőbe későbbi használatra.

Képernyőkép a fiók Kulcsok paneljéről.

Helyi horgony feltöltése a felhőbe

Miután megkapta az Azure Spatial Anchors-fiók azonosítóját, kulcsát és tartományát, visszatérhet app\java\<PackageName>\MainActivity, és hozzáadhatja a következő importálásokat:

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;

Ezután adja hozzá a következő tagváltozókat az MainActivity osztályhoz:

private boolean sessionInitialized = false;

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

Most adja hozzá a következő kódot a initializeSession() metódushoz. Először is ez a kód lehetővé teszi az alkalmazás számára, hogy figyelje az Azure Spatial Anchors SDK által a képkockák kameracsatornából történő gyűjtése során elért előrehaladást. Ahogy ez is, a szín a gömb kezd változni az eredeti fekete, a szürke. Ezután fehérre vált, amint elegendő képkockát gyűjtünk össze, hogy elküldjük a horgonyt a felhőbe. Másodszor, ez a kód megadja a felhő háttérrendszerével való kommunikációhoz szükséges hitelesítő adatokat. Itt konfigurálhatja az alkalmazást a fiókazonosító, a kulcs és a tartomány használatára. A Térbeli horgonyok erőforrás beállításakor átmásolta őket egy szövegszerkesztőbe.

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

Ezután adja hozzá a következő uploadCloudAnchorAsync() metódust az osztályban mainActivity . A meghívás után ez a módszer aszinkron módon megvárja, amíg elegendő képkockát gyűjt az eszközről. Amint ez megtörténik, a gömb színét sárga színre váltja, majd elkezdi feltölteni a helyi Azure Spatial Anchort a felhőbe. A feltöltés befejezése után a kód egy horgonyazonosítót ad vissza.

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

Végül kapcsoljunk össze mindent. handleTap() A metódusban adja hozzá a következő kódot. A gömb létrehozása után azonnal meghívja a uploadCloudAnchorAsync() metódust. A metódus visszatérése után az alábbi kód egy utolsó frissítést hajt végre a gömbön, és kékre módosítja a színét.

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

Ismét üzembe helyezheti az alkalmazást. Mozogjon az eszközön, koppintson a képernyőre, és helyezze el a gömböt. Ezúttal azonban a gömb színe feketéről fehérre változik, ahogy a kamerakeretek összegyűjtve lesznek. Ha elegendő képkockánk van, a gömb sárga színűre változik, és elindul a felhőfeltöltés. Győződjön meg arról, hogy a telefonja csatlakozik az internethez. A feltöltés befejeződése után a gömb kék színűre változik. Az Android Studióban az ablak figyelésével Logcat megtekintheti az alkalmazás által küldött naplóüzeneteket. A naplózandó üzenetek közé tartozik például a munkamenet állapota a keretrögzítés során, valamint a felhő által a feltöltés befejezése után visszaadott horgonyazonosító.

Feljegyzés

Ha nem látja a (hibakeresési recommendedSessionProgress naplókban más néven Session progress) változás értékét, akkor győződjön meg arról, hogy a telefonját a elhelyezett gömb körül mozgatja és elforgatja .

A felhőbeli térbeli horgony megkeresése

Miután feltöltötte a horgonyt a felhőbe, újra megkísérljük a keresését. Először adja hozzá a következő importálásokat a kódhoz.

import java.util.concurrent.Executors;

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

Ezután adja hozzá a következő kódot a handleTap() metódushoz. Ez a kód a következő lesz:

  • Távolítsa el a meglévő kék gömböt a képernyőről.
  • Inicializálja újra az Azure Spatial Anchors-munkamenetet. Ez a művelet biztosítja, hogy a megtalálandó horgony a felhőből származik a létrehozott helyi horgony helyett.
  • Adjon ki egy lekérdezést a felhőbe feltöltött horgonyhoz.
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;
                                }
                            });
                });
            });
}

Most kapcsoljuk be azt a kódot, amely akkor lesz meghívva, amikor a lekérdezett horgony található. A metóduson initializeSession() belül adja hozzá a következő kódot. Ez a kódrészlet egy zöld gömböt hoz létre a felhőbeli térbeli horgony elhelyezése után. Emellett ismét engedélyezi a képernyő koppintást, így ismét megismételheti az egész forgatókönyvet: hozzon létre egy másik helyi horgonyt, töltse fel, és keresse meg újra.

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

Ennyi az egész! Az alkalmazás ismételt üzembe helyezése utolsó alkalommal a teljes forgatókönyv teljes körű kipróbálásához. Mozogjon az eszközén, és helyezze el a fekete gömböt. Ezután folytassa az eszköz áthelyezését a kamerakeretek rögzítéséhez, amíg a gömb sárga színűre nem változik. A helyi horgony fel lesz töltve, és a gömb kék színű lesz. Végül koppintson még egyszer a képernyőre, hogy a helyi horgony el legyen távolítva, majd lekérdezzük a felhőbeli megfelelőjét. Folytassa az eszköz áthelyezését, amíg el nem helyezi a felhőbeli térbeli horgonyt. A megfelelő helyen zöld gömbnek kell megjelennie, és újra öblítheti és megismételheti az egész forgatókönyvet.

Mindent összehozni

A teljes MainActivity osztályfájlnak így kell kinéznie, miután az összes különböző elem össze lett állítva. Hivatkozásként használhatja, hogy összehasonlítsa a saját fájlját, és észlelje, hogy van-e még különbség.

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>

}

Következő lépések

Ebben az oktatóanyagban megismerkedett egy új Android-alkalmazás létrehozásával, amely integrálja az ARCore funkcióit az Azure Spatial Anchors szolgáltatással. Az Azure Spatial Anchors-kódtárról további információt a horgonyok létrehozásáról és megkereséséről szóló útmutatónkban talál.