Skip to content

Commit

Permalink
RD-327: add terrain animation events (#119)
Browse files Browse the repository at this point in the history
* add terrain animation events

* linting

* terrain events doc
  • Loading branch information
jonathanlurie authored Oct 9, 2024
1 parent d33e66d commit 5d3f7a7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

## NEXT
### New Features

- The `Map` class instances now have a `.setTerrainAnimationDuration(d: number)` method
- The `Map` class instances now have events related to terrain animation `"terrainAnimationStart"` and `"terrainAnimationStop"`
- expose the function `getWebGLSupportError()` to detect WebGL compatibility


## 2.3.0
### Bug Fixes
- Updating from MapLibre v4.4.1 to v4.7.0. See Maplibre changelogs for [v4.5.0](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#450), [v4.5.1](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#451), [v4.5.2](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#452), and [v4.6.0](https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#460)
Expand Down
49 changes: 49 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,55 @@ map.disableTerrain()
> 📣 *__Note 2:__* please be aware that due to the volume and elevation of the map floor in 3D space, the navigation with the terrain enabled is slightly different than without.
By default, enabling, disabling or even just updating the terrain exaggeration will result in a 1-second animation. This is possible to modify with the following `Map` method:

```ts
// Duration in milliseconds
map.setTerrainAnimationDuration(500);
```

## Terrain events
- `"terrain"` event

As an extension of Maplibre GL JS, MapTiler SDK is also exposing the terrain event `"terrain"`. This event is triggered when a terrain source is added or removed:

```ts
map.on("terrain", (e) => {
// your logic here
})
```

Since MapTiler SDK adds animation and the terrain data is necessary all along, the `"terrain"` event will be called at the very begining of the terrain animation when enabling and at the very end when disabling.

- `"terrainAnimationStart"` and `"terrainAnimationStop"` events

With the animation of the terrain, it can sometimes be convenient to know when the animation starts and ends. These two events are made just for that, here are how they work:

```ts
map.on("terrainAnimationStart", (event) => {
console.log("Terrain animation is starting...");
});

map.on("terrainAnimationStop", (event) => {
console.log("Terrain animation is finished");
});
```

The `event` argument is an object that contains (amond other things) a `terrain` attribute. In the case of `"terrainAnimationStop"`, this terrain attribute is `null` if the animation was about disabling the terrain, otherwise, this is just a propagation of `map.terrain`.

In the following example, we decide to associate the terrain animation with a change of camera, e.g. from clicking on the terrain control:
- when the terrain is enabled, it pops up with an animation and only **then** the camera is animated to take a lower point of view
- when the terrain is disabled, it is flattened with an animation and only **then** the camera is animated to a top view

```ts
map.on("terrainAnimationStop", (e) => {
map.easeTo({
pitch: e.terrain ? 60 : 0,
duration: 500,
});
});
```


# Easy language switching
The language generally depends on the style but we made it possible to easily set and update from a built-in list of languages.
Expand Down
32 changes: 24 additions & 8 deletions src/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export class Map extends maplibregl.Map {
private forceLanguageUpdate: boolean;
private languageAlwaysBeenStyle: boolean;
private isReady = false;
private terrainAnimationDuration = 1000;

constructor(options: MapOptions) {
displayNoWebGlWarning(options.container);
Expand Down Expand Up @@ -557,6 +558,14 @@ export class Map extends maplibregl.Map {
}
}

/**
* Set the duration (millisec) of the terrain animation for growing or flattening.
* Must be positive. (Built-in default: `1000` milliseconds)
*/
setTerrainAnimationDuration(d: number) {
this.terrainAnimationDuration = Math.max(d, 0);
}

/**
* Awaits for _this_ Map instance to be "loaded" and returns a Promise to the Map.
* If _this_ Map instance is already loaded, the Promise is resolved directly,
Expand Down Expand Up @@ -1012,7 +1021,7 @@ export class Map extends maplibregl.Map {
return this.isTerrainEnabled;
}

private growTerrain(exaggeration: number, durationMs = 1000) {
private growTerrain(exaggeration: number) {
// This method assumes the terrain is already built
if (!this.terrain) {
return;
Expand All @@ -1037,7 +1046,7 @@ export class Map extends maplibregl.Map {
}

// normalized value in interval [0, 1] of where we are currently in the animation loop
const positionInLoop = (performance.now() - startTime) / durationMs;
const positionInLoop = (performance.now() - startTime) / this.terrainAnimationDuration;

// The animation goes on until we reached 99% of the growing sequence duration
if (positionInLoop < 0.99) {
Expand All @@ -1049,13 +1058,18 @@ export class Map extends maplibregl.Map {
this.terrainGrowing = false;
this.terrainFlattening = false;
this.terrain.exaggeration = exaggeration;
this.fire("terrainAnimationStop", { terrain: this.terrain });
}

// When growing the terrain, this is only necessary before rendering
this._elevationFreeze = false;
this.triggerRepaint();
};

if (!this.terrainGrowing && !this.terrainFlattening) {
this.fire("terrainAnimationStart", { terrain: this.terrain });
}

this.terrainGrowing = true;
this.terrainFlattening = false;
requestAnimationFrame(updateExaggeration);
Expand Down Expand Up @@ -1157,10 +1171,7 @@ export class Map extends maplibregl.Map {
}

this.isTerrainEnabled = false;
// this.stopFlattening = false;

// Duration of the animation in millisec
const animationLoopDuration = 1 * 1000;
const startTime = performance.now();
// This is supposedly 0, but it could be something else (e.g. already in the middle of growing, or user defined other)
const currentExaggeration = this.terrain.exaggeration;
Expand All @@ -1172,14 +1183,14 @@ export class Map extends maplibregl.Map {
return;
}

// If the growing animation is triggered while flattening,
// then we exist the flatening
// If the growing animation is triggered while flattening is in progress.
// We exit the flatening
if (this.terrainGrowing) {
return;
}

// normalized value in interval [0, 1] of where we are currently in the animation loop
const positionInLoop = (performance.now() - startTime) / animationLoopDuration;
const positionInLoop = (performance.now() - startTime) / this.terrainAnimationDuration;

// At disabling, this should be togled fo both the setTerrain() (at the end of the animation)
// and also just before triggerRepain(), this is why we moved it this high
Expand All @@ -1201,11 +1212,16 @@ export class Map extends maplibregl.Map {
if (this.getSource(defaults.terrainSourceId)) {
this.removeSource(defaults.terrainSourceId);
}
this.fire("terrainAnimationStop", { terrain: null });
}

this.triggerRepaint();
};

if (!this.terrainGrowing && !this.terrainFlattening) {
this.fire("terrainAnimationStart", { terrain: this.terrain });
}

this.terrainGrowing = false;
this.terrainFlattening = true;
requestAnimationFrame(updateExaggeration);
Expand Down

0 comments on commit 5d3f7a7

Please sign in to comment.