diff options
Diffstat (limited to 'src/mbgl/style/properties.hpp')
-rw-r--r-- | src/mbgl/style/properties.hpp | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/src/mbgl/style/properties.hpp b/src/mbgl/style/properties.hpp new file mode 100644 index 0000000000..dfcf7993a7 --- /dev/null +++ b/src/mbgl/style/properties.hpp @@ -0,0 +1,248 @@ +#pragma once + +#include <mbgl/style/transition_options.hpp> +#include <mbgl/style/conversion/stringify.hpp> +#include <mbgl/renderer/transition_parameters.hpp> +#include <mbgl/renderer/paint_property_binder.hpp> +#include <mbgl/renderer/property_evaluation_parameters.hpp> +#include <mbgl/renderer/transition_parameters.hpp> +#include <mbgl/util/indexed_tuple.hpp> +#include <mbgl/util/ignore.hpp> + +namespace mbgl { + +class GeometryTileFeature; + +namespace style { + +template <class Value> +class Transitioning { +public: + Transitioning() = default; + + explicit Transitioning(Value value_) + : value(std::move(value_)) { + } + + Transitioning(Value value_, + Transitioning<Value> prior_, + TransitionOptions transition, + TimePoint now) + : begin(now + transition.delay.value_or(Duration::zero())), + end(begin + transition.duration.value_or(Duration::zero())), + value(std::move(value_)) { + if (transition.isDefined()) { + prior = { std::move(prior_) }; + } + } + + template <class Evaluator> + auto evaluate(const Evaluator& evaluator, TimePoint now) const { + auto finalValue = value.evaluate(evaluator); + if (!prior) { + // No prior value. + return finalValue; + } else if (now >= end) { + // Transition from prior value is now complete. + prior = {}; + return finalValue; + } else if (value.isDataDriven()) { + // Transitions to data-driven properties are not supported. + // We snap immediately to the data-driven value so that, when we perform layout, + // we see the data-driven function and can use it to populate vertex buffers. + prior = {}; + return finalValue; + } else if (now < begin) { + // Transition hasn't started yet. + return prior->get().evaluate(evaluator, now); + } else { + // Interpolate between recursively-calculated prior value and final. + float t = std::chrono::duration<float>(now - begin) / (end - begin); + return util::interpolate(prior->get().evaluate(evaluator, now), finalValue, + util::DEFAULT_TRANSITION_EASE.solve(t, 0.001)); + } + } + + bool hasTransition() const { + return bool(prior); + } + + bool isUndefined() const { + return value.isUndefined(); + } + + const Value& getValue() const { + return value; + } + +private: + mutable optional<mapbox::util::recursive_wrapper<Transitioning<Value>>> prior; + TimePoint begin; + TimePoint end; + Value value; +}; + +template <class Value> +class Transitionable { +public: + Value value; + TransitionOptions options; + + Transitioning<Value> transition(const TransitionParameters& params, Transitioning<Value> prior) const { + return Transitioning<Value>(value, + std::move(prior), + options.reverseMerge(params.transition), + params.now); + } +}; + +template <class P> +struct IsDataDriven : std::integral_constant<bool, P::IsDataDriven> {}; + +template <class... Ps> +class Properties { +public: + /* + For style properties we implement a two step evaluation process: if you have a zoom level, + you can evaluate a set of unevaluated property values, producing a set of possibly evaluated + values, where undefined, constant, or camera function values have been fully evaluated, and + source or composite function values have not. + + Once you also have a particular feature, you can evaluate that set of possibly evaluated values + fully, producing a set of fully evaluated values. + + This is in theory maximally efficient in terms of avoiding repeated evaluation of camera + functions, though it's more of a historical accident than a purposeful optimization. + */ + + using PropertyTypes = TypeList<Ps...>; + using TransitionableTypes = TypeList<typename Ps::TransitionableType...>; + using UnevaluatedTypes = TypeList<typename Ps::UnevaluatedType...>; + using PossiblyEvaluatedTypes = TypeList<typename Ps::PossiblyEvaluatedType...>; + using EvaluatedTypes = TypeList<typename Ps::Type...>; + + using DataDrivenProperties = FilteredTypeList<PropertyTypes, IsDataDriven>; + using Binders = PaintPropertyBinders<DataDrivenProperties>; + + template <class TypeList> + using Tuple = IndexedTuple<PropertyTypes, TypeList>; + + class Evaluated : public Tuple<EvaluatedTypes> { + public: + template <class... Us> + Evaluated(Us&&... us) + : Tuple<EvaluatedTypes>(std::forward<Us>(us)...) { + } + }; + + class PossiblyEvaluated : public Tuple<PossiblyEvaluatedTypes> { + public: + template <class... Us> + PossiblyEvaluated(Us&&... us) + : Tuple<PossiblyEvaluatedTypes>(std::forward<Us>(us)...) { + } + + template <class T> + static T evaluate(float, const GeometryTileFeature&, const T& t, const T&) { + return t; + } + + template <class T> + static T evaluate(float z, const GeometryTileFeature& feature, + const PossiblyEvaluatedPropertyValue<T>& v, const T& defaultValue) { + return v.match( + [&] (const T& t) { + return t; + }, + [&] (const SourceFunction<T>& t) { + return t.evaluate(feature, defaultValue); + }, + [&] (const CompositeFunction<T>& t) { + return t.evaluate(z, feature, defaultValue); + }); + } + + template <class P> + auto evaluate(float z, const GeometryTileFeature& feature) const { + return evaluate(z, feature, this->template get<P>(), P::defaultValue()); + } + + Evaluated evaluate(float z, const GeometryTileFeature& feature) const { + return Evaluated { + evaluate<Ps>(z, feature)... + }; + } + }; + + class Unevaluated : public Tuple<UnevaluatedTypes> { + public: + template <class... Us> + Unevaluated(Us&&... us) + : Tuple<UnevaluatedTypes>(std::forward<Us>(us)...) { + } + + bool hasTransition() const { + bool result = false; + util::ignore({ result |= this->template get<Ps>().hasTransition()... }); + return result; + } + + template <class P> + auto evaluate(const PropertyEvaluationParameters& parameters) const { + using Evaluator = typename P::EvaluatorType; + return this->template get<P>() + .evaluate(Evaluator(parameters, P::defaultValue()), parameters.now); + } + + PossiblyEvaluated evaluate(const PropertyEvaluationParameters& parameters) const { + return PossiblyEvaluated { + evaluate<Ps>(parameters)... + }; + } + + template <class Writer> + void stringify(Writer& writer) const { + writer.StartObject(); + util::ignore({ (conversion::stringify<Ps>(writer, this->template get<Ps>()), 0)... }); + writer.EndObject(); + } + }; + + class Transitionable : public Tuple<TransitionableTypes> { + public: + template <class... Us> + Transitionable(Us&&... us) + : Tuple<TransitionableTypes>(std::forward<Us>(us)...) { + } + + Unevaluated transitioned(const TransitionParameters& parameters, Unevaluated&& prior) const { + return Unevaluated { + this->template get<Ps>() + .transition(parameters, std::move(prior.template get<Ps>()))... + }; + } + + Unevaluated untransitioned() const { + return Unevaluated { + typename Ps::UnevaluatedType(this->template get<Ps>().value)... + }; + } + + bool hasDataDrivenPropertyDifference(const Transitionable& other) const { + bool result = false; + util::ignore({ (result |= this->template get<Ps>().value.hasDataDrivenPropertyDifference(other.template get<Ps>().value))... }); + return result; + } + }; +}; + +template <class...> +struct ConcatenateProperties; + +template <class... As, class... Bs> +struct ConcatenateProperties<TypeList<As...>, TypeList<Bs...>> { + using Type = Properties<As..., Bs...>; +}; + +} // namespace style +} // namespace mbgl |