Praca z renderowaniem płytek 3D

Fotorealistyczne kafelki 3D są dostępne standardowy format glTF OGC, co oznacza, że możesz użyć dowolnego mechanizmu renderowania, który obsługuje specyfikację kafelków 3D OGC, dzięki wizualizacjom 3D. Przykład: Cez to podstawowa biblioteka typu open source do renderowania wizualizacji 3D.

Praca z CesiumJS

CesiumJS to biblioteka JavaScript typu open source do wizualizacji 3D w internecie. Więcej informacji o korzystaniu z cesiumJS znajdziesz na stronie Poznaj CesiumJS.

Kontrola użytkowników

Mechanizm renderowania kafelków CesiumJS ma standardowy zestaw elementów sterujących użytkownika.

Działanie Opis
Widok przesuwany Kliknięcie lewym przyciskiem myszy i przeciągać
Powiększenie Kliknij prawym przyciskiem myszy i przeciągnij lub obróć kółko myszy
Obróć widok Ctrl + kliknięcie w lewo/prawo i przeciągnij lub kliknij środkowym przyciskiem & przeciągać

Sprawdzone metody

Istnieje kilka sposobów na przyspieszenie wczytywania 3D CesiumJS. razy. Na przykład:

  • Aby włączyć jednoczesne żądania, dodaj następującą instrukcję do kodu HTML renderowania:

    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = <REQUEST_COUNT>
    

    Im większa wartość REQUEST_COUNT, tym szybciej kafelki zostaną wczytane. Jednak w przypadku wczytywania w przeglądarce Chrome z kontem REQUEST_COUNT więcej niż 10, a pamięć podręczna jest wyłączona, możesz napotkać znany błąd Problem z Chrome. W większości przypadków zalecamy REQUEST_COUNT o wartości 18, skuteczność reklam.

  • Włącz pomijanie poziomów szczegółów. Więcej informacji znajdziesz tutaj Problem z cezem.

Upewnij się, że atrybucje danych są prawidłowo wyświetlane, włączając showCreditsOnScreen: true Więcej informacji: Zasady.

Dane renderowania

Aby poznać liczbę klatek, sprawdź, ile razy na sekundę requestAnimationFrame .

Aby dowiedzieć się, jak obliczamy opóźnienie klatek, spójrz na PerformanceDisplay zajęcia.

Przykłady mechanizmu renderowania CesiumJS

Możesz użyć mechanizmu renderowania CesiumJS z kafelkami 3D interfejsu Map Tiles API, przez podanie głównego adresu URL zbioru kafelków.

Prosty przykład

Ten przykład inicjuje mechanizm renderowania CesiumJS, a następnie wczytuje plik główny z różnych krajów.

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>CesiumJS 3D Tiles Simple Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>

    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer('cesiumContainer', {
      imageryProvider: false,
      baseLayerPicker: false,
      geocoder: false,
      globe: false,
      // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#enabling-request-render-mode
      requestRenderMode: true,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
      url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
      // This property is needed to appropriately display attributions
      // as required.
      showCreditsOnScreen: true,
    }));
  </script>
</body>

Więcej informacji o requestRenderMode: Włącz tryb renderowania żądania.

Strona HTML będzie się renderować w sposób pokazany tutaj.

Integracja interfejsu Places API

CesiumJS możesz używać z Interfejs Places API aby uzyskać więcej informacji. Możesz użyć widżetu autouzupełniania, aby przejść do sekcji który jest widoczny w widoku Miejsc. W tym przykładzie korzystamy z interfejsu Places Autocomplete API. co jest włączone przez postępując zgodnie z tymi instrukcjami, oraz Maps JavaScript API, który jest włączony przez postępując zgodnie z tymi instrukcjami.

