Polyline rendering

This commit is contained in:
Ashley Rogers 2025-04-22 10:58:29 -04:00
parent 228d7678bf
commit 7ba4865ffc
3 changed files with 97 additions and 70 deletions

View File

@ -12,6 +12,7 @@
#include <blend2d/image.h>
#include <cstddef>
#include <span>
namespace CesiumVectorData {
@ -34,6 +35,11 @@ public:
const CesiumGeospatial::CartographicPolygon& polygon,
const Color& drawColor);
void drawPolyline(
const std::span<CesiumGeospatial::Cartographic>& points,
const Color& drawColor
);
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> finalize();
private:

View File

@ -25,7 +25,7 @@ uint32_t Color::toRgba32() const {
}
VectorRasterizer::VectorRasterizer(
const CesiumGeospatial::GlobeRectangle& bounds,
const GlobeRectangle& bounds,
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset>& imageAsset)
: _bounds(bounds), _image(), _context(), _imageAsset(imageAsset) {
CESIUM_ASSERT(imageAsset->channels == 1 || imageAsset->channels == 4);
@ -36,39 +36,57 @@ VectorRasterizer::VectorRasterizer(
this->_imageAsset->channels == 1 ? BL_FORMAT_A8 : BL_FORMAT_PRGB32,
reinterpret_cast<void*>(this->_imageAsset->pixelData.data()),
(int64_t)this->_imageAsset->width * (int64_t)this->_imageAsset->channels);
_context.begin(this->_image);
// Set up transformation matrix to transform from LLH to pixel coordinates.
_context.translate(-bounds.getWest(), -bounds.getSouth());
_context.scale(
(double)this->_imageAsset->width / bounds.computeWidth(),
(double)this->_imageAsset->height / bounds.computeHeight());
// We don't want the stroke to be scaled, so set it to perform the scale
// before we stroke.
_context.setStrokeTransformOrder(BL_STROKE_TRANSFORM_ORDER_BEFORE);
// Initialize the image as all transparent.
_context.clearAll();
}
void VectorRasterizer::drawPolygon(
const CesiumGeospatial::CartographicPolygon& polygon,
const CartographicPolygon& polygon,
const Color& color) {
BLRgba32 style(color.toRgba32());
const double boundsWidth = this->_bounds.computeWidth();
const double boundsHeight = this->_bounds.computeHeight();
std::vector<BLPoint> points;
const std::vector<glm::dvec2>& vertices = polygon.getVertices();
for (const uint32_t& idx : polygon.getIndices()) {
const glm::dvec2& vertex = vertices[idx];
points.emplace_back(
(vertex.x - this->_bounds.getWest()) / boundsWidth *
this->_image.width(),
(vertex.y - this->_bounds.getSouth()) / boundsHeight *
this->_image.height());
// Since glm::dvec2 and BLPoint are both structs containing two doubles in the
// same order, we can treat one as the other to avoid copying.
CESIUM_ASSERT(sizeof(BLPoint) == sizeof(glm::dvec2));
this->_context.fillPolygon(
reinterpret_cast<const BLPoint*>(vertices.data()),
vertices.size(),
style);
}
void VectorRasterizer::drawPolyline(
const std::span<Cartographic>& points,
const Color& color) {
BLRgba32 style(color.toRgba32());
// Unfortunately Cartographic has an extra component that BLPoint does not, so
// we can't use it directly.
std::vector<BLPoint> vertices;
vertices.reserve(points.size());
for (Cartographic& vertex : points) {
vertices.emplace_back(vertex.longitude, vertex.latitude);
}
this->_context.fillPolygon(points.data(), points.size(), style);
this->_context.strokePolyline(vertices.data(), vertices.size(), style);
}
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset>
VectorRasterizer::finalize() {
this->_context.end();
this->_image.writeToFile("triangle.png");
if (this->_imageAsset->channels == 4) {
// Blend2D writes in BGRA whereas ImageAsset is RGBA.
// We need to swap the channels to fix the values.

View File

@ -7,7 +7,10 @@
#include <doctest/doctest.h>
#include <glm/fwd.hpp>
#include <chrono>
#include <cstddef>
#include <iostream>
#include <random>
using namespace CesiumGeospatial;
using namespace CesiumVectorData;
@ -15,8 +18,7 @@ using namespace CesiumVectorData;
TEST_CASE("VectorRasterizer::rasterize") {
GlobeRectangle rect{0.0, 0.0, 1.0, 1.0};
SUBCASE("Generates a single triangle") {
SUBCASE("Renders a single triangle") {
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> asset;
asset.emplace();
asset->width = 256;
@ -24,7 +26,8 @@ TEST_CASE("VectorRasterizer::rasterize") {
asset->channels = 4;
asset->bytesPerChannel = 1;
asset->pixelData.resize(
(size_t)(asset->width * asset->height * asset->channels * asset->bytesPerChannel),
(size_t)(asset->width * asset->height * asset->channels *
asset->bytesPerChannel),
std::byte{255});
VectorRasterizer rasterizer(rect, asset);
@ -41,45 +44,39 @@ TEST_CASE("VectorRasterizer::rasterize") {
asset->writeTga("triangle.tga");
}
/*SUBCASE("Alpha blends properly") {
VectorRasterizer rasterizer(
std::vector<CartographicPolygon>{
CartographicPolygon(std::vector<glm::dvec2>{
glm::dvec2(0.25, 0.25),
glm::dvec2(0.5, 0.75),
glm::dvec2(0.75, 0.25)}),
CartographicPolygon(std::vector<glm::dvec2>{
glm::dvec2(0.25, 0.25),
glm::dvec2(0.25, 0.75),
glm::dvec2(0.9, 0.1)})},
std::vector<std::array<std::byte, 4>>{
std::array<std::byte, 4>{
std::byte{0},
std::byte{255},
std::byte{255},
std::byte{127}},
std::array<std::byte, 4>{
std::byte{0},
std::byte{255},
std::byte{0},
std::byte{60}}});
CesiumGltf::ImageAsset asset;
asset.width = 256;
asset.height = 256;
asset.channels = 4;
asset.bytesPerChannel = 1;
asset.pixelData.resize(
(size_t)(asset.width * asset.height * asset.channels *
asset.bytesPerChannel),
SUBCASE("Renders a polyline") {
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> asset;
asset.emplace();
asset->width = 256;
asset->height = 256;
asset->channels = 4;
asset->bytesPerChannel = 1;
asset->pixelData.resize(
(size_t)(asset->width * asset->height * asset->channels *
asset->bytesPerChannel),
std::byte{255});
rasterizer.rasterize(rect, asset);
asset.writeTga("blending.tga");
}*/
VectorRasterizer rasterizer(rect, asset);
std::vector<Cartographic> polyline {
Cartographic(0.25, 0.25),
Cartographic(0.25, 0.5),
Cartographic(0.3, 0.7),
Cartographic(0.25, 0.8),
Cartographic(0.8, 1.0),
Cartographic(0.8, 0.9),
Cartographic(0.9, 0.9)
};
rasterizer.drawPolyline(
polyline,
Color{std::byte{0}, std::byte{255}, std::byte{255}, std::byte{255}});
rasterizer.finalize();
asset->writeTga("polyline.tga");
}
}
/*TEST_CASE("VectorRasterizer::rasterize benchmark") {
TEST_CASE("VectorRasterizer::rasterize benchmark") {
GlobeRectangle rect{0.0, 0.0, 1.0, 1.0};
std::chrono::steady_clock clock;
std::random_device r;
@ -87,9 +84,20 @@ TEST_CASE("VectorRasterizer::rasterize") {
std::chrono::microseconds total(0);
CesiumUtility::IntrusivePointer<CesiumGltf::ImageAsset> asset;
asset.emplace();
asset->width = 256;
asset->height = 256;
asset->channels = 4;
asset->bytesPerChannel = 1;
asset->pixelData.resize(
(size_t)(asset->width * asset->height * asset->channels *
asset->bytesPerChannel),
std::byte{255});
for (int i = 0; i < 100; i++) {
std::vector<CartographicPolygon> polygons;
std::vector<std::array<std::byte, 4>> colors;
std::vector<Color> colors;
std::uniform_real_distribution<double> uniformDist;
for (int j = 0; j < 1000; j++) {
polygons.emplace_back(std::vector<glm::dvec2>{
@ -97,37 +105,32 @@ TEST_CASE("VectorRasterizer::rasterize") {
glm::dvec2(uniformDist(rand), uniformDist(rand)),
glm::dvec2(uniformDist(rand), uniformDist(rand)),
});
colors.emplace_back(std::array<std::byte, 4>{
colors.emplace_back(Color{
(std::byte)(uniformDist(rand) * 255.0),
(std::byte)(uniformDist(rand) * 255.0),
(std::byte)(uniformDist(rand) * 255.0),
(std::byte)(uniformDist(rand) * 255.0)});
}
VectorRasterizer rasterizer(polygons, colors);
CesiumGltf::ImageAsset asset;
asset.width = 256;
asset.height = 256;
asset.channels = 4;
asset.bytesPerChannel = 1;
asset.pixelData.resize(
(size_t)(asset.width * asset.height * asset.channels *
asset.bytesPerChannel),
std::byte{255});
std::chrono::steady_clock::time_point start = clock.now();
rasterizer.rasterize(rect, asset);
VectorRasterizer rasterizer(rect, asset);
for (size_t j = 0; j < polygons.size(); j++) {
rasterizer.drawPolygon(polygons[j], colors[j]);
}
rasterizer.finalize();
std::chrono::microseconds time =
std::chrono::duration_cast<std::chrono::microseconds>(
clock.now() - start);
total += time;
std::cout << "rasterized 1000 triangles in " << time.count()
<< " microseconds\n";
asset.writeTga("rand.tga");
asset->writeTga("rand.tga");
}
double seconds =
std::chrono::duration_cast<std::chrono::duration<double>>(total).count();
std::cout << "100 runs in " << seconds << " seconds, avg per run "
<< (seconds / 100.0) << " seconds\n";
}*/
}