Tworzenie funkcji map 3D za pomocą widoku nakładki WebGL

1. Zanim zaczniesz

Z tego laboratorium dowiesz się, jak używać funkcji interfejsu Maps JavaScript API opartych na WebGL do sterowania mapą wektorową i renderowania jej w 3D.

Final 3D Pin

Wymagania wstępne

W tym samouczku zakłada się, że masz średnio zaawansowaną wiedzę o JavaScript i interfejsie Maps JavaScript API. Aby poznać podstawy korzystania z interfejsu Maps JS API, wypróbuj ćwiczenia z programowania dotyczące dodawania mapy do witryny (JavaScript).

Czego się nauczysz

  • Generowanie identyfikatora mapy z włączoną mapą wektorową w JavaScript.
  • Sterowanie mapą za pomocą programowego pochylania i obracania.
  • Renderowanie obiektów 3D na mapie za pomocą WebGLOverlayViewThree.js.
  • Animowanie ruchów kamery za pomocą moveCamera.

Czego potrzebujesz

  • konto Google Cloud Platform z włączonymi płatnościami;
  • klucz interfejsu API Google Maps Platform z włączonym interfejsem Maps JavaScript API;
  • średnio zaawansowana znajomość języków JavaScript, HTML i CSS;
  • wybrany edytor tekstu lub IDE;
  • Node.js

2. Konfiguracja

W kroku włączania poniżej musisz włączyć interfejs Maps JavaScript API.

Konfigurowanie Google Maps Platform

Jeśli nie masz jeszcze konta Google Cloud Platform i projektu z włączonymi płatnościami, zapoznaj się z przewodnikiem Pierwsze kroki z Google Maps Platform, aby utworzyć konto rozliczeniowe i projekt.

  1. W konsoli Google Cloud kliknij menu projektu i wybierz projekt, którego chcesz użyć w tym samouczku.

  1. Włącz interfejsy API i pakiety SDK Google Maps Platform wymagane w tym samouczku w Google Cloud Marketplace. Aby to zrobić, wykonaj czynności opisane w tym filmie lub tej dokumentacji.
  2. Wygeneruj klucz interfejsu API na stronie Dane logowania w konsoli Cloud. Możesz wykonać czynności opisane w tym filmie lub tej dokumentacji. Wszystkie żądania wysyłane do Google Maps Platform wymagają klucza interfejsu API.

Konfiguracja Node.js

Jeśli nie masz jeszcze środowiska wykonawczego Node.js, pobierz je i zainstaluj na komputerze, przechodząc na stronę https://nodejs.org/.

Node.js zawiera menedżera pakietów npm, który jest potrzebny do zainstalowania zależności w tym ćwiczeniu.

Pobieranie szablonu projektu startowego

Zanim zaczniesz to ćwiczenie (w Codelabs), wykonaj te czynności, aby pobrać szablon projektu początkowego oraz pełny kod rozwiązania:

  1. Pobierz lub sklonuj repozytorium GitHub tego modułu na stronie https://github.com/googlecodelabs/maps-platform-101-webgl/. Projekt początkowy znajduje się w katalogu /starter i zawiera podstawową strukturę plików potrzebną do ukończenia ćwiczeń z programowania. Wszystko, czego potrzebujesz do pracy, znajduje się w katalogu /starter/src.
  2. Po pobraniu projektu początkowego uruchom polecenie npm install w katalogu /starter. Spowoduje to zainstalowanie wszystkich potrzebnych zależności wymienionych w pliku package.json.
  3. Po zainstalowaniu zależności uruchom w katalogu polecenie npm start.

Projekt startowy został skonfigurowany tak, aby używać webpack-dev-server, który kompiluje i uruchamia pisany przez Ciebie kod lokalnie. webpack-dev-server automatycznie przeładowuje też aplikację w przeglądarce za każdym razem, gdy wprowadzasz zmiany w kodzie.

Jeśli chcesz zobaczyć działający pełny kod rozwiązania, wykonaj powyższe czynności konfiguracyjne w katalogu /solution.

Dodawanie klucza interfejsu API

Aplikacja startowa zawiera cały kod potrzebny do wczytania mapy za pomocą JS API Loader, więc wystarczy, że podasz klucz interfejsu API i identyfikator mapy. JS API Loader to prosta biblioteka, która abstrahuje tradycyjną metodę wczytywania interfejsu Maps JS API wbudowanego w szablon HTML za pomocą tagu script, umożliwiając obsługę wszystkiego w kodzie JavaScript.

Aby dodać klucz interfejsu API, wykonaj w projekcie początkowym te czynności:

  1. Otwórz pokój app.js.
  2. W obiekcie apiOptions ustaw klucz interfejsu API jako wartość apiOptions.apiKey.

3. Generowanie i używanie identyfikatora mapy

Aby korzystać z funkcji interfejsu Maps JavaScript API opartych na WebGL, potrzebujesz identyfikatora mapy z włączoną mapą wektorową.

Generowanie identyfikatora mapy

Generowanie identyfikatora mapy

  1. W konsoli Google Cloud kliknij „Google Maps Platform” > „Zarządzanie mapami”.
  2. Kliknij „UTWÓRZ NOWY IDENTYFIKATOR MAPY”.
  3. W polu „Nazwa mapy” wpisz nazwę identyfikatora mapy.
  4. W menu „Typ mapy” wybierz „JavaScript”. Pojawi się opcja „Opcje JavaScriptu”.
  5. W sekcji „Opcje JavaScript” wybierz przycisk „Wektor”, pole wyboru „Pochylenie” i pole wyboru „Obrót”.
  6. Opcjonalnie. W polu „Opis” wpisz opis klucza interfejsu API.
  7. Kliknij przycisk „Dalej”. Pojawi się strona „Szczegóły identyfikatora mapy”.

    Strona Szczegóły mapy
  8. Skopiuj identyfikator mapy. Użyjesz go w następnym kroku, aby wczytać mapę.

Korzystanie z identyfikatora mapy

Aby wczytać mapę wektorową, musisz podać identyfikator mapy jako właściwość w opcjach podczas tworzenia instancji mapy. Opcjonalnie możesz też podać ten sam identyfikator mapy podczas wczytywania interfejsu Maps JavaScript API.

Aby wczytać mapę za pomocą identyfikatora mapy:

  1. Ustaw identyfikator mapy jako wartość mapOptions.mapId.

     Podanie identyfikatora mapy podczas tworzenia instancji mapy informuje Google Maps Platform, którą z Twoich map ma wczytać w przypadku danej instancji. Możesz używać tego samego identyfikatora mapy w kilku aplikacjach lub w kilku widokach w ramach tej samej aplikacji.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

Sprawdź aplikację działającą w przeglądarce. Mapa wektorowa z włączonym pochyleniem i obrotem powinna się wczytać. Aby sprawdzić, czy pochylenie i obrót są włączone, przytrzymaj klawisz Shift i przeciągnij myszą lub użyj klawiszy strzałek na klawiaturze.

Jeśli mapa się nie wczytuje, sprawdź, czy w apiOptions podano prawidłowy klucz API. Jeśli mapa nie przechyla się i nie obraca, sprawdź, czy w elementach apiOptionsmapOptions podano identyfikator mapy z włączonymi funkcjami przechylania i obracania.

Pochylona mapa

Plik app.js powinien teraz wyglądać tak:

    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. Implementowanie WebGLOverlayView

WebGLOverlayView zapewnia bezpośredni dostęp do tego samego kontekstu renderowania WebGL, który jest używany do renderowania wektorowej mapy bazowej. Oznacza to, że możesz renderować obiekty 2D i 3D bezpośrednio na mapie za pomocą WebGL, a także popularnych bibliotek graficznych opartych na WebGL.

WebGLOverlayView udostępnia 5 punktów zaczepienia w cyklu życia kontekstu renderowania WebGL mapy, z których możesz korzystać. Oto krótki opis każdego z nich i informacje o tym, do czego służą:

  • onAdd(): wywoływana, gdy nakładka jest dodawana do mapy przez wywołanie metody setMap na instancji WebGLOverlayView. W tym miejscu należy wykonywać wszystkie działania związane z WebGL, które nie wymagają bezpośredniego dostępu do kontekstu WebGL.
  • onContextRestored(): wywoływana, gdy kontekst WebGL staje się dostępny, ale zanim cokolwiek zostanie wyrenderowane. W tym miejscu należy zainicjować obiekty, powiązać stan i wykonać inne czynności, które wymagają dostępu do kontekstu WebGL, ale można je wykonać poza wywołaniem onDraw(). Dzięki temu możesz skonfigurować wszystko, czego potrzebujesz, bez dodawania nadmiernego obciążenia do rzeczywistego renderowania mapy, które już jest wymagające dla procesora graficznego.
  • onDraw(): wywoływana raz na klatkę, gdy WebGL zaczyna renderować mapę i wszystkie inne elementy, o które prosisz. W funkcji onDraw() należy wykonywać jak najmniej działań, aby uniknąć problemów z wydajnością podczas renderowania mapy.
  • onContextLost(): wywoływana, gdy kontekst renderowania WebGL zostanie utracony z dowolnego powodu.
  • onRemove(): wywoływana, gdy nakładka zostanie usunięta z mapy przez wywołanie funkcji setMap(null) na instancji WebGLOverlayView.

W tym kroku utworzysz instancję WebGLOverlayView i wdrożysz 3 jej funkcje cyklu życia: onAdd, onContextRestoredonDraw. Aby zachować przejrzystość i ułatwić śledzenie, cały kod nakładki będzie obsługiwany w funkcji initWebGLOverlayView() podanej w szablonie początkowym tego laboratorium.

  1. Utwórz instancję WebGLOverlayView().

    Nakładka jest udostępniana przez interfejs Maps JS API w google.maps.WebGLOverlayView. Aby rozpocząć, utwórz instancję, dodając do initWebGLOverlayView() ten ciąg znaków:
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. Wdrażaj punkty zaczepienia cyklu życia.

    Aby wdrożyć funkcje cyklu życia, dodaj do initWebGLOverlayView() ten kod:
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  3. Dodaj instancję nakładki do mapy.

     Teraz wywołaj setMap() w instancji nakładki i przekaż mapę, dodając do initWebGLOverlayView() ten kod:
    webGLOverlayView.setMap(map)
    
  4. Zadzwoń do firmy initWebGLOverlayView.

     Ostatnim krokiem jest wykonanie funkcji initWebGLOverlayView() przez dodanie do natychmiast wywoływanej funkcji u dołu pliku app.js tego kodu:
    initWebGLOverlayView(map);
    

Funkcja initWebGLOverlayView i funkcja wywoływana natychmiast powinny teraz wyglądać tak:

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

To wszystko, co musisz zrobić, aby wdrożyć WebGLOverlayView. Następnie skonfigurujesz wszystko, co jest potrzebne do renderowania obiektu 3D na mapie za pomocą Three.js.

5. Konfigurowanie sceny three.js

Korzystanie z WebGL może być bardzo skomplikowane, ponieważ wymaga ręcznego zdefiniowania wszystkich aspektów każdego obiektu i nie tylko. Aby ułatwić sobie pracę, w tym ćwiczeniu użyjesz Three.js, popularnej biblioteki graficznej, która zapewnia uproszczoną warstwę abstrakcji na WebGL. Three.js zawiera wiele funkcji ułatwiających pracę, które wykonują różne zadania, od tworzenia renderera WebGL po rysowanie popularnych kształtów obiektów 2D i 3D oraz sterowanie kamerami, przekształceniami obiektów i wieloma innymi elementami.

W Three.js są 3 podstawowe typy obiektów, które są wymagane do wyświetlania czegokolwiek:

  • Scena: „kontener”, w którym renderowane i wyświetlane są wszystkie obiekty, źródła światła, tekstury itp.
  • Kamera: kamera, która reprezentuje punkt widzenia sceny. Dostępnych jest kilka typów kamer, a do jednej sceny można dodać jedną lub więcej kamer.
  • Renderer: renderer, który obsługuje przetwarzanie i wyświetlanie wszystkich obiektów w scenie. W Three.js najczęściej używany jest element WebGLRenderer, ale w przypadku, gdy klient nie obsługuje WebGL, dostępne są też inne elementy.

