Crea esperienze di mappe 3D con la visualizzazione sovrapposta WebGL

1. Prima di iniziare

Questo codelab ti insegna a utilizzare le funzionalità basate su WebGL dell'API Maps JavaScript per controllare ed eseguire il rendering sulla mappa vettoriale in tre dimensioni.

Spilla 3D finale

Prerequisiti

Questo codelab presuppone una conoscenza intermedia di JavaScript e dell'API Maps JavaScript. Per scoprire le nozioni di base sull'utilizzo dell'API Maps JavaScript, prova il codelab Aggiungere una mappa al tuo sito web (JavaScript).

Obiettivi didattici

  • Generazione di un ID mappa con la mappa vettoriale per JavaScript abilitata.
  • Controllare la mappa con l'inclinazione e la rotazione programmatica.
  • Rendering di oggetti 3D sulla mappa con WebGLOverlayView e Three.js.
  • Animare i movimenti della videocamera con moveCamera.

Che cosa ti serve

  • Un account Google Cloud Platform con la fatturazione attivata
  • Una chiave API Google Maps Platform con l'API Maps JavaScript attivata
  • Conoscenza intermedia di JavaScript, HTML e CSS
  • Un editor di testo o un IDE a tua scelta
  • Node.js

2. Configurazione

Per il passaggio di attivazione riportato di seguito, devi abilitare l'API Maps JavaScript.

Configurare Google Maps Platform

Se non hai ancora un account Google Cloud Platform e un progetto con la fatturazione abilitata, consulta la guida Guida introduttiva a Google Maps Platform per creare un account di fatturazione e un progetto.

  1. Nella console Cloud, fai clic sul menu a discesa del progetto e seleziona il progetto che vuoi utilizzare per questo codelab.

  1. Abilita le API e gli SDK di Google Maps Platform richiesti per questo codelab in Google Cloud Marketplace. Per farlo, segui i passaggi descritti in questo video o in questa documentazione.
  2. Genera una chiave API nella pagina Credenziali di Cloud Console. Puoi seguire i passaggi descritti in questo video o in questa documentazione. Tutte le richieste a Google Maps Platform richiedono una chiave API.

Configurazione di Node.js

Se non lo hai ancora fatto, vai alla pagina https://nodejs.org/ per scaricare e installare il runtime Node.js sul computer.

Node.js include il gestore di pacchetti npm, che devi installare per le dipendenze di questo codelab.

Scaricare il modello di progetto iniziale

Prima di iniziare questo codelab, segui questi passaggi per scaricare il modello di progetto iniziale e il codice della soluzione completa:

  1. Scarica o crea una fork del repository GitHub per questo codelab all'indirizzo https://github.com/googlecodelabs/maps-platform-101-webgl/. Il progetto iniziale si trova nella directory /starter e include la struttura di base dei file necessaria per completare il codelab. Tutto ciò che ti serve per lavorare si trova nella directory /starter/src.
  2. Dopo aver scaricato il progetto iniziale, esegui npm install nella directory /starter. Vengono installate tutte le dipendenze necessarie elencate in package.json.
  3. Una volta installate le dipendenze, esegui npm start nella directory.

Il progetto iniziale è stato configurato per l'utilizzo di webpack-dev-server, che compila ed esegue il codice che scrivi localmente. webpack-dev-server ricarica automaticamente l'app nel browser ogni volta che apporti modifiche al codice.

Se vuoi vedere l'esecuzione del codice della soluzione completa, puoi completare i passaggi di configurazione riportati sopra nella directory /solution.

Aggiungere la chiave API

L'app iniziale include tutto il codice necessario per caricare la mappa con JS API Loader, quindi tutto ciò che devi fare è fornire la chiave API e l'ID mappa. JS API Loader è una semplice libreria che astrae il metodo tradizionale di caricamento dell'API Maps JavaScript in linea nel modello HTML con un tag script, consentendoti di gestire tutto nel codice JavaScript.

Per aggiungere la chiave API, procedi nel seguente modo nel progetto iniziale:

  1. Apri app.js.
  2. Nell'oggetto apiOptions, imposta la chiave API come valore di apiOptions.apiKey.

