Merge pull request #235 from mapbox/transitions

support for style transitions
This commit is contained in:
Justin R. Miller
2014-05-22 12:58:28 -07:00
10 changed files with 1183 additions and 108 deletions

View File

@@ -5,12 +5,34 @@
#include <vector>
#include <string>
#include <limits>
#include <set>
namespace llmr {
// Stores a premultiplied color, with all four channels ranging from 0..1
typedef std::array<float, 4> Color;
struct PropertyTransition {
uint16_t duration = 0;
uint16_t delay = 0;
};
enum class TransitionablePropertyKey {
Translate = 1,
FillColor = 2,
StrokeColor = 3,
Opacity = 4,
Width = 5,
Offset = 6,
Color = 7,
DashArray = 8,
Radius = 9,
Blur = 10,
Halo = 11,
HaloRadius = 12,
HaloBlur = 13,
};
enum class Winding {
EvenOdd = 1,
NonZero = 2
@@ -63,13 +85,19 @@ struct FunctionProperty {
struct IconClass {
FunctionProperty enabled = true;
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
PropertyTransition translate_transition;
TranslateAnchor translateAnchor = TranslateAnchor::Map;
FunctionProperty size;
Color color = {{ 1, 1, 1, 1 }};
PropertyTransition color_transition;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::string image;
FunctionProperty radius = 0;
PropertyTransition radius_transition;
FunctionProperty blur = 0;
PropertyTransition blur_transition;
std::set<std::string> specifiers;
};
struct IconProperties {
@@ -87,12 +115,19 @@ struct IconProperties {
struct LineClass {
FunctionProperty enabled = true;
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
PropertyTransition translate_transition;
TranslateAnchor translateAnchor = TranslateAnchor::Map;
FunctionProperty width;
PropertyTransition width_transition;
FunctionProperty offset;
PropertyTransition offset_transition;
Color color = {{ 0, 0, 0, 1 }};
PropertyTransition color_transition;
std::array<FunctionProperty, 2> dash_array = {{ 1, -1 }};
PropertyTransition dash_array_transition;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::set<std::string> specifiers;
};
struct LineProperties {
@@ -109,13 +144,18 @@ struct LineProperties {
struct FillClass {
FunctionProperty enabled = true;
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
PropertyTransition translate_transition;
TranslateAnchor translateAnchor = TranslateAnchor::Map;
Winding winding = Winding::NonZero;
FunctionProperty antialias = true;
Color fill_color = {{ 0, 0, 0, 1 }};
PropertyTransition fill_color_transition;
Color stroke_color = {{ 0, 0, 0, std::numeric_limits<float>::infinity() }};
PropertyTransition stroke_color_transition;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::string image;
std::set<std::string> specifiers;
};
struct FillProperties {
@@ -133,15 +173,22 @@ struct FillProperties {
struct TextClass {
FunctionProperty enabled = true;
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
PropertyTransition translate_transition;
TranslateAnchor translateAnchor = TranslateAnchor::Map;
Color color = {{ 0, 0, 0, 1 }};
PropertyTransition color_transition;
Color halo = {{ 1, 1, 1, 0.75 }};
PropertyTransition halo_transition;
FunctionProperty halo_radius = 0.25f;
PropertyTransition halo_radius_transition;
FunctionProperty halo_blur = 1.0f;
PropertyTransition halo_blur_transition;
FunctionProperty size = 12.0f;
FunctionProperty rotate = 0.0f;
FunctionProperty alwaysVisible = false;
FunctionProperty always_visible = false;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::set<std::string> specifiers;
};
struct TextProperties {
@@ -154,13 +201,16 @@ struct TextProperties {
float halo_blur = 1.0f;
float size = 12.0f;
float rotate = 0.0f;
bool alwaysVisible = false;
bool always_visible = false;
float opacity = 1.0;
};
struct BackgroundClass {
Color color = {{ 1, 1, 1, 1 }};
PropertyTransition color_transition;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::set<std::string> specifiers;
};
struct BackgroundProperties {
@@ -171,7 +221,10 @@ struct BackgroundProperties {
struct RasterClass {
FunctionProperty enabled = true;
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
PropertyTransition translate_transition;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::set<std::string> specifiers;
};
struct RasterProperties {
@@ -183,6 +236,8 @@ struct RasterProperties {
struct CompositeClass {
FunctionProperty enabled = true;
FunctionProperty opacity = 1;
PropertyTransition opacity_transition;
std::set<std::string> specifiers;
};
struct CompositeProperties {

View File

@@ -8,6 +8,8 @@
#include <llmr/style/layer_description.hpp>
#include <llmr/style/class_description.hpp>
#include <llmr/geometry/sprite_atlas.hpp>
#include <llmr/util/transition.hpp>
#include <llmr/util/uv.hpp>
#include <map>
#include <vector>
@@ -32,6 +34,12 @@ public:
size_t layerCount() const;
void cascade(float z);
bool needsTransition() const;
void updateTransitions(time now);
void cancelTransitions();
void setDefaultTransitionDuration(uint64_t duration = 0);
public:
std::shared_ptr<Sprite> sprite;
@@ -40,8 +48,7 @@ public:
std::vector<LayerDescription> layers;
std::map<std::string, ClassDescription> classes;
// This are applied settings.
// Currently applied settings.
std::set<std::string> appliedClasses;
struct {
BackgroundProperties background;
@@ -51,7 +58,47 @@ public:
std::map<std::string, TextProperties> texts;
std::map<std::string, RasterProperties> rasters;
std::map<std::string, CompositeProperties> composites;
std::map<std::string, std::map<TransitionablePropertyKey, std::string>> effective_classes;
} computed;
private:
bool transitionInProgress(std::string layer_name, TransitionablePropertyKey key);
bool transitionExists(std::string layer_name, TransitionablePropertyKey key);
bool inNeedOfTransition(std::string layer_name, TransitionablePropertyKey key);
uint64_t transitionDuration(std::string layer_name, TransitionablePropertyKey key);
uint64_t transitionDelay(std::string layer_name, TransitionablePropertyKey key);
private:
// Last applied settings.
struct {
BackgroundProperties background;
std::map<std::string, FillProperties> fills;
std::map<std::string, LineProperties> lines;
std::map<std::string, IconProperties> icons;
std::map<std::string, TextProperties> texts;
std::map<std::string, RasterProperties> rasters;
std::map<std::string, CompositeProperties> composites;
std::map<std::string, std::map<TransitionablePropertyKey, std::string>> effective_classes;
} previous;
// Settings values currently being transitioned.
struct {
BackgroundProperties background;
std::map<std::string, FillProperties> fills;
std::map<std::string, LineProperties> lines;
std::map<std::string, IconProperties> icons;
std::map<std::string, TextProperties> texts;
std::map<std::string, RasterProperties> rasters;
std::map<std::string, CompositeProperties> composites;
} transitioning;
std::map<std::string, std::map<TransitionablePropertyKey, PropertyTransition>> properties_to_transition;
std::map<std::string, std::map<TransitionablePropertyKey, std::shared_ptr<util::transition>>> transitions;
uint64_t default_transition_duration = 0;
bool initial_render_complete = false;
mutable uv::rwlock mtx;
};
}

View File

@@ -46,6 +46,7 @@ private:
Value parseValue(JSVal value);
FunctionProperty::fn parseFunctionType(JSVal type);
FunctionProperty parseFunction(JSVal value);
PropertyTransition parseTransition(JSVal value, std::string property_name);
private:
std::map<std::string, const rapidjson::Value *> constants;

View File

@@ -3,6 +3,7 @@
#include <llmr/util/noncopyable.hpp>
#include <llmr/util/time.hpp>
#include <llmr/style/properties.hpp>
namespace llmr {
namespace util {
@@ -19,39 +20,45 @@ public:
duration(duration) {}
inline float progress(time now) const {
if (duration == 0) return 1;
if (start > now) return 0;
return (float)(now - start) / duration;
}
virtual state update(time now) const = 0;
virtual ~transition();
protected:
double interpolateDouble(double from, double to, double t) const;
float interpolateFloat(float from, float to, double t) const;
Color interpolateColor(Color from, Color to, double t) const;
std::array<float, 2> interpolateFloatArray(std::array<float, 2> from, std::array<float, 2> to, double t) const;
protected:
const time start, duration;
};
template <typename T>
class ease_transition : public transition {
public:
// Disable automatic casts.
template <typename T1, typename T2>
inline ease_transition(double from, double to, double& value, T1 start, T2 duration) = delete;
ease_transition(T from, T to, T& value, time start, time duration)
: transition(start, duration),
from(from),
to(to),
value(value) {}
// Actual constructor.
ease_transition(double from, double to, double& value, time start, time duration);
state update(time now) const;
private:
const double from, to;
double& value;
const T from, to;
T& value;
};
template <typename T>
class timeout : public transition {
public:
// Disable automatic casts.
template <typename T1, typename T2>
inline timeout(T final_value, T& value, T1 start, T2 duration) = delete;
// Actual constructor.
timeout(T final_value, T& value, time start, time duration)
: transition(start, duration),
final_value(final_value),

View File

@@ -374,17 +374,15 @@ bool Map::getDebug() const {
}
void Map::toggleRaster() {
style.setDefaultTransitionDuration(300);
style.cancelTransitions();
auto it = sources.find("satellite");
if (it != sources.end()) {
Source &satellite_source = *it->second;
if (satellite_source.enabled) {
satellite_source.enabled = false;
auto style_class = style.appliedClasses.find("satellite");
if (style_class != style.appliedClasses.end()) {
style.appliedClasses.erase(style_class);
}
style.appliedClasses.erase("satellite");
} else {
satellite_source.enabled = true;
style.appliedClasses.insert("satellite");
@@ -458,6 +456,12 @@ void Map::prepare() {
style.cascade(state.getNormalizedZoom());
}
animationTime = util::now();
if (style.needsTransition()) {
style.updateTransitions(animationTime);
update();
}
// Allow the sprite atlas to potentially pull new sprite images if needed.
if (style.sprite && style.sprite->isLoaded()) {
spriteAtlas.update(*style.sprite);

View File

@@ -59,9 +59,9 @@ void Transform::_moveBy(const double dx, const double dy, const time duration) {
// Use a common start time for all of the transitions to avoid divergent transitions.
time start = util::now();
transitions.emplace_front(
std::make_shared<util::ease_transition>(current.x, final.x, current.x, start, duration));
std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
transitions.emplace_front(
std::make_shared<util::ease_transition>(current.y, final.y, current.y, start, duration));
std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
}
}
@@ -250,12 +250,12 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
} else {
// Use a common start time for all of the transitions to avoid divergent transitions.
time start = util::now();
transitions.emplace_front(std::make_shared<util::ease_transition>(
transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
current.scale, final.scale, current.scale, start, duration));
transitions.emplace_front(
std::make_shared<util::ease_transition>(current.x, final.x, current.x, start, duration));
std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
transitions.emplace_front(
std::make_shared<util::ease_transition>(current.y, final.y, current.y, start, duration));
std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
}
const double s = final.scale * util::tileSize;
@@ -335,7 +335,7 @@ void Transform::_setAngle(double new_angle, const time duration) {
current.angle = final.angle;
} else {
time start = util::now();
transitions.emplace_front(std::make_shared<util::ease_transition>(
transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
current.angle, final.angle, current.angle, start, duration));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -477,42 +477,82 @@ FunctionProperty StyleParser::parseFunction(JSVal value) {
return property;
}
PropertyTransition StyleParser::parseTransition(JSVal value, std::string property_name) {
uint16_t duration = 0, delay = 0;
std::string transition_property = std::string("transition-").append(property_name);
if (value.HasMember(transition_property.c_str())) {
JSVal elements = value[transition_property.c_str()];
if (elements.IsObject()) {
if (elements.HasMember("duration") && elements["duration"].IsNumber()) {
duration = elements["duration"].GetUint();
}
if (elements.HasMember("delay") && elements["delay"].IsNumber()) {
delay = elements["delay"].GetUint();
}
}
}
PropertyTransition transition;
transition.duration = duration;
transition.delay = delay;
return transition;
}
FillClass StyleParser::parseFillClass(JSVal value) {
FillClass klass;
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("translate")) {
std::vector<FunctionProperty> values = parseArray(value["translate"], 2);
klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.translate_transition = parseTransition(value, "translate");
klass.specifiers.insert("translate");
}
if (value.HasMember("translate-anchor")) {
klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]);
klass.specifiers.insert("translate-anchor");
}
if (value.HasMember("winding")) {
throw std::runtime_error("winding in stylesheets not yet supported");
}
if (value.HasMember("color")) {
klass.fill_color = parseColor(value["color"]);
klass.fill_color_transition = parseTransition(value, "color");
klass.specifiers.insert("color");
}
if (value.HasMember("stroke")) {
klass.stroke_color = parseColor(value["stroke"]);
klass.stroke_color_transition = parseTransition(value, "stroke");
klass.specifiers.insert("stroke");
} else {
klass.stroke_color = klass.fill_color;
klass.specifiers.insert("stroke");
}
if (value.HasMember("antialias")) {
klass.antialias = parseBoolean(value["antialias"]);
klass.specifiers.insert("antialias");
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
klass.specifiers.insert("image");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
return klass;
@@ -523,32 +563,44 @@ LineClass StyleParser::parseLineClass(JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("translate")) {
std::vector<FunctionProperty> values = parseArray(value["translate"], 2);
klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.translate_transition = parseTransition(value, "translate");
klass.specifiers.insert("translate");
}
if (value.HasMember("translate-anchor")) {
klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]);
klass.specifiers.insert("translate-anchor");
}
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
klass.specifiers.insert("color");
}
if (value.HasMember("width")) {
klass.width = parseFunction(value["width"]);
klass.width_transition = parseTransition(value, "width");
klass.specifiers.insert("width");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
if (value.HasMember("dasharray")) {
std::vector<FunctionProperty> values = parseArray(value["dasharray"], 2);
klass.dash_array = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.dash_array_transition = parseTransition(value, "dasharray");
klass.specifiers.insert("dasharray");
}
return klass;
@@ -559,39 +611,53 @@ IconClass StyleParser::parseIconClass(JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("translate")) {
std::vector<FunctionProperty> values = parseArray(value["translate"], 2);
klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.translate_transition = parseTransition(value, "translate");
klass.specifiers.insert("translate");
}
if (value.HasMember("translate-anchor")) {
klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]);
klass.specifiers.insert("translate-anchor");
}
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
klass.specifiers.insert("color");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
klass.specifiers.insert("image");
}
if (value.HasMember("size")) {
klass.size = parseFunction(value["size"]);
klass.specifiers.insert("size");
}
if (value.HasMember("radius")) {
klass.radius = parseFunction(value["radius"]);
klass.radius_transition = parseTransition(value, "radius");
klass.specifiers.insert("radius");
}
if (value.HasMember("blur")) {
klass.blur = parseFunction(value["blur"]);
klass.blur_transition = parseTransition(value, "blur");
klass.specifiers.insert("blur");
}
return klass;
@@ -602,43 +668,64 @@ TextClass StyleParser::parseTextClass(JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("translate")) {
std::vector<FunctionProperty> values = parseArray(value["translate"], 2);
klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.translate_transition = parseTransition(value, "translate");
klass.specifiers.insert("translate");
}
if (value.HasMember("translate-anchor")) {
klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]);
klass.specifiers.insert("translate-anchor");
}
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
klass.specifiers.insert("color");
}
if (value.HasMember("stroke")) {
klass.halo = parseColor(value["stroke"]);
klass.halo_transition = parseTransition(value, "stroke");
klass.specifiers.insert("stroke");
}
if (value.HasMember("strokeWidth")) {
klass.halo_radius = parseFunction(value["strokeWidth"]);
klass.halo_radius_transition = parseTransition(value, "strokeWidth");
klass.specifiers.insert("strokeWidth");
}
if (value.HasMember("strokeBlur")) {
klass.halo_blur = parseFunction(value["strokeBlur"]);
klass.halo_blur_transition = parseTransition(value, "strokeBlur");
klass.specifiers.insert("strokeBlur");
}
if (value.HasMember("size")) {
klass.size = parseFunction(value["size"]);
klass.specifiers.insert("size");
}
if (value.HasMember("rotate")) {
klass.rotate = parseFunction(value["rotate"]);
klass.specifiers.insert("rotate");
}
if (value.HasMember("alwaysVisible")) {
klass.alwaysVisible = parseFunction(value["alwaysVisible"]);
klass.always_visible = parseFunction(value["alwaysVisible"]);
klass.specifiers.insert("alwaysVisible");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
return klass;
@@ -649,10 +736,13 @@ RasterClass StyleParser::parseRasterClass(JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
return klass;
@@ -663,10 +753,13 @@ CompositeClass StyleParser::parseCompositeClass(JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
klass.specifiers.insert("enabled");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
return klass;
@@ -677,10 +770,14 @@ BackgroundClass StyleParser::parseBackgroundClass(JSVal value) {
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
klass.specifiers.insert("color");
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
klass.specifiers.insert("opacity");
}
return klass;

View File

@@ -70,7 +70,7 @@ void Raster::bind(bool linear) {
void Raster::beginFadeInTransition() {
time start = util::now();
fade_transition = std::make_shared<util::ease_transition>(opacity, 1.0, opacity, start, 250_milliseconds);
fade_transition = std::make_shared<util::ease_transition<double>>(opacity, 1.0, opacity, start, 250_milliseconds);
}
bool Raster::needsTransition() const {

View File

@@ -8,20 +8,69 @@ UnitBezier ease(0.25, 0.1, 0.25, 1);
transition::~transition() {}
ease_transition::ease_transition(double from, double to, double &value, time start, time duration)
: transition(start, duration),
from(from),
to(to),
value(value) {
double transition::interpolateDouble(double from, double to, double t) const {
return from + (to - from) * t;
}
transition::state ease_transition::update(time now) const {
float transition::interpolateFloat(float from, float to, double t) const {
return from + (to - from) * (float)t;
}
llmr::Color transition::interpolateColor(llmr::Color from, llmr::Color to, double t) const {
return {{ interpolateFloat(from[0], to[0], t),
interpolateFloat(from[1], to[1], t),
interpolateFloat(from[2], to[2], t),
interpolateFloat(from[3], to[3], t) }};
}
std::array<float, 2> transition::interpolateFloatArray(std::array<float, 2> from, std::array<float, 2> to, double t) const {
return {{ interpolateFloat(from[0], to[0], t), interpolateFloat(from[1], to[1], t) }};
}
template <>
transition::state ease_transition<double>::update(llmr::time now) const {
float t = progress(now);
if (t >= 1) {
value = to;
return complete;
} else {
value = from + (to - from) * ease.solve(t, 0.001);
value = interpolateDouble(from, to, ease.solve(t, 0.001));
return running;
}
}
template <>
transition::state ease_transition<llmr::Color>::update(llmr::time now) const {
float t = progress(now);
if (t >= 1) {
value = to;
return complete;
} else {
value = interpolateColor(from, to, ease.solve(t, 0.001));
return running;
}
}
template <>
transition::state ease_transition<float>::update(llmr::time now) const {
float t = progress(now);
if (t >= 1) {
value = to;
return complete;
} else {
value = interpolateFloat(from, to, ease.solve(t, 0.001));
return running;
}
}
template <>
transition::state ease_transition<std::array<float, 2>>::update(llmr::time now) const {
float t = progress(now);
if (t >= 1) {
value = to;
return complete;
} else {
value = interpolateFloatArray(from, to, ease.solve(t, 0.001));
return running;
}
}