Skip to main content

Add 3D Model BabylonJS

Use a custom style layer with babylon.js to add a 3D model to the map. Requires Trimble Maps v4.0.0 or later.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Add a 3D model with babylon.js</title>
    <link rel="stylesheet" href="https://maps-sdk.trimblemaps.com/v4/trimblemaps-4.2.1.css" />
    <script src="https://maps-sdk.trimblemaps.com/v4/trimblemaps-4.2.1.js"></script>
    <style>
      body {
        margin: 0;
        padding: 0;
      }
      html,
      body,
      #map {
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://unpkg.com/babylonjs@5.42.2/babylon.js"></script>
    <script src="https://unpkg.com/babylonjs-loaders@5.42.2/babylonjs.loaders.min.js"></script>
    <div id="map"></div>
    <script>
      const BABYLON = window.BABYLON;

      TrimbleMaps.setAPIKey("68B06901AF7DA34884CE5FB7A202A743");
      const map = (window.map = new TrimbleMaps.Map({
        container: "map",
        style: TrimbleMaps.Common.Style.TRANSPORTATION,
        zoom: 18,
        center: [148.9819, -35.3981],
        pitch: 60,
        antialias: true, // create the gl context with MSAA antialiasing, so custom layers are antialiased
      }));

      // World matrix parameters
      const worldOrigin = [148.9819, -35.39847];
      const worldAltitude = 0;

      // Trimblemaps.js default coordinate system (no rotations)
      // +x east, -y north, +z up
      //var worldRotate = [0, 0, 0];

      // Babylon.js default coordinate system
      // +x east, +y up, +z north
      const worldRotate = [Math.PI / 2, 0, 0];

      // Calculate mercator coordinates and scale
      const worldOriginMercator = TrimbleMaps.MercatorCoordinate.fromLngLat(worldOrigin, worldAltitude);
      const worldScale = worldOriginMercator.meterInMercatorCoordinateUnits();

      // Calculate world matrix
      const worldMatrix = BABYLON.Matrix.Compose(
        new BABYLON.Vector3(worldScale, worldScale, worldScale),
        BABYLON.Quaternion.FromEulerAngles(worldRotate[0], worldRotate[1], worldRotate[2]),
        new BABYLON.Vector3(worldOriginMercator.x, worldOriginMercator.y, worldOriginMercator.z),
      );

      // configuration of the custom layer for a 3D model per the CustomLayerInterface
      const customLayer = {
        id: "3d-model",
        type: "custom",
        renderingMode: "3d",
        onAdd(map, gl) {
          this.engine = new BABYLON.Engine(
            gl,
            true,
            {
              useHighPrecisionMatrix: true, // Important to prevent jitter at mercator scale
            },
            true,
          );
          this.scene = new BABYLON.Scene(this.engine);
          this.scene.autoClear = false;
          this.scene.detachControl();

          this.scene.beforeRender = () => {
            this.engine.wipeCaches(true);
          };

          // create simple camera (will have its project matrix manually calculated)
          this.camera = new BABYLON.Camera("Camera", new BABYLON.Vector3(0, 0, 0), this.scene);

          // create simple light
          const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 0, 100), this.scene);
          light.intensity = 0.7;

          // Add debug axes viewer, positioned at origin, 10 meter axis lengths
          new BABYLON.AxesViewer(this.scene, 10);

          // load GLTF model in to the scene
          BABYLON.SceneLoader.LoadAssetContainerAsync("/docs/assets/34M_17/34M_17.gltf", "", this.scene).then((modelContainer) => {
            modelContainer.addAllToScene();

            const rootMesh = modelContainer.createRootMesh();

            // If using trimblemaps.js coordinate system (+z up)
            //rootMesh.rotation.x = Math.PI/2

            // Create a second mesh
            const rootMesh2 = rootMesh.clone();

            // Position in babylon.js coordinate system
            rootMesh2.position.x = 25; // +east, meters
            rootMesh2.position.z = 25; // +north, meters
          });

          this.map = map;
        },
        render(gl, matrix) {
          const cameraMatrix = BABYLON.Matrix.FromArray(matrix);

          // world-view-projection matrix
          const wvpMatrix = worldMatrix.multiply(cameraMatrix);

          this.camera.freezeProjectionMatrix(wvpMatrix);

          this.scene.render(false);
          this.map.triggerRepaint();
        },
      };

      map.on("style.load", () => {
        map.addLayer(customLayer);
      });
    </script>
  </body>
</html>

Last updated March 26, 2025.