3. Generare e utilizzare un ID mappa

Per utilizzare le funzionalità basate su WebGL dell'API Maps JavaScript, devi disporre di un ID mappa con la mappa vettoriale abilitata.

Generare un ID mappa

Generazione dell'ID mappa

  1. Nella console Google Cloud, vai a "Google Maps Platform" > "Gestione mappe".
  2. Fai clic su "CREA NUOVO ID MAPPA".
  3. Nel campo "Nome mappa", inserisci un nome per l'ID mappa.
  4. Nel menu a discesa "Tipo di mappa", seleziona "JavaScript". Verrà visualizzata la sezione "Opzioni JavaScript".
  5. Nella sezione "Opzioni JavaScript", seleziona il pulsante di opzione "Vettore", la casella di controllo "Inclinazione" e la casella di controllo "Rotazione".
  6. Facoltativo. Nel campo "Descrizione", inserisci una descrizione per la chiave API.
  7. Fai clic sul pulsante "Avanti". Viene visualizzata la pagina "Dettagli ID mappa".

    Pagina Dettagli mappa
  8. Copia l'ID mappa. Lo utilizzerai nel passaggio successivo per caricare la mappa.

Utilizzare un ID mappa

Per caricare la mappa vettoriale, devi fornire un ID mappa come proprietà nelle opzioni quando crei l'istanza della mappa. Se vuoi, puoi anche fornire lo stesso ID mappa quando carichi l'API Maps JavaScript.

Per caricare la mappa con il tuo ID mappa:

  1. Imposta l'ID mappa come valore di mapOptions.mapId.

    Quando crei un'istanza della mappa, l'ID mappa indica a Google Maps Platform quale delle tue mappe caricare per una determinata istanza. Puoi riutilizzare lo stesso ID mappa in più app o in più visualizzazioni all'interno della stessa app.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

Controlla l'app in esecuzione nel browser. La mappa vettoriale con inclinazione e rotazione abilitate dovrebbe essere caricata correttamente. Per verificare se l'inclinazione e la rotazione sono attivate, tieni premuto il tasto Maiusc e trascina con il mouse o utilizza i tasti freccia della tastiera.

Se la mappa non viene caricata, verifica di aver fornito una chiave API valida in apiOptions. Se la mappa non si inclina e non ruota, verifica di aver fornito un ID mappa con inclinazione e rotazione attive in apiOptions e mapOptions.

Mappa inclinata

Il file app.js dovrebbe ora avere il seguente aspetto:

    import { Loader } from '@googlemaps/js-api-loader';

    const apiOptions = {
      "apiKey": 'YOUR_API_KEY',
    };

    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    }

    async function initMap() {
      const mapDiv = document.getElementById("map");
      const apiLoader = new Loader(apiOptions);
      await apiLoader.load();
      return new google.maps.Map(mapDiv, mapOptions);
    }

    function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      // WebGLOverlayView code goes here
    }

    (async () => {
      const map = await initMap();
    })();

4. Implementare WebGLOverlayView

WebGLOverlayView ti offre l'accesso diretto allo stesso contesto di rendering WebGL utilizzato per il rendering della mappa base vettoriale. Ciò significa che puoi eseguire il rendering di oggetti 2D e 3D direttamente sulla mappa utilizzando WebGL, nonché le librerie grafiche basate su WebGL più diffuse.

