Files
MapLibreParticleLab/src/mbgl/geometry/line_atlas.cpp
Bart Louwers 71f6da2722 Add clang-format (#1086)
Co-authored-by: mwilsnd <mwilsnd@gmail.com>
2023-05-01 16:57:37 +02:00

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