Öğretici: Azure Spatial Anchors kullanarak yeni bir Android uygulaması oluşturmak için adım adım yönergeler

Bu öğreticide ARCore işlevselliğini Azure Spatial Anchors ile tümleştiren yeni bir Android uygulamasının nasıl oluşturulacağı gösterilir.

Önkoşullar

Bu öğreticiyi tamamlamak için şunlar sahip olduğunuzdan emin olun:

Başlarken

Android Studio'yu başlatın. Android Studio'ya Hoş Geldiniz penceresinde Yeni bir Android Studio projesi başlat'a tıklayın.

  1. Dosya-Yeni> Proje'yi seçin.
  2. Yeni Proje Oluştur penceresinde, Telefon ve Tablet bölümünün altında Boş Etkinlik'i seçin ve İleri'ye tıklayın.
  3. Yeni Proje - Boş Etkinlik penceresinde aşağıdaki değerleri değiştirin:
    • Ad, Paket adı ve Kaydetme konumunu istediğiniz değerlerle değiştirin
    • Dili şu şekilde ayarla:Java
    • En Düşük API düzeyini olarak ayarlayın API 26: Android 8.0 (Oreo)
    • Diğer seçenekleri olduğu gibi bırakın
    • Finish (Son) düğmesine tıklayın.
  4. Bileşen Yükleyicisi çalışır. Biraz işlemden sonra Android Studio IDE'yi açar.

Android Studio - New Project

Deneme

Yeni uygulamanızı test etmek için geliştirici özellikli cihazınızı bir USB kablosuyla geliştirme makinenize bağlayın. Android Studio'nun sağ üst kısmında bağlı cihazınızı seçin ve Çalıştır 'uygulama' simgesine tıklayın. Android Studio, uygulamayı bağlı cihazınıza yükler ve başlatır. Artık cihazınızda çalışan uygulamada "Merhaba Dünya!" ifadesini görmeniz gerekir. Çalıştır-Durdur> 'uygulama' seçeneğine tıklayın. Android Studio - Run

ARCore'un tümleştirilmesi

ARCore , Google'ın Artırılmış Gerçeklik deneyimleri oluşturmaya yönelik platformudur ve cihazınızın hareket ettiği ve gerçek dünya hakkında kendi anlayışını oluşturması için konumunu izlemesini sağlar.

Kök <manifest> düğüme aşağıdaki girdileri dahil etmek için değiştirinapp\manifests\AndroidManifest.xml. Bu kod parçacığı birkaç şey yapar:

  • Uygulamanızın cihaz kameranıza erişmesine izin verir.
  • Ayrıca uygulamanızın yalnızca Google Play Store'da ARCore destekleyen cihazlara görünür olmasını sağlar.
  • Google Play Store'u arcore'u indirecek ve yükleyecek şekilde yapılandırır( henüz yüklü değilse, uygulamanız yüklendiğinde).
<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>

Aşağıdaki girdiyi içerecek şekilde değiştirin Gradle Scripts\build.gradle (Module: app) . Bu kod, uygulamanızın ARCore sürüm 1.25'i hedeflemesini sağlar. Bu değişiklikten sonra Gradle'dan eşitlemenizi isteyen bir bildirim alabilirsiniz: Şimdi eşitle'ye tıklayın.

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

Sahne Formunu Tümleştirme

Sceneform , OpenGL öğrenmek zorunda kalmadan Artırılmış Gerçeklik uygulamalarında gerçekçi 3B sahneleri işlemeyi kolaylaştırır.

Aşağıdaki girdileri içerecek şekilde değiştirin Gradle Scripts\build.gradle (Module: app) . Bu kod, uygulamanızın Java 8'den dil yapılarını kullanmasına olanak sağlar ve bu Sceneform da gerektirir. Ayrıca uygulamanızın sürüm 1.15'i hedeflediğinden Sceneform de emin olur. Bu değişiklikten sonra Gradle'dan eşitlemenizi isteyen bir bildirim alabilirsiniz: Şimdi eşitle'ye tıklayın.

android {
    ...

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

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

öğesini app\res\layout\activity_main.xmlaçın ve mevcut Hello Wolrd <TextView ... /> öğesini aşağıdaki ArFragment ile değiştirin. Bu kod, kamera akışının ekranınızda görüntülenmesine neden olur ve ARCore'un hareket ettikçe cihazınızın konumunu izlemesini sağlar.

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

Dekont

Ana etkinliğinizin ham xml dosyasını görmek için Android Studio'nun sağ üst kısmındaki "Kod" veya "Böl" düğmesine tıklayın.

Bir kez daha doğrulamak için uygulamanızı cihazınıza yeniden dağıtabilirsiniz . Bu kez sizden kamera izinleri istenmelidir. Onayladıktan sonra kamera akışınızın ekranınızda işlendiğini görmeniz gerekir.

Bir nesneyi gerçek dünyaya yerleştirme

Uygulamanızı kullanarak bir nesne oluşturup yerleştirelim. İlk olarak, aşağıdaki içeri aktarmaları içine app\java\<PackageName>\MainActivityekleyin:

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;

Ardından sınıfınıza MainActivity aşağıdaki üye değişkenlerini ekleyin:

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

Ardından aşağıdaki kodu yönteminize app\java\<PackageName>\MainActivityonCreate() ekleyin. Bu kod, kullanıcının cihazınızdaki ekrana ne zaman dokunduğunu algılayan adlı handleTap()bir dinleyici bağlar. Dokunma, ARCore'un izlemesi tarafından zaten tanınan gerçek bir dünya yüzeyindeyse dinleyici çalışır.

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

Son olarak, her şeyi birbirine bağlayacak aşağıdaki handleTap() yöntemi ekleyin. Bir küre oluşturur ve dokunulduğu konuma yerleştirir. Küre başlangıçta siyah olacaktır, çünkü this.recommendedSessionProgress şu anda sıfır olarak ayarlanmıştır. Bu değer daha sonra ayarlanır.

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

Bir kez daha doğrulamak için uygulamanızı cihazınıza yeniden dağıtabilirsiniz . Bu kez, arcore'un ortamınızı tanımaya başlamasını sağlamak için cihazınızda gezinebilirsiniz. Ardından, siyah kürenizi istediğiniz yüzeyin üzerine getirmek ve oluşturmak için ekrana dokunun.

Yerel bir Azure Spatial Anchor ekleme

Aşağıdaki girdiyi içerecek şekilde değiştirin Gradle Scripts\build.gradle (Module: app) . Bu örnek kod parçacığı, Azure Spatial Anchors SDK sürüm 2.10.2'i hedefler. SDK sürüm 2.7.0'ın şu anda desteklenen en düşük sürüm olduğunu ve Azure Spatial Anchors'ın daha yeni sürümlerine başvurmanın da işe yaraması gerektiğini unutmayın. Azure Spatial Anchors SDK'sının en son sürümünü kullanmanızı öneririz. SDK sürüm notlarını burada bulabilirsiniz.

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

Azure Spatial Anchors SDK 2.10.0 veya üstünü hedef alıyorsanız, projenizin settings.gradle dosyasının depolar bölümüne aşağıdaki girdiyi ekleyin. Bu, SDK 2.10.0 veya üzeri için Azure Spatial Anchors Android paketlerini barındıran Maven paket akışının URL'sini içerir:

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

-New-Java> Sınıfı'ne sağ tıklayınapp\java\<PackageName>.> Ad'ı MyFirstApp olarak ayarlayın ve Sınıf'ı seçin. adlı MyFirstApp.java bir dosya oluşturulur. Aşağıdaki içeri aktarmayı ekleyin:

import com.microsoft.CloudServices;

Üst sınıfı olarak tanımlayın android.app.Application .

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

Ardından aşağıdaki kodu yeni MyFirstApp sınıfın içine ekleyerek Azure Spatial Anchors'ın uygulamanızın bağlamıyla başlatılmasını sağlayın.

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

Şimdi değiştirerek app\manifests\AndroidManifest.xml kök <application> düğüme aşağıdaki girdiyi ekleyin. Bu kod, oluşturduğunuz Uygulama sınıfını uygulamanıza bağlar.

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

içine app\java\<PackageName>\MainActivitygeri dönün ve içine aşağıdaki içeri aktarmaları ekleyin:

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;

Ardından sınıfınıza MainActivity aşağıdaki üye değişkenlerini ekleyin:

private float recommendedSessionProgress = 0f;

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

Şimdi sınıfınıza mainActivity aşağıdaki initializeSession() yöntemi ekleyelim. Çağrıldıktan sonra, uygulamanızın başlatılması sırasında bir Azure Spatial Anchors oturumu oluşturulmasını ve düzgün bir şekilde başlatılmasını sağlar. Bu kod, çağrı aracılığıyla ASA oturumuna geçirilen sceneview oturumunun cloudSession.setSession erken dönüş yaparak null olmamasını sağlar.

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

initializeSession() sceneView oturumu henüz ayarlanmamışsa (null isesceneView.getSession()) erken bir dönüş gerçekleştirebileceğinden, sceneView oturumu oluşturulduktan sonra ASA oturumlarının başlatıldığından emin olmak için bir onUpdate çağrısı ekleriz.

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

Şimdi ve scene_OnUpdate(...) yönteminizi yönteminize initializeSession()onCreate() bağlayalım. Ayrıca, kamera akışınızdaki karelerin işlenmek üzere Azure Spatial Anchors SDK'sına gönderilmesini sağlayacağız.

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

Son olarak aşağıdaki kodu yönteminize handleTap() ekleyin. Gerçek dünyaya yerleştirdiğimiz siyah küreye yerel bir Azure Spatial Anchor ekler.

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

Uygulamanızı bir kez daha yeniden dağıt . Cihazınızda hareket edin, ekrana dokunun ve siyah küre yerleştirin. Ancak bu kez kodunuz yerel bir Azure Spatial Anchor oluşturacak ve kürenize ekleyecek.

Devam etmeden önce, henüz sahip değilseniz hesap Tanımlayıcısı, Anahtar ve Etki Alanı bilgilerini almak için bir Azure Spatial Anchors hesabı oluşturmanız gerekir. Bunları almak için aşağıdaki bölümü izleyin.

Spatial Anchors kaynağı oluşturma

Azure portalına gidin.

Sol bölmede Kaynak oluştur'u seçin.

Spatial Anchors'ı aramak için arama kutusunu kullanın.

Screenshot showing the results of a search for Spatial Anchors.

Uzamsal Tutturucular'ı ve ardından Oluştur'u seçin.

Spatial Anchors Hesabı bölmesinde aşağıdakileri yapın:

  • Normal alfasayısal karakterleri kullanarak benzersiz bir kaynak adı girin.

  • Kaynağı eklemek istediğiniz aboneliği seçin.

  • Yeni oluştur'u seçerek bir kaynak grubu oluşturun. MyResourceGroup olarak adlandırın ve tamam'ı seçin.

    Kaynak grubu, web uygulamaları, veritabanları ve depolama hesapları gibi Azure kaynaklarının dağıtıldığı ve yönetildiği mantıksal bir kapsayıcıdır. Örneğin, daha sonra tek bir basit adımda kaynak grubun tamamını silmeyi seçebilirsiniz.

  • Kaynağın yerleştirildiği konumu (bölgeyi) seçin.

  • Kaynağı oluşturmaya başlamak için Oluştur'u seçin.

Screenshot of the Spatial Anchors pane for creating a resource.

Kaynak oluşturulduktan sonra Azure portalı dağıtımınızın tamamlandığını gösterir.

Screenshot showing that the resource deployment is complete.

Kaynağa git’i seçin. Artık kaynak özelliklerini görüntüleyebilirsiniz.

Kaynağın Hesap Kimliği değerini daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Screenshot of the resource properties pane.

Ayrıca kaynağın Hesap Etki Alanı değerini daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Screenshot showing the resource's account domain value.

Ayarlar altında Erişim Anahtarı'yı seçin. Birincil anahtar değeri olan Hesap Anahtarı'nı daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Screenshot of the Keys pane for the account.

Yerel bağlantınızı buluta yükleme

Azure Spatial Anchors hesabınızın Tanımlayıcısı, Anahtarı ve Etki Alanı'na sahip olduktan sonra içine aşağıdaki içeri aktarmaları ekleyebiliriz app\java\<PackageName>\MainActivity:

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;

Ardından sınıfınıza MainActivity aşağıdaki üye değişkenlerini ekleyin:

private boolean sessionInitialized = false;

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

Şimdi aşağıdaki kodu yönteminize initializeSession() ekleyin. İlk olarak, bu kod uygulamanızın Azure Spatial Anchors SDK'sının kamera akışınızdan kareleri toplarken kaydettiği ilerlemeyi izlemesine olanak sağlar. Olduğu gibi, kürenizin rengi orijinal siyahından griye dönüşmeye başlar. Ardından, yer işaretinizi buluta göndermek için yeterli çerçeve toplandıktan sonra beyaza döner. İkincisi, bu kod bulut arka ucuyla iletişim kurmak için gereken kimlik bilgilerini sağlar. Burada uygulamanızı hesap Tanımlayıcınızı, Anahtarınızı ve Etki Alanınızı kullanacak şekilde yapılandıracaksınız. Spatial Anchors kaynağını ayarlarken bunları bir metin düzenleyicisine kopyalamıştınız.

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

Ardından aşağıdaki yöntemi sınıfınıza mainActivity ekleyinuploadCloudAnchorAsync(). Çağrıldıktan sonra, bu yöntem cihazınızdan yeterli kare toplanana kadar zaman uyumsuz olarak bekler. Böyle bir durumda kürenizin rengini sarıya dönüştürecek ve ardından yerel Azure Spatial Anchor'ınızı buluta yüklemeye başlayacaktır. Karşıya yükleme tamamlandıktan sonra kod bir bağlantı tanımlayıcısı döndürür.

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

Son olarak, her şeyi birbirine bağlayalım. yönteminize handleTap() aşağıdaki kodu ekleyin. Küreniz oluşturulur oluşturulmaz yönteminizi uploadCloudAnchorAsync() çağırır. Yöntem döndürdüğünde aşağıdaki kod kürenizde son bir güncelleştirme gerçekleştirerek rengini mavi olarak değiştirir.

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

Uygulamanızı bir kez daha yeniden dağıt . Cihazınızda gezinin, ekrana dokunun ve kürenizi yerleştirin. Ancak bu kez, kamera çerçeveleri toplandığında küreniz rengini siyahtan beyaza değiştirir. Yeterli kareye sahip olduktan sonra küre sarıya döner ve buluta yükleme başlar. Telefonunuzun İnternet'e bağlı olduğundan emin olun. Karşıya yükleme tamamlandıktan sonra küreniz maviye döner. İsteğe bağlı olarak, uygulamanızın Logcat gönderdiği günlük iletilerini görüntülemek için Android Studio'daki pencereyi izleyebilirsiniz. Günlüğe kaydedilecek iletilere örnek olarak kare yakalama sırasındaki oturum ilerleme durumu ve karşıya yükleme tamamlandıktan sonra bulutun döndürdüğü bağlantı tanımlayıcısı verilebilir.

Dekont

değerini recommendedSessionProgress görmüyorsanız (hata ayıklama günlüklerinizde olarak Session progressadlandırılır) değiştirin ve telefonunuzu yerleştirdiğiniz kürenin etrafında hareket ettirdiğinizden ve döndürdiğinizden emin olun.

Bulut uzamsal bağlantınızı bulma

Bağlantınız buluta yüklendikten sonra yeniden konumlandırmaya hazırız. İlk olarak aşağıdaki içeri aktarmaları kodunuza ekleyelim.

import java.util.concurrent.Executors;

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

Ardından aşağıdaki kodu yönteminize handleTap() ekleyelim. Bu kod:

  • Mevcut mavi küremizi ekrandan kaldırın.
  • Azure Spatial Anchors oturumumuzu yeniden başlatın. Bu eylem, bulacağımız tutturucunun oluşturduğumuz yerel bağlantı yerine buluttan gelmesini sağlar.
  • Buluta yüklediğimiz yer işareti için bir sorgu oluşturun.
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;
                                }
                            });
                });
            });
}

Şimdi sorguladığımız yer işareti bulunduğunda çağrılacak kodu bağlayalım. Yönteminizin initializeSession() içine aşağıdaki kodu ekleyin. Bu kod parçacığı, bulut uzamsal yer işareti yerleştirildikten sonra yeşil bir küre oluşturacak ve yerleştirecektir. Ayrıca ekran dokunmayı yeniden etkinleştirir, böylece senaryonun tamamını bir kez daha tekrarlayabilirsiniz: başka bir yerel bağlantı oluşturun, karşıya yükleyin ve yeniden bulun.

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

İşte hepsi bu! Senaryonun tamamını uçtan uca denemek için uygulamanızı son bir kez yeniden dağıtın. Cihazınızda hareket edin ve siyah kürenizi yerleştirin. Ardından, küre sarıya dönene kadar kamera karelerini yakalamak için cihazınızı hareket ettirmeye devam edin. Yerel bağlantınız karşıya yüklenir ve küreniz maviye döner. Son olarak, yerel bağlantınızın kaldırılması için ekranınıza bir kez daha dokunun ve ardından bulut karşılık gelenini sorgulayacağız. Bulut uzamsal bağlantı noktası bulunana kadar cihazınızı hareket ettirmeye devam edin. Doğru konumda yeşil bir küre görünmelidir ve tüm senaryoyu yeniden durulayabilir ve tekrarlayabilirsiniz.

Her şeyi bir araya getirmek

Tüm farklı öğeler bir araya getirildikten sonra tam MainActivity sınıf dosyasının nasıl görüneceği aşağıda verilmiştir. Bunu, kendi dosyanızla karşılaştırmak için başvuru olarak kullanabilir ve farklarınız varsa noktayı belirleyebilirsiniz.

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>

}

Sonraki adımlar

Bu öğreticide ARCore işlevselliğini Azure Spatial Anchors ile tümleştiren yeni bir Android uygulamasının nasıl oluşturulacağını gördünüz. Azure Spatial Anchors kitaplığı hakkında daha fazla bilgi edinmek için yer işaretleri oluşturma ve bulma kılavuzumuza geçin.