Crea esperienze mappa 3D con la visualizzazione overlay WebGL

1. Prima di iniziare

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

PIN 3D finale

Prerequisiti

Questo codelab presuppone che tu abbia una conoscenza intermedia di JavaScript e dell'API Maps JavaScript. Per imparare le nozioni di base sull'utilizzo dell'API Maps JS, prova il codelab su una mappa del tuo sito web (JavaScript).

Obiettivi didattici

  • Generazione di un ID mappa con la mappa vettoriale per JavaScript abilitata.
  • Controllo della mappa con inclinazione e rotazione programmatica.
  • Visualizzazione di oggetti 3D sulla mappa con WebGLOverlayView e Three.js.
  • Animazione dei movimenti della videocamera con moveCamera.

Che cosa ti serve

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

2. Configura

Per il passaggio di attivazione qui sotto, devi attivare 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 Utilizzo di Google Maps Platform per creare un account di fatturazione e un progetto.

  1. In Cloud Console, 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 la procedura descritta in questo video o in questa documentazione.
  2. Genera una chiave API nella pagina Credentials di Cloud Console. Puoi seguire la procedura descritta 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 già fatto, vai a https://nodejs.org/ per scaricare e installare il runtime Node.js sul tuo computer.

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

Scarica il modello di avvio progetto

Prima di iniziare questo codelab, procedi come segue per scaricare il modello di progetto iniziale e il codice di soluzione completo:

  1. Scarica o crea un 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 del file necessaria per completare il codelab. Tutto ciò che ti serve è disponibile nella directory /starter/src.
  2. Una volta scaricato il progetto iniziale, esegui npm install nella directory /starter. Vengono installate tutte le dipendenze necessarie elencate in package.json.
  3. Dopo aver installato le dipendenze, esegui npm start nella directory.

Il progetto iniziale è stato configurato in modo che tu possa utilizzare webpack-dev-server, il quale compila ed esegue il codice che scrivi in locale. webpack-dev-server ricarica automaticamente anche la tua app nel browser ogni volta che apporti modifiche al codice.

Se vuoi vedere il codice della soluzione completo in esecuzione, puoi completare i passaggi di configurazione precedenti nella directory /solution.

Aggiungi la tua chiave API

L'app iniziale include tutto il codice necessario per caricare la mappa con il caricatore dell'API JS, in modo da fornire la chiave API e l'ID mappa. Il caricatore dell'API JS è una semplice libreria che astrae il metodo tradizionale per caricare l'API Maps JS 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, è necessario un ID mappa con la mappa vettoriale abilitata.

Generazione di un ID mappa

Generazione ID mappa

  1. In Google Cloud Console, vai a "Google Maps Platform' > "Gestione delle 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'. Apparirà la voce "Opzioni JavaScript".
  5. Nella sezione "Opzioni JavaScript", seleziona il pulsante di opzione "Vector', 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'. Verrà 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 vettoriali, devi fornire un ID mappa come proprietà nelle opzioni quando crei un'istanza di una mappa. Se vuoi, puoi anche fornire lo stesso ID mappa quando carichi l'API Maps JavaScript.

Per caricare la mappa con l'ID mappa, procedi nel seguente modo:

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

    Se fornisci l'ID mappa quando crei un'istanza per la mappa, indichi a Google Maps Platform quali mappe caricare per una determinata istanza. Puoi riutilizzare lo stesso ID mappa su più app o più viste 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 attive, tieni premuto il tasto Maiusc e trascina con il mouse o utilizza i tasti freccia sulla tastiera.

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

Mappa inclinata

Il file app.js dovrebbe 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 dà accesso diretto allo stesso contesto di rendering WebGL utilizzato per visualizzare la mappa di base vettoriali. Ciò significa che puoi visualizzare oggetti 2D e 3D direttamente sulla mappa utilizzando WebGL, nonché le librerie grafiche più comuni basate su WebGL.