W tym kroku załadujesz wszystkie zależności potrzebne do Three.js i skonfigurujesz podstawową scenę.

  1. Wczytaj three.js

    W tym laboratorium potrzebne będą 2 zależności: biblioteka Three.js i GLTF Loader, czyli klasa, która umożliwia wczytywanie obiektów 3D w formacie GL Transmission Format (gLTF). Three.js oferuje specjalne moduły wczytujące dla wielu różnych formatów obiektów 3D, ale zalecamy używanie formatu gLTF.

     W poniższym kodzie importowana jest cała biblioteka Three.js. W aplikacji produkcyjnej prawdopodobnie będziesz importować tylko potrzebne klasy, ale w tym ćwiczeniu zaimportuj całą bibliotekę, aby uprościć zadanie. Zwróć też uwagę, że moduł GLTF Loader nie jest uwzględniony w bibliotece domyślnej i musi zostać zaimportowany z osobnej ścieżki w zależności – to ścieżka, w której możesz uzyskać dostęp do wszystkich modułów wczytujących udostępnianych przez Three.js.

     Aby zaimportować Three.js i GLTF Loader, dodaj ten kod na początku pliku app.js:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. Utwórz scenę three.js.

    Aby utworzyć scenę, utwórz instancję klasy Three.js Scene, dodając ten kod do elementu onAdd:
    scene = new THREE.Scene();
    
  3. Dodaj kamerę do sceny.

     Jak wspomnieliśmy wcześniej, kamera reprezentuje perspektywę oglądania sceny i określa, w jaki sposób Three.js obsługuje wizualne renderowanie obiektów w scenie. Bez kamery scena nie jest „widziana”, co oznacza, że obiekty nie będą się pojawiać, ponieważ nie będą renderowane.

    Three.js oferuje różne rodzaje kamer, które wpływają na sposób, w jaki moduł renderujący traktuje obiekty pod względem perspektywy i głębi. W tej scenie użyjesz PerspectiveCamera, czyli najczęściej używanego typu kamery w Three.js, który został zaprojektowany tak, aby naśladować sposób, w jaki ludzkie oko postrzega scenę. Oznacza to, że obiekty znajdujące się dalej od aparatu będą mniejsze niż te, które są bliżej, scena będzie miała punkt zbiegu i tak dalej.

     Aby dodać do sceny kamerę perspektywiczną, dołącz do haka onAdd ten kod:
    camera = new THREE.PerspectiveCamera();
    
    Za pomocą PerspectiveCamera możesz też skonfigurować atrybuty, które składają się na punkt widzenia, w tym płaszczyzny bliską i daleką, format obrazu oraz pole widzenia (fov). Te atrybuty tworzą tzw. stożek widzenia, czyli ważne pojęcie w pracy z grafiką 3D, ale wykraczające poza zakres tego laboratorium. Domyślna konfiguracja PerspectiveCamera będzie w zupełności wystarczająca.
  4. Dodaj do sceny źródła światła.

     Domyślnie obiekty renderowane w scenie Three.js będą wyświetlane na czarno, niezależnie od zastosowanych tekstur. Dzieje się tak, ponieważ scena Three.js naśladuje zachowanie obiektów w rzeczywistym świecie, w którym widoczność koloru zależy od światła odbijającego się od obiektu. Krótko mówiąc, bez światła nie ma koloru.

    Three.js udostępnia różne typy świateł, z których będziesz używać 2:

  5. AmbientLight: zapewnia rozproszone źródło światła, które równomiernie oświetla wszystkie obiekty na scenie ze wszystkich stron. Dzięki temu scena będzie miała podstawową ilość światła, co zapewni widoczność tekstur na wszystkich obiektach.
  6. DirectionalLight: zapewnia światło pochodzące z określonego kierunku w scenie. W przeciwieństwie do tego, jak działałoby światło w rzeczywistości, promienie świetlne emitowane z DirectionalLight są równoległe i nie rozpraszają się ani nie rozpraszają w miarę oddalania się od źródła światła.

     Możesz skonfigurować kolor i natężenie każdego światła, aby tworzyć złożone efekty świetlne. Na przykład w poniższym kodzie światło otoczenia zapewnia miękkie białe światło dla całej sceny, a światło kierunkowe zapewnia dodatkowe światło padające na obiekty pod kątem skierowanym w dół. W przypadku światła kierunkowego kąt jest ustawiany za pomocą parametru position.set(x, y ,z), gdzie każda wartość jest względna w stosunku do odpowiedniej osi. Na przykład position.set(0,1,0) umieści światło bezpośrednio nad sceną na osi Y, skierowane prosto w dół.

     Aby dodać źródła światła do sceny, dołącz do haka onAdd ten kod:
    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);
    

