import "@maptiler/sdk/style.css"; import { type LngLatLike, Map, MapStyle, config, math } from "@maptiler/sdk"; import { addPerformanceStats, setupMapTilerApiKey } from "./demo-utils"; import { AltitudeReference, Item3D, Layer3D } from "../../src"; import GUI from "lil-gui"; setupMapTilerApiKey({ config }); addPerformanceStats(); const lakeNatron: [number, number] = [36.01695746068115, -2.3536069164210653]; const makadikadi: [number, number] = [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: document.getElementById("map") as HTMLElement, style: MapStyle.AQUARELLE.VIVID, center: lakeNatron, maxPitch: 85, terrainControl: false, terrain: false, maptilerLogo: true, projectionControl: true, zoom: 10, bearing: 0, pitch: 45, attributionControl: { customAttribution: "Model by Mirada for 3 Dreams of Black", } }); (async () => { await map.onReadyAsync(); 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, }); 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"; await layer3D.addMeshFromURL(flamingoIDOne, // Model by https://mirada.com/ for https://experiments.withgoogle.com/3-dreams-of-black "models/Flamingo.glb", { lngLat: lakeNatron, heading: 12, scale: 100, altitude: 5500, 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]: false, [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); let progress = 0; const animationNames = layer3D.getItem3D(flamingoIDOne)?.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) as Item3D; const flamingoTwoMesh = layer3D.getItem3D(flamingoIDTwo) as Item3D; 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])) as LngLatLike; const position = math.haversineIntermediateWgs84(lakeNatron, makadikadi, progress) as LngLatLike; 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); } } })();