261 lines
9.1 KiB
C++
261 lines
9.1 KiB
C++
#include <cmath>
|
|
#include <mbgl/geometry/line_atlas.hpp>
|
|
#include <mbgl/gfx/upload_pass.hpp>
|
|
#include <mbgl/math/log2.hpp>
|
|
#include <mbgl/math/minmax.hpp>
|
|
#include <mbgl/util/hash.hpp>
|
|
#include <mbgl/util/logging.hpp>
|
|
#include <mbgl/util/platform.hpp>
|
|
|
|
namespace mbgl {
|
|
namespace {
|
|
|
|
size_t getDashPatternHash(const std::vector<float>& dasharray, const LinePatternCap patternCap) {
|
|
size_t key = patternCap == LinePatternCap::Round ? std::numeric_limits<size_t>::min()
|
|
: std::numeric_limits<size_t>::max();
|
|
for (const float part : dasharray) {
|
|
util::hash_combine<float>(key, part);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
std::vector<DashRange> getDashRanges(const std::vector<float>& dasharray, float stretch) {
|
|
// If dasharray has an odd length, both the first and last parts
|
|
// are dashes and should be joined seamlessly.
|
|
const bool oddDashArray = dasharray.size() % 2 == 1;
|
|
|
|
float left = oddDashArray ? -dasharray.back() * stretch : 0.0f;
|
|
float right = dasharray.front() * stretch;
|
|
bool isDash = true;
|
|
|
|
std::vector<DashRange> ranges;
|
|
ranges.reserve(dasharray.size());
|
|
ranges.push_back({left, right, isDash, dasharray.front() == 0.0f});
|
|
|
|
float currentDashLength = dasharray.front();
|
|
for (size_t i = 1; i < dasharray.size(); ++i) {
|
|
isDash = !isDash;
|
|
|
|
const float dashLength = dasharray[i];
|
|
left = currentDashLength * stretch;
|
|
currentDashLength += dashLength;
|
|
right = currentDashLength * stretch;
|
|
|
|
ranges.push_back({left, right, isDash, dashLength == 0.0f});
|
|
}
|
|
|
|
return ranges;
|
|
}
|
|
|
|
void addRoundDash(
|
|
const std::vector<DashRange>& ranges, uint32_t yOffset, float stretch, const int n, AlphaImage& image) {
|
|
const float halfStretch = stretch * 0.5f;
|
|
|
|
if (ranges.empty()) return;
|
|
|
|
for (int y = -n; y <= n; y++) {
|
|
int row = static_cast<int32_t>(yOffset) + n + y;
|
|
const uint32_t index = image.size.width * row;
|
|
uint32_t currIndex = 0;
|
|
DashRange range = ranges[currIndex];
|
|
|
|
for (uint32_t x = 0; x < image.size.width; ++x) {
|
|
if (range.right == 0) {
|
|
if (x != 0 && ++currIndex < ranges.size()) {
|
|
range = ranges[currIndex];
|
|
}
|
|
} else if (x / range.right > 1.0f && ++currIndex < ranges.size()) {
|
|
range = ranges[currIndex];
|
|
}
|
|
|
|
float distLeft = fabsf(x - range.left);
|
|
float distRight = fabsf(x - range.right);
|
|
float minDist = fminf(distLeft, distRight);
|
|
float signedDistance;
|
|
|
|
float distMiddle = static_cast<float>(y) / n * (halfStretch + 1.0f);
|
|
if (range.isDash) {
|
|
float distEdge = halfStretch - fabsf(distMiddle);
|
|
signedDistance = sqrtf(minDist * minDist + distEdge * distEdge);
|
|
} else {
|
|
signedDistance = halfStretch - sqrtf(minDist * minDist + distMiddle * distMiddle);
|
|
}
|
|
|
|
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void addRegularDash(std::vector<DashRange>& ranges, uint32_t yOffset, AlphaImage& image) {
|
|
// Collapse any zero-length range
|
|
for (auto it = ranges.begin(); it != ranges.end();) {
|
|
if (it->isZeroLength) {
|
|
it = ranges.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
if (ranges.empty()) return;
|
|
|
|
if (ranges.size() > 1) {
|
|
// Collapse neighbouring same-type parts into a single part
|
|
for (auto curr = ranges.begin(), next = ranges.begin() + 1; next != ranges.end();) {
|
|
if (next->isDash == curr->isDash) {
|
|
next->left = curr->left;
|
|
curr = ranges.erase(curr);
|
|
} else {
|
|
++curr;
|
|
}
|
|
next = curr + 1;
|
|
}
|
|
}
|
|
|
|
DashRange& first = ranges.front();
|
|
DashRange& last = ranges.back();
|
|
if (first.isDash == last.isDash) {
|
|
first.left = last.left - image.size.width;
|
|
last.right = first.right + image.size.width;
|
|
}
|
|
|
|
const uint32_t index = image.size.width * yOffset;
|
|
uint32_t currIndex = 0;
|
|
DashRange range = ranges[currIndex];
|
|
|
|
for (uint32_t x = 0; x < image.size.width; ++x) {
|
|
if (x / range.right > 1.0f && ++currIndex < ranges.size()) {
|
|
range = ranges[currIndex];
|
|
}
|
|
|
|
float distLeft = fabsf(x - range.left);
|
|
float distRight = fabsf(x - range.right);
|
|
float minDist = fminf(distLeft, distRight);
|
|
float signedDistance = range.isDash ? minDist : -minDist;
|
|
|
|
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
|
|
}
|
|
}
|
|
|
|
LinePatternPos addDashPattern(AlphaImage& image,
|
|
uint32_t yOffset,
|
|
const std::vector<float>& dasharray,
|
|
const LinePatternCap patternCap) {
|
|
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
|
|
|
|
if (dasharray.size() < 2) {
|
|
Log::Warning(Event::ParseStyle, "line dasharray requires at least two elements");
|
|
return {};
|
|
}
|
|
|
|
float length = 0;
|
|
for (const float part : dasharray) {
|
|
length += part;
|
|
}
|
|
|
|
float stretch = image.size.width / length;
|
|
std::vector<DashRange> ranges = getDashRanges(dasharray, stretch);
|
|
|
|
if (patternCap == LinePatternCap::Round) {
|
|
addRoundDash(ranges, yOffset, stretch, n, image);
|
|
} else {
|
|
addRegularDash(ranges, yOffset, image);
|
|
}
|
|
|
|
LinePatternPos position;
|
|
position.y = (0.5f + yOffset + n) / image.size.height;
|
|
position.height = (2.0f * n + 1) / image.size.height;
|
|
position.width = length;
|
|
|
|
return position;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DashPatternTexture::DashPatternTexture(const std::vector<float>& from_,
|
|
const std::vector<float>& to_,
|
|
const LinePatternCap cap) {
|
|
const bool patternsIdentical = from_ == to_;
|
|
const uint32_t patternHeight = cap == LinePatternCap::Round ? 15 : 1;
|
|
const uint32_t height = (patternsIdentical ? 1 : 2) * patternHeight;
|
|
|
|
// The OpenGL ES 2.0 spec, section 3.8.2 states:
|
|
//
|
|
// Calling a sampler from a fragment shader will return (R,G,B,A) =
|
|
// (0,0,0,1) if any of the following conditions are true: […]
|
|
// - A two-dimensional sampler is called, the corresponding texture
|
|
// image is a
|
|
// non-power-of-two image […], and either the texture wrap mode is not
|
|
// CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor
|
|
// LINEAR.
|
|
// […]
|
|
//
|
|
// This means that texture lookups won't work for NPOT textures unless they
|
|
// use GL_CLAMP_TO_EDGE. We're using GL_CLAMP_TO_EDGE for the vertical
|
|
// direction, but GL_REPEAT for the horizontal direction, which means that
|
|
// we need a power-of-two texture for our line dash patterns to work on
|
|
// OpenGL ES 2.0 conforming implementations. Fortunately, this just means
|
|
// changing the height from 15 to 16, and from 30 to 32, so we don't waste
|
|
// many pixels.
|
|
const uint32_t textureHeight = 1 << util::ceil_log2(height);
|
|
AlphaImage image({256, textureHeight});
|
|
|
|
from = addDashPattern(image, 0, from_, cap);
|
|
to = patternsIdentical ? from : addDashPattern(image, patternHeight, to_, cap);
|
|
|
|
texture = std::move(image);
|
|
}
|
|
|
|
void DashPatternTexture::upload(gfx::UploadPass& uploadPass) {
|
|
if (texture.is<AlphaImage>()) {
|
|
texture = uploadPass.createTexture(texture.get<AlphaImage>());
|
|
}
|
|
}
|
|
|
|
gfx::TextureBinding DashPatternTexture::textureBinding() const {
|
|
// The texture needs to have been uploaded already.
|
|
assert(texture.is<gfx::Texture>());
|
|
return {texture.get<gfx::Texture>().getResource(),
|
|
gfx::TextureFilterType::Linear,
|
|
gfx::TextureMipMapType::No,
|
|
gfx::TextureWrapType::Repeat,
|
|
gfx::TextureWrapType::Clamp};
|
|
}
|
|
|
|
Size DashPatternTexture::getSize() const {
|
|
return texture.match([](const auto& obj) { return obj.size; });
|
|
}
|
|
|
|
LineAtlas::LineAtlas() = default;
|
|
|
|
LineAtlas::~LineAtlas() = default;
|
|
|
|
DashPatternTexture& LineAtlas::getDashPatternTexture(const std::vector<float>& from,
|
|
const std::vector<float>& to,
|
|
const LinePatternCap cap) {
|
|
const size_t hash = util::hash(getDashPatternHash(from, cap), getDashPatternHash(to, cap));
|
|
|
|
// Note: We're not handling hash collisions here.
|
|
const auto it = textures.find(hash);
|
|
if (it == textures.end()) {
|
|
auto inserted = textures.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(from, to, cap));
|
|
assert(inserted.second);
|
|
needsUpload.emplace_back(hash);
|
|
return inserted.first->second;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
void LineAtlas::upload(gfx::UploadPass& uploadPass) {
|
|
for (const size_t hash : needsUpload) {
|
|
const auto it = textures.find(hash);
|
|
if (it != textures.end()) {
|
|
it->second.upload(uploadPass);
|
|
}
|
|
}
|
|
needsUpload.clear();
|
|
}
|
|
|
|
} // namespace mbgl
|