Skip to content

Commit

Permalink
Support fog on markers (#10516)
Browse files Browse the repository at this point in the history
* Apply fog to Markers

* Fix lint flow and cleanup

* Fix opacity not updating for terrain and fog after updating the spec properties

* Add map.getFogOpacity API

* Code style

* Evaluate opacity less often

* Update per rebased changes

* Minor tweak

* Update src/style/fog.js

Co-authored-by: Ricky Reusser <[email protected]>

* Update src/ui/marker.js

Co-authored-by: Ricky Reusser <[email protected]>

* Update src/ui/map.js

Co-authored-by: Ricky Reusser <[email protected]>

* Update variable name per review

* Fixup invalid commit from github

* Address review comments

* Update per review

Co-authored-by: Ricky Reusser <[email protected]>

Fix popup tip not being occluded
  • Loading branch information
karimnaaji committed May 6, 2021
1 parent 6bad696 commit d6b71fb
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 39 deletions.
19 changes: 17 additions & 2 deletions debug/fog.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
this.enableTerrain = true;
this.terrainExaggeration = 1.5;
this.style = 'satellite-streets-v11';
this.fogRangeStart = 2000;
this.fogRangeEnd = 3000;
this.fogRangeStart = 1000;
this.fogRangeEnd = 10000;
this.fogOpacity = 1.0;
this.fogColor = [255, 255, 255];
this.fogSkyBlend = 0.1;
Expand Down Expand Up @@ -132,6 +132,21 @@
});
};

var popup = new mapboxgl.Popup().setHTML('Pitch Alignment: auto<br>Rotation Alignment: auto');

var marker = new mapboxgl.Marker({
color: `rgb(200, 0, 0)`,
scale: 1,
draggable: true,
pitchAlignment: 'auto',
rotationAlignment: 'auto'
})
.setLngLat(map.getCenter())
.setPopup(popup)
.addTo(map);

marker.togglePopup();

map.on('style.load', function() {
map.addSource('mapbox-dem', {
"type": "raster-dem",
Expand Down
14 changes: 13 additions & 1 deletion src/css/mapbox-gl.css
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,19 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
}

.mapboxgl-marker-occluded {
opacity: 0.2;
opacity: 0.0;
}

.mapboxgl-marker-occluded-low {
opacity: 0.25;
}

.mapboxgl-marker-occluded-mid {
opacity: 0.5;
}

.mapboxgl-marker-occluded-high {
opacity: 0.75;
}

.mapboxgl-user-location-dot {
Expand Down
13 changes: 12 additions & 1 deletion src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Transform {
pixelsToGLUnits: [number, number];
cameraToCenterDistance: number;
mercatorMatrix: Array<number>;
mercatorFogMatrix: Array<number>;
projMatrix: Float64Array;
invProjMatrix: Float64Array;
alignedProjMatrix: Float64Array;
Expand Down Expand Up @@ -1455,8 +1456,18 @@ class Transform {
// matrix for conversion from location to screen coordinates
this.pixelMatrix = mat4.multiply(new Float64Array(16), this.labelPlaneMatrix, this.projMatrix);

const cameraWorldSize = this.cameraWorldSize;
const cameraPixelsPerMeter = this.cameraPixelsPerMeter;
const cameraPos = this._camera.position;

m = mat4.create();
const metersToPixel = [cameraWorldSize, cameraWorldSize, cameraPixelsPerMeter];
mat4.translate(m, m, vec3.multiply(cameraPos, vec3.scale(cameraPos, cameraPos, -1), metersToPixel));
mat4.scale(m, m, metersToPixel);
this.mercatorFogMatrix = m;

// matrix for conversion from tile coordinates to relative camera position in pixel coordinates
this.cameraMatrix = this._camera.getWorldToCameraPosition(this.cameraWorldSize, this.cameraPixelsPerMeter);
this.cameraMatrix = this._camera.getWorldToCameraPosition(cameraWorldSize, cameraPixelsPerMeter);

// inverse matrix for conversion from screen coordinates to location
m = mat4.invert(new Float64Array(16), this.pixelMatrix);
Expand Down
63 changes: 62 additions & 1 deletion src/style/fog.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import styleSpec from '../style-spec/reference/latest.js';
import {endsWith, extend, smoothstep} from '../util/util.js';
import {Evented} from '../util/evented.js';
import LngLat from '../geo/lng_lat.js';
import {vec3} from 'gl-matrix';
import {UnwrappedTileID} from '../source/tile_id.js';
import type Transform from '../geo/transform.js';
import {validateStyle, validateFog, emitValidationErrors} from './validate_style.js';
import type EvaluationParameters from './evaluation_parameters.js';
import {Properties, Transitionable, Transitioning, PossiblyEvaluated, DataConstantProperty} from './properties.js';
import type {TransitionParameters} from './properties.js';
import type {FogSpecification} from '../style-spec/types.js';
import MercatorCoordinate from '../geo/mercator_coordinate.js';
import Color from '../style-spec/util/color.js';

type Props = {|
Expand All @@ -29,16 +34,72 @@ const TRANSITION_SUFFIX = '-transition';
export const FOG_PITCH_START = 55;
export const FOG_PITCH_END = 65;

export class FogSampler {
properties: ?PossiblyEvaluated<Props>;

// As defined in _prelude_fog.fragment.glsl#fog_opacity
getFogOpacity(depth: number, pitch: number): number {
if (!this.properties) { return 0.0; }

const props = this.properties;
const range = props.get('range');
const fogOpacity = props.get('opacity') * smoothstep(FOG_PITCH_START, FOG_PITCH_END, pitch);
const [start, end] = range;

// The fog is not physically accurate, so we seek an expression which satisfies a
// couple basic constraints:
// - opacity should be 0 at the near limit
// - opacity should be 1 at the far limit
// - the onset should have smooth derivatives to avoid a sharp band
// To this end, we use an (1 - e^x)^n, where n is set to 3 to ensure the
// function is C2 continuous at the onset. The fog is about 99% opaque at
// the far limit, so we simply scale it and clip to achieve 100% opacity.
// https://www.desmos.com/calculator/3taufutxid
// The output of this function must match src/shaders/_prelude_fog.fragment.glsl
const decay = 5.5;
let falloff = Math.max(0.0, 1.0 - Math.exp(-decay * (depth - start) / (end - start)));

// Cube without pow()
falloff *= falloff * falloff;

// Scale and clip to 1 at the far limit
falloff = Math.min(1.0, 1.00747 * falloff);

return falloff * fogOpacity;
}

getOpacityAtTileCoord(x: number, y: number, z: number, tileId: UnwrappedTileID, transform: Transform): number {
const mat = transform.calculateCameraMatrix(tileId);
const pos = [x, y, z];
vec3.transformMat4(pos, pos, mat);
const depth = vec3.length(pos);

return this.getFogOpacity(depth, transform.pitch);
}

getFogOpacityAtLatLng(lngLat: LngLat, transform: Transform): number {
const meters = MercatorCoordinate.fromLngLat(lngLat);
const elevation = transform.elevation ? transform.elevation.getAtPoint(meters) : 0;
const pos = [meters.x, meters.y, elevation];
vec3.transformMat4(pos, pos, transform.mercatorFogMatrix);
const depth = vec3.length(pos);

return this.getFogOpacity(depth, transform.pitch);
}
}

class Fog extends Evented {
_transitionable: Transitionable<Props>;
_transitioning: Transitioning<Props>;
properties: PossiblyEvaluated<Props>;
sampler: FogSampler;

constructor(fogOptions?: FogSpecification) {
super();
this._transitionable = new Transitionable(properties);
this.set(fogOptions);
this._transitioning = this._transitionable.untransitioned();
this.sampler = new FogSampler();
}

get() {
Expand Down Expand Up @@ -73,7 +134,7 @@ class Fog extends Evented {
}

recalculate(parameters: EvaluationParameters) {
this.properties = this._transitioning.possiblyEvaluate(parameters);
this.properties = this.sampler.properties = this._transitioning.possiblyEvaluate(parameters);
}

_validate(validate: Function, value: mixed, options?: {validate?: boolean}) {
Expand Down
14 changes: 14 additions & 0 deletions src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,7 @@ class Style extends Evented {
delete this.stylesheet.terrain;
this.dispatcher.broadcast('enableTerrain', false);
this._force3DLayerUpdate();
this._updateMarkersOpacity();
return;
}

Expand Down Expand Up @@ -1432,6 +1433,7 @@ class Style extends Evented {
}

this._updateDrapeFirstLayers();
this._updateMarkersOpacity();
}

_createFog(fogOptions: FogSpecification) {
Expand All @@ -1447,6 +1449,15 @@ class Style extends Evented {
fog.updateTransitions(parameters);
}

_updateMarkersOpacity() {
if (this.map._markers.length === 0) {
return;
}
for (const marker of this.map._markers) {
marker._evaluateOpacity();
}
}

getFog() {
return this.fog ? this.fog.get() : null;
}
Expand All @@ -1458,6 +1469,7 @@ class Style extends Evented {
// Remove fog
delete this.fog;
delete this.stylesheet.fog;
this._updateMarkersOpacity();
return;
}

Expand All @@ -1484,6 +1496,8 @@ class Style extends Evented {
}
}
}

this._updateMarkersOpacity();
}

_updateDrapeFirstLayers() {
Expand Down
23 changes: 21 additions & 2 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {MapMouseEvent} from './events.js';
import TaskQueue from '../util/task_queue.js';
import webpSupported from '../util/webp_supported.js';
import {PerformanceMarkers, PerformanceUtils} from '../util/performance.js';
import Marker from '../ui/marker.js';

import {setCacheLimits} from '../util/tile_request_cache.js';

Expand Down Expand Up @@ -325,6 +326,7 @@ class Map extends Camera {
_renderTaskQueue: TaskQueue;
_domRenderTaskQueue: TaskQueue;
_controls: Array<IControl>;
_markers: Array<Marker>;
_logoControl: IControl;
_mapId: number;
_localIdeographFontFamily: string;
Expand Down Expand Up @@ -426,6 +428,7 @@ class Map extends Camera {
this._renderTaskQueue = new TaskQueue();
this._domRenderTaskQueue = new TaskQueue();
this._controls = [];
this._markers = [];
this._mapId = uniqueId();
this._locale = extend({}, defaultLocale, options.locale);
this._clickTolerance = options.clickTolerance;
Expand Down Expand Up @@ -2169,7 +2172,7 @@ class Map extends Camera {
* @returns {Object} terrain Terrain specification properties of the style.
*/
getTerrain(): Terrain | null {
return this.style.getTerrain();
return this.style ? this.style.getTerrain() : null;
}

setFog(fog: FogSpecification) {
Expand All @@ -2180,7 +2183,12 @@ class Map extends Camera {

// NOTE: Make fog non-optional
getFog(): Fog | null {
return this.style.getFog();
return this.style ? this.style.getFog() : null;
}

getFogOpacity(lnglat: LngLatLike): number {
if (!this.style || !this.style.fog) return 0.0;
return this.style.fog.sampler.getFogOpacityAtLatLng(LngLat.convert(lnglat), this.transform);
}

/**
Expand Down Expand Up @@ -2439,6 +2447,17 @@ class Map extends Camera {
this._canvas.style.height = `${height}px`;
}

_addMarker(marker: Marker) {
this._markers.push(marker);
}

_removeMarker(marker: Marker) {
const index = this._markers.indexOf(marker);
if (index !== -1) {
this._markers.splice(index, 1);
}
}

_setupPainter() {
const attributes = extend({}, supported.webGLContextAttributes, {
failIfMajorPerformanceCaveat: this._failIfMajorPerformanceCaveat,
Expand Down
Loading

0 comments on commit d6b71fb

Please sign in to comment.