Twój hook onAdd powinien teraz wyglądać tak:

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

Scena jest teraz skonfigurowana i gotowa do renderowania. Następnie skonfigurujesz moduł renderowania WebGL i wyrenderujesz scenę.

6. Renderowanie sceny

Czas wyrenderować scenę. Wszystko, co do tej pory zostało utworzone za pomocą Three.js, jest inicjowane w kodzie, ale w zasadzie nie istnieje, ponieważ nie zostało jeszcze wyrenderowane w kontekście renderowania WebGL. WebGL renderuje treści 2D i 3D w przeglądarce za pomocą interfejsu Canvas API. Jeśli korzystasz już z interfejsu Canvas API, prawdopodobnie znasz context elementu canvas HTML, w którym wszystko jest renderowane. Może nie wiesz, że jest to interfejs, który udostępnia kontekst renderowania grafiki OpenGL za pomocą interfejsu WebGLRenderingContext w przeglądarce.

Aby ułatwić korzystanie z renderera WebGL, Three.js udostępnia WebGLRenderer, czyli otoczkę, która ułatwia konfigurowanie kontekstu renderowania WebGL, dzięki czemu Three.js może renderować sceny w przeglądarce. W przypadku mapy nie wystarczy jednak renderować sceny Three.js w przeglądarce obok mapy. Biblioteka Three.js musi renderować w tym samym kontekście renderowania co mapa, aby zarówno mapa, jak i wszystkie obiekty ze sceny Three.js były renderowane w tej samej przestrzeni świata. Dzięki temu moduł renderujący może obsługiwać interakcje między obiektami na mapie a obiektami w scenie, takie jak zasłanianie, czyli ukrywanie obiektów znajdujących się za nimi.

Brzmi dość skomplikowanie, prawda? Na szczęście z pomocą przychodzi Three.js.

  1. Skonfiguruj moduł renderowania WebGL.

     Podczas tworzenia nowej instancji WebGLRenderer Three.js możesz podać konkretny kontekst renderowania WebGL, w którym ma być renderowana scena. Pamiętasz argument gl, który jest przekazywany do hooka onContextRestored? Ten obiekt gl to kontekst renderowania WebGL mapy. Wystarczy, że podasz kontekst, obszar i atrybuty instancji WebGLRenderer, które są dostępne w obiekcie gl. W tym kodzie właściwość autoClear renderera jest również ustawiona na false, aby renderer nie czyścił danych wyjściowych w każdej klatce.

     Aby skonfigurować moduł renderujący, dodaj do haka onContextRestored ten kod:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. Wyrenderuj scenę.

    Po skonfigurowaniu mechanizmu renderowania wywołaj funkcję requestRedraw na instancji WebGLOverlayView, aby poinformować nakładkę, że podczas renderowania następnej klatki wymagane jest ponowne rysowanie. Następnie wywołaj funkcję render na mechanizmie renderowania i przekaż jej scenę i kamerę Three.js do renderowania. Na koniec wyczyść stan kontekstu renderowania WebGL. Jest to ważny krok, który pozwala uniknąć konfliktów stanu GL, ponieważ korzystanie z widoku nakładki WebGL zależy od wspólnego stanu GL. Jeśli stan nie zostanie zresetowany na końcu każdego wywołania rysowania, konflikty stanu GL mogą spowodować awarię renderera.

     Aby to zrobić, dodaj do haka onDraw ten kod, aby był wykonywany w każdej klatce:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

Twoje hooki onContextRestoredonDraw powinny teraz wyglądać tak:

    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. Renderowanie modelu 3D na mapie

OK, wszystkie elementy są na swoim miejscu. Masz skonfigurowany widok nakładki WebGL i utworzoną scenę Three.js, ale jest jeden problem: nic w niej nie ma. Teraz czas na wyrenderowanie obiektu 3D w scenie. Aby to zrobić, użyj zaimportowanego wcześniej narzędzia GLTF Loader.

 Modele 3D są dostępne w wielu różnych formatach, ale w przypadku Three.js preferowany jest format gLTF ze względu na rozmiar i wydajność w czasie działania. W tym ćwiczeniu model do renderowania w scenie jest już dostępny w /src/pin.gltf.

  1. Utwórz instancję narzędzia do wczytywania modelu.

     Dodaj do: listy onAdd:
    loader = new GLTFLoader();
    
  2. Wczytaj model 3D.

     Ładowarki modeli są asynchroniczne i wykonują wywołanie zwrotne po pełnym załadowaniu modelu. Aby wczytać pin.gltf, dodaj do onAdd ten kod:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. Dodaj model do sceny.

     Teraz możesz dodać model do sceny, dołączając do wywołania zwrotnego loader ten kod: Pamiętaj, że dodawany jest znak gltf.scene, a nie gltf:
    scene.add(gltf.scene);
    
  4. Skonfiguruj macierz projekcji kamery.

    Ostatnią rzeczą, którą musisz zrobić, aby model był prawidłowo renderowany na mapie, jest ustawienie macierzy projekcji kamery w scenie Three.js. Macierz projekcji jest określana jako tablica Three.js Matrix4, która definiuje punkt w przestrzeni trójwymiarowej wraz z transformacjami, takimi jak obroty, ścinanie, skalowanie i inne.

     W przypadku WebGLOverlayView macierz projekcji służy do określania, gdzie i jak renderować scenę Three.js względem mapy bazowej. Ale jest pewien problem. Lokalizacje na mapie są określone jako pary współrzędnych szerokości i długości geograficznej, a lokalizacje w scenie Three.js to współrzędne Vector3. Jak zapewne się domyślasz, przeliczenie konwersji między tymi 2 systemami nie jest proste. Aby rozwiązać ten problem, WebGLOverlayView przekazuje obiekt coordinateTransformer do haka cyklu życia OnDraw, który zawiera funkcję o nazwie fromLatLngAltitude. Funkcja fromLatLngAltitude przyjmuje obiekt LatLngAltitude lub LatLngAltitudeLiteral oraz opcjonalnie zestaw argumentów, które definiują transformację sceny, a następnie przekształca je w macierz projekcji widoku modelu (MVP). Wystarczy, że określisz, w którym miejscu na mapie ma być renderowana scena Three.js, a także jak ma być przekształcana, a WebGLOverlayView zajmie się resztą. Następnie możesz przekonwertować macierz MVP na tablicę Matrix4 w Three.js i ustawić na nią macierz projekcji kamery.

    W poniższym kodzie drugi argument informuje widok nakładki WebGL, aby ustawić wysokość sceny Three.js na 120 metrów nad ziemią, co sprawi, że model będzie wyglądał, jakby unosił się w powietrzu.

    Aby ustawić macierz projekcji kamery, dodaj do haka onDraw ten kod:
    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. Przekształć model.

     Zauważysz, że pinezka nie jest ustawiona prostopadle do mapy. W grafice 3D oprócz przestrzeni świata, która ma własne osie x, y i z określające orientację, każdy obiekt ma też własną przestrzeń obiektu z niezależnym zestawem osi.

    W przypadku tego modelu nie został on utworzony w taki sposób, aby to, co zwykle uważamy za „górę” pinezki, było skierowane wzdłuż osi Y. Musisz więc przekształcić obiekt, aby zorientować go w odpowiedni sposób względem przestrzeni świata, wywołując na nim funkcję rotation.set. Pamiętaj, że w Three.js obrót jest określany w radianach, a nie w stopniach. Zwykle łatwiej jest myśleć w stopniach, więc należy dokonać odpowiedniego przeliczenia za pomocą wzoru degrees * Math.PI/180.

     Model jest też trochę mały, więc musisz go równomiernie powiększyć we wszystkich osiach, wywołując funkcję scale.set(x, y ,z).

     Aby obrócić i skalować model, dodaj te wiersze w loader wywołaniu zwrotnym onAdd przed scene.add(gltf.scene) dodaniem pliku gLTF do sceny:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

Teraz pinezka jest ustawiona pionowo względem mapy.

Pionowy pin

Twoje hooki onAddonDraw powinny teraz wyglądać tak:

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

Kolejne są animacje aparatu.

8. Animowanie kamery

Po wyrenderowaniu modelu na mapie i możliwości przesuwania wszystkiego w 3D kolejną rzeczą, którą prawdopodobnie zechcesz zrobić, jest programowe sterowanie tym ruchem. Funkcja moveCamera umożliwia jednoczesne ustawianie właściwości środka, powiększenia, pochylenia i kierunku mapy, co pozwala precyzyjnie kontrolować wrażenia użytkowników. Dodatkowo funkcję moveCamera można wywoływać w pętli animacji, aby tworzyć płynne przejścia między ramkami z częstotliwością prawie 60 klatek na sekundę.

  1. Poczekaj, aż model się wczyta.

    Aby zapewnić użytkownikom wygodę, zaczekaj z rozpoczęciem przesuwania kamery, aż model gLTF zostanie wczytany. Aby to zrobić, dołącz moduł obsługi zdarzeń onLoad narzędzia do wczytywania do haka onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. Utwórz pętlę animacji.

    Istnieje więcej niż jeden sposób tworzenia pętli animacji, np. za pomocą setInterval lub requestAnimationFrame. W tym przypadku użyjesz funkcji setAnimationLoop renderera Three.js, która automatycznie wywoła dowolny kod zadeklarowany w jej wywołaniu zwrotnym za każdym razem, gdy Three.js wyrenderuje nową klatkę. Aby utworzyć pętlę animacji, dodaj do modułu obsługi zdarzeń onLoad z poprzedniego kroku ten kod:
    renderer.setAnimationLoop(() => {});
    
  3. Ustaw pozycję kamery w pętli animacji.

    Następnie wywołaj moveCamera, aby zaktualizować mapę. W tym przypadku do określenia pozycji kamery używane są właściwości obiektu mapOptions, który został użyty do wczytania mapy:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. Aktualizuj kamerę w każdej klatce.

    Ostatni krok Zaktualizuj obiekt mapOptions na końcu każdej klatki, aby ustawić pozycję kamery dla następnej klatki. W tym kodzie instrukcja if służy do zwiększania nachylenia, aż osiągnie ono maksymalną wartość 67,5.Następnie w każdej klatce nieco zmienia się kierunek, aż kamera wykona pełny obrót o 360 stopni. Po zakończeniu animacji do funkcji setAnimationLoop przekazywana jest wartość null, aby anulować animację i zapobiec jej ciągłemu działaniu.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

Twój hook onContextRestored powinien teraz wyglądać tak:

    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. Gratulacje

Jeśli wszystko poszło zgodnie z planem, powinna się teraz wyświetlać mapa z dużym pinezką 3D, która wygląda tak:

Final 3D Pin

Czego się dowiedziałeś

W tym ćwiczeniu zdobędziesz wiele umiejętności. Oto najważniejsze z nich:

  • Wdrażanie interfejsu WebGLOverlayView i jego punktów zaczepienia cyklu życia.
  • Integracja Three.js z mapą.
  • Podstawy tworzenia sceny w Three.js, w tym kamer i oświetlenia.
  • Wczytywanie modeli 3D i manipulowanie nimi za pomocą Three.js.
  • Sterowanie kamerą i animowanie jej na potrzeby mapy za pomocą moveCamera.

Co dalej?

WebGL i grafika komputerowa to złożone zagadnienia, więc zawsze jest się czego uczyć. Oto kilka materiałów, które pomogą Ci zacząć: