Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

[core] Fix composite function approximation for non-integer stops #9289

Merged
merged 1 commit into from
Jun 16, 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
75 changes: 53 additions & 22 deletions include/mbgl/style/function/composite_function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,16 @@ class CompositeFunction {
defaultValue(std::move(defaultValue_)) {
}

std::tuple<Range<float>, Range<InnerStops>>
coveringRanges(float zoom) const {
struct CoveringRanges {
float zoom;
Range<float> coveringZoomRange;
Range<InnerStops> coveringStopsRange;
};

// Return the relevant stop zoom values and inner stops that bracket a given zoom level. This
// is the first step toward evaluating the function, and is used for in the course of both partial
// evaluation of data-driven paint properties, and full evaluation of data-driven layout properties.
CoveringRanges coveringRanges(float zoom) const {
return stops.match(
[&] (const auto& s) {
assert(!s.stops.empty());
Expand All @@ -63,7 +71,8 @@ class CompositeFunction {
minIt--;
}

return std::make_tuple(
return CoveringRanges {
zoom,
Range<float> {
minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first,
maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first
Expand All @@ -72,38 +81,49 @@ class CompositeFunction {
s.innerStops(minIt == s.stops.end() ? s.stops.rbegin()->second : minIt->second),
s.innerStops(maxIt == s.stops.end() ? s.stops.rbegin()->second : maxIt->second)
}
);
};
}
);
}

// Given a range of zoom values (typically two adjacent integer zoom levels, e.g. 5.0 and 6.0),
// return the covering ranges for both. This is used in the course of partial evaluation for
// data-driven paint properties.
Range<CoveringRanges> rangeOfCoveringRanges(Range<float> zoomRange) {
return Range<CoveringRanges> {
coveringRanges(zoomRange.min),
coveringRanges(zoomRange.max)
};
}

// Given the covering ranges for range of zoom values (typically two adjacent integer zoom levels,
// e.g. 5.0 and 6.0), and a feature, return the results of fully evaluating the function for that
// feature at each of the two zoom levels. These two results are what go into the paint vertex buffers
// for vertices associated with this feature. The shader will interpolate between them at render time.
template <class Feature>
Range<T> evaluate(Range<InnerStops> coveringStops,
const Feature& feature,
T finalDefaultValue) const {
optional<Value> v = feature.getValue(property);
if (!v) {
return {
Range<T> evaluate(const Range<CoveringRanges>& ranges, const Feature& feature, T finalDefaultValue) {
optional<Value> value = feature.getValue(property);
if (!value) {
return Range<T> {
defaultValue.value_or(finalDefaultValue),
defaultValue.value_or(finalDefaultValue)
};
}
auto eval = [&] (const auto& s) {
return s.evaluate(*v).value_or(defaultValue.value_or(finalDefaultValue));
};
return Range<T> {
coveringStops.min.match(eval),
coveringStops.max.match(eval)
evaluateFinal(ranges.min, *value, finalDefaultValue),
evaluateFinal(ranges.max, *value, finalDefaultValue)
};
}

T evaluate(float zoom, const GeometryTileFeature& feature, T finalDefaultValue) const {
std::tuple<Range<float>, Range<InnerStops>> ranges = coveringRanges(zoom);
Range<T> resultRange = evaluate(std::get<1>(ranges), feature, finalDefaultValue);
return util::interpolate(
resultRange.min,
resultRange.max,
util::interpolationFactor(1.0f, std::get<0>(ranges), zoom));
// Fully evaluate the function for a zoom value and feature. This is used when evaluating data-driven
// layout properties.
template <class Feature>
T evaluate(float zoom, const Feature& feature, T finalDefaultValue) const {
optional<Value> value = feature.getValue(property);
if (!value) {
return defaultValue.value_or(finalDefaultValue);
}
return evaluateFinal(coveringRanges(zoom), *value, finalDefaultValue);
}

friend bool operator==(const CompositeFunction& lhs,
Expand All @@ -115,6 +135,17 @@ class CompositeFunction {
std::string property;
Stops stops;
optional<T> defaultValue;

private:
T evaluateFinal(const CoveringRanges& ranges, const Value& value, T finalDefaultValue) const {
auto eval = [&] (const auto& s) {
return s.evaluate(value).value_or(defaultValue.value_or(finalDefaultValue));
};
return util::interpolate(
ranges.coveringStopsRange.min.match(eval),
ranges.coveringStopsRange.max.match(eval),
util::interpolationFactor(1.0f, ranges.coveringZoomRange, ranges.zoom));
}
};

} // namespace style
Expand Down
10 changes: 5 additions & 5 deletions src/mbgl/renderer/paint_property_binder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,11 @@ class CompositeFunctionPaintPropertyBinder : public PaintPropertyBinder<T, A> {
CompositeFunctionPaintPropertyBinder(style::CompositeFunction<T> function_, float zoom, T defaultValue_)
: function(std::move(function_)),
defaultValue(std::move(defaultValue_)),
coveringRanges(function.coveringRanges(zoom)) {
rangeOfCoveringRanges(function.rangeOfCoveringRanges({zoom, zoom + 1})) {
}

void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) override {
Range<T> range = function.evaluate(std::get<1>(coveringRanges), feature, defaultValue);
Range<T> range = function.evaluate(rangeOfCoveringRanges, feature, defaultValue);
this->statistics.add(range.min);
this->statistics.add(range.max);
AttributeValue value = zoomInterpolatedAttributeValue(
Expand All @@ -217,7 +217,7 @@ class CompositeFunctionPaintPropertyBinder : public PaintPropertyBinder<T, A> {
}

float interpolationFactor(float currentZoom) const override {
return util::interpolationFactor(1.0f, std::get<0>(coveringRanges), currentZoom);
return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, currentZoom);
}

T uniformValue(const PossiblyEvaluatedPropertyValue<T>& currentValue) const override {
Expand All @@ -230,10 +230,10 @@ class CompositeFunctionPaintPropertyBinder : public PaintPropertyBinder<T, A> {
}

private:
using InnerStops = typename style::CompositeFunction<T>::InnerStops;
style::CompositeFunction<T> function;
T defaultValue;
std::tuple<Range<float>, Range<InnerStops>> coveringRanges;
using CoveringRanges = typename style::CompositeFunction<T>::CoveringRanges;
Range<CoveringRanges> rangeOfCoveringRanges;
gl::VertexVector<Vertex> vertexVector;
optional<gl::VertexBuffer<Vertex>> vertexBuffer;
};
Expand Down
25 changes: 25 additions & 0 deletions test/style/function/composite_function.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,28 @@ TEST(CompositeFunction, ZoomInterpolation) {
}), 0.0f)
.evaluate(0.0f, oneInteger, -1.0f)) << "Should interpolate TO the first stop";
}

TEST(CompositeFunction, Issue8460) {
CompositeFunction<float> fn1("property", CompositeExponentialStops<float>({
{15.0f, {{uint64_t(1), 0.0f}}},
{15.2f, {{uint64_t(1), 600.0f}}},
}), 0.0f);

EXPECT_NEAR( 0.0f, fn1.evaluate(15.0f, oneInteger, -1.0f), 0.00);
EXPECT_NEAR(300.0f, fn1.evaluate(15.1f, oneInteger, -1.0f), 0.01);
EXPECT_NEAR(600.0f, fn1.evaluate(15.2f, oneInteger, -1.0f), 0.00);
EXPECT_NEAR(600.0f, fn1.evaluate(16.0f, oneInteger, -1.0f), 0.00);

CompositeFunction<float> fn2("property", CompositeExponentialStops<float>({
{15.0f, {{uint64_t(1), 0.0f}}},
{15.2f, {{uint64_t(1), 300.0f}}},
{18.0f, {{uint64_t(1), 600.0f}}},
}), 0.0f);

EXPECT_NEAR( 0.0f, fn2.evaluate(15.0f, oneInteger, -1.0f), 0.00);
EXPECT_NEAR(150.0f, fn2.evaluate(15.1f, oneInteger, -1.0f), 0.01);
EXPECT_NEAR(300.0f, fn2.evaluate(15.2f, oneInteger, -1.0f), 0.00);
EXPECT_NEAR(385.71f, fn2.evaluate(16.0f, oneInteger, -1.0f), 0.01);
EXPECT_NEAR(600.0f, fn2.evaluate(18.0f, oneInteger, -1.0f), 0.00);
EXPECT_NEAR(600.0f, fn2.evaluate(19.0f, oneInteger, -1.0f), 0.00);
}