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

Commit

Permalink
improve round line joins for semi-transparent lines
Browse files Browse the repository at this point in the history
mapbox/mapbox-gl-js#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.
  • Loading branch information
ansis committed Jul 8, 2015
1 parent 675dddd commit 888ae5e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 9 deletions.
3 changes: 3 additions & 0 deletions include/mbgl/style/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,16 @@ enum class JoinType : uint8_t {
Miter,
Bevel,
Round,
// the following two types are for internal use only
FakeRound,
FlipBevel
};

MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, {
{ JoinType::Miter, "miter" },
{ JoinType::Bevel, "bevel" },
{ JoinType::Round, "round" },
{ JoinType::FakeRound, "fakeround" },
{ JoinType::FlipBevel, "flipbevel" },
});

Expand Down
67 changes: 58 additions & 9 deletions src/mbgl/renderer/line_bucket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,12 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& 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) {
Expand Down Expand Up @@ -191,13 +195,13 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& 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 {
Expand All @@ -211,6 +215,29 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& 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,
Expand Down Expand Up @@ -260,15 +287,14 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
// The segment is done. Unset vertices to disconnect segments.
e1 = e2 = -1;
flip = 1;
}

} else if (beginCap == CapType::Round) {
// Start next segment with a butt
if (nextVertex) {
// Add round cap before first segment
addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true,
startVertex, triangleStore);
}

// Start next segment with a butt
if (nextVertex) {
addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false,
startVertex, triangleStore);
}
Expand Down Expand Up @@ -337,6 +363,29 @@ void LineBucket::addCurrentVertex(const Coordinate& currentVertex,
e2 = e3;
}

void LineBucket::addPieSliceVertex(const Coordinate& currentVertex,
float flip,
double distance,
const vec2<double>& extrude,
bool lineTurnsLeft,
int32_t startVertex,
std::vector<TriangleElement>& 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();
Expand Down
3 changes: 3 additions & 0 deletions src/mbgl/renderer/line_bucket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class LineBucket : public Bucket {
void addCurrentVertex(const Coordinate& currentVertex, float flip, double distance,
const vec2<double>& normal, float endLeft, float endRight, bool round,
int32_t startVertex, std::vector<LineBucket::TriangleElement>& triangleStore);
void addPieSliceVertex(const Coordinate& currentVertex, float flip, double distance,
const vec2<double>& extrude, bool lineTurnsLeft, int32_t startVertex,
std::vector<TriangleElement>& triangleStore);

public:
StyleLayoutLine layout;
Expand Down

0 comments on commit 888ae5e

Please sign in to comment.