WebGLOverlayView espone cinque hook nel ciclo di vita del contesto di rendering di WebGL, che puoi utilizzare nella mappa. Ecco una breve descrizione di ciascun gancio e la relativa finalità:

  • onAdd(): chiamato quando viene aggiunto l'overlay a una mappa chiamando setMap su un'istanza WebGLOverlayView. Qui è dove dovresti eseguire operazioni relative a WebGL che non richiedono accesso diretto al contesto di WebGL.
  • onContextRestored(): richiamato quando il contesto di WebGL diventa disponibile, ma prima che venga eseguito il rendering di qualsiasi elemento. Qui è dove inizializzare gli oggetti, associare lo stato ed eseguire tutte le altre operazioni che richiedono l'accesso al contesto WebGL ma che possono essere eseguite al di fuori della chiamata onDraw(). In questo modo puoi configurare tutto ciò che ti serve senza aumentare il sovraccarico per la visualizzazione effettiva della mappa, che richiede già un utilizzo intensivo della GPU.
  • onDraw(): chiamato una volta per frame una volta che WebGL inizia il rendering della mappa e qualsiasi altra cosa tu abbia richiesto. Dovresti fare il meno possibile in onDraw() per evitare di causare problemi di prestazioni nel rendering della mappa.
  • onContextLost(): richiamato 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 suoi hook di ciclo di vita: onAdd, onContextRestored e onDraw. Per mantenere le operazioni pulite e più facili da seguire, tutto il codice per l'overlay verrà gestito nella funzione initWebGLOverlayView() fornita nel modello di avvio per questo codelab.

  1. Crea un'istanza WebGLOverlayView().

    L'overlay viene fornito dall'API Maps JS in google.maps.WebGLOverlayView. Per iniziare, crea un'istanza attenendoti ai seguenti elementi a initWebGLOverlayView():
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Implementare gli hook di ciclo di vita.

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

    A questo punto chiama setMap() nell'istanza di overlay e passa nella mappa aggiungendo quanto segue a initWebGLOverlayView():
    webGLOverlayView.setMap(map)
    
  4. Chiama initWebGLOverlayView.

    L'ultimo passaggio consiste nell'eseguire initWebGLOverlayView() aggiungendo quanto segue alla funzione richiamata immediatamente in fondo a app.js:
    initWebGLOverlayView(map);
    

La initWebGLOverlayView e la funzione immediatamente richiamata dovrebbero avere il seguente aspetto:

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

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

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

È tutto ciò che ti serve per implementare WebGLOverlayView. Il passaggio successivo consiste nel configurare tutto ciò che ti serve per visualizzare un oggetto 3D sulla mappa con Three.js.

5. Configurare una scena a tre.js

L'uso di WebGL può essere molto complicato perché richiede la definizione manuale di tutti gli aspetti di ciascun oggetto e poi di alcuni. Per semplificare le cose, in questo codelab utilizzerai Three.js, una popolare libreria grafica che fornisce un livello di astrazione semplificato oltre a WebGL. Three.js include un'ampia gamma di funzioni che consentono di eseguire operazioni che vanno dalla creazione di un renderer WebGL alla progettazione di forme 2D e 3D comuni, al controllo di videocamere, trasformazioni di oggetti e molto altro ancora.