WebGLOverlayView espone cinque hook nel ciclo di vita del contesto di rendering WebGL della mappa che puoi utilizzare. Ecco una breve descrizione di ogni hook e del relativo utilizzo:

  • onAdd(): Chiamato quando l'overlay viene aggiunto a una mappa chiamando setMap su un'istanza WebGLOverlayView. Qui devi svolgere qualsiasi lavoro correlato a WebGL che non richiede l'accesso diretto al contesto WebGL.
  • onContextRestored(): chiamato quando il contesto WebGL diventa disponibile, ma prima che venga eseguito il rendering di qualsiasi elemento. Qui devi inizializzare gli oggetti, associare lo stato ed eseguire qualsiasi altra operazione che richieda l'accesso al contesto WebGL, ma che può essere eseguita al di fuori della chiamata onDraw(). In questo modo puoi configurare tutto ciò che ti serve senza aggiungere un sovraccarico eccessivo al rendering effettivo della mappa, che è già a uso intensivo della GPU.
  • onDraw(): chiamato una volta per frame quando WebGL inizia a eseguire il rendering della mappa e di qualsiasi altro elemento che hai richiesto. Dovresti svolgere il minor lavoro possibile in onDraw() per evitare problemi di prestazioni nel rendering della mappa.
  • onContextLost(): chiamato quando il contesto di rendering WebGL viene perso per qualsiasi motivo.
  • onRemove(): chiamato quando l'overlay viene rimosso dalla mappa chiamando setMap(null) su un'istanza WebGLOverlayView.

In questo passaggio, creerai un'istanza di WebGLOverlayView e implementerai tre dei relativi hook del ciclo di vita: onAdd, onContextRestored e onDraw. Per mantenere il codice pulito e più facile da seguire, tutto il codice per l'overlay verrà gestito nella funzione initWebGLOverlayView() fornita nel modello iniziale per questo codelab.

  1. Crea un'istanza WebGLOverlayView().

    L'overlay è fornito dall'API Maps JavaScript in google.maps.WebGLOverlayView. Per iniziare, crea un'istanza aggiungendo quanto segue a initWebGLOverlayView():
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Implementa hook del ciclo di vita.

    Per implementare gli hook del ciclo di vita, aggiungi quanto segue a initWebGLOverlayView():
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  3. Aggiungi l'istanza dell'overlay alla mappa.

    Ora chiama setMap() nell'istanza di overlay e passa la mappa aggiungendo quanto segue a initWebGLOverlayView():
    webGLOverlayView.setMap(map)
    
  4. Chiama il numero initWebGLOverlayView.

    L'ultimo passaggio consiste nell'eseguire initWebGLOverlayView() aggiungendo quanto segue alla funzione immediatamente richiamata nella parte inferiore di app.js:
    initWebGLOverlayView(map);
    

Il tuo initWebGLOverlayView e la funzione immediatamente richiamata dovrebbero ora essere simili a questo:

    async function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      const webGLOverlayView = new google.maps.WebGLOverlayView();

      webGLOverlayView.onAdd = () => {}
      webGLOverlayView.onContextRestored = ({gl}) => {}
      webGLOverlayView.onDraw = ({gl, transformer}) => {}
      webGLOverlayView.setMap(map);
    }

    (async () => {
      const map = await initMap();
      initWebGLOverlayView(map);
    })();

Questo è tutto ciò che ti serve per implementare WebGLOverlayView. Successivamente, configurerai tutto il necessario per visualizzare un oggetto 3D sulla mappa utilizzando Three.js.

5. Configurare una scena three.js

L'utilizzo di WebGL può essere molto complicato perché richiede di definire manualmente tutti gli aspetti di ogni oggetto e altro ancora. Per semplificare notevolmente le cose, in questo codelab utilizzerai Three.js, una popolare libreria grafica che fornisce un livello di astrazione semplificato sopra WebGL. Three.js offre un'ampia gamma di funzioni utili che fanno di tutto, dalla creazione di un renderer WebGL al disegno di forme di oggetti 2D e 3D comuni, al controllo di telecamere, trasformazioni di oggetti e molto altro ancora.

In Three.js esistono tre tipi di oggetti di base necessari per visualizzare qualsiasi elemento:

  • Scena: un "contenitore" in cui vengono visualizzati e sottoposti a rendering tutti gli oggetti, le sorgenti luminose, le texture e così via.
  • Fotocamera: una fotocamera che rappresenta il punto di vista della scena. Sono disponibili più tipi di videocamere e a una singola scena possono essere aggiunte una o più videocamere.
  • Renderer: un renderer che gestisce l'elaborazione e la visualizzazione di tutti gli oggetti nella scena. In Three.js, WebGLRenderer è il più utilizzato, ma alcuni altri sono disponibili come fallback nel caso in cui il client non supporti WebGL.