<!DOCTYPE html>
<head>
 <meta charset="utf-8" />
 <title>CesiumJS 3D Tiles Places API Integration Demo</title>
 <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
 <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
 <label for="pacViewPlace">Go to a place: </label>
 <input
   type="text"
   id="pacViewPlace"
   name="pacViewPlace"
   placeholder="Enter a location..."
   style="width: 300px"
 />
 <div id="cesiumContainer"></div>
 <script>
   // Enable simultaneous requests.
   Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

   // Create the viewer.
   const viewer = new Cesium.Viewer("cesiumContainer", {
     imageryProvider: false,
     baseLayerPicker: false,
     requestRenderMode: true,
     geocoder: false,
     globe: false,
   });

   // Add 3D Tiles tileset.
   const tileset = viewer.scene.primitives.add(
     new Cesium.Cesium3DTileset({
       url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
       // This property is required to display attributions as required.
       showCreditsOnScreen: true,
     })
   );

   const zoomToViewport = (viewport) => {
     viewer.entities.add({
       polyline: {
         positions: Cesium.Cartesian3.fromDegreesArray([
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
         ]),
         width: 10,
         clampToGround: true,
         material: Cesium.Color.RED,
       },
     });
     viewer.flyTo(viewer.entities);
   };

   function initAutocomplete() {
     const autocomplete = new google.maps.places.Autocomplete(
       document.getElementById("pacViewPlace"),
       {
         fields: [
           "geometry",
           "name",
         ],
       }
     );
     autocomplete.addListener("place_changed", () => {
       viewer.entities.removeAll();
       const place = autocomplete.getPlace();
       if (!place.geometry || !place.geometry.viewport) {
         window.alert("No viewport for input: " + place.name);
         return;
       }
       zoomToViewport(place.geometry.viewport);
     });
   }
 </script>
 <script
   async=""
   src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
 ></script>
</body>

Obracający się widok drona

Możesz sterować kamerą, aby animować się za pomocą pola kafelkowego. W połączeniu z interfejsu Places API i Elevation API, ta animacja symuluje interaktywną na locie drona w dowolne miejsce.

Omówimy ten przykładowy kod, który przemieści się w miejscu wybranym Widżet autouzupełniania.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Rotating Drone View Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <label for="pacViewPlace">Go to a place: </label>
  <input type="text" id="pacViewPlace" name="pacViewPlace" placeholder="Enter a location..." style="width: 300px" />
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer and remove unneeded options.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      homeButton: false,
      fullscreenButton: false,
      navigationHelpButton: false,
      vrButton: false,
      sceneModePicker: false,
      geocoder: false,
      globe: false,
      infobox: false,
      selectionIndicator: false,
      timeline: false,
      projectionPicker: false,
      clockViewModel: null,
      animation: false,
      requestRenderMode: true,
    });

    // Add 3D Tile set.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
        // This property is required to display attributions.
        showCreditsOnScreen: true,
      })
    );

    // Point the camera at a location and elevation, at a viewport-appropriate distance.
    function pointCameraAt(location, viewport, elevation) {
      const distance = Cesium.Cartesian3.distance(
        Cesium.Cartesian3.fromDegrees(
          viewport.getSouthWest().lng(), viewport.getSouthWest().lat(), elevation),
        Cesium.Cartesian3.fromDegrees(
          viewport.getNorthEast().lng(), viewport.getNorthEast().lat(), elevation)
      ) / 2;
      const target = new Cesium.Cartesian3.fromDegrees(location.lng(), location.lat(), elevation);
      const pitch = -Math.PI / 4;
      const heading = 0;
      viewer.camera.lookAt(target, new Cesium.HeadingPitchRange(heading, pitch, distance));
    }

    // Rotate the camera around a location and elevation, at a viewport-appropriate distance.
    let unsubscribe = null;
    function rotateCameraAround(location, viewport, elevation) {
      if(unsubscribe) unsubscribe();
      pointCameraAt(location, viewport, elevation);
      unsubscribe = viewer.clock.onTick.addEventListener(() => {
        viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z);
      });
    }

    function initAutocomplete() {
      const autocomplete = new google.maps.places.Autocomplete(
        document.getElementById("pacViewPlace"), {
          fields: [
            "geometry",
            "name",
          ],
        }
      );
      
      autocomplete.addListener("place_changed", async () => {
        const place = autocomplete.getPlace();
        
        if (!(place.geometry && place.geometry.viewport && place.geometry.location)) {
          window.alert(`Insufficient geometry data for place: ${place.name}`);
          return;
        }
        // Get place elevation using the ElevationService.
        const elevatorService = new google.maps.ElevationService();
        const elevationResponse =  await elevatorService.getElevationForLocations({
          locations: [place.geometry.location],
        });

        if(!(elevationResponse.results && elevationResponse.results.length)){
          window.alert(`Insufficient elevation data for place: ${place.name}`);
          return;
        }
        const elevation = elevationResponse.results[0].elevation || 10;

        rotateCameraAround(
          place.geometry.location,
          place.geometry.viewport,
          elevation
        );
      });
    }
  </script>
  <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"></script>
</body>

Rysuj linie łamane i etykiety

Ten przykładowy kod pokazuje, jak dodać do mapy linie łamane i etykiety. Dostępne opcje dodaj linie łamane do mapy, aby wyświetlać trasy samochodowe i piesze, lub granic nieruchomości ani obliczać czasu jazdy i trasy pieszych. Możesz też uzyskać atrybuty bez renderowania sceny.

Możesz zabrać użytkowników na specjalną wycieczkę po okolicy lub pokazać sąsiadujące nieruchomości, które są aktualnie na sprzedaż, można dodać widok 3D takich jak billboardy.

Możesz podsumować wycieczkę, wymienić obejrzane przez Ciebie miejsca lub szczegóły w wirtualnych obiektach.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Polyline and Label Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link 
    href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css"
    rel="stylesheet"
  />
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      requestRenderMode: true,
      geocoder: false,
      globe: false,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",

        // This property is required to display attributions as required.
        showCreditsOnScreen: true,
      })
    );

    // Draws a circle at the position, and a line from the previous position.
    const drawPointAndLine = (position, prevPosition) => {
      viewer.entities.removeAll();
      if (prevPosition) {
        viewer.entities.add({
          polyline: {
            positions: [prevPosition, position],
            width: 3,
            material: Cesium.Color.WHITE,
            clampToGround: true,
            classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
          },
        });
      }
      viewer.entities.add({
        position: position,
        ellipsoid: {
          radii: new Cesium.Cartesian3(1, 1, 1),
          material: Cesium.Color.RED,
        },
      });
    };

    // Compute, draw, and display the position's height relative to the previous position.
    var prevPosition;
    const processHeights = (newPosition) => {
      drawPointAndLine(newPosition, prevPosition);

      const newHeight = Cesium.Cartographic.fromCartesian(newPosition).height;
      let labelText = "Current altitude (meters above sea level):\n\t" + newHeight;
      if (prevPosition) {
        const prevHeight =
          Cesium.Cartographic.fromCartesian(prevPosition).height;
        labelText += "\nHeight from previous point (meters):\n\t" + Math.abs(newHeight - prevHeight);
      }
      viewer.entities.add({
        position: newPosition,
        label: {
          text: labelText,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          pixelOffset: new Cesium.Cartesian2(0, -10),
          showBackground: true,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        }
      });

      prevPosition = newPosition;
    };

    const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
    handler.setInputAction(function (event) {
      const earthPosition = viewer.scene.pickPosition(event.position);
      if (Cesium.defined(earthPosition)) {
        processHeights(earthPosition);
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  </script>
</body>

Orbita kamery

W Cezu można obracać aparat wokół wybranego miejsca, unikając zderzeń z budynkami. Możesz też ustawić budynki przezroczyste gdy porusza się przez nie kamera.

Najpierw zablokuj kamerę w określonym punkcie, a potem utwórz orbitę, aby i zaprezentuj swój zasób. Możesz to zrobić za pomocą lookAtTransform z detektorem zdarzeń, jak widać w tym przykładowym kodzie.

// Lock the camera onto a point.
const center = Cesium.Cartesian3.fromRadians(
  2.4213211833389243,
  0.6171926869414084,
  3626.0426275055174
);

const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

viewer.scene.camera.lookAtTransform(
  transform,
  new Cesium.HeadingPitchRange(0, -Math.PI / 8, 2900)
);

// Orbit around this point.
viewer.clock.onTick.addEventListener(function (clock) {
  viewer.scene.camera.rotateRight(0.005);
});

Więcej informacji o sterowaniu kamerą znajdziesz w sekcji Sterowanie kamerą

Praca z cesium w Unreal

Aby używać wtyczki Cesium for Unreal z interfejsem 3D Tiles API, wykonaj te czynności poniżej.

  1. Zainstaluj wtyczkę Cesium for Unreal.

  2. Utwórz nowy projekt Unreal.

  3. Połącz się z interfejsem Google PhotoRealistyczny 3D Tiles API.

    1. Otwórz okno Cesium, wybierając Cesium > Cesium.

    2. Wybierz Pusty zestaw kafelków 3D.

    3. W Szkicowniku świata otwórz panel Szczegóły, wybierając tę opcję Cesium3DTileset.

    4. Zmień wartość opcji Źródło z Z cezium Ion na Z adresu URL.

    5. Jako adres URL wpisz URL kafelków Google 3D.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Włącz opcję Pokaż środki na ekranie, aby prawidłowo wyświetlać atrybucje.
  4. To otwiera przed Tobą świat. Aby przejść do dowolnej długości geograficznej, wybierz CesiumGeoreference (Geolokalizacja w Cesium) w panelu CesiumGeoreference i edytuj go Szerokość/długość geograficzna/wysokość punktu początkowego w panelu Szczegóły.

Praca z Cesium w Unity

Aby używać fotorealistycznych kafelków z cesium w Unity, wykonaj te czynności.

  1. Utwórz nowy projekt w Unity.

  2. Dodaj nowy rejestr o ograniczonym zakresie w sekcji Menedżera pakietów (za pomocą Edytora > Ustawienia projektu).

    • Nazwa: Cesium

    • URL: https://unity.pkg.cesium.com

    • Zakresy: com.cesium.unity

  3. Zainstaluj pakiet Cesium for Unity.

  4. Połącz się z interfejsem Google Photoreal 3D Tiles API.

    1. Otwórz okno Cesium, wybierając Cesium > Cesium.

    2. Kliknij Pusty zestaw kafelków 3D.

    3. W panelu bocznym po lewej stronie, w opcji Źródło kafelków w sekcji Źródło, wybierz From URL (Z adresu URL) (zamiast Od Cesium Ion).

    4. Jako adres URL wpisz adres URL kafelków Google 3D.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Włącz opcję Pokaż środki na ekranie, aby prawidłowo wyświetlać atrybucje.
  5. To otwiera przed Tobą świat. Aby przejść do dowolnej długości geograficznej, wybierz CesiumGeoreference (Dane geograficzne) w CesiumGeoreference, a potem zmodyfikuj Szerokość/długość geograficzna/wysokość punktu początkowego w inspektorze.

Praca z deklaracja.gl

deck.gl, oparte na technologii WebGL to platforma JavaScript typu open source zwiększająca wydajność, i wizualizacji danych na dużą skalę.

Atrybucja

Zadbaj o prawidłowe wyświetlanie atrybucji danych, pobierając copyright z kafelków gltf asset, a następnie wyświetlając je w wyrenderowanym widoku. Dla: więcej informacji znajdziesz w Atrybucja danych w sieci reklamowej.

przykłady mechanizmu renderowania site.gl

Prosty przykład

Poniższy przykład inicjuje mechanizm renderowania site.gl, a następnie wczytuje miejsce. w 3D. Pamiętaj, aby w kodzie zastąpić YOUR_API_KEY nazwą rzeczywisty klucz interfejsu API.

<!DOCTYPE html>
<html>
 <head>
   <title>deck.gl Photorealistic 3D Tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const creditsElement = document.getElementById('credits');
     new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: {minZoom: 8},
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
           onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           }
         })
       ]
     });
   </script>
 </body>
</html>

Wizualizacja warstw 2D na tle realistycznych kafelków 3D w Zdjęciach Google

Deck.gl TerrainExtension renderuje na powierzchni 3D dane 2D. Możesz na przykład zasłonić GeoJSON przedstawiający podstawę budynku na tle geometrii kafelków 3D.

W poniższym przykładzie wizualizowana jest warstwa budynków za pomocą wielokątów. dostosowane do powierzchni fotorealistycznych kafelków 3D.

<!DOCTYPE html>
<html>
 <head>
   <title>Google 3D tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const BUILDINGS_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson'
     const creditsElement = document.getElementById('credits');
     const deckgl = new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: true,
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
          onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           },
           operation: 'terrain+draw'
         }),
         new deck.GeoJsonLayer({
           id: 'buildings',
           // This dataset is created by CARTO, using other Open Datasets available. More info at: https://3dtiles.carto.com/#about.
           data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson',
           stroked: false,
           filled: true,
           getFillColor: ({properties}) => {
             const {tpp} = properties;
             // quantiles break
             if (tpp < 0.6249)
               return [254, 246, 181]
             else if (tpp < 0.6780)
               return [255, 194, 133]
             else if (tpp < 0.8594)
               return [250, 138, 118]
             return [225, 83, 131]
           },
           opacity: 0.2,
           extensions: [new deck._TerrainExtension()]
         })
       ]
     });
   </script>
 </body>
</html>