In Three.js sono disponibili tre tipi di oggetti di base in cui è necessario visualizzare qualsiasi elemento:

  • Scena: un "container" in cui vengono visualizzati e visualizzati tutti gli oggetti, le fonti luminose, le texture e così via.
  • Fotocamera: una videocamera che rappresenta il punto di vista della scena. Sono disponibili più tipi di videocamere e puoi aggiungere una o più videocamere a una singola scena.
  • 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 riserva 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 tre.js

    Ti serviranno due dipendenze per questo codelab: la libreria Three.js e il caricatore GLTF, una classe che consente di caricare oggetti 3D nel formato di trasmissione GL (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 è probabile che tu voglia importare solo i corsi che ti servono, ma per questo codelab dovrai importare l'intera libreria per semplificare le cose. Tieni presente inoltre che il caricatore GLTF non è incluso nella libreria predefinita e deve essere importato da un percorso separato nella dipendenza, ovvero il percorso da cui puoi accedere a tutti i caricatori forniti da Three.js.

    Per importare Three.js e il GLTF Caricamenti, 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 a tre.js.

    Per creare una scena, crea un'istanza della classe Scene 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 della visualizzazione 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 è "visto", a indicare che gli oggetti non verranno visualizzati perché non vengono visualizzati.

    Three.js offre una varietà di videocamere diverse che influiscono sul modo in cui il renderer tratta gli oggetti in relazione a elementi come la prospettiva e la profondità. In questa scena utilizzerai il PerspectiveCamera, il tipo di videocamera più utilizzato in Three.js, progettato per emulare il modo in cui l'occhio umano percepisce la scena. Ciò significa che gli oggetti più lontani dalla fotocamera sembreranno più piccoli degli oggetti più vicini, la scena avrà un punto di fuga e altro ancora.

    Per aggiungere una videocamera di prospettiva alla scena, aggiungi quanto segue al gancio onAdd:
    camera = new THREE.PerspectiveCamera();
    
    Con PerspectiveCamera puoi anche configurare gli attributi che costituiscono il punto di vista, inclusi piani vicini e lontani, proporzioni e campo visivo. Collettivamente, questi attributi costituiscono il cosiddetto frustum, un concetto importante da comprendere quando si lavora in 3D, ma non nell'ambito di questo codelab. La configurazione predefinita di PerspectiveCamera è sufficiente.
  4. Aggiungi fonti di luce alla scena.

    Per impostazione predefinita, gli oggetti visualizzati in una scena Three.js verranno visualizzati in nero, indipendentemente dalle texture applicate. Ciò è dovuto al fatto che una scena Three.js imita il comportamento degli oggetti nel mondo reale, in cui la visibilità del colore dipende dalla luce riflettente un oggetto. In breve, nessuna luce, nessun colore.

    Three.js fornisce una varietà di tipi di luci di cui ne utilizzerai due:

  5. AmbientLight: fornisce una fonte di luce diffusa che illumina in modo uniforme tutti gli oggetti presenti da tutti gli angoli. In questo modo, alla luce verrà mostrata una quantità di luce di base per garantire che le texture su tutti gli oggetti siano visibili.
  6. DirectionalLight: fornisce una luce che proviene da una direzione della scena. A differenza di come funziona una luce posizionata nel mondo reale, i raggi di luce che emettono da DirectionalLight sono tutti paralleli e non si espandono e si diffondono quando si allontanano dalla fonte di luce.

    Puoi configurare il colore e l'intensità di ciascuna luce per creare effetti di illuminazione aggregati. Ad esempio, nel codice riportato di seguito, la luce ambientale fornisce una luce bianca tenue 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) posiziona la luce direttamente sopra la scena sull'asse Y e punta direttamente verso il basso.

    Per aggiungere le sorgenti di luce alla scena, aggiungi quanto segue al gancio 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);
    

Ora il tuo hook onAdd 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);
    }

Ora la tua scena è configurata e pronta per il rendering. Dopodiché configurerai il renderer WebGL e il rendering della scena.

6. Visualizza la scena

È il momento di eseguire il rendering della scena. Fino a questo momento, tutto ciò che hai creato con Three.js è stato inizializzato nel codice, ma essenzialmente non esiste perché non è stato ancora visualizzato nel contesto di rendering di WebGL. WebGL visualizza i contenuti 2D e 3D nel browser utilizzando l'API Canvas. Se hai già utilizzato l'API Canvas, probabilmente hai familiarità con l'context di un canvas HTML, che è il punto in cui viene visualizzato tutto. Potresti non sapere che si tratta di un'interfaccia che espone il contesto del rendering grafico OpenGL tramite l'API WebGLRenderingContext nel browser.

Per semplificare la gestione del renderer WebGL, Three.js fornisce WebGLRenderer, un wrapper che rende relativamente facile configurare il contesto del rendering WebGL in modo che Three.js possa visualizzare scene nel browser. Nel caso della mappa, tuttavia, non è sufficiente eseguire il rendering della scena Three.js nel browser accanto alla mappa. Three.js deve avere lo stesso contesto di rendering della mappa, in modo che sia la mappa sia gli oggetti della scena di Three.js vengano visualizzati nello stesso spazio globale. In questo modo, il renderer può gestire le interazioni tra gli oggetti sulla mappa e gli oggetti nella scena, come la copertura oculate, che è un modo elegante per dire che un oggetto nasconde gli oggetti dietro di sé.

Sembra piuttosto complicato, vero? Fortunatamente, Three.js torna in nostro soccorso.

  1. Configurare il renderer WebGL.

    Quando crei una nuova istanza di Three.js WebGLRenderer, puoi specificare il contesto di rendering WebGL specifico in cui vuoi visualizzare la scena. Ricordi l'argomento gl che viene trasferito nell'hook onContextRestored? L'oggetto gl è il contesto di rendering di WebGL della mappa. Tutto quello che devi fare è fornire il contesto, la sua tela e i suoi 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 non cancelli l'output ogni frame.

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

    Dopo aver configurato il renderer, richiama requestRedraw nell'istanza WebGLOverlayView per comunicare all'overlay che è necessario un nuovo rendering quando viene visualizzato il frame successivo, quindi chiama render sul renderer e trasmetti la scena e la fotocamera Three.js per il rendering. Infine, cancella lo stato del contesto di rendering di WebGL. Si tratta di un passaggio importante per evitare conflitti nello stato GL, poiché l'utilizzo della visualizzazione overlay di WebGL si basa sullo stato GL condiviso. Se lo stato non viene reimpostato alla fine di ogni chiamata di disegno, i conflitti di stato in GL potrebbero causare un errore del renderer.

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

Ora i tuoi hook onContextRestored e onDraw 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. Visualizzare un modello 3D sulla mappa

Ok, hai preparato tutti i componenti. Hai configurato la visualizzazione overlay WebGl e creato una scena Three.js, ma c'è un problema: non contiene nulla. Quindi, è il momento di eseguire il rendering di un oggetto 3D nella scena. Per farlo, devi utilizzare il caricatore GLTF importato in precedenza.

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

  1. Creare un'istanza del caricatore di modelli.

    Aggiungi i seguenti elementi a onAdd:
    loader = new GLTFLoader();
    
  2. Carica un modello 3D.

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

    A questo punto puoi aggiungere il modello alla scena aggiungendo quanto segue al callback loader. Tieni presente che è in corso l'aggiunta di gltf.scene, non di gltf:
    scene.add(gltf.scene);
    
  4. Configura la matrice di proiezione della videocamera.

    L'ultima cosa di cui hai bisogno per una corretta visualizzazione del modello sulla mappa è impostare la matrice di proiezione della videocamera nella scena Three.js. La matrice di proiezione è specificata come array di Three.js Matrix4, che definisce un punto in uno spazio tridimensionale insieme alle trasformazioni, quali rotazioni, taglio, scala e altro ancora.

    Nel caso di WebGLOverlayView, la matrice di proiezione viene utilizzata per indicare al renderer dove e come visualizzare la scena Three.js in relazione alla mappa di base. Ma c'è un problema. Le posizioni sulla mappa sono indicate come coppie di coordinate di latitudine e longitudine, mentre quelle della scena di Three.js sono Vector3. Come potrai immaginare, calcolare la conversione tra i due sistemi non è banale. Per risolvere questo problema, WebGLOverlayView passa un oggetto coordinateTransformer all'hook di ciclo di vita OnDraw che contiene una funzione chiamata fromLatLngAltitude. fromLatLngAltitude accetta un oggetto LatLngAltitude o LatLngAltitudeLiteral e, facoltativamente, un insieme di argomenti che definisce una trasformazione per la scena, quindi li copre a una matrice di proiezione delle visualizzazioni modello. Non devi far altro che specificare dove vuoi che venga visualizzata la scena di Three.js sulla mappa e come vuoi che venga trasformata. Al resto, WebGLOverlayView. Puoi quindi convertire la matrice MVP in un array di Three.js Matrix4 e impostare la matrice di proiezione della fotocamera.

    Nel codice riportato di seguito, il secondo argomento indica alla vista overlay WebGl di impostare l'altitudine della scena Three.js a 120 metri dal suolo, rendendo il modello mobile.

    Per impostare la matrice delle proiezioni della videocamera, aggiungi quanto segue al gancio 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.

    Vedrai 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 con un insieme indipendente di assi.

    Nel caso di questo modello, non è stato creato con quello che normalmente consideriamo "top' del segnaposto rivolto 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 viene specificata in radianti, non in gradi. In genere è più facile pensare in gradi, quindi è necessario effettuare la conversione appropriata utilizzando la formula degrees * Math.PI/180.

    Inoltre, il modello è un po' piccolo, quindi puoi anche scalarlo 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), aggiungendo così il gLTF alla scena:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Ora il segnaposto è posizionato in alto rispetto alla mappa.

