Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cross-fading of *-pattern and line-dasharray #5791

Merged
merged 7 commits into from
Dec 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 0 additions & 39 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import type IndexBuffer from '../../gl/index_buffer';
import type VertexBuffer from '../../gl/vertex_buffer';
import type {SymbolQuad} from '../../symbol/quads';
import type {SizeData} from '../../symbol/symbol_size';
import type {PossiblyEvaluatedPropertyValue} from '../../style/properties';

export type SingleCollisionBox = {
x1: number;
Expand Down Expand Up @@ -359,27 +358,8 @@ class SymbolBucket implements Bucket {
sdfIcons: boolean;
iconsNeedLinear: boolean;

// The symbol layout process needs `text-size` evaluated at up to five different zoom levels, and
// `icon-size` at up to three:
//
// 1. `text-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `text-size`
// expressions, and to calculate the box dimensions for icon-text-fit.
// 2. `icon-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `icon-size`
// expressions.
// 3. `text-size` and `icon-size` at the zoom level of the bucket, plus one. Used to calculate collision boxes.
// 4. `text-size` at zoom level 18. Used for something line-symbol-placement-related.
// 5. For composite `*-size` expressions: two zoom levels of curve stops that "cover" the zoom level of the
// bucket. These go into a vertex buffer and are used by the shader to interpolate the size at render time.
//
// (1) and (2) are stored in `this.layers[0].layout`. The remainder are below.
//
textSizeData: SizeData;
iconSizeData: SizeData;
layoutTextSize: PossiblyEvaluatedPropertyValue<number>; // (3)
layoutIconSize: PossiblyEvaluatedPropertyValue<number>; // (3)
textMaxSize: PossiblyEvaluatedPropertyValue<number>; // (4)
compositeTextSizes: [PossiblyEvaluatedPropertyValue<number>, PossiblyEvaluatedPropertyValue<number>]; // (5)
compositeIconSizes: [PossiblyEvaluatedPropertyValue<number>, PossiblyEvaluatedPropertyValue<number>]; // (5)

placedGlyphArray: StructArray;
placedIconArray: StructArray;
Expand Down Expand Up @@ -413,26 +393,7 @@ class SymbolBucket implements Bucket {
const unevaluatedLayoutValues = layer._unevaluatedLayout._values;

this.textSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['text-size']);
if (this.textSizeData.functionType === 'composite') {
const {min, max} = this.textSizeData.zoomRange;
this.compositeTextSizes = [
unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: min}),
unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: max})
];
}

this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']);
if (this.iconSizeData.functionType === 'composite') {
const {min, max} = this.iconSizeData.zoomRange;
this.compositeIconSizes = [
unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: min}),
unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: max})
];
}

this.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: this.zoom + 1});
this.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: this.zoom + 1});
this.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: 18});

const layout = this.layers[0].layout;
this.sortFeaturesByY = layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') ||
Expand Down
13 changes: 3 additions & 10 deletions src/source/worker_tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {makeImageAtlas} = require('../render/image_atlas');
const {makeGlyphAtlas} = require('../render/glyph_atlas');
const {serialize} = require('../util/web_worker_transfer');
const TileCoord = require('./tile_coord');
const EvaluationParameters = require('../style/evaluation_parameters');

import type {Bucket} from '../data/bucket';
import type Actor from '../util/actor';
Expand Down Expand Up @@ -182,17 +183,9 @@ class WorkerTile {

function recalculateLayers(layers: $ReadOnlyArray<StyleLayer>, zoom: number) {
// Layers are shared and may have been used by a WorkerTile with a different zoom.
const parameters = new EvaluationParameters(zoom);
for (const layer of layers) {
layer.recalculate({
zoom,
now: Number.MAX_VALUE,
defaultFadeDuration: 0,
zoomHistory: {
lastIntegerZoom: 0,
lastIntegerZoomTime: 0,
lastZoom: 0
}
});
layer.recalculate(parameters);
}
}

Expand Down
37 changes: 37 additions & 0 deletions src/style/evaluation_parameters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @flow

const ZoomHistory = require('./zoom_history');

class EvaluationParameters {
zoom: number;
now: number;
fadeDuration: number;
zoomHistory: ZoomHistory;
transition: TransitionSpecification;

constructor(zoom: number, options?: *) {
this.zoom = zoom;

if (options) {
this.now = options.now;
this.fadeDuration = options.fadeDuration;
this.zoomHistory = options.zoomHistory;
this.transition = options.transition;
} else {
this.now = 0;
this.fadeDuration = 0;
this.zoomHistory = new ZoomHistory();
this.transition = {};
}
}

crossFadingFactor() {
if (this.fadeDuration === 0) {
return 1;
} else {
return Math.min((this.now - this.zoomHistory.lastIntegerZoomTime) / this.fadeDuration, 1);
}
}
}

module.exports = EvaluationParameters;
8 changes: 4 additions & 4 deletions src/style/light.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// @flow

import type {StylePropertySpecification} from "../style-spec/style-spec";

const styleSpec = require('../style-spec/reference/latest');
const util = require('../util/util');
const Evented = require('../util/evented');
Expand All @@ -10,6 +8,9 @@ const {sphericalToCartesian} = require('../util/util');
const Color = require('../style-spec/util/color');
const interpolate = require('../style-spec/util/interpolate');

import type {StylePropertySpecification} from '../style-spec/style-spec';
import type EvaluationParameters from './evaluation_parameters';

const {
Properties,
Transitionable,
Expand All @@ -21,8 +22,7 @@ const {
import type {
Property,
PropertyValue,
TransitionParameters,
EvaluationParameters
TransitionParameters
} from './properties';

type LightPosition = {
Expand Down
14 changes: 3 additions & 11 deletions src/style/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {register} = require('../util/web_worker_transfer');

import type {StylePropertySpecification} from '../style-spec/style-spec';
import type {CrossFaded} from './cross_faded';
import type {ZoomHistory} from './style';
import type EvaluationParameters from './evaluation_parameters';

import type {
Feature,
Expand All @@ -21,12 +21,6 @@ import type {

type TimePoint = number;

export type EvaluationParameters = GlobalProperties & {
now?: TimePoint,
fadeDuration?: number,
zoomHistory?: ZoomHistory
};

/**
* Implements a number of classes that define state and behavior for paint and layout properties, most
* importantly their respective evaluation chains:
Expand Down Expand Up @@ -610,11 +604,10 @@ class CrossFadedProperty<T> implements Property<T, ?CrossFaded<T>> {
}
}

_calculate(min: T, mid: T, max: T, parameters: any): ?CrossFaded<T> {
_calculate(min: T, mid: T, max: T, parameters: EvaluationParameters): ?CrossFaded<T> {
const z = parameters.zoom;
const fraction = z - Math.floor(z);
const d = parameters.fadeDuration;
const t = d !== 0 ? Math.min((parameters.now - parameters.zoomHistory.lastIntegerZoomTime) / d, 1) : 1;
const t = parameters.crossFadingFactor();
return z > parameters.zoomHistory.lastIntegerZoom ?
{ from: min, to: mid, fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } :
{ from: max, to: mid, fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction };
Expand Down Expand Up @@ -681,7 +674,6 @@ class Properties<Props: Object> {
}
}

register(PossiblyEvaluatedPropertyValue);
register(DataDrivenProperty);
register(DataConstantProperty);
register(CrossFadedProperty);
Expand Down
129 changes: 44 additions & 85 deletions src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const deref = require('../style-spec/deref');
const diff = require('../style-spec/diff');
const rtlTextPlugin = require('../source/rtl_text_plugin');
const Placement = require('./placement');
const ZoomHistory = require('./zoom_history');

import type Map from '../ui/map';
import type Transform from '../geo/transform';
Expand All @@ -33,6 +34,7 @@ import type {StyleImage} from './style_image';
import type {StyleGlyph} from './style_glyph';
import type CollisionIndex from '../symbol/collision_index';
import type {Callback} from '../types/callback';
import type EvaluationParameters from './evaluation_parameters';

const supportedDiffOperations = util.pick(diff.operations, [
'addLayer',
Expand Down Expand Up @@ -62,12 +64,6 @@ export type StyleOptions = {
localIdeographFontFamily?: string
};

export type ZoomHistory = {
lastIntegerZoom: number,
lastIntegerZoomTime: number,
lastZoom: number
};

/**
* @private
*/
Expand All @@ -83,7 +79,7 @@ class Style extends Evented {
_layers: {[string]: StyleLayer};
_order: Array<string>;
sourceCaches: {[string]: SourceCache};
zoomHistory: ZoomHistory | {};
zoomHistory: ZoomHistory;
_loaded: boolean;
_rtlTextPluginCallback: Function;
_changed: boolean;
Expand All @@ -109,7 +105,7 @@ class Style extends Evented {
this._layers = {};
this._order = [];
this.sourceCaches = {};
this.zoomHistory = {};
this.zoomHistory = new ZoomHistory();
this._loaded = false;

this._resetUpdates();
Expand Down Expand Up @@ -271,32 +267,6 @@ class Style extends Evented {
return ids.map((id) => this._layers[id].serialize());
}

_recalculate(z: number, fadeDuration: number) {
if (!this._loaded) return;

for (const sourceId in this.sourceCaches)
this.sourceCaches[sourceId].used = false;

const parameters = {
zoom: z,
now: browser.now(),
fadeDuration,
zoomHistory: this._updateZoomHistory(z)
};

for (const layerId of this._order) {
const layer = this._layers[layerId];

layer.recalculate(parameters);
if (!layer.isHidden(z) && layer.source) {
this.sourceCaches[layer.source].used = true;
}
}

this.light.recalculate(parameters);
this.z = z;
}

hasTransitions() {
if (this.light && this.light.hasTransition()) {
return true;
Expand All @@ -317,74 +287,63 @@ class Style extends Evented {
return false;
}

_updateZoomHistory(z: number) {

const zh: ZoomHistory = (this.zoomHistory: any);

if (zh.lastIntegerZoom === undefined) {
// first time
zh.lastIntegerZoom = Math.floor(z);
zh.lastIntegerZoomTime = 0;
zh.lastZoom = z;
}

// check whether an integer zoom level as passed since the last frame
// and if yes, record it with the time. Used for transitioning patterns.
if (Math.floor(zh.lastZoom) < Math.floor(z)) {
zh.lastIntegerZoom = Math.floor(z);
zh.lastIntegerZoomTime = browser.now();

} else if (Math.floor(zh.lastZoom) > Math.floor(z)) {
zh.lastIntegerZoom = Math.floor(z + 1);
zh.lastIntegerZoomTime = browser.now();
}

zh.lastZoom = z;
return zh;
}

_checkLoaded() {
if (!this._loaded) {
throw new Error('Style is not done loading');
}
}

/**
* Apply queued style updates in a batch
* Apply queued style updates in a batch and recalculate zoom-dependent paint properties.
*/
update() {
if (!this._changed || !this._loaded) return;
update(parameters: EvaluationParameters) {
if (!this._loaded) {
return;
}

const updatedIds = Object.keys(this._updatedLayers);
const removedIds = Object.keys(this._removedLayers);
if (this._changed) {
const updatedIds = Object.keys(this._updatedLayers);
const removedIds = Object.keys(this._removedLayers);

if (updatedIds.length || removedIds.length) {
this._updateWorkerLayers(updatedIds, removedIds);
}
for (const id in this._updatedSources) {
const action = this._updatedSources[id];
assert(action === 'reload' || action === 'clear');
if (action === 'reload') {
this._reloadSource(id);
} else if (action === 'clear') {
this._clearSource(id);
if (updatedIds.length || removedIds.length) {
this._updateWorkerLayers(updatedIds, removedIds);
}
}
for (const id in this._updatedSources) {
const action = this._updatedSources[id];
assert(action === 'reload' || action === 'clear');
if (action === 'reload') {
this._reloadSource(id);
} else if (action === 'clear') {
this._clearSource(id);
}
}

for (const id in this._updatedPaintProps) {
this._layers[id].updateTransitions(parameters);
}

this.light.updateTransitions(parameters);

const parameters = {
now: browser.now(),
transition: util.extend({ duration: 300, delay: 0 }, this.stylesheet.transition)
};
this._resetUpdates();

for (const id in this._updatedPaintProps) {
this._layers[id].updateTransitions(parameters);
this.fire('data', {dataType: 'style'});
}

this.light.updateTransitions(parameters);
for (const sourceId in this.sourceCaches) {
this.sourceCaches[sourceId].used = false;
}

this._resetUpdates();
for (const layerId of this._order) {
const layer = this._layers[layerId];

this.fire('data', {dataType: 'style'});
layer.recalculate(parameters);
if (!layer.isHidden(parameters.zoom) && layer.source) {
this.sourceCaches[layer.source].used = true;
}
}

this.light.recalculate(parameters);
this.z = parameters.zoom;
}

_updateWorkerLayers(updatedIds: Array<string>, removedIds: Array<string>) {
Expand Down
Loading