diff options
Diffstat (limited to 'src/mbgl/renderer/line_bucket.cpp')
-rw-r--r-- | src/mbgl/renderer/line_bucket.cpp | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp new file mode 100644 index 0000000000..8267cbaba2 --- /dev/null +++ b/src/mbgl/renderer/line_bucket.cpp @@ -0,0 +1,403 @@ +#include <mbgl/renderer/line_bucket.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/geometry.hpp> + +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/map/vector_tile.hpp> + +#include <mbgl/util/math.hpp> +#include <mbgl/platform/gl.hpp> + +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) + +#include <cassert> + +struct geometry_too_long_exception : std::exception {}; + +using namespace mbgl; + +LineBucket::LineBucket(LineVertexBuffer& vertexBuffer_, + TriangleElementsBuffer& triangleElementsBuffer_, + PointElementsBuffer& pointElementsBuffer_, + const StyleBucketLine& properties_) + : properties(properties_), + vertexBuffer(vertexBuffer_), + triangleElementsBuffer(triangleElementsBuffer_), + pointElementsBuffer(pointElementsBuffer_), + vertex_start(vertexBuffer_.index()), + triangle_elements_start(triangleElementsBuffer_.index()), + point_elements_start(pointElementsBuffer_.index()) +{ +} + +void LineBucket::addGeometry(pbf& geom) { + 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.empty()) { + addGeometry(line); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addGeometry(line); + } +} + +struct TriangleElement { + TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {} + uint16_t a, b, c; +}; + +typedef uint16_t PointElement; + +void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { + // TODO: use roundLimit + // const float roundLimit = geometry.round_limit; + + if (vertices.size() < 2) { + // fprintf(stderr, "a line must have at least two vertices\n"); + return; + } + + Coordinate firstVertex = vertices.front(); + Coordinate lastVertex = vertices.back(); + bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y; + + if (vertices.size() == 2 && closed) { + // fprintf(stderr, "a line may not have coincident points\n"); + return; + } + + CapType beginCap = properties.cap; + CapType endCap = closed ? CapType::Butt : properties.cap; + + JoinType currentJoin = JoinType::Miter; + + Coordinate currentVertex = Coordinate::null(), + prevVertex = Coordinate::null(), + nextVertex = Coordinate::null(); + vec2<double> prevNormal = vec2<double>::null(), + nextNormal = vec2<double>::null(); + + int32_t e1 = -1, e2 = -1, e3 = -1; + + int8_t flip = 1; + double distance = 0; + + if (closed) { + currentVertex = vertices[vertices.size() - 2]; + nextNormal = util::normal<double>(currentVertex, lastVertex); + } + + int32_t start_vertex = (int32_t)vertexBuffer.index(); + + std::vector<TriangleElement> triangle_store; + std::vector<PointElement> point_store; + + for (size_t i = 0; i < vertices.size(); ++i) { + if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y }; + if (currentVertex) prevVertex = currentVertex; + + currentVertex = vertices[i]; + currentJoin = properties.join; + + 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(); + } + + // 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 = std::fmax(std::abs(joinNormal.x), std::abs(joinNormal.y)); + + + // Switch to miter joins if the angle is very low. + if (currentJoin != JoinType::Miter) { + if (std::fabs(joinAngularity) < 0.5 && roundness < properties.miter_limit) { + currentJoin = JoinType::Miter; + } + } + + // Add offset square begin cap. + if (!prevVertex && beginCap == CapType::Square) { + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + + // Add offset square end cap. + else if (!nextVertex && endCap == CapType::Square) { + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + + else if (currentJoin == JoinType::Miter) { + // MITER JOIN + if (std::fabs(joinAngularity) < 0.01) { + // The two normals are almost parallel. + joinNormal.x = -nextNormal.y; + joinNormal.y = nextNormal.x; + } else if (roundness > properties.miter_limit) { + // 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; + } + + if (roundness > properties.miter_limit) { + flip = -flip; + } + + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * joinNormal.x, flip * joinNormal.y, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * joinNormal.x, -flip * joinNormal.y, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + if ((!prevVertex && beginCap == CapType::Round) || + (!nextVertex && endCap == CapType::Round)) { + point_store.emplace_back(e1); + } + } + + else { + // Close up the previous line + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * prevNormal.y, -flip * prevNormal.x, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex. + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * prevNormal.y, flip * prevNormal.x, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + prevNormal = { -nextNormal.x, -nextNormal.y }; + flip = 1; + + + // begin/end caps + if ((!prevVertex && beginCap == CapType::Round) || + (!nextVertex && endCap == CapType::Round)) { + point_store.emplace_back(e1); + } + + + if (currentJoin == JoinType::Round) { + if (prevVertex && nextVertex && (!closed || i > 0)) { + point_store.emplace_back(e1); + } + + // Reset the previous vertices so that we don't accidentally create + // any triangles. + e1 = -1; e2 = -1; e3 = -1; + } + + // Start the new quad. + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * nextNormal.y, flip * nextNormal.x, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * nextNormal.y, -flip * nextNormal.x, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + } + + size_t end_vertex = vertexBuffer.index(); + size_t vertex_count = end_vertex - start_vertex; + + // Store the triangle/line groups. + { + if (!triangleGroups.size() || (triangleGroups.back().vertex_length + vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + triangleGroups.emplace_back(); + } + + triangle_group_type& group = triangleGroups.back(); + for (const TriangleElement& triangle : triangle_store) { + triangleElementsBuffer.add( + group.vertex_length + triangle.a, + group.vertex_length + triangle.b, + group.vertex_length + triangle.c + ); + } + + group.vertex_length += vertex_count; + group.elements_length += triangle_store.size(); + } + + // Store the line join/cap groups. + { + if (!pointGroups.size() || (pointGroups.back().vertex_length + vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + pointGroups.emplace_back(); + } + + point_group_type& group = pointGroups.back(); + for (PointElement point : point_store) { + pointElementsBuffer.add(group.vertex_length + point); + } + + group.vertex_length += vertex_count; + group.elements_length += point_store.size(); + } +} + +void LineBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + painter.renderLine(*this, layer_desc, id, matrix); +} + +bool LineBucket::hasData() const { + return !triangleGroups.empty() || !pointGroups.empty(); +} + +bool LineBucket::hasPoints() const { + if (!pointGroups.empty()) { + for (const point_group_type& group : pointGroups) { + if (group.elements_length) { + return true; + } + } + } + return false; +} + +void LineBucket::drawLines(LineShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + for (triangle_group_type& group : triangleGroups) { + if (!group.elements_length) { + continue; + } + group.array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer.itemSize; + elements_index += group.elements_length * triangleElementsBuffer.itemSize; + } +} + +void LineBucket::drawLinePatterns(LinepatternShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + for (triangle_group_type& group : triangleGroups) { + if (!group.elements_length) { + continue; + } + group.array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer.itemSize; + elements_index += group.elements_length * triangleElementsBuffer.itemSize; + } +} + +void LineBucket::drawPoints(LinejoinShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize); + for (point_group_type& group : pointGroups) { + if (!group.elements_length) { + continue; + } + group.array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index); + glDrawElements(GL_POINTS, group.elements_length, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer.itemSize; + elements_index += group.elements_length * pointElementsBuffer.itemSize; + } +} |