Files
MapLibreParticleLab/platform/glfw/example_custom_drawable_style_layer.cpp
Bart Louwers 2a8ebc4906 core: rename mbgl directories to mln (#4511)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-22 20:50:14 +02:00

611 lines
23 KiB
C++

#include "example_custom_drawable_style_layer.hpp"
#include <mln/style/layer.hpp>
#include <mln/style/layers/custom_drawable_layer.hpp>
#include <mln/util/io.hpp>
#include <mln/gfx/drawable.hpp>
#include <mln/util/logging.hpp>
#include <memory>
#include <cmath>
#include <filesystem>
#define TINYOBJLOADER_IMPLEMENTATION
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "tiny_obj_loader.h"
#pragma GCC diagnostic pop
ExampleCustomDrawableStyleLayerHost::ExampleCustomDrawableStyleLayerHost(const std::string& assetsPath_)
: assetsPath(assetsPath_) {}
ExampleCustomDrawableStyleLayerHost::~ExampleCustomDrawableStyleLayerHost() {}
void ExampleCustomDrawableStyleLayerHost::initialize() {}
void ExampleCustomDrawableStyleLayerHost::deinitialize() {}
void ExampleCustomDrawableStyleLayerHost::update(Interface& interface) {
// if we have built our drawable(s) already, either update or skip
if (interface.getDrawableCount() == 0) {
createDrawables(interface);
return;
}
}
mln::Point<double> ExampleCustomDrawableStyleLayerHost::project(const mln::LatLng& c, const mln::TransformState& s) {
mln::LatLng unwrappedLatLng = c.wrapped();
unwrappedLatLng.unwrapForShortestPath(s.getLatLng(mln::LatLng::Wrapped));
return mln::Projection::project(unwrappedLatLng, s.getScale());
}
void ExampleCustomDrawableStyleLayerHost::createDrawables(Interface& interface) {
constexpr float extent = mln::util::EXTENT;
// add classic polylines
{
using namespace mln;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/8.0f,
/*color=*/Color::red()},
{/*geometry=*/{},
/*blur=*/4.0f,
/*opacity=*/1.0f,
/*gapWidth=*/2.0f,
/*offset=*/-1.0f,
/*width=*/4.0f,
/*color=*/Color::blue()},
{/*geometry=*/{},
/*blur=*/16.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/2.0f,
/*width=*/16.0f,
/*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{},
/*blur=*/2.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/-2.0f,
/*width=*/2.0f,
/*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{},
/*blur=*/0.5f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/0.5f,
/*width=*/0.5f,
/*color=*/Color::black()},
{/*geometry=*/{},
/*blur=*/24.0f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/-5.0f,
/*width=*/24.0f,
/*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto& opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(
static_cast<int16_t>(ipoint * extent / numPoints),
static_cast<int16_t>(std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f));
}
for (auto index{0}; index < numLines; ++index) {
for (auto& p : polyline) {
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
}
}
// add wide vector polylines with tile coordinates
{
using namespace mln;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/8.0f,
/*color=*/Color::red()},
{/*geometry=*/{},
/*blur=*/4.0f,
/*opacity=*/1.0f,
/*gapWidth=*/2.0f,
/*offset=*/-1.0f,
/*width=*/4.0f,
/*color=*/Color::blue()},
{/*geometry=*/{},
/*blur=*/16.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/2.0f,
/*width=*/16.0f,
/*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{},
/*blur=*/2.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/-2.0f,
/*width=*/2.0f,
/*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{},
/*blur=*/0.5f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/0.5f,
/*width=*/0.5f,
/*color=*/Color::black()},
{/*geometry=*/{},
/*blur=*/24.0f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/-5.0f,
/*width=*/24.0f,
/*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto& opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(
static_cast<int16_t>(ipoint * extent / numPoints),
static_cast<int16_t>(std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f));
}
for (auto index{0}; index < numLines; ++index) {
for (auto& p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone
for (auto& p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for (auto& p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
}
// add fill polygon
{
using namespace mln;
// set tile
interface.setTileID({11, 327, 790});
GeometryCollection geometry{
{
// ring 1
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
{static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
},
{
// ring 2
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
{static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
},
};
// set properties
interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
// add fill
interface.addFill(geometry);
}
// add symbol
{
using namespace mln;
// set tile
interface.setTileID({11, 327, 789});
GeometryCoordinate position{static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)};
// load image
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(
mln::decodeImage(mln::util::read_file(assetsPath + "puck_hat.png")));
// set symbol options
Interface::SymbolOptions options;
options.texture = interface.context.createTexture2D();
options.texture->setImage(image);
options.texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
.wrapU = gfx::TextureWrapType::Repeat,
.wrapV = gfx::TextureWrapType::Repeat});
const std::array<std::array<float, 2>, 2> textureCoordinates = {{{0.0f, 0.0f}, {10.0f, 10.0f}}};
options.size = {static_cast<uint32_t>(image->size.width), static_cast<uint32_t>(image->size.height)};
options.anchor = {0.5f, 0.95f};
options.angleDegrees = 45.0f;
options.scaleWithMap = true;
options.pitchWithMap = false;
interface.setSymbolOptions(options);
// add symbol
interface.addSymbol(position, textureCoordinates);
}
{
using namespace mln;
GeometryCoordinate position{static_cast<int16_t>(extent * 1.0f), static_cast<int16_t>(extent * 0.5f)};
// load image
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(
mln::decodeImage(mln::util::read_file(assetsPath + "puck_hat.png")));
// set symbol options
Interface::SymbolOptions options;
options.texture = interface.context.createTexture2D();
options.texture->setImage(image);
options.texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
.wrapU = gfx::TextureWrapType::Clamp,
.wrapV = gfx::TextureWrapType::Clamp});
options.size = {static_cast<uint32_t>(image->size.width), static_cast<uint32_t>(image->size.height)};
options.anchor = {0.5f, 0.95f};
options.angleDegrees = 45.0f;
options.scaleWithMap = true;
options.pitchWithMap = false;
interface.setSymbolOptions(options);
// add symbol
interface.addSymbol(position);
}
// add polylines using wide vectors using geographic coordinates
{
using namespace mln;
// add polyline with geographic coordinates
Interface::LineOptions options = {/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/12.0f,
/*color=*/{.0f, .0f, .0f, .5f}};
options.geometry.beginCap = style::LineCapType::Square;
options.geometry.endCap = style::LineCapType::Square;
options.geometry.joinType = style::LineJoinType::Bevel;
options.geometry.type = FeatureType::LineString;
interface.setLineOptions(options);
LineString<double> polyline_geo{
// San Francisco
{-122.38186800073211, 37.77466003457463},
{-122.3869373450997, 37.774352128895615},
{-122.38680767979824, 37.773294612880306},
{-122.38476465260224, 37.773350946288765},
{-122.38146223043374, 37.77194168964067},
{-122.6813560305925, 37.666084247570964},
{-122.26765538866474, 37.65037232584494},
{-122.42528413673159, 38.020443518012584},
// Seattle
{-122.34927775216809, 47.62050663596438},
// New York
{-74.04454331829972, 40.6892168305434},
};
interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector);
}
// add polylines using wide vectors in tile coordinates
{
using namespace mln;
// set tile
interface.setTileID({11, 327, 790});
Interface::LineOptions options{/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/7.0f,
/*color=*/{1.0f, 0, 0, .5f}};
options.geometry.beginCap = style::LineCapType::Round;
options.geometry.endCap = style::LineCapType::Round;
options.geometry.joinType = style::LineJoinType::Round;
// add polyline with tile coordinates
GeometryCollection polyline_tile{
{
// ring 1
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
{static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
},
{
// ring 2
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
{static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
},
};
options.geometry.type = FeatureType::Polygon;
interface.setLineOptions(options);
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
}
generateGeometry(interface);
loadGeometry(interface);
// finish
interface.finish();
}
void ExampleCustomDrawableStyleLayerHost::generateGeometry(Interface& interface) {
constexpr float itemScale = 10.0f;
const mln::LatLng location{37.78, -122.47};
Interface::GeometryOptions options;
// load image
std::shared_ptr<mln::PremultipliedImage> image = std::make_shared<mln::PremultipliedImage>(
mln::decodeImage(mln::util::read_file(assetsPath + "puck.png")));
options.texture = interface.context.createTexture2D();
options.texture->setImage(image);
options.texture->setSamplerConfiguration({.filter = mln::gfx::TextureFilterType::Linear,
.wrapU = mln::gfx::TextureWrapType::Clamp,
.wrapV = mln::gfx::TextureWrapType::Clamp});
const std::shared_ptr<VertexVector> sharedVertices = std::make_shared<VertexVector>();
VertexVector& vertices = *sharedVertices;
const std::shared_ptr<TriangleIndexVector> sharedIndices = std::make_shared<TriangleIndexVector>();
TriangleIndexVector& indices = *sharedIndices;
const unsigned long numVtxCircumference = 72;
const float bearingStep = 360.0f / static_cast<float>(numVtxCircumference - 1);
vertices.emplace_back(Interface::GeometryVertex{.position = {0.0f, 0.0f, 0.0f}, .texcoords = {0.5f, 0.5f}});
for (unsigned long i = 1; i <= numVtxCircumference; ++i) {
const float rad = mln::util::deg2radf((i - 1) * bearingStep);
Interface::GeometryVertex vertex;
vertex.position = {sinf(rad) / 2.0f, cosf(rad) / 2.0f, 0.0f};
vertex.texcoords = {0.5f + vertex.position[0], 0.5f - vertex.position[1]};
vertices.emplace_back(std::move(vertex));
}
for (uint16_t i = 1; i < vertices.elements() - 1; ++i) {
indices.emplace_back(0, i, static_cast<uint16_t>(i + 1));
}
indices.emplace_back(0, static_cast<uint16_t>(vertices.elements() - 1), 1);
interface.setGeometryOptions(options);
interface.setGeometryTweakerCallback([=, frameCount = 0]([[maybe_unused]] mln::gfx::Drawable& drawable,
const mln::PaintParameters& params,
Interface::GeometryOptions& currentOptions) mutable {
frameCount++;
const mln::Point<double>& center = project(location, params.state);
const float scale = itemScale * static_cast<float>(std::pow(2.f, params.state.getZoom())) *
params.pixelsToGLUnits[0];
mln::mat4 matrix = mln::matrix::identity4();
mln::matrix::translate(matrix, matrix, center.x, center.y, 0.0);
mln::matrix::rotate_z(matrix, matrix, frameCount * 0.05f);
mln::matrix::scale(matrix, matrix, scale, scale, 1.0f);
mln::matrix::multiply(currentOptions.matrix, params.transformParams.nearClippedProjMatrix, matrix);
if (frameCount % 100 == 0) {
if (currentOptions.color.g > 0.0f) {
currentOptions.color = mln::Color::red();
} else {
currentOptions.color = mln::Color::white();
}
}
});
interface.addGeometry(sharedVertices, sharedIndices, false);
}
void ExampleCustomDrawableStyleLayerHost::loadGeometry(Interface& interface) {
constexpr float itemScale = 0.1f;
constexpr std::array<float, 3> itemRotation = {90.0f, 0.0f, 0.0f};
const mln::LatLng location{37.76, -122.47};
Interface::GeometryOptions options;
interface.setGeometryTweakerCallback([=]([[maybe_unused]] mln::gfx::Drawable& drawable,
const mln::PaintParameters& params,
Interface::GeometryOptions& currentOptions) mutable {
const mln::Point<double>& center = project(location, params.state);
const float scale = itemScale * static_cast<float>(std::pow(2.f, params.state.getZoom())) *
params.pixelsToGLUnits[0];
mln::mat4 matrix = mln::matrix::identity4();
mln::matrix::translate(matrix, matrix, center.x, center.y, 0.0);
mln::matrix::rotate_x(matrix, matrix, mln::util::deg2radf(itemRotation[0]));
mln::matrix::rotate_y(matrix, matrix, mln::util::deg2radf(itemRotation[1]));
mln::matrix::rotate_z(matrix, matrix, mln::util::deg2radf(itemRotation[2]));
mln::matrix::scale(matrix, matrix, scale, scale, scale);
mln::matrix::multiply(currentOptions.matrix, params.transformParams.nearClippedProjMatrix, matrix);
});
const std::shared_ptr<VertexVector> sharedVertices = std::make_shared<VertexVector>();
const std::shared_ptr<TriangleIndexVector> sharedIndices = std::make_shared<TriangleIndexVector>();
// importObj(interface, "../../_deps/tinyobjloader-src/models/cube.obj", *sharedVertices, *sharedIndices, options);
importObj(interface, assetsPath + "sphere.obj", *sharedVertices, *sharedIndices, options);
interface.setGeometryOptions(options);
interface.addGeometry(sharedVertices, sharedIndices, true);
}
void ExampleCustomDrawableStyleLayerHost::importObj(Interface& interface,
const std::string& filename,
VertexVector& vertices,
TriangleIndexVector& indices,
Interface::GeometryOptions& options) {
tinyobj::ObjReaderConfig readerConfig;
readerConfig.triangulate = true;
readerConfig.vertex_color = false;
tinyobj::ObjReader reader;
if (!reader.ParseFromFile(filename, readerConfig)) {
if (!reader.Error().empty()) {
mln::Log::Error(mln::Event::General, reader.Error());
}
assert(false);
throw std::runtime_error(std::string("Cannot read obj file ") + filename);
}
if (!reader.Warning().empty()) {
mln::Log::Error(mln::Event::General, reader.Warning());
}
const tinyobj::attrib_t& attributes = reader.GetAttrib();
const std::vector<tinyobj::shape_t>& shapes = reader.GetShapes();
if (attributes.texcoords.empty()) {
options.color = mln::Color::green();
} else {
// TODO parse material
options.texture = createCheckerboardTexture(interface, 32, 32, 40, {255, 255, 255, 255}, {0, 0, 0, 255});
}
for (const auto& shape : shapes) {
const auto shapeIndexStart = static_cast<uint16_t>(indices.elements());
for (uint16_t face = 0; face < shape.mesh.indices.size(); face += 3) {
for (size_t i = 0; i < 3; ++i) {
const tinyobj::index_t index = shape.mesh.indices[face + i];
Interface::GeometryVertex vertex = {};
std::copy(attributes.vertices.begin() + vertex.position.size() * index.vertex_index,
attributes.vertices.begin() + vertex.position.size() * (index.vertex_index + 1),
vertex.position.begin());
if (!attributes.texcoords.empty()) {
std::copy(attributes.texcoords.begin() + vertex.texcoords.size() * index.texcoord_index,
attributes.texcoords.begin() + vertex.texcoords.size() * (index.texcoord_index + 1),
vertex.texcoords.begin());
}
vertices.emplace_back(std::move(vertex));
}
indices.emplace_back(shapeIndexStart + face, shapeIndexStart + face + 1, shapeIndexStart + face + 2);
}
}
}
mln::gfx::Texture2DPtr ExampleCustomDrawableStyleLayerHost::createCheckerboardTexture(
Interface& interface,
uint16_t wb,
uint16_t hb,
uint16_t blockSize,
const std::array<uint8_t, 4>& color1,
const std::array<uint8_t, 4>& color2) {
std::shared_ptr<mln::PremultipliedImage> image = std::make_shared<mln::PremultipliedImage>(
mln::Size(wb * blockSize, hb * blockSize));
constexpr uint8_t pixelSize = sizeof(uint8_t) * 4;
for (uint16_t row = 0; row < hb; ++row) {
for (uint16_t blockRow = 0; blockRow < blockSize; ++blockRow) {
for (uint16_t col = 0; col < wb; ++col) {
for (uint16_t blockCol = 0; blockCol < blockSize; ++blockCol) {
auto index = (row * blockSize + blockRow) * image->stride() +
(col * blockSize + blockCol) * pixelSize;
memcpy(image->data.get() + index, (row + col) % 2 == 0 ? color1.data() : color2.data(), pixelSize);
}
}
}
}
auto texture = interface.context.createTexture2D();
texture->setSamplerConfiguration({.filter = mln::gfx::TextureFilterType::Linear,
.wrapU = mln::gfx::TextureWrapType::Clamp,
.wrapV = mln::gfx::TextureWrapType::Clamp});
texture->setImage(std::move(image));
return texture;
}