1 #include <mbgl/geometry/line_atlas.hpp>
2 #include <mbgl/gl/context.hpp>
3 #include <mbgl/util/logging.hpp>
4 #include <mbgl/util/platform.hpp>
5
6 #include <boost/functional/hash.hpp>
7
8 #include <sstream>
9 #include <cmath>
10
11 namespace mbgl {
12
LineAtlas(const Size size)13 LineAtlas::LineAtlas(const Size size)
14 : image(size),
15 dirty(true) {
16 }
17
18 LineAtlas::~LineAtlas() = default;
19
getDashPosition(const std::vector<float> & dasharray,LinePatternCap patternCap)20 LinePatternPos LineAtlas::getDashPosition(const std::vector<float>& dasharray,
21 LinePatternCap patternCap) {
22 size_t key = patternCap == LinePatternCap::Round ? std::numeric_limits<size_t>::min()
23 : std::numeric_limits<size_t>::max();
24 for (const float part : dasharray) {
25 boost::hash_combine<float>(key, part);
26 }
27
28 // Note: We're not handling hash collisions here.
29 const auto it = positions.find(key);
30 if (it == positions.end()) {
31 auto inserted = positions.emplace(key, addDash(dasharray, patternCap));
32 assert(inserted.second);
33 return inserted.first->second;
34 } else {
35 return it->second;
36 }
37 }
38
addDash(const std::vector<float> & dasharray,LinePatternCap patternCap)39 LinePatternPos LineAtlas::addDash(const std::vector<float>& dasharray, LinePatternCap patternCap) {
40 const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
41 const uint8_t dashheight = 2 * n + 1;
42 const uint8_t offset = 128;
43
44 if (dasharray.size() < 2) {
45 return LinePatternPos();
46 }
47
48 if (nextRow + dashheight > image.size.height) {
49 Log::Warning(Event::OpenGL, "line atlas bitmap overflow");
50 return LinePatternPos();
51 }
52
53 float length = 0;
54 for (const float part : dasharray) {
55 length += part;
56 }
57
58 float stretch = image.size.width / length;
59 float halfWidth = stretch * 0.5;
60 // If dasharray has an odd length, both the first and last parts
61 // are dashes and should be joined seamlessly.
62 bool oddLength = dasharray.size() % 2 == 1;
63
64 for (int y = -n; y <= n; y++) {
65 int row = nextRow + n + y;
66 int index = image.size.width * row;
67
68 float left = 0;
69 float right = dasharray[0];
70 unsigned int partIndex = 1;
71
72 if (oddLength) {
73 left -= dasharray.back();
74 }
75
76 for (uint32_t x = 0; x < image.size.width; x++) {
77
78 while (right < x / stretch) {
79 left = right;
80 if (partIndex >= dasharray.size()) {
81 return LinePatternPos();
82 }
83 right = right + dasharray[partIndex];
84
85 if (oddLength && partIndex == dasharray.size() - 1) {
86 right += dasharray.front();
87 }
88
89 partIndex++;
90 }
91
92 float distLeft = fabs(x - left * stretch);
93 float distRight = fabs(x - right * stretch);
94 float dist = fmin(distLeft, distRight);
95 bool inside = (partIndex % 2) == 1;
96 int signedDistance;
97
98 if (patternCap == LinePatternCap::Round) {
99 float distMiddle = n ? (float)y / n * (halfWidth + 1) : 0;
100 if (inside) {
101 float distEdge = halfWidth - fabs(distMiddle);
102 signedDistance = sqrt(dist * dist + distEdge * distEdge);
103 } else {
104 signedDistance = halfWidth - sqrt(dist * dist + distMiddle * distMiddle);
105 }
106
107 } else {
108 signedDistance = int((inside ? 1 : -1) * dist);
109 }
110
111 image.data[index + x] = fmax(0, fmin(255, signedDistance + offset));
112 }
113 }
114
115 LinePatternPos position;
116 position.y = (0.5 + nextRow + n) / image.size.height;
117 position.height = (2.0 * n) / image.size.height;
118 position.width = length;
119
120 nextRow += dashheight;
121
122 dirty = true;
123
124 return position;
125 }
126
getSize() const127 Size LineAtlas::getSize() const {
128 return image.size;
129 }
130
upload(gl::Context & context,gl::TextureUnit unit)131 void LineAtlas::upload(gl::Context& context, gl::TextureUnit unit) {
132 if (!texture) {
133 texture = context.createTexture(image, unit);
134 } else if (dirty) {
135 context.updateTexture(*texture, image, unit);
136 }
137
138 dirty = false;
139 }
140
bind(gl::Context & context,gl::TextureUnit unit)141 void LineAtlas::bind(gl::Context& context, gl::TextureUnit unit) {
142 upload(context, unit);
143 context.bindTexture(*texture, unit, gl::TextureFilter::Linear, gl::TextureMipMap::No,
144 gl::TextureWrap::Repeat, gl::TextureWrap::Clamp);
145 }
146
147 } // namespace mbgl
148