In questo passaggio, caricherai tutte le dipendenze necessarie per Three.js e configurerai una scena di base.

  1. Carica three.js

    Per questo codelab avrai bisogno di due dipendenze: la libreria Three.js e GLTF Loader, una classe che ti consente di caricare oggetti 3D nel formato GL Trasmission Format (gLTF). Three.js offre caricatori specializzati per molti formati di oggetti 3D diversi, ma è consigliabile utilizzare gLTF.

    Nel codice riportato di seguito, viene importata l'intera libreria Three.js. In un'app di produzione probabilmente vorrai importare solo le classi di cui hai bisogno, ma per questo codelab importa l'intera libreria per semplificare le cose. Tieni presente inoltre che GLTF Loader non è incluso nella libreria predefinita e deve essere importato da un percorso separato nella dipendenza. Questo è il percorso in cui puoi accedere a tutti i caricatori forniti da Three.js.

    Per importare Three.js e GLTF Loader, aggiungi quanto segue all'inizio di app.js:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. Crea una scena three.js.

    Per creare una scena, crea un'istanza della classe Scene di Three.js aggiungendo quanto segue all'hook onAdd:
    scene = new THREE.Scene();
    
  3. Aggiungi una videocamera alla scena.

    Come accennato in precedenza, la videocamera rappresenta la prospettiva di visualizzazione della scena e determina il modo in cui Three.js gestisce il rendering visivo degli oggetti all'interno di una scena. Senza una videocamera, la scena non viene "vista", il che significa che gli oggetti non vengono visualizzati perché non vengono renderizzati.

    Three.js offre una serie di diverse videocamere che influiscono sul modo in cui il renderer tratta gli oggetti rispetto a elementi come prospettiva e profondità. In questa scena, utilizzerai PerspectiveCamera, il tipo di videocamera più comunemente utilizzato in Three.js, progettato per emulare il modo in cui l'occhio umano percepirebbe la scena. Ciò significa che gli oggetti più lontani dalla videocamera appariranno più piccoli di quelli più vicini, la scena avrà un punto di fuga e così via.

    Per aggiungere una videocamera prospettica alla scena, aggiungi quanto segue all'hook onAdd:
    camera = new THREE.PerspectiveCamera();
    
    Con PerspectiveCamera, puoi anche configurare gli attributi che compongono il punto di vista, inclusi i piani vicino e lontano, le proporzioni e il campo visivo (FOV). Nel complesso, questi attributi costituiscono ciò che è noto come frustum di visualizzazione, un concetto importante da comprendere quando si lavora in 3D, ma al di fuori dell'ambito di questo codelab. La configurazione predefinita di PerspectiveCamera sarà sufficiente.
  4. Aggiungi fonti di luce alla scena.

    Per impostazione predefinita, gli oggetti visualizzati in una scena Three.js appaiono neri, indipendentemente dalle texture applicate. Questo perché una scena Three.js imita il comportamento degli oggetti nel mondo reale, dove la visibilità del colore dipende dalla luce che si riflette su un oggetto. In breve, niente luce, niente colore.

    Three.js fornisce una serie di diversi tipi di luci, di cui ne utilizzerai due:

  5. AmbientLight: fornisce una sorgente luminosa diffusa che illumina uniformemente tutti gli oggetti della scena da tutte le angolazioni. In questo modo, la scena avrà una quantità di luce di base per garantire che le texture di tutti gli oggetti siano visibili.
  6. DirectionalLight: fornisce una luce che proviene da una direzione della scena. A differenza di come funzionerebbe una luce posizionata nel mondo reale, i raggi luminosi che emanano da DirectionalLight sono tutti paralleli e non si diffondono man mano che si allontanano dalla sorgente luminosa.

    Puoi configurare il colore e l'intensità di ogni luce per creare effetti di illuminazione aggregati. Ad esempio, nel codice riportato di seguito, la luce ambientale fornisce una luce bianca soffusa per l'intera scena, mentre la luce direzionale fornisce una luce secondaria che colpisce gli oggetti con un'angolazione verso il basso. Nel caso della luce direzionale, l'angolo viene impostato utilizzando position.set(x, y ,z), dove ogni valore è relativo al rispettivo asse. Ad esempio, position.set(0,1,0) posizionerebbe la luce direttamente sopra la scena sull'asse Y, puntando verso il basso.

    Per aggiungere le sorgenti luminose alla scena, aggiungi quanto segue all'hook onAdd:
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
    

L'hook onAdd ora dovrebbe avere il seguente aspetto:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
      scene.add(ambientLight);
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);
    }

La scena è ora configurata e pronta per il rendering. Successivamente, configurerai il renderer WebGL e visualizzerai la scena.

6. Esegui il rendering della scena

È il momento di eseguire il rendering della scena. Fino a questo punto, tutto ciò che hai creato con Three.js è inizializzato nel codice, ma è essenzialmente inesistente perché non è ancora stato sottoposto a rendering nel contesto di rendering WebGL. WebGL esegue il rendering di contenuti 2D e 3D nel browser utilizzando l'API Canvas. Se hai già utilizzato l'API Canvas, probabilmente hai familiarità con il context di un canvas HTML, in cui viene eseguito il rendering di tutti gli elementi. Quello che forse non sai è che questa è un'interfaccia che espone il contesto di rendering grafico OpenGL tramite l'API WebGLRenderingContext nel browser.

Per semplificare la gestione del renderer WebGL, Three.js fornisce WebGLRenderer, un wrapper che semplifica la configurazione del contesto di rendering WebGL in modo che Three.js possa eseguire il rendering delle scene nel browser. Nel caso della mappa, tuttavia, non è sufficiente eseguire il rendering della scena Three.js nel browser insieme alla mappa. Three.js deve essere visualizzato nello stesso contesto di rendering della mappa, in modo che sia la mappa sia gli oggetti della scena Three.js vengano visualizzati nello stesso spazio del mondo. In questo modo, il renderer può gestire le interazioni tra gli oggetti sulla mappa e quelli nella scena, ad esempio l'occlusione, che è un modo elegante per dire che un oggetto nasconde alla vista gli oggetti che si trovano dietro di lui.

Sembra piuttosto complicato, vero? Fortunatamente, Three.js viene di nuovo in soccorso.

  1. Configura il renderer WebGL.

    Quando crei una nuova istanza di WebGLRenderer Three.js, puoi fornire il contesto di rendering WebGL specifico in cui vuoi che venga eseguito il rendering della scena. Ricordi l'argomento gl passato all'hook onContextRestored? L'oggetto gl è il contesto di rendering WebGL della mappa. Devi solo fornire il contesto, il canvas e gli attributi all'istanza WebGLRenderer, tutti disponibili tramite l'oggetto gl. In questo codice, anche la proprietà autoClear del renderer è impostata su false, in modo che il renderer non cancelli il suo output ogni frame.

    Per configurare il renderer, aggiungi quanto segue all'hook onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. Esegui il rendering della scena.

    Una volta configurato il renderer, chiama requestRedraw sull'istanza WebGLOverlayView per comunicare all'overlay che è necessario un nuovo disegno al rendering del frame successivo, quindi chiama render sul renderer e trasmetti la scena e la videocamera Three.js da eseguire il rendering. Infine, cancella lo stato del contesto di rendering WebGL. Si tratta di un passaggio importante per evitare conflitti di stato GL, poiché l'utilizzo di WebGL Overlay View si basa sullo stato GL condiviso. Se lo stato non viene reimpostato alla fine di ogni chiamata di disegno, i conflitti di stato GL potrebbero causare l'errore del renderer.

    Per farlo, aggiungi quanto segue all'hook onDraw in modo che venga eseguito ogni frame:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

I tuoi hook onContextRestored e onDraw ora dovrebbero avere il seguente aspetto:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

7. Eseguire il rendering di un modello 3D sulla mappa

Ok, hai tutti i pezzi al loro posto. Hai configurato la visualizzazione overlay WebGL e creato una scena Three.js, ma c'è un problema: non c'è nulla. A questo punto, è il momento di eseguire il rendering di un oggetto 3D nella scena. Per farlo, utilizzerai il caricatore GLTF importato in precedenza.