Perno verticale

Ora i tuoi hook onAdd e onDraw 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();
    }

Passiamo alle animazioni della fotocamera!

8. Animazione della videocamera

Ora che hai eseguito il rendering di un modello sulla mappa e puoi spostare tutti e tre in modo dimensionale, l'attività successiva che probabilmente vorrai fare è controllare il movimento in modo programmatico. La funzione moveCamera ti consente di impostare le proprietà centrale, zoom, inclinazione e intestazione della mappa contemporaneamente, offrendo un controllo granulare sull'esperienza utente. Inoltre, puoi chiamare moveCamera in un loop di animazione per creare transizioni di fluido tra i frame a una frequenza fotogrammi di quasi 60 frame al secondo.

  1. Attendi il caricamento del modello.

    Per creare un'esperienza utente fluida, attendi di iniziare a spostare la videocamera fino al caricamento del modello gLTF. A tale scopo, aggiungi il gestore di eventi onLoad del caricatore al hook onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. Crea un loop di animazione.

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

    Successivamente, chiama moveCamera per aggiornare la mappa. In questo caso, le proprietà dell'oggetto mapOptions utilizzate per caricare la mappa vengono usate per definire la posizione della fotocamera:
    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 frame per impostare la posizione della fotocamera per il frame successivo. In questo codice, un'istruzione if viene utilizzata per aumentare il valore di inclinazione fino a raggiungere il valore di inclinazione massimo di 67, 5.L'intestazione viene modificata un po' ogni frame finché la fotocamera non completa una rotazione completa di 360°. Una volta completata l'animazione, 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)
    }
    

Ora il tuo hook onContextRestored 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 per il meglio, ora dovresti visualizzare una mappa con un segnaposto 3D grande, simile al seguente:

PIN 3D finale

Che cosa hai imparato

In questo codelab hai imparato molto; ecco i punti salienti:

  • L'implementazione di WebGLOverlayView e dei suoi hook di ciclo di vita.
  • Integrazione di Three.js nella mappa.
  • Nozioni di base sulla creazione di una scena Three.js, tra cui videocamere e illuminazione.
  • Caricamento e manipolazione dei modelli 3D tramite Three.js.
  • Controllo e animazione della fotocamera per la mappa utilizzando moveCamera.

Passaggi successivi

WebGL e la grafica del computer in generale sono argomenti complessi, quindi c'è sempre molto da imparare. Per iniziare, fai riferimento alle seguenti risorse: