diff options
-rw-r--r-- | include/llmr/map/vector_tile.hpp | 22 | ||||
-rw-r--r-- | include/llmr/renderer/fill_bucket.hpp | 5 | ||||
-rw-r--r-- | include/llmr/renderer/line_bucket.hpp | 8 | ||||
-rw-r--r-- | include/llmr/style/bucket_description.hpp | 7 | ||||
-rw-r--r-- | include/llmr/style/properties.hpp | 3 | ||||
-rw-r--r-- | include/llmr/util/math.hpp | 18 | ||||
-rw-r--r-- | include/llmr/util/vec.hpp | 11 | ||||
-rw-r--r-- | proto/style.proto | 3 | ||||
-rw-r--r-- | src/map/tile.cpp | 4 | ||||
-rw-r--r-- | src/map/vector_tile.cpp | 21 | ||||
-rw-r--r-- | src/renderer/fill_bucket.cpp | 105 | ||||
-rw-r--r-- | src/renderer/line_bucket.cpp | 239 | ||||
-rw-r--r-- | src/renderer/painter.cpp | 2 | ||||
-rw-r--r-- | src/style/style.cpp | 5 |
14 files changed, 375 insertions, 78 deletions
diff --git a/include/llmr/map/vector_tile.hpp b/include/llmr/map/vector_tile.hpp index c0cfc9bfbc..14b497709f 100644 --- a/include/llmr/map/vector_tile.hpp +++ b/include/llmr/map/vector_tile.hpp @@ -14,6 +14,28 @@ class VectorTileLayer; struct pbf; +struct Coordinate { + Coordinate() : x(0), y(0) {} + Coordinate(int16_t x, int16_t y) : x(x), y(y) {} + int16_t x; + int16_t y; + + inline bool operator==(const Coordinate& other) const { + return x == other.x && y == other.y; + } + + inline operator bool() const { + return *this == null; + } + + static const Coordinate null; +}; + +/* + * Allows iterating over the features of a VectorTileLayer using a + * BucketDescription as filter. Only features matching the descriptions will + * be returned (as pbf). + */ class FilteredVectorTileLayer { public: class iterator { diff --git a/include/llmr/renderer/fill_bucket.hpp b/include/llmr/renderer/fill_bucket.hpp index a64350a7ee..e4d813a95e 100644 --- a/include/llmr/renderer/fill_bucket.hpp +++ b/include/llmr/renderer/fill_bucket.hpp @@ -9,6 +9,8 @@ namespace llmr { class Style; class FillBuffer; +class BucketDescription; +struct Coordinate; struct pbf; class FillBucket : public Bucket { @@ -17,7 +19,8 @@ public: virtual void render(Painter& painter, const std::string& layer_name); - void addGeometry(pbf& data); + void addGeometry(pbf& data, const BucketDescription& bucket_desc); + void addGeometry(const std::vector<Coordinate>& line, const BucketDescription& bucket_desc); void drawElements(int32_t attrib); void drawVertices(int32_t attrib); diff --git a/include/llmr/renderer/line_bucket.hpp b/include/llmr/renderer/line_bucket.hpp index 8feff69b3c..852f265e0d 100644 --- a/include/llmr/renderer/line_bucket.hpp +++ b/include/llmr/renderer/line_bucket.hpp @@ -9,6 +9,8 @@ namespace llmr { class Style; class LineBuffer; +class BucketDescription; +struct Coordinate; struct pbf; class LineBucket : public Bucket { @@ -17,11 +19,13 @@ public: virtual void render(Painter& painter, const std::string& layer_name); - void addGeometry(pbf& data); + void addGeometry(pbf& data, const BucketDescription& bucket_desc); + void addGeometry(const std::vector<Coordinate>& line, const BucketDescription& bucket_desc); private: std::shared_ptr<LineBuffer> buffer; - + uint32_t start; + uint32_t length; }; } diff --git a/include/llmr/style/bucket_description.hpp b/include/llmr/style/bucket_description.hpp index c8933d4190..fb72b7bb5a 100644 --- a/include/llmr/style/bucket_description.hpp +++ b/include/llmr/style/bucket_description.hpp @@ -18,12 +18,15 @@ enum class BucketType { enum class CapType { None = 0, Round = 1, + Butt = 2, + Square = 3 }; enum class JoinType { None = 0, Butt = 1, - Bevel = 2 + Bevel = 2, + Round = 3 }; @@ -42,6 +45,8 @@ public: JoinType join = JoinType::None; std::string font; float font_size = 0.0f; + float miter_limit = 2.0f; + float round_limit = 1.0f; }; std::ostream& operator<<(std::ostream&, const BucketDescription& bucket); diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index 5b4f7d6275..66a1cebf4e 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -22,7 +22,6 @@ enum class Winding { // Bevel = 2 // }; - enum class Property { Null = 1, Constant = 2, @@ -59,6 +58,7 @@ struct LineClass { FunctionProperty<float> width; FunctionProperty<float> offset; Color color = {{ 0, 0, 0, 1 }}; + FunctionProperty<float> opacity = 1; }; // LineProperties is the one we resolve this to. @@ -67,6 +67,7 @@ struct LineProperties { float width = 0; float offset = 0; Color color = {{ 0, 0, 0, 1 }}; + float opacity = 1.0; }; diff --git a/include/llmr/util/math.hpp b/include/llmr/util/math.hpp index 744a9d9473..8ea4a6b792 100644 --- a/include/llmr/util/math.hpp +++ b/include/llmr/util/math.hpp @@ -3,6 +3,8 @@ #include <cmath> +#include "vec.hpp" + namespace llmr { namespace util { @@ -10,6 +12,22 @@ inline double angle_between(double ax, double ay, double bx, double by) { return atan2((ax * by - ay * bx), ax * bx + ay * by); } +template <typename T, typename S1, typename S2> +inline vec2<T> normal(const S1& a, const S2& b) { + T dx = b.x - a.x; + T dy = b.y - a.y; + T c = sqrt(dx * dx + dy * dy); + return { dx / c, dy / c }; +} + +template <typename T, typename S1, typename S2> +inline T dist(const S1& a, const S2& b) { + T dx = b.x - a.x; + T dy = b.y - a.y; + T c = sqrt(dx * dx + dy * dy); + return c; +}; + } } diff --git a/include/llmr/util/vec.hpp b/include/llmr/util/vec.hpp index 782573d7fd..8ef938faab 100644 --- a/include/llmr/util/vec.hpp +++ b/include/llmr/util/vec.hpp @@ -1,18 +1,29 @@ #ifndef llmr_util_vec2_ #define llmr_util_vec2_ +#include <limits> + namespace llmr { template <typename T = double> struct vec2 { + struct null {}; + T x, y; inline vec2() {} + inline vec2(null) : x(std::numeric_limits<T>::quiet_NaN()), y(std::numeric_limits<T>::quiet_NaN()) {} + inline vec2(const vec2& o) : x(o.x), y(o.y) {} inline vec2(T x, T y) : x(x), y(y) {} inline bool operator==(const vec2& rhs) const { return x == rhs.x && y == rhs.y; } + + template<typename U = T, typename std::enable_if<std::numeric_limits<U>::has_quiet_NaN, int>::type = 0> + inline operator bool() const { + return isnan(x) || isnan(y); + } }; template <typename T = double> diff --git a/proto/style.proto b/proto/style.proto index 5a977d799c..b2b103e6da 100644 --- a/proto/style.proto +++ b/proto/style.proto @@ -8,11 +8,14 @@ enum bucket_type { enum cap_type { round = 1; + butt = 2; + square = 3; } enum join_type { butt = 1; bevel = 2; + round = 3; } enum winding_type { diff --git a/src/map/tile.cpp b/src/map/tile.cpp index 71b01d15b8..cd4a9ba861 100644 --- a/src/map/tile.cpp +++ b/src/map/tile.cpp @@ -170,7 +170,7 @@ std::shared_ptr<Bucket> Tile::createFillBucket(const VectorTileLayer& layer, con while (feature.next(4)) { // geometry pbf geometry_pbf = feature.message(); if (geometry_pbf) { - bucket->addGeometry(geometry_pbf); + bucket->addGeometry(geometry_pbf, bucket_desc); } } } @@ -186,7 +186,7 @@ std::shared_ptr<Bucket> Tile::createLineBucket(const VectorTileLayer& layer, con while (feature.next(4)) { // geometry pbf geometry_pbf = feature.message(); if (geometry_pbf) { - bucket->addGeometry(geometry_pbf); + bucket->addGeometry(geometry_pbf, bucket_desc); } } } diff --git a/src/map/vector_tile.cpp b/src/map/vector_tile.cpp index e8ef7f05b4..df9043ac19 100644 --- a/src/map/vector_tile.cpp +++ b/src/map/vector_tile.cpp @@ -4,6 +4,11 @@ using namespace llmr; +const Coordinate Coordinate::null( + std::numeric_limits<int16_t>::min(), + std::numeric_limits<int16_t>::min() +); + VectorTile::VectorTile() {} VectorTile::VectorTile(pbf tile) { @@ -151,19 +156,3 @@ bool FilteredVectorTileLayer::iterator::operator!=(const iterator& other) const const pbf& FilteredVectorTileLayer::iterator:: operator*() const { return feature; } - -// class FilteredVectorTileLayer { -// public: -// class iterator { -// public: -// void operator++(); -// bool operator!=(const iterator& other) const; -// const VectorTileFeature& operator*() const; - -// private: -// iterator(const pbf& data); -// VectorTileFeature feature; -// pbf data; -// }; - -// }; diff --git a/src/renderer/fill_bucket.cpp b/src/renderer/fill_bucket.cpp index 07282a05ae..bd23ce6bdd 100644 --- a/src/renderer/fill_bucket.cpp +++ b/src/renderer/fill_bucket.cpp @@ -22,78 +22,77 @@ FillBucket::FillBucket(const std::shared_ptr<FillBuffer>& buffer) length(0) { } -void FillBucket::addGeometry(pbf& geom) { - std::vector<std::vector<std::pair<int16_t, int16_t>>> lines; - - { - std::vector<std::pair<int16_t, int16_t>> line; - Geometry::command cmd; - int32_t x, y; - Geometry geometry(geom); - while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - if (line.size()) { - lines.push_back(line); - line.clear(); - } +void FillBucket::addGeometry(pbf& geom, const BucketDescription& bucket_desc) { + std::vector<Coordinate> line; + Geometry::command cmd; + + Coordinate coord; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (line.size()) { + addGeometry(line, bucket_desc); + line.clear(); } - line.emplace_back(x, y); - } - if (line.size()) { - lines.push_back(line); } + line.emplace_back(x, y); + } + if (line.size()) { + addGeometry(line, bucket_desc); } +} +void FillBucket::addGeometry(const std::vector<Coordinate>& line, const BucketDescription& bucket_desc) { // Alias this. FillBuffer& buffer = *this->buffer; - for (const std::vector<std::pair<int16_t, int16_t>>& line : lines) { - uint32_t vertex_start = buffer.vertex_length(); - - buffer.addDegenerate(); - for (const std::pair<int16_t, int16_t>& coord : line) { - buffer.addCoordinate(coord.first, coord.second); - } + uint32_t vertex_start = buffer.vertex_length(); - uint32_t vertex_end = buffer.vertex_length(); + buffer.addDegenerate(); + for (const Coordinate& coord : line) { + buffer.addCoordinate(coord.x, coord.y); + } - if (vertex_end - vertex_start > 65535) { - throw geometry_too_long_exception(); - } + uint32_t vertex_end = buffer.vertex_length(); - FillBucket& index = *this; - if (!index.groups.size()) { - index.groups.emplace_back(); - } + if (vertex_end - vertex_start > 65535) { + throw geometry_too_long_exception(); + } - uint32_t vertex_count = vertex_end - vertex_start; - index.length += vertex_count; + FillBucket& index = *this; + if (!index.groups.size()) { + index.groups.emplace_back(); + } - if (index.groups.back().vertex_length + vertex_count > 65535) { - // Move to a new group because the old one can't hold the geometry. - index.groups.emplace_back(); - } + uint32_t vertex_count = vertex_end - vertex_start; + index.length += vertex_count; - group& group = index.groups.back(); + if (index.groups.back().vertex_length + vertex_count > 65535) { + // Move to a new group because the old one can't hold the geometry. + index.groups.emplace_back(); + } - // We're generating triangle fans, so we always start with the first - // coordinate in this polygon. - // The first valid index that is not a degenerate. - uint16_t firstIndex = group.vertex_length + 1; + group& group = index.groups.back(); - assert(firstIndex + vertex_count - 1 < 65536); + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + // The first valid index that is not a degenerate. + uint16_t firstIndex = group.vertex_length + 1; - uint32_t elements_start = buffer.elements_length(); + assert(firstIndex + vertex_count - 1 < 65536); - for (uint32_t i = 2; i < vertex_count; i++) { - buffer.addElements(firstIndex, firstIndex + i - 1, firstIndex + i); - } + uint32_t elements_start = buffer.elements_length(); - uint32_t elements_end = buffer.elements_length(); - uint32_t elements_count = elements_end - elements_start; - group.vertex_length += vertex_count; - group.elements_length += elements_count; + for (uint32_t i = 2; i < vertex_count; i++) { + buffer.addElements(firstIndex, firstIndex + i - 1, firstIndex + i); } + + uint32_t elements_end = buffer.elements_length(); + uint32_t elements_count = elements_end - elements_start; + group.vertex_length += vertex_count; + group.elements_length += elements_count; + } void FillBucket::drawElements(int32_t attrib) { diff --git a/src/renderer/line_bucket.cpp b/src/renderer/line_bucket.cpp index b4efa2f0b1..f3052242da 100644 --- a/src/renderer/line_bucket.cpp +++ b/src/renderer/line_bucket.cpp @@ -5,6 +5,7 @@ #include <llmr/renderer/painter.hpp> #include <llmr/style/style.hpp> +#include <llmr/util/math.hpp> #include <llmr/platform/gl.hpp> #define BUFFER_OFFSET(i) ((char *)NULL + (i)) @@ -16,11 +17,245 @@ struct geometry_too_long_exception : std::exception {}; using namespace llmr; LineBucket::LineBucket(const std::shared_ptr<LineBuffer>& buffer) - : buffer(buffer) { + : buffer(buffer), + start(buffer->length()), + length(0) { } -void LineBucket::addGeometry(pbf& geom) { +void LineBucket::addGeometry(pbf& geom, const BucketDescription& bucket_desc) { + std::vector<Coordinate> line; + Geometry::command cmd; + Coordinate coord; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (line.size()) { + addGeometry(line, bucket_desc); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addGeometry(line, bucket_desc); + } +} + +void LineBucket::addGeometry(const std::vector<Coordinate>& vertices, const BucketDescription& bucket_desc) { + JoinType join = bucket_desc.join; + CapType cap = bucket_desc.cap; + float miterLimit = bucket_desc.miter_limit; + float roundLimit = bucket_desc.round_limit; + + if (vertices.size() < 1) { + // alert('a line must have at least one vertex'); + return; + } + + // Point + if (vertices.size() == 1) { + Coordinate coord = vertices.front(); + buffer->add(coord.x, coord.y, 1, 0, 0, 0); + return; + } + + Coordinate firstVertex = vertices.front(); + Coordinate lastVertex = vertices.back(); + bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y; + + if (vertices.size() == 2 && closed) { + // alert('a line may not have coincident points'); + return; + } + + CapType beginCap = cap; + CapType endCap = closed ? CapType::Butt : cap; + + Coordinate currentVertex = Coordinate::null, + prevVertex = Coordinate::null, + nextVertex = Coordinate::null; + vec2<double> prevNormal = vec2<double>::null(), + nextNormal = vec2<double>::null(); + + int8_t flip = 1; + double distance = 0; + + if (closed) { + currentVertex = vertices[vertices.size() - 2]; + nextNormal = util::normal<double>(currentVertex, lastVertex); + } + + // Start all lines with a degenerate vertex + buffer->addDegenerate(); + + for (size_t i = 0; i < vertices.size(); i++) { + if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y }; + if (currentVertex) prevVertex = currentVertex; + + currentVertex = vertices[i]; + + if (prevVertex) distance += util::dist<double>(currentVertex, prevVertex); + + // Find the next vertex. + if (i + 1 < vertices.size()) { + nextVertex = vertices[i + 1]; + } else { + nextVertex = Coordinate::null; + } + + bool segment = false; + + if (closed && i >= 2 && (i + 1 < vertices.size())) { + segment = true; + } + + // If the line is closed, we treat the last vertex like the first vertex. + if (!nextVertex && closed) { + nextVertex = vertices[1]; + } + + + if (nextVertex) { + // if two consecutive vertices exist, skip one + if (currentVertex.x == nextVertex.x && currentVertex.y == nextVertex.y) continue; + } + + // Calculate the normal towards the next vertex in this line. In case + // there is no next vertex, pretend that the line is continuing straight, + // meaning that we are just reversing the previous normal + if (nextVertex) { + nextNormal = util::normal<double>(currentVertex, nextVertex); + } else { + nextNormal = { -prevNormal.x, -prevNormal.y }; + } + + // If we still don't have a previous normal, this is the beginning of a + // non-closed line, so we're doing a straight "join". + if (!prevNormal) { + prevNormal = { -nextNormal.x, -nextNormal.y }; + } + + // Determine the normal of the join extrusion. It is the angle bisector + // of the segments between the previous line and the next line. + vec2<double> joinNormal = { + prevNormal.x + nextNormal.x, + prevNormal.y + nextNormal.y + }; + + // Cross product yields 0..1 depending on whether they are parallel + // or perpendicular. + double joinAngularity = nextNormal.x * joinNormal.y - nextNormal.y * joinNormal.x; + joinNormal.x /= joinAngularity; + joinNormal.y /= joinAngularity; + double roundness = fmax(abs(joinNormal.x), abs(joinNormal.y)); + + + bool roundJoin = false; + // Determine whether we should actually draw a round/bevelled/butt line join. It looks + // better if we do, but we can get away with drawing a mitered join for + // joins that have a very small angle. For this, we have the "roundLimit" + // parameter. We do this to reduce the number of vertices we have to + // write into the line vertex buffer. Note that joinAngularity may be 0, + // so the roundness grows to infinity. This is intended. + if ((join == JoinType::Round || join == JoinType::Bevel || join == JoinType::Butt) && roundness > roundLimit) { + roundJoin = true; + } + + // Close up the previous line for a round join + if (roundJoin && prevVertex && nextVertex) { + // Add first vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + flip * prevNormal.y, -flip * prevNormal.x, // extrude normal + 0, 0, distance); // texture normal + + // Add second vertex. + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + -flip * prevNormal.y, flip * prevNormal.x, // extrude normal + 0, 1, distance); // texture normal + + // Degenerate triangle + if (join == JoinType::Round || join == JoinType::Butt) { + buffer->addDegenerate(); + } + + if (join == JoinType::Round) prevVertex = Coordinate::null; + + prevNormal = { -nextNormal.x, -nextNormal.y }; + flip = 1; + } + + // Add a cap. + if (!prevVertex && (beginCap == CapType::Round || beginCap == CapType::Square || (roundJoin && join == JoinType::Round))) { + int8_t tex = beginCap == CapType::Round || roundJoin ? 1 : 0; + + // Add first vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal + tex, 0, distance); // texture normal + + // Add second vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal + tex, 1, distance); // texture normal + } + + if (roundJoin) { + // ROUND JOIN + // Add first vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + -flip * nextNormal.y, flip * nextNormal.x, // extrude normal + 0, 0, distance); // texture normal + + // Add second vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + flip * nextNormal.y, -flip * nextNormal.x, // extrude normal + 0, 1, distance); // texture normal + } else if ((nextVertex || endCap != CapType::Square) && (prevVertex || beginCap != CapType::Square)) { + // MITER JOIN + if (fabs(joinAngularity) < 0.01) { + // The two normals are almost parallel. + joinNormal.x = -nextNormal.y; + joinNormal.y = nextNormal.x; + } else if (roundness > miterLimit) { + // If the miter grows too large, flip the direction to make a + // bevel join. + joinNormal.x = (prevNormal.x - nextNormal.x) / joinAngularity; + joinNormal.y = (prevNormal.y - nextNormal.y) / joinAngularity; + flip = -flip; + } + + // Add first vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + flip * joinNormal.x, flip * joinNormal.y, // extrude normal + 0, 0, distance); // texture normal + + // Add second vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + -flip * joinNormal.x, -flip * joinNormal.y, // extrude normal + 0, 1, distance); // texture normal + } + + // Add the end cap, but only if this vertex is distinct from the begin + // vertex. + if (!nextVertex && (endCap == CapType::Round || endCap == CapType::Square)) { + int8_t capTex = endCap == CapType::Round ? 1 : 0; + + // Add first vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal + capTex, 0, distance); // texture normal + + // Add second vertex + buffer->add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal + capTex, 1, distance); // texture normal + } + } + + // Update the length. + length = buffer->length() - start; } void LineBucket::render(Painter& painter, const std::string& layer_name) { diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 3323ddcbb9..2e391b031e 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -330,6 +330,8 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name) { void Painter::renderLine(LineBucket& bucket, const std::string& layer_name) { const LineProperties& properties = style.computed.lines[layer_name]; + + // TODO } void Painter::renderDebug(const Tile::Ptr& tile) { diff --git a/src/style/style.cpp b/src/style/style.cpp index 6e468962b1..a3a73c2526 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -206,6 +206,8 @@ void Style::cascade(float z) { const std::string& layer_name = fill_pair.first; const llmr::FillClass& layer = fill_pair.second; + // TODO: This should be restricted to fill styles that have actual + // values so as to not override with default values. llmr::FillProperties& fill = computed.fills[layer_name]; fill.hidden = layer.hidden(z); fill.winding = layer.winding; @@ -220,11 +222,14 @@ void Style::cascade(float z) { const std::string& layer_name = line_pair.first; const llmr::LineClass& layer = line_pair.second; + // TODO: This should be restricted to fill styles that have actual + // values so as to not override with default values. llmr::LineProperties& stroke = computed.lines[layer_name]; stroke.hidden = layer.hidden(z); stroke.width = layer.width(z); stroke.offset = layer.offset(z); stroke.color = layer.color; + stroke.opacity = layer.opacity(z); } } } |