I modelli 3D sono disponibili in molti formati diversi, ma per Three.js il formato gLTF è il preferito per le sue dimensioni e le prestazioni di runtime. In questo codelab, un modello da visualizzare nella scena è già fornito in /src/pin.gltf.

  1. Crea un'istanza del caricatore di modelli.

    Aggiungi quanto segue a onAdd:
    loader = new GLTFLoader();
    
  2. Carica un modello 3D.

    I caricatori di modelli sono asincroni ed eseguono un callback una volta che il modello è stato caricato completamente. Per caricare pin.gltf, aggiungi il seguente codice a onAdd:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. Aggiungi il modello alla scena.

    Ora puoi aggiungere il modello alla scena aggiungendo quanto segue al callback loader. Tieni presente che viene aggiunto gltf.scene, non gltf:
    scene.add(gltf.scene);
    
  4. Configura la matrice di proiezione della videocamera.

    L'ultima cosa che devi fare per visualizzare correttamente il modello sulla mappa è impostare la matrice di proiezione della videocamera nella scena Three.js. La matrice di proiezione è specificata come array Matrix4 di Three.js, che definisce un punto nello spazio tridimensionale insieme a trasformazioni, come rotazioni, taglio, scala e altro ancora.

    Nel caso di WebGLOverlayView, la matrice di proiezione viene utilizzata per indicare al renderer dove e come eseguire il rendering della scena Three.js rispetto alla basemap. Ma c'è un problema. Le posizioni sulla mappa sono specificate come coppie di coordinate di latitudine e longitudine, mentre le posizioni nella scena Three.js sono coordinate Vector3. Come avrai intuito, calcolare la conversione tra i due sistemi non è semplice. Per risolvere il problema, WebGLOverlayView passa un oggetto coordinateTransformer all'hook del ciclo di vita OnDraw che contiene una funzione chiamata fromLatLngAltitude. fromLatLngAltitude accetta un oggetto LatLngAltitude o LatLngAltitudeLiteral e, facoltativamente, un insieme di argomenti che definiscono una trasformazione per la scena, quindi li converte in una matrice di proiezione della visualizzazione del modello (MVP). Tutto quello che devi fare è specificare dove sulla mappa vuoi che venga visualizzata la scena Three.js, nonché come vuoi che venga trasformata, e WebGLOverlayView fa il resto. A questo punto, puoi convertire la matrice MVP in un array Matrix4 Three.js e impostare la matrice di proiezione della videocamera.

    Nel codice riportato di seguito, il secondo argomento indica a WebGl Overlay View di impostare l'altitudine della scena Three.js a 120 metri dal suolo, in modo che il modello sembri fluttuare.

    Per impostare la matrice di proiezione della videocamera, aggiungi quanto segue all'hook onDraw:
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. Trasforma il modello.

    Noterai che il segnaposto non è perpendicolare alla mappa. Nella grafica 3D, oltre allo spazio globale che ha i propri assi x, y e z che determinano l'orientamento, ogni oggetto ha anche il proprio spazio oggetto con un insieme indipendente di assi.

    Nel caso di questo modello, non è stato creato con quella che normalmente considereremmo la "parte superiore" del pin rivolta verso l'asse Y, quindi devi trasformare l'oggetto per orientarlo nel modo desiderato rispetto allo spazio globale chiamando rotation.set. Tieni presente che in Three.js la rotazione è specificata in radianti, non in gradi. In genere è più facile pensare in gradi, quindi è necessario eseguire la conversione appropriata utilizzando la formula degrees * Math.PI/180.

    Inoltre, il modello è un po' piccolo, quindi lo ridimensionerai in modo uniforme su tutti gli assi chiamando scale.set(x, y ,z).

    Per ruotare e scalare il modello, aggiungi quanto segue nel callback loader di onAdd prima di scene.add(gltf.scene) che aggiunge il file gLTF alla scena:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Ora il pin è verticale rispetto alla mappa.

Birillo verticale

I tuoi hook onAdd e onDraw ora dovrebbero avere il seguente aspetto:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
      scene.add( ambientLight );
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);

      loader = new GLTFLoader();
      const source = 'pin.gltf';
      loader.load(
        source,
        gltf => {
          gltf.scene.scale.set(25,25,25);
          gltf.scene.rotation.x = 180 * Math.PI/180;
          scene.add(gltf.scene);
        }
      );
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 100
      }

      const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
      camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

Poi ci sono le animazioni della videocamera.

8. Animare la videocamera

Ora che hai eseguito il rendering di un modello sulla mappa e puoi spostare tutto in tre dimensioni, la cosa successiva che probabilmente vorrai fare è controllare il movimento a livello di programmazione. La funzione moveCamera ti consente di impostare contemporaneamente le proprietà di centro, zoom, inclinazione e direzione della mappa, offrendoti un controllo preciso sull'esperienza utente. Inoltre, moveCamera può essere chiamato in un ciclo di animazione per creare transizioni fluide tra i frame a una frequenza di quasi 60 frame al secondo.

  1. Attendi il caricamento del modello.

    Per creare un'esperienza utente fluida, ti consigliamo di attendere l'inizio dello spostamento della videocamera fino al caricamento del modello gLTF. Per farlo, aggiungi il gestore di eventi onLoad del caricatore all'hook onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. Crea un loop di animazione.

    Esistono diversi modi per creare un ciclo di animazione, ad esempio utilizzando setInterval o requestAnimationFrame. In questo caso, utilizzerai la funzione setAnimationLoop del renderer Three.js, che chiamerà automaticamente qualsiasi codice dichiarato nel suo callback ogni volta che Three.js esegue il rendering di un nuovo frame. Per creare il ciclo di animazione, aggiungi quanto segue al gestore eventi onLoad nel passaggio precedente:
    renderer.setAnimationLoop(() => {});
    
  3. Imposta la posizione della videocamera nel ciclo di animazione.

    Dopodiché, chiama moveCamera per aggiornare la mappa. Qui, le proprietà dell'oggetto mapOptions utilizzato per caricare la mappa vengono utilizzate per definire la posizione della videocamera:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. Aggiorna la videocamera ogni frame.

    Ultimo passaggio. Aggiorna l'oggetto mapOptions alla fine di ogni fotogramma per impostare la posizione della videocamera per il fotogramma successivo. In questo codice, viene utilizzata un'istruzione if per incrementare l'inclinazione fino a raggiungere il valore massimo di 67,5, quindi la direzione viene modificata leggermente a ogni frame finché la videocamera non ha completato una rotazione di 360 gradi. Una volta completata l'animazione desiderata, null viene passato a setAnimationLoop per annullare l'animazione in modo che non venga eseguita per sempre.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

L'hook onContextRestored ora dovrebbe avere il seguente aspetto:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;

      loader.manager.onLoad = () => {
        renderer.setAnimationLoop(() => {
           map.moveCamera({
            "tilt": mapOptions.tilt,
            "heading": mapOptions.heading,
            "zoom": mapOptions.zoom
          });

          if (mapOptions.tilt < 67.5) {
            mapOptions.tilt += 0.5
          } else if (mapOptions.heading <= 360) {
            mapOptions.heading += 0.2;
          } else {
            renderer.setAnimationLoop(null)
          }
        });
      }
    }

9. Complimenti

Se tutto è andato secondo i piani, ora dovresti avere una mappa con un grande segnaposto 3D simile a questo:

Spilla 3D finale

Che cosa hai imparato

In questo codelab hai imparato molte cose. Ecco i punti salienti:

  • Implementare WebGLOverlayView e i relativi hook del ciclo di vita.
  • Integrazione di Three.js nella mappa.
  • Le nozioni di base per creare una scena Three.js, incluse le videocamere e l'illuminazione.
  • Caricamento e manipolazione di modelli 3D utilizzando Three.js.
  • Controllare e animare la videocamera per la mappa utilizzando moveCamera.

Passaggi successivi

WebGL e la computer grafica in generale sono argomenti complessi, quindi c'è sempre molto da imparare. Ecco alcune risorse per iniziare: