Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
312 lines
14 KiB
C++
312 lines
14 KiB
C++
#include <mln/gfx/drawable_builder_impl.hpp>
|
|
|
|
#include <mln/gfx/drawable_impl.hpp>
|
|
#include <mln/gfx/polyline_generator.hpp>
|
|
#include <mln/style/types.hpp>
|
|
#include <mln/util/logging.hpp>
|
|
#include <mln/util/math.hpp>
|
|
#include <mln/util/projection.hpp>
|
|
|
|
#include <string>
|
|
|
|
namespace mln {
|
|
namespace gfx {
|
|
|
|
using namespace shaders;
|
|
|
|
// MARK: - Polylines
|
|
|
|
DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex(
|
|
Point<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/) {
|
|
/*
|
|
* Scale the extrusion vector so that the normal length is this value.
|
|
* Contains the "texture" normals (-1..1). This is distinct from the extrude
|
|
* normals for line joins, because the x-value remains 0 for the texture
|
|
* normal array, while the extrude normal actually moves the vertex to
|
|
* create the acute/bevelled line join.
|
|
*/
|
|
static const int8_t extrudeScale = 63;
|
|
return LineLayoutVertex{
|
|
.a1 = {{static_cast<int16_t>((p.x * 2) | (round ? 1 : 0)), static_cast<int16_t>((p.y * 2) | (up ? 1 : 0))}},
|
|
.a2 = {{// add 128 to store a byte in an unsigned byte
|
|
static_cast<uint8_t>(::round(extrudeScale * e.x) + 128),
|
|
static_cast<uint8_t>(::round(extrudeScale * e.y) + 128),
|
|
|
|
// Encode the -1/0/1 direction value into the first two bits of .z
|
|
// of a_data. Combine it with the lower 6 bits of `linesofar`
|
|
// (shifted by 2 bites to make room for the direction value). The
|
|
// upper 8 bits of `linesofar` are placed in the `w` component.
|
|
// `linesofar` is scaled down by `LINE_DISTANCE_SCALE` so that we
|
|
// can store longer distances while sacrificing precision.
|
|
|
|
// Encode the -1/0/1 direction value into .zw coordinates of
|
|
// a_data, which is normally covered by linesofar, so we need to
|
|
// merge them. The z component's first bit, as well as the sign
|
|
// bit is reserved for the direction, so we need to shift the
|
|
// linesofar.
|
|
static_cast<uint8_t>(((dir == 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofar & 0x3F) << 2)),
|
|
static_cast<uint8_t>(linesofar >> 6)}}};
|
|
}
|
|
|
|
void DrawableBuilder::Impl::addPolyline(gfx::DrawableBuilder& builder,
|
|
const GeometryCoordinates& coordinates,
|
|
const gfx::PolylineGeneratorOptions& options) {
|
|
using namespace std::placeholders;
|
|
gfx::PolylineGenerator<DrawableBuilder::Impl::LineLayoutVertex, std::unique_ptr<Drawable::DrawSegment>> generator(
|
|
polylineVertices,
|
|
std::bind(&DrawableBuilder::Impl::layoutVertex, this, _1, _2, _3, _4, _5, _6),
|
|
segments,
|
|
[&builder](std::size_t vertexOffset, std::size_t indexOffset) -> std::unique_ptr<Drawable::DrawSegment> {
|
|
return builder.createSegment(gfx::Triangles(), SegmentBase(vertexOffset, indexOffset));
|
|
},
|
|
[](auto& uniqueSegment) -> SegmentBase& { return uniqueSegment->getSegment(); },
|
|
polylineIndexes);
|
|
generator.generate(coordinates, options);
|
|
}
|
|
|
|
void DrawableBuilder::Impl::setupForPolylines(gfx::Context& context, gfx::DrawableBuilder& builder) {
|
|
// setup vertex attributes
|
|
builder.setVertexAttrId(idLinePosNormalVertexAttribute);
|
|
builder.setRawVertices({}, polylineVertices.elements(), gfx::AttributeDataType::Short2);
|
|
|
|
auto attrs = context.createVertexAttributeArray();
|
|
using VertexVector = gfx::VertexVector<LineLayoutVertex>;
|
|
std::shared_ptr<VertexVector> verts = std::make_shared<VertexVector>(std::move(polylineVertices));
|
|
|
|
if (const auto& attr = attrs->set(idLinePosNormalVertexAttribute)) {
|
|
attr->setSharedRawData(verts,
|
|
offsetof(LineLayoutVertex, a1),
|
|
/*vertexOffset=*/0,
|
|
sizeof(LineLayoutVertex),
|
|
gfx::AttributeDataType::Short2);
|
|
}
|
|
|
|
if (const auto& attr = attrs->set(idLineDataVertexAttribute)) {
|
|
attr->setSharedRawData(verts,
|
|
offsetof(LineLayoutVertex, a2),
|
|
/*vertexOffset=*/0,
|
|
sizeof(LineLayoutVertex),
|
|
gfx::AttributeDataType::UByte4);
|
|
}
|
|
|
|
builder.setVertexAttributes(std::move(attrs));
|
|
|
|
sharedIndexes = std::make_shared<gfx::IndexVectorBase>(std::move(polylineIndexes));
|
|
}
|
|
|
|
// MARK: - Wide Vector Polylines
|
|
|
|
namespace {
|
|
inline void genInstanceLinks(
|
|
int32_t& outPrev, int32_t& outNext, const int base, const int coord_size, const int index, bool loop) {
|
|
if (loop) {
|
|
// loop line
|
|
outPrev = base + (index - 1 + coord_size) % coord_size;
|
|
outNext = base + (index + 1) % coord_size;
|
|
} else {
|
|
// line string
|
|
outPrev = (0 == index) ? -1 : base + index - 1;
|
|
outNext = (index + 1 >= coord_size) ? -1 : base + index + 1;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void DrawableBuilder::Impl::addWideVectorPolylineLocal(gfx::DrawableBuilder& /*builder*/,
|
|
const GeometryCoordinates& coordinates,
|
|
const gfx::PolylineGeneratorOptions& options) {
|
|
// add instance data
|
|
const int base = static_cast<int>(wideVectorInstanceData.elements());
|
|
const int coord_size = static_cast<int>(coordinates.size());
|
|
int index = 0;
|
|
for (const auto& coord : coordinates) {
|
|
VertexTriWideVecInstance data;
|
|
data.center = {static_cast<float>(coord.x), static_cast<float>(coord.y), 0};
|
|
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
|
|
wideVectorInstanceData.emplace_back(std::move(data));
|
|
++index;
|
|
}
|
|
}
|
|
|
|
mln::Point<double> DrawableBuilder::Impl::addWideVectorPolylineGlobal(gfx::DrawableBuilder& /*builder*/,
|
|
const LineString<double>& coordinates,
|
|
const gfx::PolylineGeneratorOptions& options) {
|
|
constexpr int32_t zoom = 0;
|
|
|
|
// get center
|
|
constexpr double maxd = std::numeric_limits<double>::max();
|
|
constexpr double mind = std::numeric_limits<double>::min();
|
|
Point<double> minPoint{maxd, maxd}, maxPoint{mind, mind};
|
|
for (const auto& coord : coordinates) {
|
|
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
|
|
Point<double> pSource{merc.x * mln::util::EXTENT, merc.y * mln::util::EXTENT};
|
|
minPoint.x = std::min(pSource.x, minPoint.x);
|
|
minPoint.y = std::min(pSource.y, minPoint.y);
|
|
maxPoint.x = std::max(pSource.x, maxPoint.x);
|
|
maxPoint.y = std::max(pSource.y, maxPoint.y);
|
|
}
|
|
Point<double> pCenter{(minPoint.x + maxPoint.x) / 2.0, (minPoint.y + maxPoint.y) / 2.0};
|
|
|
|
// add centerline instance data
|
|
const int base = static_cast<int>(wideVectorInstanceData.elements());
|
|
const int coord_size = static_cast<int>(coordinates.size());
|
|
int index = 0;
|
|
for (const auto& coord : coordinates) {
|
|
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
|
|
Point<double> pSource{merc.x * mln::util::EXTENT, merc.y * mln::util::EXTENT};
|
|
pSource.x -= pCenter.x;
|
|
pSource.y -= pCenter.y;
|
|
VertexTriWideVecInstance data;
|
|
data.center = {static_cast<float>(pSource.x), static_cast<float>(pSource.y), 0};
|
|
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
|
|
wideVectorInstanceData.emplace_back(std::move(data));
|
|
++index;
|
|
}
|
|
|
|
return pCenter;
|
|
}
|
|
|
|
void DrawableBuilder::Impl::setupForWideVectors(gfx::Context& context, gfx::DrawableBuilder& builder) {
|
|
// vertices
|
|
if (!wideVectorVertices) {
|
|
constexpr shaders::VertexTriWideVecB kWideVectorVertices[]{
|
|
{.screenPos = {0}, .color = {0}, .index = (0 << 16) + 0},
|
|
{.screenPos = {0}, .color = {0}, .index = (1 << 16) + 0},
|
|
{.screenPos = {0}, .color = {0}, .index = (2 << 16) + 0},
|
|
{.screenPos = {0}, .color = {0}, .index = (3 << 16) + 0},
|
|
|
|
{.screenPos = {0}, .color = {0}, .index = (4 << 16) + 1},
|
|
{.screenPos = {0}, .color = {0}, .index = (5 << 16) + 1},
|
|
{.screenPos = {0}, .color = {0}, .index = (6 << 16) + 1},
|
|
{.screenPos = {0}, .color = {0}, .index = (7 << 16) + 1},
|
|
|
|
{.screenPos = {0}, .color = {0}, .index = (8 << 16) + 2},
|
|
{.screenPos = {0}, .color = {0}, .index = (9 << 16) + 2},
|
|
{.screenPos = {0}, .color = {0}, .index = (10 << 16) + 2},
|
|
{.screenPos = {0}, .color = {0}, .index = (11 << 16) + 2},
|
|
};
|
|
wideVectorVertices = std::make_shared<gfx::VertexVector<shaders::VertexTriWideVecB>>();
|
|
for (auto& v : kWideVectorVertices) {
|
|
wideVectorVertices->emplace_back(v);
|
|
}
|
|
}
|
|
|
|
// instance data
|
|
using InstanceVector = gfx::VertexVector<VertexTriWideVecInstance>;
|
|
const std::shared_ptr<InstanceVector> sharedInstanceData = std::make_shared<InstanceVector>(
|
|
std::move(wideVectorInstanceData));
|
|
|
|
// indexes
|
|
if (!wideVectorIndexes) {
|
|
wideVectorIndexes = std::make_shared<gfx::IndexVector<gfx::Triangles>>();
|
|
wideVectorIndexes->emplace_back(
|
|
0, 3, 1, 0, 2, 3, 4 + 0, 4 + 3, 4 + 1, 4 + 0, 4 + 2, 4 + 3, 8 + 0, 8 + 3, 8 + 1, 8 + 0, 8 + 2, 8 + 3);
|
|
}
|
|
|
|
// segments
|
|
SegmentVector triangleSegments;
|
|
triangleSegments.emplace_back(0, 0, 12, 18);
|
|
|
|
// add to builder
|
|
{
|
|
// add vertex attributes
|
|
auto vertexAttributes = context.createVertexAttributeArray();
|
|
if (const auto& attr = vertexAttributes->set(idWideVectorScreenPos)) {
|
|
attr->setSharedRawData(wideVectorVertices,
|
|
offsetof(VertexTriWideVecB, screenPos),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecB),
|
|
gfx::AttributeDataType::Float3);
|
|
}
|
|
if (const auto& attr = vertexAttributes->set(idWideVectorColor)) {
|
|
attr->setSharedRawData(wideVectorVertices,
|
|
offsetof(VertexTriWideVecB, color),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecB),
|
|
gfx::AttributeDataType::Float4);
|
|
}
|
|
if (const auto& attr = vertexAttributes->set(idWideVectorIndex)) {
|
|
attr->setSharedRawData(wideVectorVertices,
|
|
offsetof(VertexTriWideVecB, index),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecB),
|
|
gfx::AttributeDataType::Int);
|
|
}
|
|
builder.setVertexAttributes(std::move(vertexAttributes));
|
|
}
|
|
|
|
{
|
|
// add instance attributes
|
|
auto instanceAttributes = context.createVertexAttributeArray();
|
|
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceCenter)) {
|
|
attr->setSharedRawData(sharedInstanceData,
|
|
offsetof(VertexTriWideVecInstance, center),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecInstance),
|
|
gfx::AttributeDataType::Float3);
|
|
}
|
|
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceColor)) {
|
|
attr->setSharedRawData(sharedInstanceData,
|
|
offsetof(VertexTriWideVecInstance, color),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecInstance),
|
|
gfx::AttributeDataType::Float4);
|
|
}
|
|
if (const auto& attr = instanceAttributes->set(idWideVectorInstancePrevious)) {
|
|
attr->setSharedRawData(sharedInstanceData,
|
|
offsetof(VertexTriWideVecInstance, prev),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecInstance),
|
|
gfx::AttributeDataType::Int);
|
|
}
|
|
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceNext)) {
|
|
attr->setSharedRawData(sharedInstanceData,
|
|
offsetof(VertexTriWideVecInstance, next),
|
|
/*vertexOffset=*/0,
|
|
sizeof(VertexTriWideVecInstance),
|
|
gfx::AttributeDataType::Int);
|
|
}
|
|
builder.setInstanceAttributes(std::move(instanceAttributes));
|
|
}
|
|
|
|
builder.setRawVertices({}, wideVectorVertices->elements(), gfx::AttributeDataType::Float2);
|
|
builder.setSegments(gfx::Triangles(), wideVectorIndexes, triangleSegments.data(), triangleSegments.size());
|
|
}
|
|
|
|
// MARK: - Common
|
|
|
|
bool DrawableBuilder::Impl::checkAndSetMode(Mode target) {
|
|
if (target != mode && vertexCount()) {
|
|
// log error
|
|
using namespace std::string_literals;
|
|
auto to_string = [](Mode value) -> std::string {
|
|
switch (value) {
|
|
case Mode::Primitives:
|
|
return "Mode::Primitives";
|
|
case Mode::Polylines:
|
|
return "Mode::Polylines";
|
|
case Mode::WideVectorLocal:
|
|
return "Mode::WideVectorLocal";
|
|
case Mode::WideVectorGlobal:
|
|
return "Mode::WideVectorGlobal";
|
|
case Mode::Custom:
|
|
return "Mode::Custom";
|
|
default:
|
|
return "Unknown"; // Handle unknown enum values if needed
|
|
}
|
|
};
|
|
mln::Log::Error(
|
|
mln::Event::General,
|
|
"DrawableBuilder mode mismatch. Target is "s + to_string(target) + ", current is " + to_string(mode));
|
|
|
|
// the builder is building in a different mode
|
|
assert(false);
|
|
// TODO: throw?
|
|
return false;
|
|
}
|
|
mode = target;
|
|
return true;
|
|
}
|
|
|
|
} // namespace gfx
|
|
} // namespace mln
|