Como criar e localizar âncoras usando as Âncoras Espaciais do Azure no Java

Âncoras Espaciais do Azure permitem que você compartilhe âncoras no mundo entre diferentes dispositivos. É compatível com vários ambientes de desenvolvimento diferentes. Neste artigo, nós nos aprofundaremos em como usar o SDK das Âncoras Espaciais do Azure no Java para:

  • Configurar e gerenciar corretamente uma sessão das Âncoras Espaciais do Azure.
  • Criar e definir propriedades em âncoras locais.
  • Fazer upload delas na nuvem.
  • Localizar e excluir âncoras espaciais na nuvem.

Pré-requisitos

Para concluir este guia, verifique se você:

Cross Platform

Inicializar a sessão

O ponto de entrada principal para o SDK é a classe que representa sua sessão. Normalmente, você vai declarar um campo na classe que gerencia sua exibição e a sessão do RA nativo.

Saiba mais sobre a classe CloudSpatialAnchorSession.

    private CloudSpatialAnchorSession mCloudSession;
    // In your view handler
    mCloudSession = new CloudSpatialAnchorSession();

Configurar a autenticação

Para acessar o serviço, você precisa fornecer uma chave de conta, um token de acesso ou um token de autenticação do Microsoft Entra. Leia também mais sobre isso na página sobre o conceito de autenticação.

Chaves de conta

Chaves de conta são uma credencial que permite que o aplicativo seja autenticado no serviço Âncoras Espaciais do Azure. A finalidade pretendida das chaves de conta é ajudar você com uma rápida introdução. Especialmente durante a fase de desenvolvimento da integração do aplicativo às Âncoras Espaciais do Azure. Assim, você pode usar chaves de conta inserindo-as em seus aplicativos cliente durante o desenvolvimento. À medida que você avança além do desenvolvimento, é altamente recomendável migrar para um mecanismo de autenticação que seja de nível de produção, que dê suporte a tokens de acesso ou à autenticação de usuário do Microsoft Entra. Para obter uma chave de conta para desenvolvimento, visite a sua conta das Âncoras Espaciais do Azure e navegue até a guia "Chaves".

Saiba mais sobre a classe SessionConfiguration.

    mCloudSession.getConfiguration().setAccountKey("MyAccountKey");

Tokens de acesso

Tokens de acesso são um método mais robusto para autenticar com Âncoras Espaciais do Azure. Isso é válido especialmente à medida que você prepara seu aplicativo para uma implantação de produção. O resumo dessa abordagem é configurar um serviço de back-end com o qual seu aplicativo cliente possa se autenticar com segurança. O serviço de back-end interage com o AAD em runtime e com o serviço de token de Âncoras Espaciais do Azure para solicitar um token de acesso. Esse token é então entregue ao aplicativo cliente e usado no SDK para autenticar com as Âncoras Espaciais do Azure.

    mCloudSession.getConfiguration().setAccessToken("MyAccessToken");

Se um token de acesso não estiver definido, você deverá manipular o evento TokenRequired ou implementar o método tokenRequired no protocolo delegado.

Você pode manipular o evento de maneira síncrona definindo a propriedade nos argumentos do evento.

Saiba mais sobre a interface TokenRequiredListener.

    mCloudSession.addTokenRequiredListener(args -> {
        args.setAccessToken("MyAccessToken");
    });

Se precisar executar o trabalho assíncrono em seu manipulador, você poderá adiar a configuração do token solicitando um objeto deferral e depois concluindo-o, como no exemplo a seguir.

    mCloudSession.addTokenRequiredListener(args -> {
        CloudSpatialAnchorSessionDeferral deferral = args.getDeferral();
        MyGetTokenAsync(myToken -> {
            if (myToken != null) args.setAccessToken(myToken);
            deferral.complete();
        });
    });

autenticação do Microsoft Entra

As Âncoras Espaciais do Azure também permitem que os aplicativos se autentiquem com tokens do Microsoft Entra ID (Active Directory). Por exemplo, você pode usar tokens do Microsoft Entra para se integrar ao serviço Âncoras Espaciais do Azure. Se uma empresa mantiver usuários no Microsoft Entra ID, você poderá fornecer um token de usuário do Microsoft Entra no SDK do Âncoras Espaciais do Azure. Isso permite que você se autentique diretamente no serviço Âncoras Espaciais do Azure para uma conta que faz parte do mesmo locatário do Microsoft Entra.

    mCloudSession.getConfiguration().setAuthenticationToken("MyAuthenticationToken");

Assim como acontece com tokens de acesso, se um token do Microsoft Entra não estiver definido, você deverá manipular o evento TokenRequired ou implementar o método tokenRequired no protocolo delegado.

Você pode manipular o evento de maneira síncrona definindo a propriedade nos argumentos do evento.

    mCloudSession.addTokenRequiredListener(args -> {
        args.setAuthenticationToken("MyAuthenticationToken");
    });

Se precisar executar o trabalho assíncrono em seu manipulador, você poderá adiar a configuração do token solicitando um objeto deferral e depois concluindo-o, como no exemplo a seguir.

    mCloudSession.addTokenRequiredListener(args -> {
        CloudSpatialAnchorSessionDeferral deferral = args.getDeferral();
        MyGetTokenAsync(myToken -> {
            if (myToken != null) args.setAuthenticationToken(myToken);
            deferral.complete();
        });
    });

Configurar a sessão

Invoque Start() para habilitar sua sessão a processar os dados do ambiente.

Para manipular eventos gerados pela sua sessão, anexe um manipulador de eventos.

    mCloudSession.setSession(mSession);
    mCloudSession.start();

Fornecer quadros para a sessão

A sessão de âncora espacial funciona pelo mapeamento do espaço em torno do usuário. Fazer isso ajuda a determinar o local em que se encontram as âncoras. As plataformas móveis (iOS e Android) exigem uma chamada nativa ao feed da câmera para obter quadros da biblioteca RA da plataforma. Por outro lado, o HoloLens examina constantemente o ambiente e, portanto, não há necessidade de uma chamada específica como em plataformas móveis.

    mCloudSession.processFrame(mSession.update());

Fornecer comentários ao usuário

Você pode escrever um código para processar o evento atualizado da sessão. Esse evento é acionado toda vez que a sessão aprimora a compreensão do ambiente em que você está. Essa ação permite que você:

  • Use a classe UserFeedback para fornecer comentários ao usuário à medida que o dispositivo for movido e a sessão atualizar a compreensão do ambiente. Para fazer isso,
  • Determine em que ponto há dados espaciais controlados suficientes para criar âncoras espaciais. Determine isso com ReadyForCreateProgress ou RecommendedForCreateProgress. Depois que ReadyForCreateProgress estiver acima de 1, teremos dados suficientes para salvar uma âncora espacial de nuvem, embora recomendemos aguardar até que RecommendedForCreateProgress esteja acima de 1 para fazer isso.

Saiba mais sobre a interface SessionUpdatedListener.

    mCloudSession.addSessionUpdatedListener(args -> {
        auto status = args->Status();
        if (status->UserFeedback() == SessionUserFeedback::None) return;
        NumberFormat percentFormat = NumberFormat.getPercentInstance();
        percentFormat.setMaximumFractionDigits(1);
        mFeedback = String.format("Feedback: %s - Recommend Create=%s",
            FeedbackToString(status.getUserFeedback()),
            percentFormat.format(status.getRecommendedForCreateProgress()));
    });

Criar uma âncora espacial de nuvem

Para criar uma âncora espacial de nuvem, primeiro crie uma âncora no sistema RA da plataforma e, em seguida, crie um equivalente na nuvem. Use o método CreateAnchorAsync().

Saiba mais sobre a classe CloudSpatialAnchor.

    // Create a local anchor, perhaps by hit-testing and creating an ARAnchor
    Anchor localAnchor = null;
    List<HitResult> hitResults = mSession.update().hitTest(0.5f, 0.5f);
    for (HitResult hit : hitResults) {
        Trackable trackable = hit.getTrackable();
        if (trackable instanceof Plane) {
            if (((Plane) trackable).isPoseInPolygon(hit.getHitPose())) {
                localAnchor = hit.createAnchor();
                break;
            }
        }
    }

    // If the user is placing some application content in their environment,
    // you might show content at this anchor for a while, then save when
    // the user confirms placement.
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(localAnchor);
    Future createAnchorFuture = mCloudSession.createAnchorAsync(cloudAnchor);
    CheckForCompletion(createAnchorFuture, cloudAnchor);

    // ...

    private void CheckForCompletion(Future createAnchorFuture, CloudSpatialAnchor cloudAnchor) {
        new android.os.Handler().postDelayed(() -> {
            if (createAnchorFuture.isDone()) {
                try {
                    createAnchorFuture.get();
                    mFeedback = String.format("Created a cloud anchor with ID=%s", cloudAnchor.getIdentifier());
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Save Failed:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Save Failed:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion(createAnchorFuture, cloudAnchor);
            }
        }, 500);
    }

Conforme descrito anteriormente, você precisará ter dados do ambiente suficientes capturados antes de tentar criar uma âncora espacial de nuvem. Isso significa que ReadyForCreateProgress precisa estar acima de 1, embora recomendemos aguardar até que RecommendedForCreateProgress esteja acima de 1 para fazer isso.

    Future<SessionStatus> sessionStatusFuture = mCloudSession.getSessionStatusAsync();
    CheckForCompletion(sessionStatusFuture);

    // ...

    private void CheckForCompletion(Future<SessionStatus> sessionStatusFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (sessionStatusFuture.isDone()) {
                try {
                    SessionStatus value = sessionStatusFuture.get();
                    if (value.getRecommendedForCreateProgress() < 1.0f) return;
                    // Issue the creation request...
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Session status error:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Session status error:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion(sessionStatusFuture);
            }
        }, 500);
    }

Definir propriedades

Você pode optar por adicionar algumas propriedades ao salvar suas âncoras espaciais de nuvem. Como o tipo de objeto que está sendo salvo ou as propriedades básicas, como se elas devem ser habilitadas para interação. Fazer isso pode ser útil na descoberta: você pode renderizar imediatamente o objeto para o usuário, por exemplo, um quadro de imagem com o conteúdo em branco. Em seguida, um download diferente em segundo plano obtém detalhes de estado adicionais, por exemplo, a imagem a ser exibida no quadro.

    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(localAnchor);
    Map<String,String> properties = cloudAnchor.getAppProperties();
    properties.put("model-type", "frame");
    properties.put("label", "my latest picture");
    Future createAnchorFuture = mCloudSession.createAnchorAsync(cloudAnchor);
    // ...

Atualizar as propriedades

Para atualizar as propriedades em uma âncora, use o método UpdateAnchorProperties(). Se dois ou mais dispositivos tentarem atualizar as propriedades para a mesma âncora ao mesmo tempo, usaremos um modelo de simultaneidade otimista. Ou seja, a primeira gravação ganhará. Todas as outras gravações receberão um erro de "Simultaneidade": uma atualização das propriedades será necessária antes que seja possível tentar novamente.

    CloudSpatialAnchor anchor = /* locate your anchor */;
    anchor.getAppProperties().put("last-user-access", "just now");
    Future updateAnchorPropertiesFuture = mCloudSession.updateAnchorPropertiesAsync(anchor);
    CheckForCompletion(updateAnchorPropertiesFuture);

    // ...

    private void CheckForCompletion(Future updateAnchorPropertiesFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (updateAnchorPropertiesFuture.isDone()) {
                try {
                    updateAnchorPropertiesFuture.get();
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Updating Properties Failed:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Updating Properties Failed:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion1(updateAnchorPropertiesFuture);
            }
        }, 500);
    }

Não é possível atualizar o local de uma âncora após ela ter sido criada no serviço: você precisa criar uma âncora e excluir a antiga para acompanhar uma nova posição.

Se você não precisar localizar uma âncora para atualizar as propriedades dela, poderá usar o método GetAnchorPropertiesAsync(), que retorna um objeto CloudSpatialAnchor com propriedades.

    Future<CloudSpatialAnchor> getAnchorPropertiesFuture = mCloudSession.getAnchorPropertiesAsync("anchorId");
    CheckForCompletion(getAnchorPropertiesFuture);

    // ...

    private void CheckForCompletion(Future<CloudSpatialAnchor> getAnchorPropertiesFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (getAnchorPropertiesFuture.isDone()) {
                try {
                    CloudSpatialAnchor anchor = getAnchorPropertiesFuture.get();
                    if (anchor != null) {
                        anchor.getAppProperties().put("last-user-access", "just now");
                        Future updateAnchorPropertiesFuture = mCloudSession.updateAnchorPropertiesAsync(anchor);
                        // ...
                    }
                } catch (InterruptedException e) {
                    mFeedback = String.format("Getting Properties Failed:%s", e.getMessage());
                } catch (ExecutionException e) {
                    mFeedback = String.format("Getting Properties Failed:%s", e.getMessage());
                }
            } else {
                CheckForCompletion(getAnchorPropertiesFuture);
            }
        }, 500);
    }

Definir a validade

Também é possível configurar sua âncora para expirar automaticamente em uma determinada data no futuro. Quando uma âncora expirar, ela não será mais localizada nem atualizada. A expiração só pode ser definida quando a âncora é criada, antes de salvá-la na nuvem. Não é possível atualizar a expiração posteriormente. Se nenhuma expiração for definida durante a criação da âncora, a âncora só expirará quando excluída manualmente.

    Date now = new Date();
    Calendar cal = Calendar.getInstance();
    cal.setTime(now);
    cal.add(Calendar.DATE, 7);
    Date oneWeekFromNow = cal.getTime();
    cloudAnchor.setExpiration(oneWeekFromNow);

Localizar uma âncora espacial de nuvem

Ser capaz de localizar uma âncora espacial de nuvem salva anteriormente é um dos principais motivos para usar as Âncoras Espaciais do Azure. Para isso, estamos usando "Observadores". Você só pode usar um Observador por vez; não há suporte para vários Observadores. Há várias maneiras diferentes (também conhecidas como Estratégias de Localização de Âncora) para um Observador localizar uma âncora espacial de nuvem. Você pode usar uma estratégia em um observador por vez.

  • Localizar âncoras por identificador.
  • Localizar âncoras conectadas a uma âncora localizada anteriormente. Saiba mais sobre as relações entre âncoras aqui.
  • Localizar âncora usando a Relocalização grosseira.

Observação

Cada vez que você localizar uma âncora, as Âncoras Espaciais do Azure tentarão usar os dados do ambiente coletados para aumentar as informações visuais na âncora. Se você tiver problemas para localizar uma âncora, poderá ser útil criar uma âncora e, em seguida, localizá-la várias vezes de diferentes ângulos e condições de iluminação.

Se estiver localizando âncoras espaciais de nuvem por identificador, você poderá armazenar o identificador da âncora espacial de nuvem no serviço de back-end do seu aplicativo e torná-lo acessível para todos os dispositivos que puderem se autenticar a ele corretamente. Para ver um exemplo disso, confira Tutorial: Compartilhar âncoras espaciais entre dispositivos.

Crie uma instância de um objeto AnchorLocateCriteria, defina os identificadores que está procurando e invoque o método CreateWatcher na sessão fornecendo seu AnchorLocateCriteria.

    AnchorLocateCriteria criteria = new AnchorLocateCriteria();
    criteria.setIdentifiers(new String[] { "id1", "id2", "id3" });
    mCloudSession.createWatcher(criteria);

Depois que o observador for criado, o evento AnchorLocated será acionado para cada âncora solicitada. Esse evento é acionado quando uma âncora é localizada ou se a âncora não puder ser localizada. Se essa situação ocorrer, o motivo será indicado no status. Após todas as âncoras de um observador serem processadas, sejam elas encontradas ou não, o evento LocateAnchorsCompleted será acionado. Há um limite de 35 identificadores por observador.

Saiba mais sobre a interface AnchorLocatedListener.

    mCloudSession.addAnchorLocatedListener(args -> {
        switch (args.getStatus()) {
            case Located:
                CloudSpatialAnchor foundAnchor = args.getAnchor();
                // Go add your anchor to the scene...
                break;
            case AlreadyTracked:
                // This anchor has already been reported and is being tracked
                break;
            case NotLocatedAnchorDoesNotExist:
                // The anchor was deleted or never existed in the first place
                // Drop it, or show UI to ask user to anchor the content anew
                break;
            case NotLocated:
                // The anchor hasn't been found given the location data
                // The user might in the wrong location, or maybe more data will help
                // Show UI to tell user to keep looking around
                break;
        }
    });

Excluir âncoras

Excluir âncoras quando elas não forem mais usadas é uma boa prática para incluir no início do seu processo e práticas de desenvolvimento para manter os recursos do Azure limpos.

    Future deleteAnchorFuture = mCloudSession.deleteAnchorAsync(cloudAnchor);
    // Perform any processing you may want when delete finishes (deleteAnchorFuture is done)

Pausar, redefinir ou interromper a sessão

Para interromper a sessão temporariamente, você pode invocar Stop(). Isso interromperá inspetores e o processamento de ambiente, mesmo se você invocar ProcessFrame(). Em seguida, você pode invocar Start() para retomar o processamento. Ao retomar, os dados de ambiente já capturados na sessão são mantidos.

    mCloudSession.stop();

Para redefinir os dados de ambiente que foram capturados em sua sessão, você pode invocar Reset().

    mCloudSession.reset();

Para realizar a limpeza corretamente após uma sessão, invoque close().

    mCloudSession.close();

Próximas etapas

Neste guia, você aprendeu sobre como criar e localizar âncoras usando o SDK das Âncoras Espaciais do Azure. Para saber mais sobre os relacionamentos de âncora, vá para o próximo guia.