Import and play GLTF animations from GLTF files

Use the MapTiler 3D JS module to play animations in a GLTF model and move the models to simulate a flight over the map

This demonstration showcases how to add a animated 3D model in a map and moved it acroos the map.

NPM module setup


npm install --save @maptiler/sdk @maptiler/3d lil-gui
import { Map, MapStyle, config, math } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import { Layer3D, AltitudeReference } from '@maptiler/3d';
import { GUI } from 'lil-gui';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';

const lakeNatron = [36.01695746068115, -2.3536069164210653];
const makadikadi = [25.5527055978211, -20.78468929963636];

function calculateHeading(lat1, lon1, lat2, lon2) {
  const φ1 = math.toRadians(lat1);
  const φ2 = math.toRadians(lat2);
  const Δλ = math.toRadians(lon2 - lon1);

  const y = Math.sin(Δλ) * Math.cos(φ2);
  const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);

  let θ = Math.atan2(y, x);
  θ = math.toDegrees(θ);
  return ((θ + 360) % 360) + 90; // Normalize to 0–360 and add 90 degrees
}

const map = new Map({
  container: 'map',
  style: MapStyle.AQUARELLE.VIVID,
  center: lakeNatron, // starting position [lng, lat]
  zoom: 10, // starting zoom
  hash: true,
  bearing: 0,
  pitch: 45,
  attributionControl: {
    customAttribution: "Model by <a href='https://mirada.com/' target='_blank'>Mirada</a> for <a href='https://experiments.withgoogle.com/3-dreams-of-black' target='_blank'>3 Dreams of Black</a>",
  }
});

// Waiting for the map to be ready
map.on('ready', async () => {

  map.setSky({
    "sky-color": "#0C2E4B",
    "horizon-color": "#09112F",
    "fog-color": "#09112F",
    "fog-ground-blend": 0.5,
    "horizon-fog-blend": 0.1,
    "sky-horizon-blend": 1.0,
    "atmosphere-blend": 0.5,
  });

  // Create a Layer3D and add it
  const layer3D = new Layer3D("custom-3D-layer");
  map.addLayer(layer3D);

   // Increasing the intensity of the ambient light
  layer3D.setAmbientLight({ intensity: 2 });

  // Adding a point light
  layer3D.addPointLight("point-light", { intensity: 30 });

  const flamingoIDOne = "flamingo";
  const flamingoIDTwo = "flamingo-clone";

  // The call can be awaited for the whole download of the mesh to complete
  await layer3D.addMeshFromURL(
    // ID to give to this mesh, unique within this Layer3D instance
    flamingoIDOne,

    // The URL of the mesh
    "https://docs-media.maptiler.com/docs/models/flamingo.glb",

    // A set of options, these can be modified later
    {
      lngLat: lakeNatron,
      heading: 12,
      scale: 100,
      animationMode: "continuous",
      altitudeReference: AltitudeReference.MEAN_SEA_LEVEL,
      transform: {
        rotation: {
          x: 0,
          y: Math.PI / 2,
          z: 0,
        },
        offset: {
          x: 0,
          y: 0,
          z: -150,
        },
      },
    }
  );

  const fly = "fly!";
  const migrate = "migrate with the flamingo";
  const speed = "migration speed";

  const guiObj = {
    [fly]: true,
    [migrate]: true,
    [speed]: 0.0005,
  };

  const gui = new GUI({ width: 500 });

  gui.add(guiObj, migrate);

  gui.add(guiObj, fly).onChange((play) => {
    if (play) {
      playAnimation();
    }
  });

  gui.add(guiObj, speed, 0, 0.001);

  const mesh = layer3D.getItem3D("flamingo");

  let progress = 0;

  const animationNames = mesh?.getAnimationNames();

  const animationName = animationNames?.[0];

  if (!animationName) {
    throw new Error(`No animation found with name '${animationName}'`);
  }
    
  layer3D.cloneMesh(flamingoIDOne, flamingoIDTwo, {
    animationMode: "manual",
    transform: {
      offset: {
        x: 0,
        y: 0,
        z: 300,
      },
    },
  });

  layer3D.getItem3D(flamingoIDOne)?.playAnimation(
    animationName,
    "loop",
  );

  layer3D.getItem3D(flamingoIDTwo)?.playAnimation(
    animationName,
    "loop",
  );

  const distance = math.haversineDistanceWgs84(lakeNatron, makadikadi);

  const initialHeading = calculateHeading(makadikadi[1], makadikadi[0], lakeNatron[1], lakeNatron[0]);

  const flamingoOneMesh = layer3D.getItem3D(flamingoIDOne);
  const flamingoTwoMesh = layer3D.getItem3D(flamingoIDTwo);

  flamingoOneMesh.modify({ heading: initialHeading });
  flamingoTwoMesh.modify({ heading: initialHeading });

  function playAnimation() {
    progress += guiObj[speed];

    if (progress > 1) {
      progress = 0;
    }

    const nextPosition = math.haversineIntermediateWgs84(lakeNatron, makadikadi, progress - (guiObj[speed] === 0 ? 0.001 : guiObj[speed]));
    const position = math.haversineIntermediateWgs84(lakeNatron, makadikadi, progress);

    const roughHeading = calculateHeading(position[1], position[0], nextPosition[1], nextPosition[0]);

    // `updateAnimation` is only needed if you want to control the animation manually
    // to automatically play the animation independently of map updates, set the item
    // animationMode: "continuous"

    flamingoTwoMesh.updateAnimation(guiObj[speed] * 50);

    const distanceFromStart = math.haversineDistanceWgs84(lakeNatron, [position[0], position[1]]);
    const progressPercentage = distanceFromStart / distance;

    flamingoOneMesh.modify({ lngLat: position, heading: roughHeading });
    flamingoTwoMesh.modify({ lngLat: position, heading: roughHeading });

    if (guiObj[migrate]) {
      map.setCenter(position);
      map.setZoom(10 - Math.sin(progressPercentage * Math.PI) * 2);
      map.setBearing(progressPercentage * -45);
    }

    if (guiObj[fly]) {
      requestAnimationFrame(playAnimation);
    }
  }

  if (guiObj[fly]) {
    playAnimation()
  }

});

Model by Mirada for 3 Dreams of Black 

Display an animated 3D model

Animate a 3D model

Examples

Animate 3D models and display them on a map.

Add events on 3D models

3D model events

Examples

Listen for mouse events on 3D models in maps.

Add a airplane 3D model

Add a plane 3D model

Examples

Add and position flying 3D airplane models on maps.

Add multiple 3D models to the map

Add multiple 3D models

Examples

Incorporate multiple 3D models with adjustable parameters on maps.

Was this helpful?