Aller au contenu

Démarrage rapide

Créez votre première expérience de navigation intérieure en quelques minutes ! Ce guide vous accompagnera dans la création d'une application de navigation de base utilisant MINE - Moteur de Navigation Intérieure, de l'initialisation à l'affichage d'une carte 3D interactive.

Avant de commencer

Assurez-vous d'avoir complété les étapes d'installation avant de suivre ce guide.


Ce que vous allez créer

À la fin de ce guide, vous aurez une application Android entièrement fonctionnelle qui :

  • ✅ Initialise le Moteur de Navigation Intérieure
  • ✅ Affiche une carte 3D intérieure interactive
  • ✅ Gère les événements du cycle de vie de la carte
  • ✅ Implémente des contrôles caméra de base
  • ✅ Charge des données de carte personnalisées

Temps estimé : 10-15 minutes


Étape 1 : Configurer votre Activity

Tout d'abord, créez une Activity qui hébergera le moteur de navigation. Nous utiliserons MineSceneView comme composant principal pour afficher les cartes.

package com.example.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.machinestalk.indoornavigationengine.ui.MineSceneView

class NavigationActivity : AppCompatActivity() {

    private lateinit var sceneView: MineSceneView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialiser la vue de scène
        sceneView = MineSceneView(this)
        setContentView(sceneView)
    }

    override fun onResume() {
        super.onResume()
        sceneView.onResume()
    }

    override fun onPause() {
        super.onPause()
        sceneView.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        sceneView.onDestroy()
    }
}
package com.example.myapp;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.machinestalk.indoornavigationengine.ui.MineSceneView;

public class NavigationActivity extends AppCompatActivity {

    private MineSceneView sceneView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialiser la vue de scène
        sceneView = new MineSceneView(this);
        setContentView(sceneView);
    }

    @Override
    protected void onResume() {
        super.onResume();
        sceneView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        sceneView.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        sceneView.onDestroy();
    }
}

Gestion du cycle de vie

Une bonne gestion du cycle de vie de MineSceneView est cruciale pour des performances optimales et pour éviter les fuites mémoire. Appelez toujours onResume(), onPause(), et onDestroy() dans les méthodes correspondantes du cycle de vie de l'Activity.


Étape 2 : Charger une carte

Maintenant, chargeons une carte intérieure. Le moteur prend en charge divers formats de carte, y compris les modèles glTF/GLB.

import com.machinestalk.indoornavigationengine.models.MapData
import com.machinestalk.indoornavigationengine.util.MapLoader

class NavigationActivity : AppCompatActivity() {

    private lateinit var sceneView: MineSceneView
    private lateinit var mapLoader: MapLoader

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        sceneView = MineSceneView(this)
        setContentView(sceneView)

        // Initialiser le chargeur de carte
        mapLoader = MapLoader(sceneView)

        // Charger la carte depuis les assets
        loadMap()
    }

    private fun loadMap() {
        val mapPath = "maps/my_venue.glb" // Chemin vers votre fichier de carte dans assets

        mapLoader.loadFromAssets(
            context = this,
            assetPath = mapPath,
            onSuccess = { mapData ->
                // Carte chargée avec succès
                setupCamera(mapData)
                showToast("Carte chargée avec succès !")
            },
            onError = { error ->
                // Gérer l'erreur
                showToast("Échec du chargement de la carte : ${error.message}")
            }
        )
    }

    private fun setupCamera(mapData: MapData) {
        // Positionner la caméra pour voir toute la carte
        sceneView.camera.apply {
            lookAt(
                eye = floatArrayOf(0f, 10f, 10f),
                center = floatArrayOf(0f, 0f, 0f),
                up = floatArrayOf(0f, 1f, 0f)
            )
        }
    }

    private fun showToast(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}
import com.machinestalk.indoornavigationengine.models.MapData;
import com.machinestalk.indoornavigationengine.util.MapLoader;
import android.widget.Toast;

public class NavigationActivity extends AppCompatActivity {

    private MineSceneView sceneView;
    private MapLoader mapLoader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        sceneView = new MineSceneView(this);
        setContentView(sceneView);

        // Initialiser le chargeur de carte
        mapLoader = new MapLoader(sceneView);

        // Charger la carte depuis les assets
        loadMap();
    }

    private void loadMap() {
        String mapPath = "maps/my_venue.glb"; // Chemin vers votre fichier de carte dans assets

        mapLoader.loadFromAssets(
            this,
            mapPath,
            mapData -> {
                // Carte chargée avec succès
                setupCamera(mapData);
                showToast("Carte chargée avec succès !");
            },
            error -> {
                // Gérer l'erreur
                showToast("Échec du chargement de la carte : " + error.getMessage());
            }
        );
    }

    private void setupCamera(MapData mapData) {
        // Positionner la caméra pour voir toute la carte
        sceneView.getCamera().lookAt(
            new float[]{0f, 10f, 10f},  // eye
            new float[]{0f, 0f, 0f},    // center
            new float[]{0f, 1f, 0f}     // up
        );
    }

    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

Chargement de carte

Le moteur prend en charge le chargement de cartes depuis plusieurs sources, y compris les assets, le stockage local et les URL distantes. Consultez le Guide de chargement de carte pour plus de détails.


Étape 3 : Ajouter l'interaction utilisateur

Permettez aux utilisateurs d'interagir avec la carte via des gestes tactiles comme le panoramique, le zoom et la rotation.

import com.machinestalk.indoornavigationengine.components.CameraController
import com.machinestalk.indoornavigationengine.components.GestureHandler

class NavigationActivity : AppCompatActivity() {

    private lateinit var sceneView: MineSceneView
    private lateinit var gestureHandler: GestureHandler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        sceneView = MineSceneView(this)
        setContentView(sceneView)

        // Activer les contrôles gestuels
        setupGestureControls()
    }

    private fun setupGestureControls() {
        gestureHandler = GestureHandler(sceneView).apply {
            // Activer le geste de panoramique
            isPanEnabled = true

            // Activer le pincement pour zoomer
            isZoomEnabled = true

            // Activer la rotation
            isRotationEnabled = true

            // Définir les limites de zoom
            minZoom = 1.0f
            maxZoom = 10.0f

            // Ajouter un écouteur de geste
            setOnGestureListener(object : GestureHandler.OnGestureListener {
                override fun onSingleTap(x: Float, y: Float) {
                    // Gérer le toucher sur la carte
                    handleMapTap(x, y)
                }

                override fun onLongPress(x: Float, y: Float) {
                    // Gérer l'appui long
                    showContextMenu(x, y)
                }
            })
        }
    }

    private fun handleMapTap(x: Float, y: Float) {
        // Effectuer un test de collision pour détecter les objets touchés
        sceneView.hitTest(x, y) { result ->
            result?.let {
                // Afficher les informations sur l'emplacement touché
                showLocationInfo(it)
            }
        }
    }
}
import com.machinestalk.indoornavigationengine.components.GestureHandler;

public class NavigationActivity extends AppCompatActivity {

    private MineSceneView sceneView;
    private GestureHandler gestureHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        sceneView = new MineSceneView(this);
        setContentView(sceneView);

        // Activer les contrôles gestuels
        setupGestureControls();
    }

    private void setupGestureControls() {
        gestureHandler = new GestureHandler(sceneView);

        // Activer le geste de panoramique
        gestureHandler.setPanEnabled(true);

        // Activer le pincement pour zoomer
        gestureHandler.setZoomEnabled(true);

        // Activer la rotation
        gestureHandler.setRotationEnabled(true);

        // Définir les limites de zoom
        gestureHandler.setMinZoom(1.0f);
        gestureHandler.setMaxZoom(10.0f);

        // Ajouter un écouteur de geste
        gestureHandler.setOnGestureListener(new GestureHandler.OnGestureListener() {
            @Override
            public void onSingleTap(float x, float y) {
                // Gérer le toucher sur la carte
                handleMapTap(x, y);
            }

            @Override
            public void onLongPress(float x, float y) {
                // Gérer l'appui long
                showContextMenu(x, y);
            }
        });
    }

    private void handleMapTap(float x, float y) {
        // Effectuer un test de collision pour détecter les objets touchés
        sceneView.hitTest(x, y, result -> {
            if (result != null) {
                // Afficher les informations sur l'emplacement touché
                showLocationInfo(result);
            }
        });
    }
}

Étape 4 : Configurer l'affichage de la carte

Personnalisez l'apparence et le comportement de l'affichage de votre carte.

import com.machinestalk.indoornavigationengine.models.DisplayConfig

private fun configureMapDisplay() {
    sceneView.apply {
        // Définir la couleur d'arrière-plan
        setBackgroundColor(android.graphics.Color.WHITE)

        // Configurer la qualité de rendu
        displayConfig = DisplayConfig(
            antiAliasing = true,
            shadowsEnabled = true,
            ambientOcclusion = true,
            renderQuality = DisplayConfig.RenderQuality.HIGH
        )

        // Activer le compteur FPS pour le débogage
        showFpsCounter = BuildConfig.DEBUG

        // Définir les conditions d'éclairage
        lighting.apply {
            ambientLightIntensity = 0.5f
            directionalLightIntensity = 0.8f
            directionalLightDirection = floatArrayOf(0f, -1f, 0.5f)
        }
    }
}
import com.machinestalk.indoornavigationengine.models.DisplayConfig;

private void configureMapDisplay() {
    // Définir la couleur d'arrière-plan
    sceneView.setBackgroundColor(android.graphics.Color.WHITE);

    // Configurer la qualité de rendu
    DisplayConfig config = new DisplayConfig.Builder()
        .setAntiAliasing(true)
        .setShadowsEnabled(true)
        .setAmbientOcclusion(true)
        .setRenderQuality(DisplayConfig.RenderQuality.HIGH)
        .build();
    sceneView.setDisplayConfig(config);

    // Activer le compteur FPS pour le débogage
    sceneView.setShowFpsCounter(BuildConfig.DEBUG);

    // Définir les conditions d'éclairage
    sceneView.getLighting().setAmbientLightIntensity(0.5f);
    sceneView.getLighting().setDirectionalLightIntensity(0.8f);
    sceneView.getLighting().setDirectionalLightDirection(
        new float[]{0f, -1f, 0.5f}
    );
}

Exemple complet

Voici un exemple complet et prêt pour la production qui combine toutes les étapes :

package com.example.myapp

import android.graphics.Color
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.machinestalk.indoornavigationengine.components.GestureHandler
import com.machinestalk.indoornavigationengine.models.DisplayConfig
import com.machinestalk.indoornavigationengine.models.MapData
import com.machinestalk.indoornavigationengine.ui.MineSceneView
import com.machinestalk.indoornavigationengine.util.MapLoader

class NavigationActivity : AppCompatActivity() {

    private lateinit var sceneView: MineSceneView
    private lateinit var mapLoader: MapLoader
    private lateinit var gestureHandler: GestureHandler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialiser la vue de scène
        sceneView = MineSceneView(this)
        setContentView(sceneView)

        // Configurer l'affichage
        configureMapDisplay()

        // Configurer les interactions
        setupGestureControls()

        // Charger la carte
        mapLoader = MapLoader(sceneView)
        loadMap()
    }

    private fun configureMapDisplay() {
        sceneView.apply {
            setBackgroundColor(Color.parseColor("#F5F5F5"))

            displayConfig = DisplayConfig(
                antiAliasing = true,
                shadowsEnabled = true,
                ambientOcclusion = true,
                renderQuality = DisplayConfig.RenderQuality.HIGH
            )

            showFpsCounter = BuildConfig.DEBUG
        }
    }

    private fun setupGestureControls() {
        gestureHandler = GestureHandler(sceneView).apply {
            isPanEnabled = true
            isZoomEnabled = true
            isRotationEnabled = true
            minZoom = 1.0f
            maxZoom = 10.0f
        }
    }

    private fun loadMap() {
        val mapPath = "maps/shopping_mall.glb"

        mapLoader.loadFromAssets(
            context = this,
            assetPath = mapPath,
            onSuccess = { mapData ->
                setupCamera(mapData)
                Toast.makeText(this, "Carte chargée !", Toast.LENGTH_SHORT).show()
            },
            onError = { error ->
                Toast.makeText(
                    this, 
                    "Erreur : ${error.message}", 
                    Toast.LENGTH_LONG
                ).show()
            }
        )
    }

    private fun setupCamera(mapData: MapData) {
        sceneView.camera.lookAt(
            eye = floatArrayOf(0f, 15f, 15f),
            center = floatArrayOf(0f, 0f, 0f),
            up = floatArrayOf(0f, 1f, 0f)
        )
    }

    override fun onResume() {
        super.onResume()
        sceneView.onResume()
    }

    override fun onPause() {
        super.onPause()
        sceneView.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        sceneView.onDestroy()
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.machinestalk.indoornavigationengine.ui.MineSceneView
        android:id="@+id/sceneView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Optionnel : Ajouter une superposition de contrôles UI -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="16dp"
        android:orientation="horizontal"
        android:padding="8dp"
        android:background="@drawable/rounded_background">

        <Button
            android:id="@+id/btnZoomIn"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:text="+" />

        <Button
            android:id="@+id/btnZoomOut"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:text="-"
            android:layout_marginStart="8dp" />

    </LinearLayout>

</FrameLayout>

Tester votre application

Compilez et lancez votre application pour voir le moteur de navigation en action !

  1. Connectez un appareil ou démarrez un émulateur
  2. Compilez le projet : Build → Make Project
  3. Lancez l'application : Cliquez sur le bouton Run ou appuyez sur Shift + F10

Félicitations ! 🎉

Vous avez créé avec succès votre première application de navigation intérieure ! La carte devrait maintenant s'afficher avec un support gestuel complet.


Prochaines étapes

Maintenant que vous avez une application de navigation de base en fonctionnement, explorez ces fonctionnalités avancées :


Problèmes courants

La carte ne s'affiche pas ?

Vérifiez ces éléments :

  • Vérifiez que le fichier de carte existe dans votre dossier assets/maps/
  • Assurez-vous que le format de fichier est pris en charge (GLB, GLTF)
  • Consultez logcat pour les messages d'erreur
  • Vérifiez que les méthodes du cycle de vie sont correctement appelées
Les gestes tactiles ne fonctionnent pas ?

Solution : Assurez-vous que vous ne consommez pas les événements tactiles dans une vue parente. Vérifiez également que GestureHandler est correctement initialisé.

Problèmes de performance ?

Essayez ces optimisations :

  • Réduisez la RenderQuality à MEDIUM ou LOW
  • Désactivez les ombres ou l'occlusion ambiante
  • Optimisez la taille du fichier de votre modèle 3D
  • Testez sur un appareil physique plutôt qu'un émulateur

Pour plus d'aide au dépannage, consultez notre Guide de dépannage.


Ressources supplémentaires

Rejoignez notre communauté

Vous avez des questions ou souhaitez partager votre projet ? Connectez-vous avec d'autres développeurs utilisant MINE !