From 888ae5ebdc11703023ada9bc3dd1d113cfe52314 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 6 Jul 2015 16:47:04 +0200 Subject: [PATCH] improve round line joins for semi-transparent lines https://github.com/mapbox/mapbox-gl-js/pull/1359 Round line joins used to be drawn by adding a semicircle cap to the end of each segment. This looked fine for opaque lines not for semi-transparent lines. This changes the triangulation so that round line joins don't overlap with segments. The gap between segments is filled with small triangles that look like pie slices. The edge of the round linejoin is made up of many short straight lines that look round at the sizes we draw lines. Since sharp angles are infrequent, this does not significantly affect the total number of triangles created. Joins for angles that are really sharp are still drawn with overlap. --- include/mbgl/style/types.hpp | 3 ++ src/mbgl/renderer/line_bucket.cpp | 69 ++++++++++++++++++++++++++----- src/mbgl/renderer/line_bucket.hpp | 3 ++ 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index f6ffcd686..e46ece6f4 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -92,6 +92,8 @@ enum class JoinType : uint8_t { Miter, Bevel, Round, + // the following two types are for internal use only + FakeRound, FlipBevel }; @@ -99,6 +101,7 @@ MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, { { JoinType::Miter, "miter" }, { JoinType::Bevel, "bevel" }, { JoinType::Round, "round" }, + { JoinType::FakeRound, "fakeround" }, { JoinType::FlipBevel, "flipbevel" }, }); diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index 0ba91af34..446c75de7 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -146,8 +146,12 @@ void LineBucket::addGeometry(const std::vector& vertices) { const CapType currentCap = nextVertex ? beginCap : endCap; if (middleVertex) { - if (currentJoin == JoinType::Round && miterLength < layout.round_limit) { - currentJoin = JoinType::Miter; + if (currentJoin == JoinType::Round) { + if (miterLength < layout.round_limit) { + currentJoin = JoinType::Miter; + } else if (miterLength <= 2) { + currentJoin = JoinType::FakeRound; + } } if (currentJoin == JoinType::Miter && miterLength > miterLimit) { @@ -191,13 +195,13 @@ void LineBucket::addGeometry(const std::vector& vertices) { triangleStore); flip = -flip; - } else if (middleVertex && currentJoin == JoinType::Bevel) { - const float dir = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x; + } else if (middleVertex && (currentJoin == JoinType::Bevel || currentJoin == JoinType::FakeRound)) { + const bool lineTurnsLeft = flip * (prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x) > 0; const float offset = -std::sqrt(miterLength * miterLength - 1); float offsetA; float offsetB; - if (flip * dir > 0) { + if (lineTurnsLeft) { offsetB = 0; offsetA = offset; } else { @@ -211,6 +215,29 @@ void LineBucket::addGeometry(const std::vector& vertices) { startVertex, triangleStore); } + if (currentJoin == JoinType::FakeRound) { + // The join angle is sharp enough that a round join would be visible. + // Bevel joins fill the gap between segments with a single pie slice triangle. + // Create a round join by adding multiple pie slices. The join isn't actually round, but + // it looks like it is at the sizes we render lines at. + + // Add more triangles for sharper angles. + // This math is just a good enough approximation. It isn't "correct". + const int n = std::floor((0.5 - (cosHalfAngle - 0.5)) * 8); + + for (int m = 0; m < n; m++) { + auto approxFractionalJoinNormal = util::unit(nextNormal * ((m + 1.0f) / (n + 1.0f)) + prevNormal); + addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore); + } + + addPieSliceVertex(currentVertex, flip, distance, joinNormal, lineTurnsLeft, startVertex, triangleStore); + + for (int k = n - 1; k >= 0; k--) { + auto approxFractionalJoinNormal = util::unit(prevNormal * ((k + 1.0f) / (n + 1.0f)) + nextNormal); + addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore); + } + } + // Start next segment if (nextVertex) { addCurrentVertex(currentVertex, flip, distance, nextNormal, -offsetA, -offsetB, @@ -260,15 +287,14 @@ void LineBucket::addGeometry(const std::vector& vertices) { // The segment is done. Unset vertices to disconnect segments. e1 = e2 = -1; flip = 1; - - } else if (beginCap == CapType::Round) { - // Add round cap before first segment - addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true, - startVertex, triangleStore); } // Start next segment with a butt if (nextVertex) { + // Add round cap before first segment + addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true, + startVertex, triangleStore); + addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false, startVertex, triangleStore); } @@ -337,6 +363,29 @@ void LineBucket::addCurrentVertex(const Coordinate& currentVertex, e2 = e3; } +void LineBucket::addPieSliceVertex(const Coordinate& currentVertex, + float flip, + double distance, + const vec2& extrude, + bool lineTurnsLeft, + int32_t startVertex, + std::vector& triangleStore) { + int8_t ty = lineTurnsLeft; + + auto flippedExtrude = extrude * (flip * (lineTurnsLeft ? -1 : 1)); + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, flippedExtrude.x, flippedExtrude.y, 0, ty, + distance) - startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(e1, e2, e3); + } + + if (lineTurnsLeft) { + e2 = e3; + } else { + e1 = e3; + } +} + void LineBucket::upload() { vertexBuffer.upload(); triangleElementsBuffer.upload(); diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp index 6f13e3c81..cd5fce3cb 100644 --- a/src/mbgl/renderer/line_bucket.hpp +++ b/src/mbgl/renderer/line_bucket.hpp @@ -47,6 +47,9 @@ private: void addCurrentVertex(const Coordinate& currentVertex, float flip, double distance, const vec2& normal, float endLeft, float endRight, bool round, int32_t startVertex, std::vector& triangleStore); + void addPieSliceVertex(const Coordinate& currentVertex, float flip, double distance, + const vec2& extrude, bool lineTurnsLeft, int32_t startVertex, + std::vector& triangleStore); public: StyleLayoutLine layout;