From dcbe7d8ead63500d7d793fc6ea808d02c00e3036 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Fri, 22 Nov 2024 18:43:41 +0100 Subject: [PATCH 01/33] Vectorlayer - started transition to utils/rasterizer (with more generic rasterizer) - removed vector_layer vertex shader (not needed anymore) --- nucleus/CMakeLists.txt | 7 +- nucleus/utils/rasterizer.cpp | 85 +++++++++++++++++++++ nucleus/utils/rasterizer.h | 143 +++++++++++++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 nucleus/utils/rasterizer.cpp create mode 100644 nucleus/utils/rasterizer.h diff --git a/nucleus/CMakeLists.txt b/nucleus/CMakeLists.txt index af10ac71..a503b7f7 100644 --- a/nucleus/CMakeLists.txt +++ b/nucleus/CMakeLists.txt @@ -29,6 +29,7 @@ if(ALP_ENABLE_LABELS) alp_add_git_repository(vector_tiles URL https://github.com/AlpineMapsOrg/vector-tile.git COMMITISH faba88257716c4bc01ebd44d8b8b98f711ecb78c) endif() alp_add_git_repository(goofy_tc URL https://github.com/AlpineMapsOrgDependencies/Goofy_slim.git COMMITISH 13b228784960a6227bb6ca704ff34161bbac1b91 DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT) add_library(zppbits INTERFACE) target_include_directories(zppbits SYSTEM INTERFACE ${zppbits_SOURCE_DIR}) @@ -40,6 +41,9 @@ set_target_properties(goofy_tc PROPERTIES SYSTEM true) add_library(tl_expected INTERFACE) target_include_directories(tl_expected INTERFACE ${tl_expected_SOURCE_DIR}/include) +add_library(cdt INTERFACE) +target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include) + set(alp_version_out ${CMAKE_BINARY_DIR}/alp_version/nucleus/version.cpp) # cmake tests for existance of ${alp_version_out}.do_always_run. since it's always missing, cmake tries to generate it using this command. @@ -113,6 +117,7 @@ qt_add_library(nucleus STATIC tile/GeometryScheduler.h tile/GeometryScheduler.cpp utils/error.h utils/lang.h + utils/rasterizer.h utils/rasterizer.cpp ) if (ALP_ENABLE_AVLANCHE_WARNING_LAYER) @@ -144,7 +149,7 @@ endif() target_include_directories(nucleus PUBLIC ${CMAKE_SOURCE_DIR}) # Please keep Qt::Gui outside the nucleus. If you need it optional via a cmake based switch -target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc) +target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt) qt_add_resources(nucleus "icons" PREFIX "/map_icons" diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp new file mode 100644 index 00000000..b572e203 --- /dev/null +++ b/nucleus/utils/rasterizer.cpp @@ -0,0 +1,85 @@ +/***************************************************************************** + * AlpineMaps.org + * Copyright (C) 2024 Lucas Dworschak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include "rasterizer.h" + +#include + +namespace nucleus::utils::rasterizer { + +std::vector triangulize(std::vector polygons) +{ + std::vector processed_triangles; + + std::vector edges; + { // create the edges + edges.reserve(polygons.size()); + for (size_t i = 0; i < polygons.size() - 1; i++) { + edges.push_back(glm::ivec2(int(i), int(i + 1))); + } + + // last edge between start and end vertex + edges.push_back(glm::ivec2(polygons.size() - 1, 0)); + } + + // triangulation + CDT::Triangulation cdt; + cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + cdt.eraseOuterTrianglesAndHoles(); + + // fill our own data structures + for (size_t i = 0; i < cdt.triangles.size(); ++i) { + auto tri = cdt.triangles[i]; + + std::vector tri_indices = { tri.vertices[0], tri.vertices[1], tri.vertices[2] }; + + int top_index = (cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[1]].y) ? ((cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[2]].y) ? 0 : 2) + : ((cdt.vertices[tri.vertices[1]].y < cdt.vertices[tri.vertices[2]].y) ? 1 : 2); + // for middle and bottom index we first initialize them randomly with the values that still need to be tested + int middle_index; + int bottom_index; + if (top_index == 0) { + middle_index = 1; + bottom_index = 2; + } else if (top_index == 1) { + middle_index = 2; + bottom_index = 0; + } else { + middle_index = 0; + bottom_index = 1; + } + + // and now we test if we assigned them correctly + if (cdt.vertices[tri.vertices[middle_index]].y > cdt.vertices[tri.vertices[bottom_index]].y) { + // if not we have to interchange them + int tmp = middle_index; + middle_index = bottom_index; + bottom_index = tmp; + } + + // lastly add the vertices to the vector in the correct order + processed_triangles.push_back({ cdt.vertices[tri.vertices[top_index]].x, cdt.vertices[tri.vertices[top_index]].y }); + processed_triangles.push_back({ cdt.vertices[tri.vertices[middle_index]].x, cdt.vertices[tri.vertices[middle_index]].y }); + processed_triangles.push_back({ cdt.vertices[tri.vertices[bottom_index]].x, cdt.vertices[tri.vertices[bottom_index]].y }); + } + + return processed_triangles; +} + +} // namespace nucleus::utils::rasterizer diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h new file mode 100644 index 00000000..6c990184 --- /dev/null +++ b/nucleus/utils/rasterizer.h @@ -0,0 +1,143 @@ +/***************************************************************************** + * AlpineMaps.org + * Copyright (C) 2024 Lucas Dworschak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include + +#include + +namespace nucleus::utils::rasterizer { + +namespace details { + + inline float get_x_for_y_on_line(const glm::vec2 origin, const glm::vec2 line, float y) + { + // pos_on_line = origin + t * line + float t = (y - origin.y) / line.y; + return origin.x + t * line.x; + } + + inline std::pair calculate_dda_steps(const glm::vec2 line) + { + int steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); + + auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); + + return std::make_pair(step_size, steps); + } + + template + void dda_triangle_line( + const PixelWriterFunction& pixel_writer, const glm::vec2 origin0, const glm::vec2 line0, const glm::vec2 origin1, const glm::vec2 line1, unsigned int data_index, int fill_direction) + { + + glm::vec2 step_size; + int steps; + std::tie(step_size, steps) = calculate_dda_steps(line0); + + // make sure that at least one step is applied + steps = std::max(steps, 1); + + glm::vec2 current_start_position = origin0; + + glm::vec2 current_position = current_start_position; + + for (int j = 0; j < steps; j++) { + pixel_writer(current_position, data_index); + + // add a write step to x/y -> this prevents holes from thickness steps to appear by making the lines thicker + if (step_size.x < 1) + pixel_writer(current_position + glm::vec2(1, 0) * glm::sign(step_size.x), data_index); + if (step_size.y < 1) + pixel_writer(current_position + glm::vec2(0, 1) * glm::sign(step_size.y), data_index); + + if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value + + int x = get_x_for_y_on_line(origin1, line1, current_position.y); + + for (int j = current_position.x; j != x; j += fill_direction) { + pixel_writer(glm::vec2(j, current_position.y), data_index); + } + } + current_position += step_size; + } + } + + template void dda_triangle(const PixelWriterFunction& pixel_writer, std::vector triangle, unsigned int triangle_index) + { + auto edge_top_bottom = triangle[2] - triangle[0]; + auto edge_top_middle = triangle[1] - triangle[0]; + auto edge_middle_bottom = triangle[2] - triangle[1]; + + // TODO apply make triangle bigger lambda function if it was passed + + // auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); + // auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); + // auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); + + // { // swap normal direction if they are incorrect (pointing to center) + // glm::vec2 centroid = (triangle[0] + triangle[1] + triangle[2]) / 3.0f; + // if (glm::dot(normal_top_bottom, centroid - triangle[0]) > 0) { + // normal_top_bottom *= -1; + // } + // if (glm::dot(normal_top_middle, centroid - triangle[0]) > 0) { + // normal_top_middle *= -1; + // } + // if (glm::dot(normal_middle_bottom, centroid - triangle[1]) > 0) { + // normal_middle_bottom *= -1; + // } + // } + + // top bottom line + // dda_line(pixel_writer, , triangle_index, 0, true); + + // do we have to fill the triangle from right to left(-1) or from left to right(1) + int fill_direction = (triangle[1].x < triangle[2].x) ? 1 : -1; + + // // top middle line + dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, triangle[0], edge_top_bottom, triangle_index, fill_direction); + // // middle bottom line + dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom, triangle_index, fill_direction); + + // DEBUG draw lines with certain index + // dda_line(triangle[0], edge_top_bottom, normal_top_bottom * 1.0f, 100, 0, true); + // dda_line(triangle[0], edge_top_middle, normal_top_middle * 1.0f, 100, fill_direction, true); + // dda_line(triangle[1], edge_middle_bottom, normal_middle_bottom * 1.0f, 100, fill_direction, true); + + // TODO enable again + // add_end_cap(triangle_collection.cell_to_data, triangle[0], triangle_index, thickness); + // add_end_cap(triangle_collection.cell_to_data, triangle[1], triangle_index, thickness); + // add_end_cap(triangle_collection.cell_to_data, triangle[2], triangle_index, thickness); + } + +} // namespace details + +// triangulizes polygons and orders the vertices by y position per triangle +// output: top, middle, bottom, top, middle,... +std::vector triangulize(std::vector polygons); + +template void rasterize_triangle(const PixelWriterFunction& pixel_writer, std::vector triangles) +{ + for (size_t i = 0; i < triangles.size() / 3; ++i) { + details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i); + } +} + +// template +// void rasterize_line(const PixelWriterFunction& pixel_writer, std::vector line_points); + +} // namespace nucleus::utils::rasterizer From 6723eaf4a8fff7a8ea6570e57e31b9d77b8e0fa1 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 25 Nov 2024 16:07:59 +0100 Subject: [PATCH 02/33] Vector Layer - Rasterizer - moved tests to rasterizer.cpp - added sdf version - test sdf vs dda rasterizer --- nucleus/utils/rasterizer.h | 120 +++++++++++++++++----- unittests/nucleus/CMakeLists.txt | 1 + unittests/nucleus/rasterizer.cpp | 169 +++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 27 deletions(-) create mode 100644 unittests/nucleus/rasterizer.cpp diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 6c990184..87a089fb 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -16,12 +16,22 @@ * along with this program. If not, see . *****************************************************************************/ +#include +#include #include #include namespace nucleus::utils::rasterizer { +// Possible future improvements: +// - DDA -> prevent fill_inner_triangle to be called twice for the same row (if we switch from one line to another) +// - also check overall how often each pixel is written to + +// TODOs: +// - enlarge triangles +// - line renderer + namespace details { inline float get_x_for_y_on_line(const glm::vec2 origin, const glm::vec2 line, float y) @@ -33,51 +43,69 @@ namespace details { inline std::pair calculate_dda_steps(const glm::vec2 line) { - int steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); + int steps = ceil(fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y)); + // should only be triggered if two vertices of a triangle are exactly on the same position + // -> the input data is wrong + assert(steps > 0); + // steps = std::max(steps, 1); // alternative force at least one dda step -> wrong output but it should at least "work" auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); + // double the steps -> otherwise we might miss important pixels + steps *= 2; + step_size /= 2.0f; + return std::make_pair(step_size, steps); } template - void dda_triangle_line( - const PixelWriterFunction& pixel_writer, const glm::vec2 origin0, const glm::vec2 line0, const glm::vec2 origin1, const glm::vec2 line1, unsigned int data_index, int fill_direction) + inline void fill_inner_triangle( + const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& top_point, const glm::vec2& top_bottom_line, unsigned int data_index, int fill_direction) { + // calculate the x value of the top_bottom edge + int x = get_x_for_y_on_line(top_point, top_bottom_line, current_position.y) + fill_direction; + + for (int j = current_position.x; j != x; j += fill_direction) { + pixel_writer(glm::vec2(j, current_position.y), data_index); + } + } + template + void dda_triangle_line(const PixelWriterFunction& pixel_writer, + const glm::vec2& line_start_point, + const glm::vec2& line, + const glm::vec2& top_point, + const glm::vec2& top_bottom_line, + unsigned int data_index, + int fill_direction) + { glm::vec2 step_size; int steps; - std::tie(step_size, steps) = calculate_dda_steps(line0); + std::tie(step_size, steps) = calculate_dda_steps(line); - // make sure that at least one step is applied - steps = std::max(steps, 1); + int last_y = 0; - glm::vec2 current_start_position = origin0; - - glm::vec2 current_position = current_start_position; + glm::vec2 current_position = line_start_point; for (int j = 0; j < steps; j++) { pixel_writer(current_position, data_index); - // add a write step to x/y -> this prevents holes from thickness steps to appear by making the lines thicker - if (step_size.x < 1) - pixel_writer(current_position + glm::vec2(1, 0) * glm::sign(step_size.x), data_index); - if (step_size.y < 1) - pixel_writer(current_position + glm::vec2(0, 1) * glm::sign(step_size.y), data_index); - if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value - int x = get_x_for_y_on_line(origin1, line1, current_position.y); - - for (int j = current_position.x; j != x; j += fill_direction) { - pixel_writer(glm::vec2(j, current_position.y), data_index); - } + fill_inner_triangle(pixel_writer, current_position, top_point, top_bottom_line, data_index, fill_direction); + last_y = current_position.y; } current_position += step_size; } + + current_position -= step_size; + + // check if we have to apply the fill_inner_triangle alg for the current row + if (last_y != floor(current_position.y)) + fill_inner_triangle(pixel_writer, current_position, top_point, top_bottom_line, data_index, fill_direction); } - template void dda_triangle(const PixelWriterFunction& pixel_writer, std::vector triangle, unsigned int triangle_index) + template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index) { auto edge_top_bottom = triangle[2] - triangle[0]; auto edge_top_middle = triangle[1] - triangle[0]; @@ -113,24 +141,62 @@ namespace details { // // middle bottom line dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom, triangle_index, fill_direction); - // DEBUG draw lines with certain index - // dda_line(triangle[0], edge_top_bottom, normal_top_bottom * 1.0f, 100, 0, true); - // dda_line(triangle[0], edge_top_middle, normal_top_middle * 1.0f, 100, fill_direction, true); - // dda_line(triangle[1], edge_middle_bottom, normal_middle_bottom * 1.0f, 100, fill_direction, true); - // TODO enable again // add_end_cap(triangle_collection.cell_to_data, triangle[0], triangle_index, thickness); // add_end_cap(triangle_collection.cell_to_data, triangle[1], triangle_index, thickness); // add_end_cap(triangle_collection.cell_to_data, triangle[2], triangle_index, thickness); } + template + void triangle_sdf(const PixelWriterFunction& pixel_writer, const glm::vec2 current_position, const std::array points, unsigned int data_index, float distance) + { + // https://iquilezles.org/articles/distfunctions2d/ + + glm::vec2 e0 = points[1] - points[0], e1 = points[2] - points[1], e2 = points[0] - points[2]; + float s = glm::sign(e0.x * e2.y - e0.y * e2.x); + + glm::vec2 v0 = current_position - points[0], v1 = current_position - points[1], v2 = current_position - points[2]; + glm::vec2 pq0 = v0 - e0 * glm::clamp(glm::dot(v0, e0) / glm::dot(e0, e0), 0.0f, 1.0f); + glm::vec2 pq1 = v1 - e1 * glm::clamp(glm::dot(v1, e1) / glm::dot(e1, e1), 0.0f, 1.0f); + glm::vec2 pq2 = v2 - e2 * glm::clamp(glm::dot(v2, e2) / glm::dot(e2, e2), 0.0f, 1.0f); + glm::vec2 d + = min(min(glm::vec2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)), glm::vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))), glm::vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x))); + float dist_from_tri = -sqrt(d.x) * glm::sign(d.y); + + if (dist_from_tri < distance) { + pixel_writer(current_position, data_index); + } + } + } // namespace details // triangulizes polygons and orders the vertices by y position per triangle // output: top, middle, bottom, top, middle,... std::vector triangulize(std::vector polygons); -template void rasterize_triangle(const PixelWriterFunction& pixel_writer, std::vector triangles) +// ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size +// in this method every pixel traverses every triangle +template void rasterize_triangle_sdf(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance, const glm::ivec2 grid_size) +{ + // we sample from the center + // and have a radius of a half pixel diagonal + // this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel + distance += sqrt(0.5); + + for (uint8_t i = 0; i < grid_size.x; i++) { + for (uint8_t j = 0; j < grid_size.y; j++) { + auto current_position = glm::vec2 { i + 0.5, j + 0.5 }; + + for (size_t i = 0; i < triangles.size() / 3; ++i) { + details::triangle_sdf(pixel_writer, current_position, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); + } + } + } +} + +// ideal for many triangles. especially if each triangle only covers small parts of the raster +// in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. +template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles) { for (size_t i = 0; i < triangles.size() / 3; ++i) { details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i); diff --git a/unittests/nucleus/CMakeLists.txt b/unittests/nucleus/CMakeLists.txt index ef97c1d6..f097a660 100644 --- a/unittests/nucleus/CMakeLists.txt +++ b/unittests/nucleus/CMakeLists.txt @@ -25,6 +25,7 @@ alp_add_unittest(unittests_nucleus DrawListGenerator.cpp test_helpers.h test_helpers.cpp raster.cpp + rasterizer.cpp terrain_mesh_index_generator.cpp srs.cpp track.cpp diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp new file mode 100644 index 00000000..ec25f140 --- /dev/null +++ b/unittests/nucleus/rasterizer.cpp @@ -0,0 +1,169 @@ +/***************************************************************************** + * AlpineMaps.org + * Copyright (C) 2024 Lucas Dworschak + * Copyright (C) 2024 Adam Celarek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include +#include + +#include + +#include "nucleus/Raster.h" +#include "nucleus/tile/conversion.h" +#include "nucleus/utils/rasterizer.h" + +TEST_CASE("nucleus/rasterizer") +{ + SECTION("Triangulation") + { + // 5 point polygon + // basically a square where one side contains an inward facing triangle + // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape + const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; + const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; + + CDT::Triangulation cdt; + cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + cdt.eraseOuterTrianglesAndHoles(); + + auto tri = cdt.triangles; + auto vert = cdt.vertices; + + // check if only 3 triangles have been found + CHECK(tri.size() == 3); + + // 1st triangle + CHECK(vert[tri[0].vertices[0]].x == 0.0); + CHECK(vert[tri[0].vertices[0]].y == 2.0); + CHECK(vert[tri[0].vertices[1]].x == 1.0); + CHECK(vert[tri[0].vertices[1]].y == 1.0); + CHECK(vert[tri[0].vertices[2]].x == 2.0); + CHECK(vert[tri[0].vertices[2]].y == 2.0); + + // 2nd triangle + CHECK(vert[tri[1].vertices[0]].x == 1.0); + CHECK(vert[tri[1].vertices[0]].y == 1.0); + CHECK(vert[tri[1].vertices[1]].x == 2.0); + CHECK(vert[tri[1].vertices[1]].y == 0.0); + CHECK(vert[tri[1].vertices[2]].x == 2.0); + CHECK(vert[tri[1].vertices[2]].y == 2.0); + + // 3rd triangle + CHECK(vert[tri[2].vertices[0]].x == 1.0); + CHECK(vert[tri[2].vertices[0]].y == 1.0); + CHECK(vert[tri[2].vertices[1]].x == 0.0); + CHECK(vert[tri[2].vertices[1]].y == 0.0); + CHECK(vert[tri[2].vertices[2]].x == 2.0); + CHECK(vert[tri[2].vertices[2]].y == 0.0); + + // DEBUG print out all the points of the triangles (to check what might have went wrong) + // for (std::size_t i = 0; i < tri.size(); i++) { + // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", + // vert[tri[i].vertices[0]].x, // x0 + // vert[tri[i].vertices[0]].y, // y0 + // vert[tri[i].vertices[1]].x, // x1 + // vert[tri[i].vertices[1]].y, // y1 + // vert[tri[i].vertices[2]].x, // x2 + // vert[tri[i].vertices[2]].y // y2 + // ); + // } + } + + SECTION("Triangle y ordering") + { + // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value + const std::vector triangle_points_012 = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + const std::vector triangle_points_021 = { glm::vec2(30, 10), glm::vec2(50, 50), glm::vec2(10, 30) }; + const std::vector triangle_points_102 = { glm::vec2(10, 30), glm::vec2(30, 10), glm::vec2(50, 50) }; + const std::vector triangle_points_201 = { glm::vec2(10, 30), glm::vec2(50, 50), glm::vec2(30, 10) }; + const std::vector triangle_points_120 = { glm::vec2(50, 50), glm::vec2(30, 10), glm::vec2(10, 30) }; + const std::vector triangle_points_210 = { glm::vec2(50, 50), glm::vec2(10, 30), glm::vec2(30, 10) }; + + const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); + } + + SECTION("rasterize triangle") + { + // two triangles + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + + nucleus::Raster output2({ 64, 64 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // image.save(QString("rasterizer_output.png")); + // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // image2.save(QString("rasterizer_output_sdf.png")); + } + + SECTION("rasterize triangle small") + { + const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; + nucleus::Raster output({ 6, 6 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); + + nucleus::Raster output2({ 6, 6 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // image.save(QString("rasterizer_output_small.png")); + // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // image2.save(QString("rasterizer_output_small_sdf.png")); + } + + SECTION("rasterize triangle smallest") + { + // less than one pixel + const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); + + nucleus::Raster output2({ 64, 64 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // image.save(QString("rasterizer_output_smallest.png")); + // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // image2.save(QString("rasterizer_output_smallest_sdf.png")); + } +} From 1c990addd095f91c1516317afbcf15df22158abe Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 26 Nov 2024 15:52:48 +0100 Subject: [PATCH 03/33] Vector layer - Rasterizer: - added a method that combines two single channels to a qimage - implemented first iteration of a three part triangle rasterizer for better enlarged triangle rasterization --- nucleus/tile/conversion.h | 34 +++++++ nucleus/utils/rasterizer.h | 164 +++++++++++++++++++++--------- unittests/nucleus/rasterizer.cpp | 167 ++++++++++++++++++++++--------- 3 files changed, 270 insertions(+), 95 deletions(-) diff --git a/nucleus/tile/conversion.h b/nucleus/tile/conversion.h index 935fef4e..f4af4d11 100644 --- a/nucleus/tile/conversion.h +++ b/nucleus/tile/conversion.h @@ -107,4 +107,38 @@ inline glm::u8vec4 uint162alpineRGBA(uint16_t v) { return { v >> 8, v & 255, 0, 255 }; } + +inline QImage u8raster_to_qimage(const nucleus::Raster& raster) +{ + size_t width = raster.width(); + size_t height = raster.height(); + + QImage image(width, height, QImage::Format_Grayscale8); + + for (size_t y = 0; y < height; ++y) { + uchar* line = image.scanLine(y); + for (size_t x = 0; x < width; ++x) { + line[x] = raster.pixel({ x, y }); + } + } + + return image; +} + +inline QImage u8raster_2_to_qimage(const nucleus::Raster& raster1, const nucleus::Raster& raster2) +{ + size_t width = raster1.width(); + size_t height = raster1.height(); + + QImage image(width, height, QImage::Format_RGB888); + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + image.setPixel(QPoint(x, y), (raster1.pixel({ x, y }) << 16) + (raster2.pixel({ x, y }) << 8)); + } + } + + return image; +} + } // namespace nucleus::tile::conversion diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 87a089fb..57e7e01c 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -30,7 +30,10 @@ namespace nucleus::utils::rasterizer { // TODOs: // - enlarge triangles +// - current problems: some irregularities with the get_x_for_y_on_line result/ fill_inner_triangle function +// - we start too late and stop to early with the top and bottom lines -> maybe we need to round first? // - line renderer +// sdf box filter instead by circle to only consider values of the current cell namespace details { @@ -38,9 +41,19 @@ namespace details { { // pos_on_line = origin + t * line float t = (y - origin.y) / line.y; + return origin.x + t * line.x; } + // half vector between n1 and n2 projected onto the half vector gives us the y value that can go through to the other side, without causing problems + // the problem is that we might still remove things we want to render + // solution: calculate the half vector and the projection on it for upper lower bound of our rendering pass + // and only render from extended line to this half vector -> the last render pass renders from the other side also to the half vector + // -> problem we split the line in two parts (up to middle - middle to bottom -> so we only have to render the largest line to middle with first half vector and from middle with second half vector + // -> other problem what to do with middle part -> calculate new line between both enlarged vertices? and render this new line? -> fully through + + // scanne bis zum orig bottom vertice y value -> until then use the half vector for end position from then on use the normal vector as end position. + inline std::pair calculate_dda_steps(const glm::vec2 line) { int steps = ceil(fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y)); @@ -70,15 +83,31 @@ namespace details { } } + // pixel_writer, topmost_renderable_point, top_bisector, + // enlarged_top_bottom_origin, edge_top_bottom, + // enlarged_top_middle_origin, edge_top_middle, triangle_index template void dda_triangle_line(const PixelWriterFunction& pixel_writer, const glm::vec2& line_start_point, const glm::vec2& line, - const glm::vec2& top_point, - const glm::vec2& top_bottom_line, - unsigned int data_index, - int fill_direction) + const glm::vec2& outer_point_a, + const glm::vec2& outer_line_a, + const glm::vec2& outer_point_b, + const glm::vec2& outer_line_b, + // const glm::vec2&, // outer_point_a, + // const glm::vec2&, // outer_line_a, + // const glm::vec2&, // outer_point_b, + // const glm::vec2&, // outer_line_b, + unsigned int data_index) { + int fill_direction; + bool only_fill_once = false; // only fill from line to outer line a + if (outer_line_b == glm::vec2 { 0, 0 }) { + fill_direction = (outer_point_a.x < line_start_point.x) ? -1 : 1; + only_fill_once = true; + } else + fill_direction = (outer_point_a.x < outer_point_b.x) ? -1 : 1; + glm::vec2 step_size; int steps; std::tie(step_size, steps) = calculate_dda_steps(line); @@ -91,8 +120,9 @@ namespace details { pixel_writer(current_position, data_index); if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value - - fill_inner_triangle(pixel_writer, current_position, top_point, top_bottom_line, data_index, fill_direction); + fill_inner_triangle(pixel_writer, current_position, outer_point_a, outer_line_a, data_index, fill_direction); + if (!only_fill_once) + fill_inner_triangle(pixel_writer, current_position, outer_point_b, outer_line_b, data_index, -fill_direction); last_y = current_position.y; } current_position += step_size; @@ -101,50 +131,94 @@ namespace details { current_position -= step_size; // check if we have to apply the fill_inner_triangle alg for the current row - if (last_y != floor(current_position.y)) - fill_inner_triangle(pixel_writer, current_position, top_point, top_bottom_line, data_index, fill_direction); + if (last_y != floor(current_position.y)) { + fill_inner_triangle(pixel_writer, current_position, outer_point_a, outer_line_a, data_index, fill_direction); + if (!only_fill_once) + fill_inner_triangle(pixel_writer, current_position, outer_point_b, outer_line_b, data_index, -fill_direction); + } } - template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index) + template void add_end_cap(const PixelWriterFunction& pixel_writer, const glm::vec2 position, unsigned int data_index, float distance) { + distance += sqrt(0.5); + float distance_test = ceil(distance); + + for (int i = -distance_test; i < distance_test; i++) { + for (int j = -distance_test; j < distance_test; j++) { + const auto offset = glm::vec2(i + 0.0, j + 0.0); + if (glm::length(offset) < distance) + pixel_writer(position + offset, data_index); + } + } + } + + template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index, float distance) + { + // In order to process enlarged triangles, the triangle is separated into 3 parts: top, middle and bottom + // top and bottom parts are rendered by calculating the bisector line of both normal vectors and rendering everything left and everyting right of this line by using a fill operation + + // for the middle part: + // if we enlarge the triangle by moving the edge along an edge normal we generate a gap between the top-middle and middle-bottom edge (if those lines are only translated) + // we have to somehow rasterize this gap between the bottom vertice of the translated top-middle edge and the top vertice of the middle-bottom edge. + // in order to do this we draw a new edge between those vertices and rasterize everything between this new edge and the top-bottom edge. + + // in the worst case here we have one triangle line that is paralell to scan line + // in this case it might be that we still have to consider that we are writing outside of the actual triangle !!!! -> find a solution!!! + + // distance += sqrt(0.5); + auto edge_top_bottom = triangle[2] - triangle[0]; auto edge_top_middle = triangle[1] - triangle[0]; auto edge_middle_bottom = triangle[2] - triangle[1]; - // TODO apply make triangle bigger lambda function if it was passed - - // auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); - // auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); - // auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); - - // { // swap normal direction if they are incorrect (pointing to center) - // glm::vec2 centroid = (triangle[0] + triangle[1] + triangle[2]) / 3.0f; - // if (glm::dot(normal_top_bottom, centroid - triangle[0]) > 0) { - // normal_top_bottom *= -1; - // } - // if (glm::dot(normal_top_middle, centroid - triangle[0]) > 0) { - // normal_top_middle *= -1; - // } - // if (glm::dot(normal_middle_bottom, centroid - triangle[1]) > 0) { - // normal_middle_bottom *= -1; - // } - // } - - // top bottom line - // dda_line(pixel_writer, , triangle_index, 0, true); - - // do we have to fill the triangle from right to left(-1) or from left to right(1) - int fill_direction = (triangle[1].x < triangle[2].x) ? 1 : -1; - - // // top middle line - dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, triangle[0], edge_top_bottom, triangle_index, fill_direction); - // // middle bottom line - dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom, triangle_index, fill_direction); - - // TODO enable again - // add_end_cap(triangle_collection.cell_to_data, triangle[0], triangle_index, thickness); - // add_end_cap(triangle_collection.cell_to_data, triangle[1], triangle_index, thickness); - // add_end_cap(triangle_collection.cell_to_data, triangle[2], triangle_index, thickness); + auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); + auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); + auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); + + { // swap normal direction if they are incorrect (pointing to center) + glm::vec2 centroid = (triangle[0] + triangle[1] + triangle[2]) / 3.0f; + if (glm::dot(normal_top_bottom, centroid - triangle[0]) > 0) { + normal_top_bottom *= -1; + } + if (glm::dot(normal_top_middle, centroid - triangle[0]) > 0) { + normal_top_middle *= -1; + } + if (glm::dot(normal_middle_bottom, centroid - triangle[1]) > 0) { + normal_middle_bottom *= -1; + } + } + + auto half_vector_top = (normal_top_bottom + normal_top_middle) / glm::vec2(2.0); + auto half_vector_bottom = (normal_top_bottom + normal_middle_bottom) / glm::vec2(2.0); + + auto enlarged_top_bottom_origin = triangle[0] + normal_top_bottom * distance; + auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; + + auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; + auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; + + auto topmost_renderable_point = triangle[0] + half_vector_top; + auto bottommost_renderable_point = triangle[2] + half_vector_bottom; + + auto top_bisector = glm::vec2(get_x_for_y_on_line(topmost_renderable_point, -half_vector_top, enlarged_top_middle_end.y), enlarged_top_middle_end.y) - topmost_renderable_point; + auto middle_to_bottom_origin = glm::vec2(get_x_for_y_on_line(bottommost_renderable_point, -half_vector_bottom, enlarged_middle_bottom_origin.y), enlarged_middle_bottom_origin.y); + + auto middle_bisector = bottommost_renderable_point - middle_to_bottom_origin; + + // top to middle(1) + dda_triangle_line(pixel_writer, topmost_renderable_point, top_bisector, enlarged_top_bottom_origin, edge_top_bottom, enlarged_top_middle_origin, edge_top_middle, triangle_index); + + // middle(1) to middle(2) + dda_triangle_line( + pixel_writer, enlarged_top_middle_end, enlarged_middle_bottom_origin - enlarged_top_middle_end, enlarged_top_bottom_origin, edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); + + // middle(2) to bottom + dda_triangle_line(pixel_writer, middle_to_bottom_origin, middle_bisector, enlarged_top_bottom_origin, edge_top_bottom, enlarged_middle_bottom_origin, edge_middle_bottom, triangle_index); + + // lastly we have to add endcaps on each original vertice position with the passed through distance + add_end_cap(pixel_writer, triangle[0], triangle_index, distance); + add_end_cap(pixel_writer, triangle[1], triangle_index, distance); + add_end_cap(pixel_writer, triangle[2], triangle_index, distance); } template @@ -196,10 +270,10 @@ template void rasterize_triangle_sdf(const PixelW // ideal for many triangles. especially if each triangle only covers small parts of the raster // in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. -template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles) +template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) { for (size_t i = 0; i < triangles.size() / 3; ++i) { - details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i); + details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); } } diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index ec25f140..96d993ab 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -26,6 +26,8 @@ #include "nucleus/tile/conversion.h" #include "nucleus/utils/rasterizer.h" +#include + TEST_CASE("nucleus/rasterizer") { SECTION("Triangulation") @@ -104,66 +106,131 @@ TEST_CASE("nucleus/rasterizer") CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); } - SECTION("rasterize triangle") + // SECTION("rasterize triangle") + // { + // // two triangles + // const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + // nucleus::Raster output({ 64, 64 }, 0u); + // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + + // nucleus::Raster output2({ 64, 64 }, 0u); + // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); + + // CHECK(output.buffer() == output2.buffer()); + + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // // image.save(QString("rasterizer_output.png")); + // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // // image2.save(QString("rasterizer_output_sdf.png")); + // } + + // SECTION("rasterize triangle small") + // { + // const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; + // nucleus::Raster output({ 6, 6 }, 0u); + // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); + + // nucleus::Raster output2({ 6, 6 }, 0u); + // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); + + // CHECK(output.buffer() == output2.buffer()); + + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // // image.save(QString("rasterizer_output_small.png")); + // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // // image2.save(QString("rasterizer_output_small_sdf.png")); + // } + + // SECTION("rasterize triangle smallest") + // { + // // less than one pixel + // const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; + // nucleus::Raster output({ 64, 64 }, 0u); + // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); + + // nucleus::Raster output2({ 64, 64 }, 0u); + // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); + + // CHECK(output.buffer() == output2.buffer()); + + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // // image.save(QString("rasterizer_output_smallest.png")); + // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // // image2.save(QString("rasterizer_output_smallest_sdf.png")); + // } + + SECTION("rasterize triangle enlarged endcaps only") { - // two triangles - const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; - - nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); - - nucleus::Raster output2({ 64, 64 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); - - CHECK(output.buffer() == output2.buffer()); - - // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // image.save(QString("rasterizer_output.png")); - // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // image2.save(QString("rasterizer_output_sdf.png")); - } - - SECTION("rasterize triangle small") - { - const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; - nucleus::Raster output({ 6, 6 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); - - nucleus::Raster output2({ 6, 6 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); + // less than one pixel + const std::vector triangles = { glm::vec2(5.35, 5.35), glm::vec2(5.40, 5.40), glm::vec2(5.45, 5.35) }; + auto size = glm::vec2(10, 10); + nucleus::Raster output(size, 0u); + float distance = 4.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[0], 1, distance); + nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[1], 1, distance); + nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[2], 1, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // image.save(QString("rasterizer_output_small.png")); - // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // image2.save(QString("rasterizer_output_small_sdf.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_endcap.png")); } - SECTION("rasterize triangle smallest") + SECTION("rasterize triangle enlarged") { // less than one pixel - const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; - nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); - - nucleus::Raster output2({ 64, 64 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); - - CHECK(output.buffer() == output2.buffer()); + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + // const std::vector triangles = { glm::vec2(5.35, 5.35), glm::vec2(5.40, 5.40), glm::vec2(5.45, 5.35) }; + auto size = glm::vec2(64, 64); + nucleus::Raster output(size, 0u); + float distance = 4.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[0], 1, distance); + // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[1], 1, distance); + // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[2], 1, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + + // CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // image.save(QString("rasterizer_output_smallest.png")); - // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // image2.save(QString("rasterizer_output_smallest_sdf.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged.png")); } } From 23920c94615ef364603505713656646d28eda2af Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Wed, 27 Nov 2024 07:00:34 +0100 Subject: [PATCH 04/33] Vector Layer - Rasterizer - reworked that triangles without distances work again - minor adjustments so that enlarged triangles rasterize all that they should (currently rasterize more than they should) --- nucleus/utils/rasterizer.h | 48 +++++++++--- unittests/nucleus/rasterizer.cpp | 126 +++++++++++++++---------------- 2 files changed, 98 insertions(+), 76 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 57e7e01c..014e05c0 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -103,10 +103,13 @@ namespace details { int fill_direction; bool only_fill_once = false; // only fill from line to outer line a if (outer_line_b == glm::vec2 { 0, 0 }) { - fill_direction = (outer_point_a.x < line_start_point.x) ? -1 : 1; + if (outer_point_a.x == line_start_point.x) // determine fill direction at line end + fill_direction = (outer_point_a.x + outer_line_a.x < line_start_point.x + line.x) ? -1 : 1; + else // determine fill direction at line start + fill_direction = (outer_point_a.x < line_start_point.x) ? -1 : 1; only_fill_once = true; } else - fill_direction = (outer_point_a.x < outer_point_b.x) ? -1 : 1; + fill_direction = (outer_point_a.x + outer_line_a.x < outer_point_b.x + outer_line_b.x) ? -1 : 1; glm::vec2 step_size; int steps; @@ -152,6 +155,23 @@ namespace details { } } + template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index) + { + auto edge_top_bottom = triangle[2] - triangle[0]; + auto edge_top_middle = triangle[1] - triangle[0]; + auto edge_middle_bottom = triangle[2] - triangle[1]; + + // top middle + dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, triangle[0], edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); + // middle bottom + dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); + + // lastly we have to add endcaps on each original vertice position with the passed through distance + // add_end_cap(pixel_writer, triangle[0], triangle_index, distance); + // add_end_cap(pixel_writer, triangle[1], triangle_index, distance); + // add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + } + template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index, float distance) { // In order to process enlarged triangles, the triangle is separated into 3 parts: top, middle and bottom @@ -192,16 +212,21 @@ namespace details { auto half_vector_bottom = (normal_top_bottom + normal_middle_bottom) / glm::vec2(2.0); auto enlarged_top_bottom_origin = triangle[0] + normal_top_bottom * distance; + auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; + auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; + + float top_y = std::min(std::min(triangle[0].y + half_vector_top.y, enlarged_top_bottom_origin.y), enlarged_top_middle_origin.y); + float bottom_y = std::max(std::max(triangle[2].y + half_vector_bottom.y, enlarged_top_bottom_end.y), enlarged_middle_bottom_end.y); - auto topmost_renderable_point = triangle[0] + half_vector_top; - auto bottommost_renderable_point = triangle[2] + half_vector_bottom; + auto topmost_renderable_point = glm::vec2(get_x_for_y_on_line(triangle[0], half_vector_top, top_y), top_y); + auto bottommost_renderable_point = glm::vec2(get_x_for_y_on_line(triangle[2], half_vector_bottom, bottom_y), bottom_y); - auto top_bisector = glm::vec2(get_x_for_y_on_line(topmost_renderable_point, -half_vector_top, enlarged_top_middle_end.y), enlarged_top_middle_end.y) - topmost_renderable_point; - auto middle_to_bottom_origin = glm::vec2(get_x_for_y_on_line(bottommost_renderable_point, -half_vector_bottom, enlarged_middle_bottom_origin.y), enlarged_middle_bottom_origin.y); + auto top_bisector = glm::vec2(get_x_for_y_on_line(topmost_renderable_point, half_vector_top, enlarged_top_middle_end.y), enlarged_top_middle_end.y) - topmost_renderable_point; + auto middle_to_bottom_origin = glm::vec2(get_x_for_y_on_line(bottommost_renderable_point, half_vector_bottom, enlarged_middle_bottom_origin.y), enlarged_middle_bottom_origin.y); auto middle_bisector = bottommost_renderable_point - middle_to_bottom_origin; @@ -216,9 +241,9 @@ namespace details { dda_triangle_line(pixel_writer, middle_to_bottom_origin, middle_bisector, enlarged_top_bottom_origin, edge_top_bottom, enlarged_middle_bottom_origin, edge_middle_bottom, triangle_index); // lastly we have to add endcaps on each original vertice position with the passed through distance - add_end_cap(pixel_writer, triangle[0], triangle_index, distance); - add_end_cap(pixel_writer, triangle[1], triangle_index, distance); - add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + // add_end_cap(pixel_writer, triangle[0], triangle_index, distance); + // add_end_cap(pixel_writer, triangle[1], triangle_index, distance); + // add_end_cap(pixel_writer, triangle[2], triangle_index, distance); } template @@ -273,7 +298,10 @@ template void rasterize_triangle_sdf(const PixelW template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) { for (size_t i = 0; i < triangles.size() / 3; ++i) { - details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); + if (distance == 0.0) + details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i); + else + details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); } } diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 96d993ab..210c9ac4 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -106,68 +106,66 @@ TEST_CASE("nucleus/rasterizer") CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); } - // SECTION("rasterize triangle") - // { - // // two triangles - // const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; - - // nucleus::Raster output({ 64, 64 }, 0u); - // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); - - // nucleus::Raster output2({ 64, 64 }, 0u); - // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); - - // CHECK(output.buffer() == output2.buffer()); - - // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // // image.save(QString("rasterizer_output.png")); - // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // // image2.save(QString("rasterizer_output_sdf.png")); - // } - - // SECTION("rasterize triangle small") - // { - // const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; - // nucleus::Raster output({ 6, 6 }, 0u); - // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); - - // nucleus::Raster output2({ 6, 6 }, 0u); - // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); - - // CHECK(output.buffer() == output2.buffer()); - - // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // // image.save(QString("rasterizer_output_small.png")); - // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // // image2.save(QString("rasterizer_output_small_sdf.png")); - // } - - // SECTION("rasterize triangle smallest") - // { - // // less than one pixel - // const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; - // nucleus::Raster output({ 64, 64 }, 0u); - // const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); - - // nucleus::Raster output2({ 64, 64 }, 0u); - // const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); - - // CHECK(output.buffer() == output2.buffer()); - - // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); - // // image.save(QString("rasterizer_output_smallest.png")); - // // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // // image2.save(QString("rasterizer_output_smallest_sdf.png")); - // } + SECTION("rasterize triangle") + { + // two triangles + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + + nucleus::Raster output2({ 64, 64 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output.png")); + } + + SECTION("rasterize triangle small") + { + const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; + nucleus::Raster output({ 6, 6 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); + + nucleus::Raster output2({ 6, 6 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // image.save(QString("rasterizer_output_small.png")); + // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // image2.save(QString("rasterizer_output_small_sdf.png")); + } + + SECTION("rasterize triangle smallest") + { + // less than one pixel + const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); + + nucleus::Raster output2({ 64, 64 }, 0u); + const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // image.save(QString("rasterizer_output_smallest.png")); + // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); + // image2.save(QString("rasterizer_output_smallest_sdf.png")); + } SECTION("rasterize triangle enlarged endcaps only") { @@ -182,7 +180,6 @@ TEST_CASE("nucleus/rasterizer") if (bounds.contains(pos)) output.pixel(pos) = 255; }; - // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[0], 1, distance); nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[1], 1, distance); nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[2], 1, distance); @@ -216,9 +213,6 @@ TEST_CASE("nucleus/rasterizer") output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); - // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[0], 1, distance); - // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[1], 1, distance); - // nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[2], 1, distance); nucleus::Raster output2(size, 0u); const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { From 6b28b89e4d29afa619d5ab05f36669a65403b6b1 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Wed, 27 Nov 2024 09:17:06 +0100 Subject: [PATCH 05/33] Vector layer - rasterizer - enlarged triangles more or less work, only problem for horizontal lines --- nucleus/utils/rasterizer.h | 162 ++++++++++++++++--------------- unittests/nucleus/rasterizer.cpp | 28 +++--- 2 files changed, 101 insertions(+), 89 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 014e05c0..aace24ae 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -30,8 +30,7 @@ namespace nucleus::utils::rasterizer { // TODOs: // - enlarge triangles -// - current problems: some irregularities with the get_x_for_y_on_line result/ fill_inner_triangle function -// - we start too late and stop to early with the top and bottom lines -> maybe we need to round first? +// - current problem: horizontal triangle lines // - line renderer // sdf box filter instead by circle to only consider values of the current cell @@ -45,15 +44,6 @@ namespace details { return origin.x + t * line.x; } - // half vector between n1 and n2 projected onto the half vector gives us the y value that can go through to the other side, without causing problems - // the problem is that we might still remove things we want to render - // solution: calculate the half vector and the projection on it for upper lower bound of our rendering pass - // and only render from extended line to this half vector -> the last render pass renders from the other side also to the half vector - // -> problem we split the line in two parts (up to middle - middle to bottom -> so we only have to render the largest line to middle with first half vector and from middle with second half vector - // -> other problem what to do with middle part -> calculate new line between both enlarged vertices? and render this new line? -> fully through - - // scanne bis zum orig bottom vertice y value -> until then use the half vector for end position from then on use the normal vector as end position. - inline std::pair calculate_dda_steps(const glm::vec2 line) { int steps = ceil(fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y)); @@ -72,44 +62,56 @@ namespace details { } template - inline void fill_inner_triangle( - const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& top_point, const glm::vec2& top_bottom_line, unsigned int data_index, int fill_direction) + inline void fill_inner_triangle(const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& other_point, const glm::vec2& other_line, unsigned int data_index) { - // calculate the x value of the top_bottom edge - int x = get_x_for_y_on_line(top_point, top_bottom_line, current_position.y) + fill_direction; + // calculate the x value of the other line + int x = get_x_for_y_on_line(other_point, other_line, current_position.y); - for (int j = current_position.x; j != x; j += fill_direction) { + int fill_direction = (current_position.x < x) ? 1 : -1; + + for (int j = current_position.x; j != x + fill_direction; j += fill_direction) { pixel_writer(glm::vec2(j, current_position.y), data_index); } } + template + inline void fill_inner_triangle_decider(const PixelWriterFunction& pixel_writer, + const glm::vec2& current_position, + const glm::vec2& line_start, + const glm::vec2& line, + const glm::vec2& normal_line, + const glm::vec2& other_point, + const glm::vec2& other_line, + bool is_enlarged_triangle, + unsigned int data_index) + { + // before we fill the inner triangle we want to first decide the line to which we want to fill. + // for enlarged triangles, if we render the top most or the bottom most part of the triangle, + // it can happen that we fill it too much, in those cases we only want to fill to the normal + if (is_enlarged_triangle) { + if (current_position.y > other_point.y + other_line.y) // fill only to the normal line located at the bottom + fill_inner_triangle(pixel_writer, current_position, line_start + line, normal_line, data_index); + else if (current_position.y < other_point.y) // fill only to the normal line + fill_inner_triangle(pixel_writer, current_position, line_start, normal_line, data_index); + else // normal fill operation + fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); + } else { // normal fill operation + fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); + } + } + // pixel_writer, topmost_renderable_point, top_bisector, // enlarged_top_bottom_origin, edge_top_bottom, // enlarged_top_middle_origin, edge_top_middle, triangle_index template void dda_triangle_line(const PixelWriterFunction& pixel_writer, - const glm::vec2& line_start_point, + const glm::vec2& line_start, const glm::vec2& line, - const glm::vec2& outer_point_a, - const glm::vec2& outer_line_a, - const glm::vec2& outer_point_b, - const glm::vec2& outer_line_b, - // const glm::vec2&, // outer_point_a, - // const glm::vec2&, // outer_line_a, - // const glm::vec2&, // outer_point_b, - // const glm::vec2&, // outer_line_b, + const glm::vec2& normal_line, + const glm::vec2& other_point, + const glm::vec2& other_line, unsigned int data_index) { - int fill_direction; - bool only_fill_once = false; // only fill from line to outer line a - if (outer_line_b == glm::vec2 { 0, 0 }) { - if (outer_point_a.x == line_start_point.x) // determine fill direction at line end - fill_direction = (outer_point_a.x + outer_line_a.x < line_start_point.x + line.x) ? -1 : 1; - else // determine fill direction at line start - fill_direction = (outer_point_a.x < line_start_point.x) ? -1 : 1; - only_fill_once = true; - } else - fill_direction = (outer_point_a.x + outer_line_a.x < outer_point_b.x + outer_line_b.x) ? -1 : 1; glm::vec2 step_size; int steps; @@ -117,15 +119,17 @@ namespace details { int last_y = 0; - glm::vec2 current_position = line_start_point; + const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; + + glm::vec2 current_position = line_start; for (int j = 0; j < steps; j++) { pixel_writer(current_position, data_index); if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value - fill_inner_triangle(pixel_writer, current_position, outer_point_a, outer_line_a, data_index, fill_direction); - if (!only_fill_once) - fill_inner_triangle(pixel_writer, current_position, outer_point_b, outer_line_b, data_index, -fill_direction); + + fill_inner_triangle_decider(pixel_writer, current_position, line_start, line, normal_line, other_point, other_line, is_enlarged_triangle, data_index); + last_y = current_position.y; } current_position += step_size; @@ -135,9 +139,9 @@ namespace details { // check if we have to apply the fill_inner_triangle alg for the current row if (last_y != floor(current_position.y)) { - fill_inner_triangle(pixel_writer, current_position, outer_point_a, outer_line_a, data_index, fill_direction); - if (!only_fill_once) - fill_inner_triangle(pixel_writer, current_position, outer_point_b, outer_line_b, data_index, -fill_direction); + fill_inner_triangle_decider(pixel_writer, current_position, line_start, line, normal_line, other_point, other_line, is_enlarged_triangle, data_index); + + // fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index, fill_direction); } } @@ -162,31 +166,31 @@ namespace details { auto edge_middle_bottom = triangle[2] - triangle[1]; // top middle - dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, triangle[0], edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); + dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, { 0, 0 }, triangle[0], edge_top_bottom, triangle_index); // middle bottom - dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); - - // lastly we have to add endcaps on each original vertice position with the passed through distance - // add_end_cap(pixel_writer, triangle[0], triangle_index, distance); - // add_end_cap(pixel_writer, triangle[1], triangle_index, distance); - // add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, { 0, 0 }, triangle[0], edge_top_bottom, triangle_index); } template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index, float distance) { // In order to process enlarged triangles, the triangle is separated into 3 parts: top, middle and bottom - // top and bottom parts are rendered by calculating the bisector line of both normal vectors and rendering everything left and everyting right of this line by using a fill operation + // we first generate the normals for each edge and offset the triangle origins by the normal multiplied by the supplied distance to get 6 "enlarged" points that define the bigger triangle + + // top / bottom part: + // the top and bottom part of the triangle are rendered by going through each y step of the line with the shorter y value and filling the inner triangle until it reaches the other side of the + // triangle for the very top and very bottom of the triangle special considerations have to be done, otherwise we would render too much if the line on the other side of the triangle is not hit + // during a fill operation (= we want to fill above or below the line segment), we use the normal of the current line as the bound for the fill operation // for the middle part: - // if we enlarge the triangle by moving the edge along an edge normal we generate a gap between the top-middle and middle-bottom edge (if those lines are only translated) + // if we enlarge the triangle by moving the edge along an edge normal we generate a gap between the top-middle and middle-bottom edge (since those lines are only translated) // we have to somehow rasterize this gap between the bottom vertice of the translated top-middle edge and the top vertice of the middle-bottom edge. // in order to do this we draw a new edge between those vertices and rasterize everything between this new edge and the top-bottom edge. + // lastly we have to add endcaps on each original vertice position with the given distance + // in the worst case here we have one triangle line that is paralell to scan line // in this case it might be that we still have to consider that we are writing outside of the actual triangle !!!! -> find a solution!!! - // distance += sqrt(0.5); - auto edge_top_bottom = triangle[2] - triangle[0]; auto edge_top_middle = triangle[1] - triangle[0]; auto edge_middle_bottom = triangle[2] - triangle[1]; @@ -208,42 +212,44 @@ namespace details { } } - auto half_vector_top = (normal_top_bottom + normal_top_middle) / glm::vec2(2.0); - auto half_vector_bottom = (normal_top_bottom + normal_middle_bottom) / glm::vec2(2.0); - auto enlarged_top_bottom_origin = triangle[0] + normal_top_bottom * distance; - auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; - auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; + // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; // not needed + auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; - auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; - auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; - float top_y = std::min(std::min(triangle[0].y + half_vector_top.y, enlarged_top_bottom_origin.y), enlarged_top_middle_origin.y); - float bottom_y = std::max(std::max(triangle[2].y + half_vector_bottom.y, enlarged_top_bottom_end.y), enlarged_middle_bottom_end.y); + auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; + // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance;// not needed - auto topmost_renderable_point = glm::vec2(get_x_for_y_on_line(triangle[0], half_vector_top, top_y), top_y); - auto bottommost_renderable_point = glm::vec2(get_x_for_y_on_line(triangle[2], half_vector_bottom, bottom_y), bottom_y); + // { // DEBUG visualize enlarged points + // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; + // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; + // pixel_writer(triangle[0], triangle_index); + // pixel_writer(enlarged_top_bottom_origin, triangle_index); + // pixel_writer(enlarged_top_middle_origin, triangle_index); - auto top_bisector = glm::vec2(get_x_for_y_on_line(topmost_renderable_point, half_vector_top, enlarged_top_middle_end.y), enlarged_top_middle_end.y) - topmost_renderable_point; - auto middle_to_bottom_origin = glm::vec2(get_x_for_y_on_line(bottommost_renderable_point, half_vector_bottom, enlarged_middle_bottom_origin.y), enlarged_middle_bottom_origin.y); + // pixel_writer(triangle[1], triangle_index); + // pixel_writer(enlarged_middle_bottom_origin, triangle_index); + // pixel_writer(enlarged_top_middle_end, triangle_index); - auto middle_bisector = bottommost_renderable_point - middle_to_bottom_origin; + // pixel_writer(triangle[2], triangle_index); + // pixel_writer(enlarged_middle_bottom_end, triangle_index); + // pixel_writer(enlarged_top_bottom_end, triangle_index); + // } - // top to middle(1) - dda_triangle_line(pixel_writer, topmost_renderable_point, top_bisector, enlarged_top_bottom_origin, edge_top_bottom, enlarged_top_middle_origin, edge_top_middle, triangle_index); + // top middle + dda_triangle_line(pixel_writer, enlarged_top_middle_origin, edge_top_middle, normal_top_middle, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); - // middle(1) to middle(2) - dda_triangle_line( - pixel_writer, enlarged_top_middle_end, enlarged_middle_bottom_origin - enlarged_top_middle_end, enlarged_top_bottom_origin, edge_top_bottom, { 0, 0 }, { 0, 0 }, triangle_index); + // middle_top_part to middle_bottom_part + dda_triangle_line(pixel_writer, enlarged_top_middle_end, enlarged_middle_bottom_origin - enlarged_top_middle_end, { 0, 0 }, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); - // middle(2) to bottom - dda_triangle_line(pixel_writer, middle_to_bottom_origin, middle_bisector, enlarged_top_bottom_origin, edge_top_bottom, enlarged_middle_bottom_origin, edge_middle_bottom, triangle_index); + // middle bottom + dda_triangle_line(pixel_writer, enlarged_middle_bottom_origin, edge_middle_bottom, normal_middle_bottom, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); - // lastly we have to add endcaps on each original vertice position with the passed through distance - // add_end_cap(pixel_writer, triangle[0], triangle_index, distance); - // add_end_cap(pixel_writer, triangle[1], triangle_index, distance); - // add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + // endcaps + add_end_cap(pixel_writer, triangle[0], triangle_index, distance); + add_end_cap(pixel_writer, triangle[1], triangle_index, distance); + add_end_cap(pixel_writer, triangle[2], triangle_index, distance); } template diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 210c9ac4..1a7cf5a0 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -122,8 +122,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output.png")); } SECTION("rasterize triangle small") @@ -140,10 +140,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); // image.save(QString("rasterizer_output_small.png")); - // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // image2.save(QString("rasterizer_output_small_sdf.png")); } SECTION("rasterize triangle smallest") @@ -161,10 +159,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); // image.save(QString("rasterizer_output_smallest.png")); - // auto image2 = nucleus::tile::conversion::u8raster_to_qimage(output2); - // image2.save(QString("rasterizer_output_smallest_sdf.png")); } SECTION("rasterize triangle enlarged endcaps only") @@ -201,11 +197,21 @@ TEST_CASE("nucleus/rasterizer") SECTION("rasterize triangle enlarged") { // less than one pixel - const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + const std::vector triangles = { glm::vec2(30.5, 10.5), + glm::vec2(10.5, 30.5), + glm::vec2(50.5, 50.5), + + glm::vec2(5.5, 5.5), + glm::vec2(15.5, 10.5), + glm::vec2(5.5, 15.5), + + glm::vec2(15.5, 50.5), + glm::vec2(5.5, 50.5), + glm::vec2(10.5, 60.5) }; // const std::vector triangles = { glm::vec2(5.35, 5.35), glm::vec2(5.40, 5.40), glm::vec2(5.45, 5.35) }; auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); - float distance = 4.0; + float distance = 5.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { @@ -221,7 +227,7 @@ TEST_CASE("nucleus/rasterizer") }; nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); - // CHECK(output.buffer() == output2.buffer()); + CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); From bfc15e6f1b530f0002e6d8da717d2a2be544e228 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Wed, 27 Nov 2024 15:40:55 +0100 Subject: [PATCH 06/33] Vector layer - rasterizer - triangles with horizontal lines are now rendered correctly --- nucleus/utils/rasterizer.h | 140 +++++++++++++++++++------------ unittests/nucleus/rasterizer.cpp | 63 ++++++++++++-- 2 files changed, 142 insertions(+), 61 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index aace24ae..2d0122ae 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -30,7 +30,7 @@ namespace nucleus::utils::rasterizer { // TODOs: // - enlarge triangles -// - current problem: horizontal triangle lines +// - current problem: narrow triangles // - line renderer // sdf box filter instead by circle to only consider values of the current cell @@ -74,43 +74,21 @@ namespace details { } } - template - inline void fill_inner_triangle_decider(const PixelWriterFunction& pixel_writer, - const glm::vec2& current_position, - const glm::vec2& line_start, - const glm::vec2& line, - const glm::vec2& normal_line, - const glm::vec2& other_point, - const glm::vec2& other_line, - bool is_enlarged_triangle, - unsigned int data_index) - { - // before we fill the inner triangle we want to first decide the line to which we want to fill. - // for enlarged triangles, if we render the top most or the bottom most part of the triangle, - // it can happen that we fill it too much, in those cases we only want to fill to the normal - if (is_enlarged_triangle) { - if (current_position.y > other_point.y + other_line.y) // fill only to the normal line located at the bottom - fill_inner_triangle(pixel_writer, current_position, line_start + line, normal_line, data_index); - else if (current_position.y < other_point.y) // fill only to the normal line - fill_inner_triangle(pixel_writer, current_position, line_start, normal_line, data_index); - else // normal fill operation - fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); - } else { // normal fill operation - fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); - } - } - // pixel_writer, topmost_renderable_point, top_bisector, // enlarged_top_bottom_origin, edge_top_bottom, // enlarged_top_middle_origin, edge_top_middle, triangle_index template void dda_triangle_line(const PixelWriterFunction& pixel_writer, + unsigned int data_index, const glm::vec2& line_start, const glm::vec2& line, - const glm::vec2& normal_line, const glm::vec2& other_point, const glm::vec2& other_line, - unsigned int data_index) + const glm::vec2& normal_line = { 0, 0 }, + const glm::vec2& fallback_point_upper = { 0, 0 }, + const glm::vec2& fallback_line_upper = { 0, 0 }, + const glm::vec2& fallback_point_lower = { 0, 0 }, + const glm::vec2& fallback_line_lower = { 0, 0 }) { glm::vec2 step_size; @@ -128,7 +106,13 @@ namespace details { if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value - fill_inner_triangle_decider(pixel_writer, current_position, line_start, line, normal_line, other_point, other_line, is_enlarged_triangle, data_index); + // decide whether or not to fill only to the fallback line + if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) + fill_inner_triangle(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); + else if (is_enlarged_triangle && (current_position.y < other_point.y)) + fill_inner_triangle(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); + else // normal fill operation + fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); last_y = current_position.y; } @@ -139,9 +123,13 @@ namespace details { // check if we have to apply the fill_inner_triangle alg for the current row if (last_y != floor(current_position.y)) { - fill_inner_triangle_decider(pixel_writer, current_position, line_start, line, normal_line, other_point, other_line, is_enlarged_triangle, data_index); - - // fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index, fill_direction); + // fill only to the fallback line + if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) + fill_inner_triangle(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); + else if (is_enlarged_triangle && (current_position.y < other_point.y)) + fill_inner_triangle(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); + else // normal fill operation + fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); } } @@ -166,9 +154,9 @@ namespace details { auto edge_middle_bottom = triangle[2] - triangle[1]; // top middle - dda_triangle_line(pixel_writer, triangle[0], edge_top_middle, { 0, 0 }, triangle[0], edge_top_bottom, triangle_index); + dda_triangle_line(pixel_writer, triangle_index, triangle[0], edge_top_middle, triangle[0], edge_top_bottom); // middle bottom - dda_triangle_line(pixel_writer, triangle[1], edge_middle_bottom, { 0, 0 }, triangle[0], edge_top_bottom, triangle_index); + dda_triangle_line(pixel_writer, triangle_index, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom); } template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index, float distance) @@ -219,37 +207,79 @@ namespace details { auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; - // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance;// not needed - - // { // DEBUG visualize enlarged points - // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; - // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; - // pixel_writer(triangle[0], triangle_index); - // pixel_writer(enlarged_top_bottom_origin, triangle_index); - // pixel_writer(enlarged_top_middle_origin, triangle_index); - - // pixel_writer(triangle[1], triangle_index); - // pixel_writer(enlarged_middle_bottom_origin, triangle_index); - // pixel_writer(enlarged_top_middle_end, triangle_index); - - // pixel_writer(triangle[2], triangle_index); - // pixel_writer(enlarged_middle_bottom_end, triangle_index); - // pixel_writer(enlarged_top_bottom_end, triangle_index); - // } + auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; // top middle - dda_triangle_line(pixel_writer, enlarged_top_middle_origin, edge_top_middle, normal_top_middle, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); + dda_triangle_line(pixel_writer, + triangle_index, + enlarged_top_middle_origin, + edge_top_middle, + enlarged_top_bottom_origin, + edge_top_bottom, + normal_top_middle, + enlarged_middle_bottom_end, + normal_middle_bottom, + enlarged_top_middle_origin, + normal_top_middle); // middle_top_part to middle_bottom_part - dda_triangle_line(pixel_writer, enlarged_top_middle_end, enlarged_middle_bottom_origin - enlarged_top_middle_end, { 0, 0 }, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); + auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); + + dda_triangle_line(pixel_writer, + triangle_index, + enlarged_top_middle_end, + half_middle_line, + enlarged_top_bottom_origin, + edge_top_bottom, + normal_top_middle, + enlarged_middle_bottom_end, + normal_middle_bottom, + enlarged_top_middle_origin, + normal_top_middle); + dda_triangle_line(pixel_writer, + triangle_index, + enlarged_top_middle_end + half_middle_line, + half_middle_line, + enlarged_top_bottom_origin, + edge_top_bottom, + normal_middle_bottom, + enlarged_middle_bottom_end, + normal_middle_bottom, + enlarged_top_middle_origin, + normal_top_middle); // middle bottom - dda_triangle_line(pixel_writer, enlarged_middle_bottom_origin, edge_middle_bottom, normal_middle_bottom, enlarged_top_bottom_origin, edge_top_bottom, triangle_index); + dda_triangle_line(pixel_writer, + triangle_index, + enlarged_middle_bottom_origin, + edge_middle_bottom, + enlarged_top_bottom_origin, + edge_top_bottom, + normal_middle_bottom, + enlarged_middle_bottom_end, + normal_middle_bottom, + enlarged_top_middle_origin, + normal_top_middle); // endcaps add_end_cap(pixel_writer, triangle[0], triangle_index, distance); add_end_cap(pixel_writer, triangle[1], triangle_index, distance); add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + + // { // DEBUG visualize enlarged points + // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; + // pixel_writer(triangle[0], 50); + // pixel_writer(enlarged_top_bottom_origin, 50); + // pixel_writer(enlarged_top_middle_origin, 50); + + // pixel_writer(triangle[1], 50); + // pixel_writer(enlarged_middle_bottom_origin, 50); + // pixel_writer(enlarged_top_middle_end, 50); + + // pixel_writer(triangle[2], 50); + // pixel_writer(enlarged_middle_bottom_end, 50); + // pixel_writer(enlarged_top_bottom_end, 50); + // } } template diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 1a7cf5a0..e718055c 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -196,18 +196,13 @@ TEST_CASE("nucleus/rasterizer") SECTION("rasterize triangle enlarged") { - // less than one pixel const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), - glm::vec2(5.5, 15.5), - - glm::vec2(15.5, 50.5), - glm::vec2(5.5, 50.5), - glm::vec2(10.5, 60.5) }; + glm::vec2(5.5, 15.5) }; // const std::vector triangles = { glm::vec2(5.35, 5.35), glm::vec2(5.40, 5.40), glm::vec2(5.45, 5.35) }; auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); @@ -233,4 +228,60 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_enlarged.png")); } + + SECTION("rasterize triangle horizontal") + { + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(45.5, 45.5), glm::vec2(10.5, 45.5) }; + auto size = glm::vec2(64, 64); + nucleus::Raster output(size, 0u); + float distance = 4.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_horizontal.png")); + } + + SECTION("rasterize triangle narrow") + { + const std::vector triangles = { glm::vec2(6.5, 6.5), glm::vec2(6.5, 10.5), glm::vec2(64.5, 8.5) }; + auto size = glm::vec2(64, 64); + nucleus::Raster output(size, 0u); + float distance = 2.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_narrow.png")); + } } From 928975d165a6569dd4c76ef54cb54857111c305e Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Thu, 28 Nov 2024 10:14:45 +0100 Subject: [PATCH 07/33] Vector layer - rasterizer: - narrow triangle test fix - improved documentation and naming conventions - started line rasterizer --- nucleus/utils/rasterizer.h | 413 +++++++++++++++++++++++-------- unittests/nucleus/rasterizer.cpp | 107 ++++++-- 2 files changed, 399 insertions(+), 121 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 2d0122ae..1a99e95f 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -17,52 +17,65 @@ *****************************************************************************/ #include -#include #include #include namespace nucleus::utils::rasterizer { -// Possible future improvements: -// - DDA -> prevent fill_inner_triangle to be called twice for the same row (if we switch from one line to another) -// - also check overall how often each pixel is written to +/* + * Possible future improvements: + * - check how often each pixel is written to -> maybe we can improve performance here + * - sdf box filter instead by circle to only consider values of the current cell + * - calculate_dda_steps currently doubles the steps to prevent missing certain pixels (this might be improved) + */ // TODOs: -// - enlarge triangles -// - current problem: narrow triangles // - line renderer -// sdf box filter instead by circle to only consider values of the current cell namespace details { - inline float get_x_for_y_on_line(const glm::vec2 origin, const glm::vec2 line, float y) + /* + * Calculates the x value for a given y. + * The resulting x value will be on the line. + */ + inline float get_x_for_y_on_line(const glm::vec2 point_on_line, const glm::vec2 line, float y) { - // pos_on_line = origin + t * line - float t = (y - origin.y) / line.y; + // pos_on_line = point_on_line + t * line + float t = (y - point_on_line.y) / line.y; - return origin.x + t * line.x; + return point_on_line.x + t * line.x; } + /* + * Calculates the steps and step_size of the given line. + * the "bigger dimension" (-> line is bigger in x or y direction) has a step size of 0.5 + * the other dimension has a step size <= 0.5 + */ inline std::pair calculate_dda_steps(const glm::vec2 line) { - int steps = ceil(fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y)); - // should only be triggered if two vertices of a triangle are exactly on the same position - // -> the input data is wrong + float steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); + + // should only be triggered if two vertices of a triangle are exactly on the same position -> the input data is wrong assert(steps > 0); // steps = std::max(steps, 1); // alternative force at least one dda step -> wrong output but it should at least "work" auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); - // double the steps -> otherwise we might miss important pixels + // double the steps -> otherwise we might miss important pixels ( see "rasterize triangle small" unittest for use case) + // it might be possible to capture the special cases during the line traversal and render an extra pixel + // -> possible performance improvement steps *= 2; step_size /= 2.0f; - return std::make_pair(step_size, steps); + return std::make_pair(step_size, ceil(steps)); } + /* + * fills a scan line from current position to x position of given other line + */ template - inline void fill_inner_triangle(const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& other_point, const glm::vec2& other_line, unsigned int data_index) + inline void fill_to_line(const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& other_point, const glm::vec2& other_line, unsigned int data_index) { // calculate the x value of the other line int x = get_x_for_y_on_line(other_point, other_line, current_position.y); @@ -74,23 +87,23 @@ namespace details { } } - // pixel_writer, topmost_renderable_point, top_bisector, - // enlarged_top_bottom_origin, edge_top_bottom, - // enlarged_top_middle_origin, edge_top_middle, triangle_index + /* + * applies dda algorithm to the given line and fills the area in between this line and the given other line + * if the other line is not located on the current scan line -> we fill to the given fall back lines + */ template - void dda_triangle_line(const PixelWriterFunction& pixel_writer, + void dda_render_line(const PixelWriterFunction& pixel_writer, unsigned int data_index, const glm::vec2& line_start, const glm::vec2& line, - const glm::vec2& other_point, - const glm::vec2& other_line, - const glm::vec2& normal_line = { 0, 0 }, + const glm::vec2& other_point = { 0, 0 }, + const glm::vec2& other_line = { 0, 0 }, + const glm::vec2& normal_line = { 0, 0 }, // TODO possible to change this to a bool? const glm::vec2& fallback_point_upper = { 0, 0 }, const glm::vec2& fallback_line_upper = { 0, 0 }, const glm::vec2& fallback_point_lower = { 0, 0 }, const glm::vec2& fallback_line_lower = { 0, 0 }) { - glm::vec2 step_size; int steps; std::tie(step_size, steps) = calculate_dda_steps(line); @@ -98,21 +111,22 @@ namespace details { int last_y = 0; const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; + const bool fill = other_line != glm::vec2 { 0, 0 }; // no other line given? -> no fill needed glm::vec2 current_position = line_start; for (int j = 0; j < steps; j++) { pixel_writer(current_position, data_index); - if (int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value + if (fill && int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value // decide whether or not to fill only to the fallback line if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) - fill_inner_triangle(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); + fill_to_line(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); else if (is_enlarged_triangle && (current_position.y < other_point.y)) - fill_inner_triangle(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); + fill_to_line(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); else // normal fill operation - fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); + fill_to_line(pixel_writer, current_position, other_point, other_line, data_index); last_y = current_position.y; } @@ -121,19 +135,22 @@ namespace details { current_position -= step_size; - // check if we have to apply the fill_inner_triangle alg for the current row - if (last_y != floor(current_position.y)) { + // check if we have to apply the fill_to_line alg for the current row + if (fill && last_y != floor(current_position.y)) { // fill only to the fallback line if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) - fill_inner_triangle(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); + fill_to_line(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); else if (is_enlarged_triangle && (current_position.y < other_point.y)) - fill_inner_triangle(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); + fill_to_line(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); else // normal fill operation - fill_inner_triangle(pixel_writer, current_position, other_point, other_line, data_index); + fill_to_line(pixel_writer, current_position, other_point, other_line, data_index); } } - template void add_end_cap(const PixelWriterFunction& pixel_writer, const glm::vec2 position, unsigned int data_index, float distance) + /* + * renders a circle at the given position + */ + template void add_circle_end_cap(const PixelWriterFunction& pixel_writer, const glm::vec2 position, unsigned int data_index, float distance) { distance += sqrt(0.5); float distance_test = ceil(distance); @@ -147,41 +164,42 @@ namespace details { } } - template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index) + /* + * In order to process enlarged triangles, the triangle is separated into 3 parts: top, middle and bottom + * we first generate the normals for each edge and offset the triangle origins by the normal multiplied by the supplied distance to get 6 "enlarged" points that define the bigger triangle + * + * top / bottom part: + * the top and bottom part of the triangle are rendered by going through each y step of the line with the shorter y value and filling the inner triangle until it reaches the other side of the + * triangle for the very top and very bottom of the triangle special considerations have to be done, otherwise we would render too much if the line on the other side of the triangle is not hit + * during a fill operation (= we want to fill above or below the line segment), we use the normal of the current line as the bound for the fill operation + * + * for the middle part: + * if we enlarge the triangle by moving the edge along an edge normal we generate a gap between the top-middle and middle-bottom edge (since those lines are only translated) + * we have to somehow rasterize this gap between the bottom vertice of the translated top-middle edge and the top vertice of the middle-bottom edge. + * in order to do this we draw a new edge between those vertices and rasterize everything between this new edge and the top-bottom edge. + * + * lastly we have to add endcaps on each original vertice position with the given distance + */ + template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) { + + assert(triangle[0].y <= triangle[1].y); + assert(triangle[1].y <= triangle[2].y); + auto edge_top_bottom = triangle[2] - triangle[0]; auto edge_top_middle = triangle[1] - triangle[0]; auto edge_middle_bottom = triangle[2] - triangle[1]; - // top middle - dda_triangle_line(pixel_writer, triangle_index, triangle[0], edge_top_middle, triangle[0], edge_top_bottom); - // middle bottom - dda_triangle_line(pixel_writer, triangle_index, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom); - } - - template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangle, unsigned int triangle_index, float distance) - { - // In order to process enlarged triangles, the triangle is separated into 3 parts: top, middle and bottom - // we first generate the normals for each edge and offset the triangle origins by the normal multiplied by the supplied distance to get 6 "enlarged" points that define the bigger triangle - - // top / bottom part: - // the top and bottom part of the triangle are rendered by going through each y step of the line with the shorter y value and filling the inner triangle until it reaches the other side of the - // triangle for the very top and very bottom of the triangle special considerations have to be done, otherwise we would render too much if the line on the other side of the triangle is not hit - // during a fill operation (= we want to fill above or below the line segment), we use the normal of the current line as the bound for the fill operation + if (distance == 0.0) { + // top middle + dda_render_line(pixel_writer, triangle_index, triangle[0], edge_top_middle, triangle[0], edge_top_bottom); + // middle bottom + dda_render_line(pixel_writer, triangle_index, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom); - // for the middle part: - // if we enlarge the triangle by moving the edge along an edge normal we generate a gap between the top-middle and middle-bottom edge (since those lines are only translated) - // we have to somehow rasterize this gap between the bottom vertice of the translated top-middle edge and the top vertice of the middle-bottom edge. - // in order to do this we draw a new edge between those vertices and rasterize everything between this new edge and the top-bottom edge. - - // lastly we have to add endcaps on each original vertice position with the given distance + return; // finished + } - // in the worst case here we have one triangle line that is paralell to scan line - // in this case it might be that we still have to consider that we are writing outside of the actual triangle !!!! -> find a solution!!! - - auto edge_top_bottom = triangle[2] - triangle[0]; - auto edge_top_middle = triangle[1] - triangle[0]; - auto edge_middle_bottom = triangle[2] - triangle[1]; + // we need to render an enlarged triangle auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); @@ -210,7 +228,7 @@ namespace details { auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; // top middle - dda_triangle_line(pixel_writer, + dda_render_line(pixel_writer, triangle_index, enlarged_top_middle_origin, edge_top_middle, @@ -222,10 +240,10 @@ namespace details { enlarged_top_middle_origin, normal_top_middle); - // middle_top_part to middle_bottom_part + // // middle_top_part to middle_bottom_part auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); - dda_triangle_line(pixel_writer, + dda_render_line(pixel_writer, triangle_index, enlarged_top_middle_end, half_middle_line, @@ -236,7 +254,7 @@ namespace details { normal_middle_bottom, enlarged_top_middle_origin, normal_top_middle); - dda_triangle_line(pixel_writer, + dda_render_line(pixel_writer, triangle_index, enlarged_top_middle_end + half_middle_line, half_middle_line, @@ -249,7 +267,7 @@ namespace details { normal_top_middle); // middle bottom - dda_triangle_line(pixel_writer, + dda_render_line(pixel_writer, triangle_index, enlarged_middle_bottom_origin, edge_middle_bottom, @@ -262,9 +280,9 @@ namespace details { normal_top_middle); // endcaps - add_end_cap(pixel_writer, triangle[0], triangle_index, distance); - add_end_cap(pixel_writer, triangle[1], triangle_index, distance); - add_end_cap(pixel_writer, triangle[2], triangle_index, distance); + add_circle_end_cap(pixel_writer, triangle[0], triangle_index, distance); + add_circle_end_cap(pixel_writer, triangle[1], triangle_index, distance); + add_circle_end_cap(pixel_writer, triangle[2], triangle_index, distance); // { // DEBUG visualize enlarged points // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; @@ -282,20 +300,157 @@ namespace details { // } } - template - void triangle_sdf(const PixelWriterFunction& pixel_writer, const glm::vec2 current_position, const std::array points, unsigned int data_index, float distance) + template void dda_line(const PixelWriterFunction& pixel_writer, const std::array line_points, unsigned int line_index, float distance) { - // https://iquilezles.org/articles/distfunctions2d/ + // edge from top to bottom + glm::vec2 origin; + glm::vec2 edge; + if (line_points[0].y > line_points[1].y) { + edge = line_points[0] - line_points[1]; + origin = line_points[1]; + } else { + edge = line_points[1] - line_points[0]; + origin = line_points[0]; + } + + if (distance == 0.0) { + dda_render_line(pixel_writer, line_index, origin, edge); - glm::vec2 e0 = points[1] - points[0], e1 = points[2] - points[1], e2 = points[0] - points[2]; - float s = glm::sign(e0.x * e2.y - e0.y * e2.x); + pixel_writer(line_points[0], 50); + pixel_writer(line_points[1], 50); + + return; // finished + } + + // we need to render an enlarged triangle + + // auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); + // auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); + // auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); + + // { // swap normal direction if they are incorrect (pointing to center) + // glm::vec2 centroid = (triangle[0] + triangle[1] + triangle[2]) / 3.0f; + // if (glm::dot(normal_top_bottom, centroid - triangle[0]) > 0) { + // normal_top_bottom *= -1; + // } + // if (glm::dot(normal_top_middle, centroid - triangle[0]) > 0) { + // normal_top_middle *= -1; + // } + // if (glm::dot(normal_middle_bottom, centroid - triangle[1]) > 0) { + // normal_middle_bottom *= -1; + // } + // } + + // auto enlarged_top_bottom_origin = triangle[0] + normal_top_bottom * distance; + // // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; // not needed + + // auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; + // auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; + + // auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; + // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; + + // // top middle + // dda_render_line(pixel_writer, + // line_index, + // enlarged_top_middle_origin, + // edge_top_middle, + // enlarged_top_bottom_origin, + // edge_top_bottom, + // normal_top_middle, + // enlarged_middle_bottom_end, + // normal_middle_bottom, + // enlarged_top_middle_origin, + // normal_top_middle); + + // // // middle_top_part to middle_bottom_part + // auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); + + // dda_render_line(pixel_writer, + // line_index, + // enlarged_top_middle_end, + // half_middle_line, + // enlarged_top_bottom_origin, + // edge_top_bottom, + // normal_top_middle, + // enlarged_middle_bottom_end, + // normal_middle_bottom, + // enlarged_top_middle_origin, + // normal_top_middle); + // dda_render_line(pixel_writer, + // line_index, + // enlarged_top_middle_end + half_middle_line, + // half_middle_line, + // enlarged_top_bottom_origin, + // edge_top_bottom, + // normal_middle_bottom, + // enlarged_middle_bottom_end, + // normal_middle_bottom, + // enlarged_top_middle_origin, + // normal_top_middle); + + // // middle bottom + // dda_render_line(pixel_writer, + // line_index, + // enlarged_middle_bottom_origin, + // edge_middle_bottom, + // enlarged_top_bottom_origin, + // edge_top_bottom, + // normal_middle_bottom, + // enlarged_middle_bottom_end, + // normal_middle_bottom, + // enlarged_top_middle_origin, + // normal_top_middle); + + // // endcaps + // add_circle_end_cap(pixel_writer, triangle[0], line_index, distance); + // add_circle_end_cap(pixel_writer, triangle[1], line_index, distance); + // add_circle_end_cap(pixel_writer, triangle[2], line_index, distance); + + // { // DEBUG visualize enlarged points + // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; + // pixel_writer(triangle[0], 50); + // pixel_writer(enlarged_top_bottom_origin, 50); + // pixel_writer(enlarged_top_middle_origin, 50); + + // pixel_writer(triangle[1], 50); + // pixel_writer(enlarged_middle_bottom_origin, 50); + // pixel_writer(enlarged_top_middle_end, 50); + + // pixel_writer(triangle[2], 50); + // pixel_writer(enlarged_middle_bottom_end, 50); + // pixel_writer(enlarged_top_bottom_end, 50); + // } + } + + /* + * calculates how far away the given position is from a triangle + * uses distance to shift the current triangle distance + * if it is within the triangle, the pixel writer function is called + * + * uses triangle distance calculation from: https://iquilezles.org/articles/distfunctions2d/ + */ + template + void triangle_sdf(const PixelWriterFunction& pixel_writer, + const glm::vec2 current_position, + const std::array points, + const std::array edges, + float s, + unsigned int data_index, + float distance) + { + // glm::vec2 v0 = current_position - points[0], v1 = current_position - points[1], v2 = current_position - points[2]; - glm::vec2 pq0 = v0 - e0 * glm::clamp(glm::dot(v0, e0) / glm::dot(e0, e0), 0.0f, 1.0f); - glm::vec2 pq1 = v1 - e1 * glm::clamp(glm::dot(v1, e1) / glm::dot(e1, e1), 0.0f, 1.0f); - glm::vec2 pq2 = v2 - e2 * glm::clamp(glm::dot(v2, e2) / glm::dot(e2, e2), 0.0f, 1.0f); - glm::vec2 d - = min(min(glm::vec2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)), glm::vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))), glm::vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x))); + + // pq# = distance from edge # + glm::vec2 pq0 = v0 - edges[0] * glm::clamp(glm::dot(v0, edges[0]) / glm::dot(edges[0], edges[0]), 0.0f, 1.0f); + glm::vec2 pq1 = v1 - edges[1] * glm::clamp(glm::dot(v1, edges[1]) / glm::dot(edges[1], edges[1]), 0.0f, 1.0f); + glm::vec2 pq2 = v2 - edges[2] * glm::clamp(glm::dot(v2, edges[2]) / glm::dot(edges[2], edges[2]), 0.0f, 1.0f); + // d.x == squared distance to triangle edge/vertice + // d.y == are we inside or outside triangle + glm::vec2 d = min(min(glm::vec2(dot(pq0, pq0), s * (v0.x * edges[0].y - v0.y * edges[0].x)), glm::vec2(dot(pq1, pq1), s * (v1.x * edges[1].y - v1.y * edges[1].x))), + glm::vec2(dot(pq2, pq2), s * (v2.x * edges[2].y - v2.y * edges[2].x))); float dist_from_tri = -sqrt(d.x) * glm::sign(d.y); if (dist_from_tri < distance) { @@ -303,45 +458,103 @@ namespace details { } } + /* + * calculates how far away the given position is from a line segment + * uses distance to shift the current triangle distance + * if it is within the triangle, the pixel writer function is called + * + * uses line segment distance calculation from: https://iquilezles.org/articles/distfunctions2d/ + */ + template + void line_sdf(const PixelWriterFunction& pixel_writer, const glm::vec2 current_position, const glm::vec2 origin, glm::vec2 edge, unsigned int data_index, float distance) + { + glm::vec2 v0 = current_position - origin; + + float dist_from_line = length(v0 - edge * glm::clamp(glm::dot(v0, edge) / glm::dot(edge, edge), 0.0f, 1.0f)); + + if (dist_from_line < distance) { + pixel_writer(current_position, data_index); + } + } + } // namespace details -// triangulizes polygons and orders the vertices by y position per triangle -// output: top, middle, bottom, top, middle,... +/* + * triangulizes polygons and orders the vertices by y position per triangle + * output: top, middle, bottom, top, middle,... + */ std::vector triangulize(std::vector polygons); -// ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size -// in this method every pixel traverses every triangle -template void rasterize_triangle_sdf(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance, const glm::ivec2 grid_size) +/* + * ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size + * in this method we traverse through every triangle and generate the bounding box and traverse the bounding box + */ +template void rasterize_triangle_sdf(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance) +{ + // we sample from the center + // and have a radius of a half pixel diagonal + // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! + distance += sqrt(0.5); + + for (size_t i = 0; i < triangles.size() / 3; ++i) { + const std::array points = { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }; + const std::array edges = { points[1] - points[0], points[2] - points[1], points[0] - points[2] }; + + auto min_bound = min(min(points[0], points[1]), points[2]) - glm::vec2(distance); + auto max_bound = max(max(points[0], points[1]), points[2]) + glm::vec2(distance); + + float s = glm::sign(edges[0].x * edges[2].y - edges[0].y * edges[2].x); + + for (size_t x = min_bound.x; x < max_bound.x; x++) { + for (size_t y = min_bound.y; y < max_bound.y; y++) { + auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; + + details::triangle_sdf(pixel_writer, current_position, points, edges, s, i, distance); + } + } + } +} + +template void rasterize_line_sdf(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance) { // we sample from the center // and have a radius of a half pixel diagonal - // this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel + // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! distance += sqrt(0.5); - for (uint8_t i = 0; i < grid_size.x; i++) { - for (uint8_t j = 0; j < grid_size.y; j++) { - auto current_position = glm::vec2 { i + 0.5, j + 0.5 }; + for (size_t i = 0; i < line_points.size() - 1; ++i) { + const std::array points = { line_points[i + 0], line_points[i + 1] }; + auto edge = points[1] - points[0]; + + auto min_bound = min(points[0], points[1]) - glm::vec2(distance); + auto max_bound = max(points[0], points[1]) + glm::vec2(distance); - for (size_t i = 0; i < triangles.size() / 3; ++i) { - details::triangle_sdf(pixel_writer, current_position, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); + for (size_t x = min_bound.x; x < max_bound.x; x++) { + for (size_t y = min_bound.y; y < max_bound.y; y++) { + auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; + + details::line_sdf(pixel_writer, current_position, points[0], edge, i, distance); } } } } -// ideal for many triangles. especially if each triangle only covers small parts of the raster -// in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. +/* + * ideal for many triangles. especially if each triangle only covers small parts of the raster + * in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. + */ template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) { for (size_t i = 0; i < triangles.size() / 3; ++i) { - if (distance == 0.0) - details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i); - else - details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); + details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); } } -// template -// void rasterize_line(const PixelWriterFunction& pixel_writer, std::vector line_points); +template void rasterize_line(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance = 0.0) +{ + for (size_t i = 0; i < line_points.size() - 1; ++i) { + details::dda_line(pixel_writer, { line_points[i + 0], line_points[i + 1] }, i, distance); + } +} } // namespace nucleus::utils::rasterizer diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index e718055c..ce918651 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -30,6 +30,12 @@ TEST_CASE("nucleus/rasterizer") { + // NOTE: most tests compare the dda renderer with the sdf renderer + // unfortunatly the sdf renderer can produce false positives (writing to cell that should be empty if near edge) + // those errors have all been subverted by choosing parameters that work with both renderers + // but future changes might cause problems, when there shouldn't be problems + // so if a test fails check the debug images to see what exactly went wrong. + SECTION("Triangulation") { // 5 point polygon @@ -117,13 +123,13 @@ TEST_CASE("nucleus/rasterizer") nucleus::Raster output2({ 64, 64 }, 0u); const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0); CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output.png")); } SECTION("rasterize triangle small") @@ -135,13 +141,13 @@ TEST_CASE("nucleus/rasterizer") nucleus::Raster output2({ 6, 6 }, 0u); const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0); CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_small.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_small.png")); } SECTION("rasterize triangle smallest") @@ -154,7 +160,7 @@ TEST_CASE("nucleus/rasterizer") nucleus::Raster output2({ 64, 64 }, 0u); const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0); CHECK(output.buffer() == output2.buffer()); @@ -176,16 +182,16 @@ TEST_CASE("nucleus/rasterizer") if (bounds.contains(pos)) output.pixel(pos) = 255; }; - nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[0], 1, distance); - nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[1], 1, distance); - nucleus::utils::rasterizer::details::add_end_cap(pixel_writer, triangles[2], 1, distance); + nucleus::utils::rasterizer::details::add_circle_end_cap(pixel_writer, triangles[0], 1, distance); + nucleus::utils::rasterizer::details::add_circle_end_cap(pixel_writer, triangles[1], 1, distance); + nucleus::utils::rasterizer::details::add_circle_end_cap(pixel_writer, triangles[2], 1, distance); nucleus::Raster output2(size, 0u); const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -220,7 +226,7 @@ TEST_CASE("nucleus/rasterizer") if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -248,40 +254,99 @@ TEST_CASE("nucleus/rasterizer") if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged_horizontal.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_horizontal.png")); } - SECTION("rasterize triangle narrow") + // can't be verified exactly, since SDF is currently slightly inaccurate + // SECTION("rasterize triangle narrow") + // { + // const std::vector triangles = { glm::vec2(6.5, 16.5), glm::vec2(55.5, 18.5), glm::vec2(6.5, 20.5), glm::vec2(55.5, 46.5), glm::vec2(6.5, 48.5), glm::vec2(55.5, 50.5) }; + // auto size = glm::vec2(64, 64); + // nucleus::Raster output(size, 0u); + // float distance = 5.0; + // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + // const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + // if (bounds.contains(pos)) + // output.pixel(pos) = 255; + // }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + + // nucleus::Raster output2(size, 0u); + // const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + // if (bounds.contains(pos)) + // output2.pixel(pos) = 255; + // }; + // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + + // CHECK(output.buffer() == output2.buffer()); + + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_narrow.png")); + // } + + SECTION("rasterize line straight") { - const std::vector triangles = { glm::vec2(6.5, 6.5), glm::vec2(6.5, 10.5), glm::vec2(64.5, 8.5) }; + const std::vector line = { glm::vec2(10.5, 10.5), glm::vec2(10.5, 50.5), glm::vec2(50.5, 50.5), glm::vec2(50.5, 10.5), glm::vec2(10.5, 10.5) }; auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); - float distance = 2.0; + float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance, output2.size()); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged_narrow.png")); + image.save(QString("rasterizer_output_line_straight.png")); } + + // SECTION("rasterize line diagonal") + // { + // const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; + // // const std::vector line = { glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5) }; + // auto size = glm::vec2(64, 64); + // nucleus::Raster output(size, 0u); + // float distance = 0.0; + // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + // const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + // if (bounds.contains(pos)) + // // output.pixel(pos) = data == 50 ? 255 : 100; + // output.pixel(pos) = 255; + // }; + // nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); + + // nucleus::Raster output2(size, 0u); + // const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + // if (bounds.contains(pos)) + // output2.pixel(pos) = 255; + // }; + // nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + + // CHECK(output.buffer() == output2.buffer()); + + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_diagonal.png")); + // } } From 48b4f3966d8d3d1045a756692b3495764b9696fe Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 2 Dec 2024 07:47:18 +0100 Subject: [PATCH 08/33] Vector layer - Rasterizer - more exact triangle and line rendering (for subpixels) --- nucleus/utils/rasterizer.h | 281 +++++++++++++++++++++++-------- unittests/nucleus/rasterizer.cpp | 144 +++++++++++----- 2 files changed, 311 insertions(+), 114 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 1a99e95f..0d1c365a 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -19,6 +19,8 @@ #include #include +#include + #include namespace nucleus::utils::rasterizer { @@ -52,23 +54,37 @@ namespace details { * the "bigger dimension" (-> line is bigger in x or y direction) has a step size of 0.5 * the other dimension has a step size <= 0.5 */ - inline std::pair calculate_dda_steps(const glm::vec2 line) - { - float steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); + // inline std::pair calculate_dda_steps(const glm::vec2 line) + // { + // float steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); + + // // should only be triggered if two vertices of a triangle are exactly on the same position -> the input data is wrong + // assert(steps > 0); + // // steps = std::max(steps, 1); // alternative force at least one dda step -> wrong output but it should at least "work" + + // auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); - // should only be triggered if two vertices of a triangle are exactly on the same position -> the input data is wrong - assert(steps > 0); - // steps = std::max(steps, 1); // alternative force at least one dda step -> wrong output but it should at least "work" + // // double the steps -> otherwise we might miss important pixels ( see "rasterize triangle small" unittest for use case) + // // it might be possible to capture the special cases during the line traversal and render an extra pixel + // // -> possible performance improvement + // steps *= 2; + // step_size /= 2.0f; - auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); + // // TODO here: possible solution: dont render anything if the current point is exactly on integer values (e.g. {2, 2]) + + // std::cout << step_size.x << " " << step_size.y << " " << ceil(steps) << std::endl; + // return std::make_pair(step_size, ceil(steps)); + // } + + template inline void fill_between(const PixelWriterFunction& pixel_writer, int y, int x1, int x2, unsigned int data_index) + { + // calculate the x value of the other line - // double the steps -> otherwise we might miss important pixels ( see "rasterize triangle small" unittest for use case) - // it might be possible to capture the special cases during the line traversal and render an extra pixel - // -> possible performance improvement - steps *= 2; - step_size /= 2.0f; + int fill_direction = (x1 < x2) ? 1 : -1; - return std::make_pair(step_size, ceil(steps)); + for (int j = x1; j != x2 + fill_direction; j += fill_direction) { + pixel_writer(glm::vec2(j, y), data_index); + } } /* @@ -80,70 +96,193 @@ namespace details { // calculate the x value of the other line int x = get_x_for_y_on_line(other_point, other_line, current_position.y); - int fill_direction = (current_position.x < x) ? 1 : -1; - - for (int j = current_position.x; j != x + fill_direction; j += fill_direction) { - pixel_writer(glm::vec2(j, current_position.y), data_index); - } + fill_between(pixel_writer, current_position.y, current_position.x, x, data_index); } /* * applies dda algorithm to the given line and fills the area in between this line and the given other line * if the other line is not located on the current scan line -> we fill to the given fall back lines + * the given line should always go from top to bottom (only exception horizontal lines) */ template - void dda_render_line(const PixelWriterFunction& pixel_writer, + void render_line(const PixelWriterFunction& pixel_writer, unsigned int data_index, - const glm::vec2& line_start, + const glm::vec2& line_point, const glm::vec2& line, + int fill_direction = 0, const glm::vec2& other_point = { 0, 0 }, const glm::vec2& other_line = { 0, 0 }, - const glm::vec2& normal_line = { 0, 0 }, // TODO possible to change this to a bool? const glm::vec2& fallback_point_upper = { 0, 0 }, const glm::vec2& fallback_line_upper = { 0, 0 }, const glm::vec2& fallback_point_lower = { 0, 0 }, const glm::vec2& fallback_line_lower = { 0, 0 }) { - glm::vec2 step_size; - int steps; - std::tie(step_size, steps) = calculate_dda_steps(line); - - int last_y = 0; + int current_y = floor(line_point.y); + // find out how many y steps lie between origin and line end + int y_steps = floor(line_point.y + line.y) - current_y; - const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; + // const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; const bool fill = other_line != glm::vec2 { 0, 0 }; // no other line given? -> no fill needed + const bool is_enlarged = fallback_line_upper != glm::vec2 { 0, 0 }; // no fallback lines given -> not enlarged + + // create variables to remember where to check for x values for the fill_between fill + // -> in the current scan line do we check the top of the pixel or the bottom of the pixel to fill a line to completion + // this assumes that line is left of other line (if it is the other way around this will be fixed further down) + bool fill_current_from_top_x = (line.x > 0) ? true : false; + // bool fill_current_from_top_x = (line.x > 0) ? false : true; + bool fill_other_from_top_x = (other_line.x > 0) ? false : true; + + // int fill_direction = 0; + if (fill) { + // look at x position of other line + // note if the other line goes through the current line there might be a problem + // but this shouldn't happen -> so we should be fine by only looking at the upper x part and compare + // int x_other_line = get_x_for_y_on_line(other_point, other_line, line_point.y + line.y); + // if (line_point.x > x_other_line) { + if (fill_direction < 0) { + // fill_direction = -1; + // we have to switch where to look for the fill line to stop + fill_current_from_top_x = !fill_current_from_top_x; + fill_other_from_top_x = !fill_other_from_top_x; + + } else { + // fill_direction = 1; + } + } - glm::vec2 current_position = line_start; + // glm::vec2 current_position = line_point; - for (int j = 0; j < steps; j++) { - pixel_writer(current_position, data_index); + // if a line would go directly through integer points, we would falsely draw a whole pixel -> we want to prevent this + constexpr float epsilon = 0.0001; + + if (y_steps == 0) { + // line is smaller than one scan line + // only draw from line start to end + if (!fill) + fill_between(pixel_writer, line_point.y, line_point.x, line.x + line_point.x, data_index); + else { + int x_current_line; + int x_other_line; - if (fill && int(current_position.y + step_size.y) > current_position.y) { // next step would go to next y value + if (fill_current_from_top_x) + x_current_line = line_point.x; + else + x_current_line = line_point.x + line.x; - // decide whether or not to fill only to the fallback line - if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) - fill_to_line(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); - else if (is_enlarged_triangle && (current_position.y < other_point.y)) - fill_to_line(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); - else // normal fill operation - fill_to_line(pixel_writer, current_position, other_point, other_line, data_index); + const float test_other_y = (fill_other_from_top_x) ? line_point.y : line_point.y + line.y; - last_y = current_position.y; + if (is_enlarged && test_other_y < other_point.y) + x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); + else if (is_enlarged && test_other_y > other_point.y + other_line.y) + x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); + else + x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); + + fill_between(pixel_writer, line_point.y, x_current_line, x_other_line, data_index); + } + + } else { + // draw first and last stretches of the line + if (!fill) { + // start of the line + int x_start_bottom = get_x_for_y_on_line(line_point, line, ceil(line_point.y) - epsilon); + fill_between(pixel_writer, line_point.y, line_point.x, x_start_bottom, data_index); + + // end of the line + int x_end_top = get_x_for_y_on_line(line_point, line, floor(line_point.y + line.y) + epsilon); + fill_between(pixel_writer, line_point.y + line.y, line_point.x + line.x, x_end_top, data_index); + } else { + + int x_current_line; + int x_other_line; + + { // start of the line + // const float line_top_y = line_point.y; + const float bottom_y = ceil(line_point.y) - epsilon; // top of the y step + + if (fill_current_from_top_x) + x_current_line = line_point.x; + else + x_current_line = get_x_for_y_on_line(line_point, line, bottom_y); + + const float test_other_y = (fill_other_from_top_x) ? line_point.y : bottom_y; + + // decide whether or not to fill only to the fallback line (if outside of other line segment) + // although this is the end of the line -> we have to test both upper and lower of the other line for large enlarged triangles and small other lines + if (is_enlarged && test_other_y < other_point.y) + x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); + else if (is_enlarged && test_other_y > other_point.y + other_line.y) + x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); + else + x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); + + fill_between(pixel_writer, line_point.y, x_current_line, x_other_line, data_index); + } + + { // end of the line + const float line_bottom_y = line_point.y + line.y; + const float top_y = floor(line_bottom_y) + epsilon; // top of the y step + + if (fill_current_from_top_x) + x_current_line = get_x_for_y_on_line(line_point, line, top_y); + else + x_current_line = line_point.x + line.x; // no calculation necessary we just take the line end + + const float test_other_y = (fill_other_from_top_x) ? top_y : line_bottom_y; + + // decide whether or not to fill only to the fallback line (if outside of other line segment) + // although this is the end of the line -> we have to test both upper and lower of the other line for large enlarged triangles and small other lines + if (is_enlarged && test_other_y < other_point.y) + x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); + else if (is_enlarged && test_other_y > other_point.y + other_line.y) + x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); + else + x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); + + // if (fill_other_from_top_x) + // x_other_line = get_x_for_y_on_line(other_point, other_line, top_y); + // else + // x_other_line = get_x_for_y_on_line(other_point, other_line, line_bottom_y); + + fill_between(pixel_writer, line_bottom_y, x_current_line, x_other_line, data_index); + } } - current_position += step_size; } - current_position -= step_size; - - // check if we have to apply the fill_to_line alg for the current row - if (fill && last_y != floor(current_position.y)) { - // fill only to the fallback line - if (is_enlarged_triangle && (current_position.y > other_point.y + other_line.y)) - fill_to_line(pixel_writer, current_position, fallback_point_upper, fallback_line_upper, data_index); - else if (is_enlarged_triangle && (current_position.y < other_point.y)) - fill_to_line(pixel_writer, current_position, fallback_point_lower, fallback_line_lower, data_index); - else // normal fill operation - fill_to_line(pixel_writer, current_position, other_point, other_line, data_index); + // TODO draw all steps in between + + for (int y = line_point.y + 1; y < line_point.y + y_steps - 1; y++) { + // draw all the steps in between + + // at a distinct y step draw the current line from floor to ceil + // if(fill_current_from_top_x) + + if (!fill) { + int x1 = get_x_for_y_on_line(line_point, line, y + epsilon); + int x2 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); + + fill_between(pixel_writer, y, x1, x2, data_index); + } else { + // fill to other line + int x1, x2; + + if (fill_current_from_top_x) + x1 = get_x_for_y_on_line(line_point, line, y + epsilon); + else + x1 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); + + float test_other_y = (fill_other_from_top_x) ? y + epsilon : y + 1 - epsilon; + + // decide whether or not to fill only to the fallback line (if outside of other line segment) + if (is_enlarged && test_other_y < other_point.y) + x2 = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); + else if (is_enlarged && test_other_y > other_point.y + other_line.y) + x2 = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); + else + x2 = get_x_for_y_on_line(other_point, other_line, test_other_y); + + fill_between(pixel_writer, y, x1, x2, data_index); + } } } @@ -180,7 +319,7 @@ namespace details { * * lastly we have to add endcaps on each original vertice position with the given distance */ - template void dda_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) + template void render_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) { assert(triangle[0].y <= triangle[1].y); @@ -190,11 +329,13 @@ namespace details { auto edge_top_middle = triangle[1] - triangle[0]; auto edge_middle_bottom = triangle[2] - triangle[1]; + int fill_direction = (triangle[1].x < triangle[2].x) ? 1 : -1; + if (distance == 0.0) { // top middle - dda_render_line(pixel_writer, triangle_index, triangle[0], edge_top_middle, triangle[0], edge_top_bottom); + render_line(pixel_writer, triangle_index, triangle[0], edge_top_middle, fill_direction, triangle[0], edge_top_bottom); // middle bottom - dda_render_line(pixel_writer, triangle_index, triangle[1], edge_middle_bottom, triangle[0], edge_top_bottom); + render_line(pixel_writer, triangle_index, triangle[1], edge_middle_bottom, fill_direction, triangle[0], edge_top_bottom); return; // finished } @@ -228,13 +369,13 @@ namespace details { auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; // top middle - dda_render_line(pixel_writer, + render_line(pixel_writer, triangle_index, enlarged_top_middle_origin, edge_top_middle, + fill_direction, enlarged_top_bottom_origin, edge_top_bottom, - normal_top_middle, enlarged_middle_bottom_end, normal_middle_bottom, enlarged_top_middle_origin, @@ -243,37 +384,37 @@ namespace details { // // middle_top_part to middle_bottom_part auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); - dda_render_line(pixel_writer, + render_line(pixel_writer, triangle_index, enlarged_top_middle_end, half_middle_line, + fill_direction, enlarged_top_bottom_origin, edge_top_bottom, - normal_top_middle, enlarged_middle_bottom_end, normal_middle_bottom, enlarged_top_middle_origin, normal_top_middle); - dda_render_line(pixel_writer, + render_line(pixel_writer, triangle_index, enlarged_top_middle_end + half_middle_line, half_middle_line, + fill_direction, enlarged_top_bottom_origin, edge_top_bottom, - normal_middle_bottom, enlarged_middle_bottom_end, normal_middle_bottom, enlarged_top_middle_origin, normal_top_middle); // middle bottom - dda_render_line(pixel_writer, + render_line(pixel_writer, triangle_index, enlarged_middle_bottom_origin, edge_middle_bottom, + fill_direction, enlarged_top_bottom_origin, edge_top_bottom, - normal_middle_bottom, enlarged_middle_bottom_end, normal_middle_bottom, enlarged_top_middle_origin, @@ -300,7 +441,7 @@ namespace details { // } } - template void dda_line(const PixelWriterFunction& pixel_writer, const std::array line_points, unsigned int line_index, float distance) + template void render_line_preprocess(const PixelWriterFunction& pixel_writer, const std::array line_points, unsigned int line_index, float distance) { // edge from top to bottom glm::vec2 origin; @@ -314,7 +455,7 @@ namespace details { } if (distance == 0.0) { - dda_render_line(pixel_writer, line_index, origin, edge); + render_line(pixel_writer, line_index, origin, edge); pixel_writer(line_points[0], 50); pixel_writer(line_points[1], 50); @@ -351,7 +492,7 @@ namespace details { // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; // // top middle - // dda_render_line(pixel_writer, + // render_line(pixel_writer, // line_index, // enlarged_top_middle_origin, // edge_top_middle, @@ -366,7 +507,7 @@ namespace details { // // // middle_top_part to middle_bottom_part // auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); - // dda_render_line(pixel_writer, + // render_line(pixel_writer, // line_index, // enlarged_top_middle_end, // half_middle_line, @@ -377,7 +518,7 @@ namespace details { // normal_middle_bottom, // enlarged_top_middle_origin, // normal_top_middle); - // dda_render_line(pixel_writer, + // render_line(pixel_writer, // line_index, // enlarged_top_middle_end + half_middle_line, // half_middle_line, @@ -390,7 +531,7 @@ namespace details { // normal_top_middle); // // middle bottom - // dda_render_line(pixel_writer, + // render_line(pixel_writer, // line_index, // enlarged_middle_bottom_origin, // edge_middle_bottom, @@ -546,14 +687,14 @@ template void rasterize_line_sdf(const PixelWrite template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) { for (size_t i = 0; i < triangles.size() / 3; ++i) { - details::dda_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); + details::render_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); } } template void rasterize_line(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance = 0.0) { for (size_t i = 0; i < line_points.size() - 1; ++i) { - details::dda_line(pixel_writer, { line_points[i + 0], line_points[i + 1] }, i, distance); + details::render_line_preprocess(pixel_writer, { line_points[i + 0], line_points[i + 1] }, i, distance); } } diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index ce918651..648d6309 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -92,25 +92,25 @@ TEST_CASE("nucleus/rasterizer") // } } - SECTION("Triangle y ordering") - { - // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value - const std::vector triangle_points_012 = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; - const std::vector triangle_points_021 = { glm::vec2(30, 10), glm::vec2(50, 50), glm::vec2(10, 30) }; - const std::vector triangle_points_102 = { glm::vec2(10, 30), glm::vec2(30, 10), glm::vec2(50, 50) }; - const std::vector triangle_points_201 = { glm::vec2(10, 30), glm::vec2(50, 50), glm::vec2(30, 10) }; - const std::vector triangle_points_120 = { glm::vec2(50, 50), glm::vec2(30, 10), glm::vec2(10, 30) }; - const std::vector triangle_points_210 = { glm::vec2(50, 50), glm::vec2(10, 30), glm::vec2(30, 10) }; - - const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; - - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); - } + // SECTION("Triangle y ordering") + // { + // // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value + // const std::vector triangle_points_012 = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + // const std::vector triangle_points_021 = { glm::vec2(30, 10), glm::vec2(50, 50), glm::vec2(10, 30) }; + // const std::vector triangle_points_102 = { glm::vec2(10, 30), glm::vec2(30, 10), glm::vec2(50, 50) }; + // const std::vector triangle_points_201 = { glm::vec2(10, 30), glm::vec2(50, 50), glm::vec2(30, 10) }; + // const std::vector triangle_points_120 = { glm::vec2(50, 50), glm::vec2(30, 10), glm::vec2(10, 30) }; + // const std::vector triangle_points_210 = { glm::vec2(50, 50), glm::vec2(10, 30), glm::vec2(30, 10) }; + + // const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); + // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); + // } SECTION("rasterize triangle") { @@ -320,33 +320,89 @@ TEST_CASE("nucleus/rasterizer") image.save(QString("rasterizer_output_line_straight.png")); } - // SECTION("rasterize line diagonal") - // { - // const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; - // // const std::vector line = { glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5) }; - // auto size = glm::vec2(64, 64); - // nucleus::Raster output(size, 0u); - // float distance = 0.0; - // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + SECTION("rasterize line minimal lower") + { + const std::vector line = { glm::vec2(2.7, 2.5), glm::vec2(3.7, 1.5) }; + auto size = glm::vec2(6, 6); + nucleus::Raster output(size, 0u); + float distance = 0.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - // const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { - // if (bounds.contains(pos)) - // // output.pixel(pos) = data == 50 ? 255 : 100; - // output.pixel(pos) = 255; - // }; - // nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); - // nucleus::Raster output2(size, 0u); - // const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { - // if (bounds.contains(pos)) - // output2.pixel(pos) = 255; - // }; - // nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); - // CHECK(output.buffer() == output2.buffer()); + CHECK(output.buffer() == output2.buffer()); - // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_diagonal.png")); - // } + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_minimal_lower.png")); + } + SECTION("rasterize line minimal upper") + { + + const std::vector line = { glm::vec2(2.3, 2.5), glm::vec2(3.5, 1.3) }; + auto size = glm::vec2(6, 6); + nucleus::Raster output(size, 0u); + float distance = 0.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_minimal_upper.png")); + } + + SECTION("rasterize line diagonal") + { + const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; + // const std::vector line = { glm::vec2(2.2, 2.5), glm::vec2(3.5, 1.1) }; + auto size = glm::vec2(64, 64); + nucleus::Raster output(size, 0u); + float distance = 0.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + // output.pixel(pos) = data == 50 ? 255 : 100; + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_diagonal.png")); + } } From ae62485620a28e4eba4f3f2e444a728e0e344f1c Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 2 Dec 2024 08:47:39 +0100 Subject: [PATCH 09/33] Vector layer - rasterizer - refactored for readability, compactness and similarity to established functions --- nucleus/utils/rasterizer.h | 298 ++++++++----------------------------- 1 file changed, 58 insertions(+), 240 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 0d1c365a..74823912 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -50,36 +50,32 @@ namespace details { } /* - * Calculates the steps and step_size of the given line. - * the "bigger dimension" (-> line is bigger in x or y direction) has a step size of 0.5 - * the other dimension has a step size <= 0.5 + * calculate the x value of a line + * uses fallback lines if the y value is outside of the line segment */ - // inline std::pair calculate_dda_steps(const glm::vec2 line) - // { - // float steps = fabs(line.x) > fabs(line.y) ? fabs(line.x) : fabs(line.y); - - // // should only be triggered if two vertices of a triangle are exactly on the same position -> the input data is wrong - // assert(steps > 0); - // // steps = std::max(steps, 1); // alternative force at least one dda step -> wrong output but it should at least "work" - - // auto step_size = glm::vec2(line.x / float(steps), line.y / float(steps)); - - // // double the steps -> otherwise we might miss important pixels ( see "rasterize triangle small" unittest for use case) - // // it might be possible to capture the special cases during the line traversal and render an extra pixel - // // -> possible performance improvement - // steps *= 2; - // step_size /= 2.0f; - - // // TODO here: possible solution: dont render anything if the current point is exactly on integer values (e.g. {2, 2]) - - // std::cout << step_size.x << " " << step_size.y << " " << ceil(steps) << std::endl; - // return std::make_pair(step_size, ceil(steps)); - // } + inline int get_x_with_fallback(const glm::vec2& point, + const glm::vec2& line, + float y, + bool is_enlarged, + const glm::vec2& fallback_point_bottom, + const glm::vec2& fallback_line_bottom, + const glm::vec2& fallback_point_top, + const glm::vec2& fallback_line_top) + { + // decide whether or not to fill only to the fallback line (if outside of other line segment) + if (is_enlarged && y < point.y) + return get_x_for_y_on_line(fallback_point_top, fallback_line_top, y); + else if (is_enlarged && y > point.y + line.y) + return get_x_for_y_on_line(fallback_point_bottom, fallback_line_bottom, y); + else + return get_x_for_y_on_line(point, line, y); + } + /* + * fills a scanline in the intveral [x1,x2] (inclusive) + */ template inline void fill_between(const PixelWriterFunction& pixel_writer, int y, int x1, int x2, unsigned int data_index) { - // calculate the x value of the other line - int fill_direction = (x1 < x2) ? 1 : -1; for (int j = x1; j != x2 + fill_direction; j += fill_direction) { @@ -87,18 +83,6 @@ namespace details { } } - /* - * fills a scan line from current position to x position of given other line - */ - template - inline void fill_to_line(const PixelWriterFunction& pixel_writer, const glm::vec2& current_position, const glm::vec2& other_point, const glm::vec2& other_line, unsigned int data_index) - { - // calculate the x value of the other line - int x = get_x_for_y_on_line(other_point, other_line, current_position.y); - - fill_between(pixel_writer, current_position.y, current_position.x, x, data_index); - } - /* * applies dda algorithm to the given line and fills the area in between this line and the given other line * if the other line is not located on the current scan line -> we fill to the given fall back lines @@ -112,10 +96,10 @@ namespace details { int fill_direction = 0, const glm::vec2& other_point = { 0, 0 }, const glm::vec2& other_line = { 0, 0 }, - const glm::vec2& fallback_point_upper = { 0, 0 }, - const glm::vec2& fallback_line_upper = { 0, 0 }, - const glm::vec2& fallback_point_lower = { 0, 0 }, - const glm::vec2& fallback_line_lower = { 0, 0 }) + const glm::vec2& fallback_point_bottom = { 0, 0 }, + const glm::vec2& fallback_line_bottom = { 0, 0 }, + const glm::vec2& fallback_point_top = { 0, 0 }, + const glm::vec2& fallback_line_top = { 0, 0 }) { int current_y = floor(line_point.y); // find out how many y steps lie between origin and line end @@ -123,35 +107,20 @@ namespace details { // const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; const bool fill = other_line != glm::vec2 { 0, 0 }; // no other line given? -> no fill needed - const bool is_enlarged = fallback_line_upper != glm::vec2 { 0, 0 }; // no fallback lines given -> not enlarged + const bool is_enlarged = fallback_line_bottom != glm::vec2 { 0, 0 }; // no fallback lines given -> not enlarged // create variables to remember where to check for x values for the fill_between fill // -> in the current scan line do we check the top of the pixel or the bottom of the pixel to fill a line to completion // this assumes that line is left of other line (if it is the other way around this will be fixed further down) bool fill_current_from_top_x = (line.x > 0) ? true : false; - // bool fill_current_from_top_x = (line.x > 0) ? false : true; bool fill_other_from_top_x = (other_line.x > 0) ? false : true; - // int fill_direction = 0; - if (fill) { - // look at x position of other line - // note if the other line goes through the current line there might be a problem - // but this shouldn't happen -> so we should be fine by only looking at the upper x part and compare - // int x_other_line = get_x_for_y_on_line(other_point, other_line, line_point.y + line.y); - // if (line_point.x > x_other_line) { - if (fill_direction < 0) { - // fill_direction = -1; - // we have to switch where to look for the fill line to stop - fill_current_from_top_x = !fill_current_from_top_x; - fill_other_from_top_x = !fill_other_from_top_x; - - } else { - // fill_direction = 1; - } + if (fill && fill_direction < 0) { + // we have to switch where to look for the fill line to stop + fill_current_from_top_x = !fill_current_from_top_x; + fill_other_from_top_x = !fill_other_from_top_x; } - // glm::vec2 current_position = line_point; - // if a line would go directly through integer points, we would falsely draw a whole pixel -> we want to prevent this constexpr float epsilon = 0.0001; @@ -161,125 +130,74 @@ namespace details { if (!fill) fill_between(pixel_writer, line_point.y, line_point.x, line.x + line_point.x, data_index); else { - int x_current_line; - int x_other_line; - - if (fill_current_from_top_x) - x_current_line = line_point.x; - else - x_current_line = line_point.x + line.x; + // go from top or bottom of line (depending on where the other line is located) + const auto x1 = (fill_current_from_top_x) ? line_point.x : line_point.x + line.x; - const float test_other_y = (fill_other_from_top_x) ? line_point.y : line_point.y + line.y; + const float test_y_other = (fill_other_from_top_x) ? line_point.y : line_point.y + line.y; + const auto x2 = get_x_with_fallback(other_point, other_line, test_y_other, is_enlarged, fallback_point_bottom, fallback_line_bottom, fallback_point_top, fallback_line_top); - if (is_enlarged && test_other_y < other_point.y) - x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); - else if (is_enlarged && test_other_y > other_point.y + other_line.y) - x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); - else - x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); - - fill_between(pixel_writer, line_point.y, x_current_line, x_other_line, data_index); + fill_between(pixel_writer, line_point.y, x1, x2, data_index); } } else { // draw first and last stretches of the line if (!fill) { - // start of the line + // start of the line (start to bottom of pixel) int x_start_bottom = get_x_for_y_on_line(line_point, line, ceil(line_point.y) - epsilon); fill_between(pixel_writer, line_point.y, line_point.x, x_start_bottom, data_index); - // end of the line + // end of the line (top of pixel to end of line) int x_end_top = get_x_for_y_on_line(line_point, line, floor(line_point.y + line.y) + epsilon); fill_between(pixel_writer, line_point.y + line.y, line_point.x + line.x, x_end_top, data_index); } else { - - int x_current_line; - int x_other_line; - { // start of the line // const float line_top_y = line_point.y; const float bottom_y = ceil(line_point.y) - epsilon; // top of the y step - if (fill_current_from_top_x) - x_current_line = line_point.x; - else - x_current_line = get_x_for_y_on_line(line_point, line, bottom_y); - - const float test_other_y = (fill_other_from_top_x) ? line_point.y : bottom_y; + // either use line start or calculate the x at the bottom of the pixel + const auto x1 = (fill_current_from_top_x) ? line_point.x : get_x_for_y_on_line(line_point, line, bottom_y); // decide whether or not to fill only to the fallback line (if outside of other line segment) // although this is the end of the line -> we have to test both upper and lower of the other line for large enlarged triangles and small other lines - if (is_enlarged && test_other_y < other_point.y) - x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); - else if (is_enlarged && test_other_y > other_point.y + other_line.y) - x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); - else - x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); - - fill_between(pixel_writer, line_point.y, x_current_line, x_other_line, data_index); + const float test_y_other = (fill_other_from_top_x) ? line_point.y : bottom_y; + const auto x2 = get_x_with_fallback(other_point, other_line, test_y_other, is_enlarged, fallback_point_bottom, fallback_line_bottom, fallback_point_top, fallback_line_top); + + fill_between(pixel_writer, line_point.y, x1, x2, data_index); } { // end of the line const float line_bottom_y = line_point.y + line.y; const float top_y = floor(line_bottom_y) + epsilon; // top of the y step - if (fill_current_from_top_x) - x_current_line = get_x_for_y_on_line(line_point, line, top_y); - else - x_current_line = line_point.x + line.x; // no calculation necessary we just take the line end - - const float test_other_y = (fill_other_from_top_x) ? top_y : line_bottom_y; + // either calculate the y value at top of pixel or use the end point of line + const auto x1 = (fill_current_from_top_x) ? get_x_for_y_on_line(line_point, line, top_y) : line_point.x + line.x; // decide whether or not to fill only to the fallback line (if outside of other line segment) - // although this is the end of the line -> we have to test both upper and lower of the other line for large enlarged triangles and small other lines - if (is_enlarged && test_other_y < other_point.y) - x_other_line = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); - else if (is_enlarged && test_other_y > other_point.y + other_line.y) - x_other_line = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); - else - x_other_line = get_x_for_y_on_line(other_point, other_line, test_other_y); - - // if (fill_other_from_top_x) - // x_other_line = get_x_for_y_on_line(other_point, other_line, top_y); - // else - // x_other_line = get_x_for_y_on_line(other_point, other_line, line_bottom_y); - - fill_between(pixel_writer, line_bottom_y, x_current_line, x_other_line, data_index); + // although this is the end of the line -> we still have to test both upper and lower of the other line for large enlarged triangles and small other lines + const float test_y_other = (fill_other_from_top_x) ? top_y : line_bottom_y; + const auto x2 = get_x_with_fallback(other_point, other_line, test_y_other, is_enlarged, fallback_point_bottom, fallback_line_bottom, fallback_point_top, fallback_line_top); + + fill_between(pixel_writer, line_bottom_y, x1, x2, data_index); } } } - // TODO draw all steps in between - + // draw all the steps in between for (int y = line_point.y + 1; y < line_point.y + y_steps - 1; y++) { - // draw all the steps in between - // at a distinct y step draw the current line from floor to ceil - // if(fill_current_from_top_x) if (!fill) { - int x1 = get_x_for_y_on_line(line_point, line, y + epsilon); - int x2 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); + const auto x1 = get_x_for_y_on_line(line_point, line, y + epsilon); + const auto x2 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); fill_between(pixel_writer, y, x1, x2, data_index); } else { // fill to other line - int x1, x2; - - if (fill_current_from_top_x) - x1 = get_x_for_y_on_line(line_point, line, y + epsilon); - else - x1 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); - - float test_other_y = (fill_other_from_top_x) ? y + epsilon : y + 1 - epsilon; + const float test_y_current = (fill_current_from_top_x) ? y + epsilon : y + 1 - epsilon; + const auto x1 = get_x_for_y_on_line(line_point, line, test_y_current); - // decide whether or not to fill only to the fallback line (if outside of other line segment) - if (is_enlarged && test_other_y < other_point.y) - x2 = get_x_for_y_on_line(fallback_point_lower, fallback_line_lower, test_other_y); - else if (is_enlarged && test_other_y > other_point.y + other_line.y) - x2 = get_x_for_y_on_line(fallback_point_upper, fallback_line_upper, test_other_y); - else - x2 = get_x_for_y_on_line(other_point, other_line, test_other_y); + const float test_y_other = (fill_other_from_top_x) ? y + epsilon : y + 1 - epsilon; + const auto x2 = get_x_with_fallback(other_point, other_line, test_y_other, is_enlarged, fallback_point_bottom, fallback_line_bottom, fallback_point_top, fallback_line_top); fill_between(pixel_writer, y, x1, x2, data_index); } @@ -462,106 +380,6 @@ namespace details { return; // finished } - - // we need to render an enlarged triangle - - // auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); - // auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); - // auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); - - // { // swap normal direction if they are incorrect (pointing to center) - // glm::vec2 centroid = (triangle[0] + triangle[1] + triangle[2]) / 3.0f; - // if (glm::dot(normal_top_bottom, centroid - triangle[0]) > 0) { - // normal_top_bottom *= -1; - // } - // if (glm::dot(normal_top_middle, centroid - triangle[0]) > 0) { - // normal_top_middle *= -1; - // } - // if (glm::dot(normal_middle_bottom, centroid - triangle[1]) > 0) { - // normal_middle_bottom *= -1; - // } - // } - - // auto enlarged_top_bottom_origin = triangle[0] + normal_top_bottom * distance; - // // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; // not needed - - // auto enlarged_top_middle_origin = triangle[0] + normal_top_middle * distance; - // auto enlarged_top_middle_end = triangle[1] + normal_top_middle * distance; - - // auto enlarged_middle_bottom_origin = triangle[1] + normal_middle_bottom * distance; - // auto enlarged_middle_bottom_end = triangle[2] + normal_middle_bottom * distance; - - // // top middle - // render_line(pixel_writer, - // line_index, - // enlarged_top_middle_origin, - // edge_top_middle, - // enlarged_top_bottom_origin, - // edge_top_bottom, - // normal_top_middle, - // enlarged_middle_bottom_end, - // normal_middle_bottom, - // enlarged_top_middle_origin, - // normal_top_middle); - - // // // middle_top_part to middle_bottom_part - // auto half_middle_line = (enlarged_middle_bottom_origin - enlarged_top_middle_end) / glm::vec2(2.0); - - // render_line(pixel_writer, - // line_index, - // enlarged_top_middle_end, - // half_middle_line, - // enlarged_top_bottom_origin, - // edge_top_bottom, - // normal_top_middle, - // enlarged_middle_bottom_end, - // normal_middle_bottom, - // enlarged_top_middle_origin, - // normal_top_middle); - // render_line(pixel_writer, - // line_index, - // enlarged_top_middle_end + half_middle_line, - // half_middle_line, - // enlarged_top_bottom_origin, - // edge_top_bottom, - // normal_middle_bottom, - // enlarged_middle_bottom_end, - // normal_middle_bottom, - // enlarged_top_middle_origin, - // normal_top_middle); - - // // middle bottom - // render_line(pixel_writer, - // line_index, - // enlarged_middle_bottom_origin, - // edge_middle_bottom, - // enlarged_top_bottom_origin, - // edge_top_bottom, - // normal_middle_bottom, - // enlarged_middle_bottom_end, - // normal_middle_bottom, - // enlarged_top_middle_origin, - // normal_top_middle); - - // // endcaps - // add_circle_end_cap(pixel_writer, triangle[0], line_index, distance); - // add_circle_end_cap(pixel_writer, triangle[1], line_index, distance); - // add_circle_end_cap(pixel_writer, triangle[2], line_index, distance); - - // { // DEBUG visualize enlarged points - // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; - // pixel_writer(triangle[0], 50); - // pixel_writer(enlarged_top_bottom_origin, 50); - // pixel_writer(enlarged_top_middle_origin, 50); - - // pixel_writer(triangle[1], 50); - // pixel_writer(enlarged_middle_bottom_origin, 50); - // pixel_writer(enlarged_top_middle_end, 50); - - // pixel_writer(triangle[2], 50); - // pixel_writer(enlarged_middle_bottom_end, 50); - // pixel_writer(enlarged_top_bottom_end, 50); - // } } /* From 74b151cf71d95fc030081c9badaa5f57376bd89f Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 2 Dec 2024 16:40:35 +0100 Subject: [PATCH 10/33] Vector layer - rasterizer - rendering lines with thickness works now --- nucleus/utils/rasterizer.h | 97 ++++++++++++++++---- unittests/nucleus/rasterizer.cpp | 148 ++++++++++++++++++++++++++----- 2 files changed, 204 insertions(+), 41 deletions(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 74823912..b6458c69 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -19,8 +19,6 @@ #include #include -#include - #include namespace nucleus::utils::rasterizer { @@ -29,12 +27,8 @@ namespace nucleus::utils::rasterizer { * Possible future improvements: * - check how often each pixel is written to -> maybe we can improve performance here * - sdf box filter instead by circle to only consider values of the current cell - * - calculate_dda_steps currently doubles the steps to prevent missing certain pixels (this might be improved) */ -// TODOs: -// - line renderer - namespace details { /* @@ -84,7 +78,8 @@ namespace details { } /* - * applies dda algorithm to the given line and fills the area in between this line and the given other line + * renders the given line + * if other line is given fills everything in between the current and the other line * if the other line is not located on the current scan line -> we fill to the given fall back lines * the given line should always go from top to bottom (only exception horizontal lines) */ @@ -101,9 +96,11 @@ namespace details { const glm::vec2& fallback_point_top = { 0, 0 }, const glm::vec2& fallback_line_top = { 0, 0 }) { - int current_y = floor(line_point.y); + // if a line would go directly through integer points, we would falsely draw a whole pixel -> we want to prevent this + constexpr float epsilon = 0.00001; + // find out how many y steps lie between origin and line end - int y_steps = floor(line_point.y + line.y) - current_y; + const int y_steps = floor(line_point.y + line.y) - floor(line_point.y); // const bool is_enlarged_triangle = normal_line != glm::vec2 { 0, 0 }; const bool fill = other_line != glm::vec2 { 0, 0 }; // no other line given? -> no fill needed @@ -121,9 +118,7 @@ namespace details { fill_other_from_top_x = !fill_other_from_top_x; } - // if a line would go directly through integer points, we would falsely draw a whole pixel -> we want to prevent this - constexpr float epsilon = 0.0001; - + // special cases for line start/end + if line is smaller than 1px if (y_steps == 0) { // line is smaller than one scan line // only draw from line start to end @@ -138,7 +133,6 @@ namespace details { fill_between(pixel_writer, line_point.y, x1, x2, data_index); } - } else { // draw first and last stretches of the line if (!fill) { @@ -372,14 +366,85 @@ namespace details { origin = line_points[0]; } + // only draw lines with 1 pixel width if (distance == 0.0) { render_line(pixel_writer, line_index, origin, edge); - pixel_writer(line_points[0], 50); - pixel_writer(line_points[1], 50); + // debug output for points + // pixel_writer(line_points[0], 50); + // pixel_writer(line_points[1], 50); return; // finished } + + // we want to draw lines with a thickness + + // end caps + add_circle_end_cap(pixel_writer, line_points[0], line_index, distance); + add_circle_end_cap(pixel_writer, line_points[1], line_index, distance); + + // distance += sqrt(0.5); + + // create normal + // make sure that the normal points downwards + glm::vec2 normal; + if (edge.x < 0) + normal = glm::normalize(glm::vec2(edge.y, -edge.x)) * distance; + else + normal = glm::normalize(glm::vec2(-edge.y, edge.x)) * distance; + + auto enlarged_top_origin = origin + normal * -1.0f; + auto enlarged_middle_origin = origin + normal; + auto enlarged_middle_end = origin + edge + normal * -1.0f; + // auto enlarged_bottom_origin = origin + edge + normal; + + // double the normal so that we construct a rectangle with the edge + normal *= 2.0f; + + // determine if the doubled normal or the edge has a larger y difference + // and switch if normal is larger in y direction + if (edge.y < normal.y) { + auto tmp = normal; + normal = edge; + edge = tmp; + + // we also need to change two enlarged points + tmp = enlarged_middle_origin; + enlarged_middle_origin = enlarged_middle_end; + enlarged_middle_end = tmp; + } + + // special case: one side is horizontal + // -> only one pass from top to bottom necessary + if (normal.y == 0) { + render_line(pixel_writer, line_index, enlarged_top_origin, edge, (enlarged_top_origin.x > enlarged_middle_origin.x) ? -1 : 1, enlarged_middle_origin, edge); + + return; + } + + // we have to fill once the edge side and once the normal side. + // one side should not need fallbacks to render everything + + int fill_direction = (edge.x > 0) ? -1 : 1; + + // go from top of the line to bottom of the line + // first fill everything between edge and 2xnormal, after this fill everything betweent edge and other edge + render_line(pixel_writer, line_index, enlarged_top_origin, edge, fill_direction, enlarged_middle_origin, edge, enlarged_top_origin, normal, enlarged_top_origin, normal); + + // render the last bit of the line (from line end top to line end bottom) -> no need for a fallback line since both lines meet at the bottom + render_line(pixel_writer, line_index, enlarged_middle_end, normal, fill_direction, enlarged_middle_origin, edge * 2.0f); + + // { // DEBUG visualize enlarged points + // // const auto enlarged_bottom_origin = origin + edge + normal * distance; + // pixel_writer(enlarged_top_origin, 50); + // pixel_writer(enlarged_middle_origin, 50); + + // pixel_writer(enlarged_middle_end, 50); + // pixel_writer(enlarged_bottom_origin, 50); + + // pixel_writer(line_points[0], 50); + // pixel_writer(line_points[1], 50); + // } } /* @@ -398,8 +463,6 @@ namespace details { unsigned int data_index, float distance) { - // - glm::vec2 v0 = current_position - points[0], v1 = current_position - points[1], v2 = current_position - points[2]; // pq# = distance from edge # diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 648d6309..9c8a2e2e 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -128,8 +128,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output.png")); } SECTION("rasterize triangle small") @@ -146,8 +146,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_small.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_small.png")); } SECTION("rasterize triangle smallest") @@ -231,8 +231,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged.png")); } SECTION("rasterize triangle horizontal") @@ -292,6 +292,107 @@ TEST_CASE("nucleus/rasterizer") // image.save(QString("rasterizer_output_enlarged_narrow.png")); // } + SECTION("rasterize triangle start/end y") + { + const std::vector triangles = { // down to right + glm::vec2(2.7, 1.5), + glm::vec2(3.7, 2.5), + glm::vec2(3.7, 2.7), + // down to left + glm::vec2(8.7, 1.5), + glm::vec2(7.7, 2.5), + glm::vec2(7.7, 2.7), + + // 1 lines + // down to right + glm::vec2(3.5, 7.3), + glm::vec2(26.5, 7.5), + glm::vec2(24.5, 7.6), + // down to left + glm::vec2(26.5, 12.3), + glm::vec2(3.5, 12.5), + glm::vec2(5.5, 12.6), + + // 2 lines + // down to right + glm::vec2(3.5, 16.7), + glm::vec2(6.5, 17.5), + glm::vec2(4.5, 17.6), + // down to left + glm::vec2(6.5, 21.7), + glm::vec2(3.5, 22.5), + glm::vec2(5.5, 22.6) + }; + + auto size = glm::vec2(30, 30); + nucleus::Raster output(size, 0u); + float distance = 0.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_triangle_start_end.png")); + } + + SECTION("rasterize line start/end y") + { + const std::vector line_lower_min = { glm::vec2(2.7, 2.5), glm::vec2(3.7, 1.5) }; + const std::vector line_upper_min = { glm::vec2(7.3, 2.5), glm::vec2(8.5, 1.3) }; + const std::vector line_left_to_right_1pixel = { glm::vec2(2.3, 7.3), glm::vec2(28.5, 7.7) }; + const std::vector line_right_to_left_1pixel = { glm::vec2(2.3, 12.7), glm::vec2(28.5, 12.3) }; + const std::vector line_left_to_right_2pixel = { glm::vec2(2.3, 17.3), glm::vec2(6.5, 18.7) }; + const std::vector line_right_to_left_2pixel = { glm::vec2(2.3, 23.7), glm::vec2(6.5, 22.3) }; + + auto size = glm::vec2(30, 30); + nucleus::Raster output(size, 0u); + float distance = 0.0; + radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_lower_min, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_upper_min, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_left_to_right_1pixel, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_right_to_left_1pixel, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_left_to_right_2pixel, distance); + nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_right_to_left_2pixel, distance); + + nucleus::Raster output2(size, 0u); + const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + if (bounds.contains(pos)) + output2.pixel(pos) = 255; + }; + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_lower_min, distance); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_upper_min, distance); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_left_to_right_1pixel, distance); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_right_to_left_1pixel, distance); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_left_to_right_2pixel, distance); + nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_right_to_left_2pixel, distance); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_start_end.png")); + } + SECTION("rasterize line straight") { const std::vector line = { glm::vec2(10.5, 10.5), glm::vec2(10.5, 50.5), glm::vec2(50.5, 50.5), glm::vec2(50.5, 10.5), glm::vec2(10.5, 10.5) }; @@ -316,14 +417,14 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_straight.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_straight.png")); } - SECTION("rasterize line minimal lower") + SECTION("rasterize line diagonal") { - const std::vector line = { glm::vec2(2.7, 2.5), glm::vec2(3.7, 1.5) }; - auto size = glm::vec2(6, 6); + const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; + auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; @@ -345,15 +446,15 @@ TEST_CASE("nucleus/rasterizer") // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_minimal_lower.png")); + // image.save(QString("rasterizer_output_line_diagonal.png")); } - SECTION("rasterize line minimal upper") - { - const std::vector line = { glm::vec2(2.3, 2.5), glm::vec2(3.5, 1.3) }; - auto size = glm::vec2(6, 6); + SECTION("rasterize line straight enlarged") + { + const std::vector line = { glm::vec2(10.5, 10.5), glm::vec2(10.5, 50.5), glm::vec2(50.5, 50.5), glm::vec2(50.5, 10.5), glm::vec2(10.5, 10.5) }; + auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); - float distance = 0.0; + float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { @@ -373,21 +474,20 @@ TEST_CASE("nucleus/rasterizer") // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_minimal_upper.png")); + // image.save(QString("rasterizer_output_line_straight_enlarged.png")); } - SECTION("rasterize line diagonal") + SECTION("rasterize line diagonal enlarged") { const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; - // const std::vector line = { glm::vec2(2.2, 2.5), glm::vec2(3.5, 1.1) }; + // const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5) }; auto size = glm::vec2(64, 64); nucleus::Raster output(size, 0u); - float distance = 0.0; + float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { if (bounds.contains(pos)) - // output.pixel(pos) = data == 50 ? 255 : 100; output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); @@ -402,7 +502,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_diagonal.png")); + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); } } From 6088e1dabbc85307242c971639d578a38511c630 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 2 Dec 2024 17:43:41 +0100 Subject: [PATCH 11/33] Vector layer - rasterizer - removed unneccessary include --- unittests/nucleus/rasterizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 9c8a2e2e..de118f54 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -23,7 +23,7 @@ #include #include "nucleus/Raster.h" -#include "nucleus/tile/conversion.h" +// #include "nucleus/tile/conversion.h" #include "nucleus/utils/rasterizer.h" #include From 752095df39a3efc8482954b2e52b8186be68ed08 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 06:37:26 +0100 Subject: [PATCH 12/33] Vector layer - rasterizer - temporarily disabled cdt triangulation to confirm failing android tests --- nucleus/CMakeLists.txt | 9 +-- nucleus/utils/rasterizer.cpp | 102 ++++++++++++++-------------- nucleus/utils/rasterizer.h | 2 +- unittests/nucleus/rasterizer.cpp | 112 +++++++++++++++---------------- 4 files changed, 113 insertions(+), 112 deletions(-) diff --git a/nucleus/CMakeLists.txt b/nucleus/CMakeLists.txt index a503b7f7..70830952 100644 --- a/nucleus/CMakeLists.txt +++ b/nucleus/CMakeLists.txt @@ -29,7 +29,7 @@ if(ALP_ENABLE_LABELS) alp_add_git_repository(vector_tiles URL https://github.com/AlpineMapsOrg/vector-tile.git COMMITISH faba88257716c4bc01ebd44d8b8b98f711ecb78c) endif() alp_add_git_repository(goofy_tc URL https://github.com/AlpineMapsOrgDependencies/Goofy_slim.git COMMITISH 13b228784960a6227bb6ca704ff34161bbac1b91 DO_NOT_ADD_SUBPROJECT) -alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT) +# alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT) add_library(zppbits INTERFACE) target_include_directories(zppbits SYSTEM INTERFACE ${zppbits_SOURCE_DIR}) @@ -41,8 +41,8 @@ set_target_properties(goofy_tc PROPERTIES SYSTEM true) add_library(tl_expected INTERFACE) target_include_directories(tl_expected INTERFACE ${tl_expected_SOURCE_DIR}/include) -add_library(cdt INTERFACE) -target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include) +# add_library(cdt INTERFACE) +# target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include) set(alp_version_out ${CMAKE_BINARY_DIR}/alp_version/nucleus/version.cpp) @@ -149,7 +149,8 @@ endif() target_include_directories(nucleus PUBLIC ${CMAKE_SOURCE_DIR}) # Please keep Qt::Gui outside the nucleus. If you need it optional via a cmake based switch -target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt) +target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc) +# target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt) qt_add_resources(nucleus "icons" PREFIX "/map_icons" diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index b572e203..d78f0454 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -18,68 +18,68 @@ #include "rasterizer.h" -#include +// #include namespace nucleus::utils::rasterizer { -std::vector triangulize(std::vector polygons) -{ - std::vector processed_triangles; +// std::vector triangulize(std::vector polygons) +// { +// std::vector processed_triangles; - std::vector edges; - { // create the edges - edges.reserve(polygons.size()); - for (size_t i = 0; i < polygons.size() - 1; i++) { - edges.push_back(glm::ivec2(int(i), int(i + 1))); - } +// std::vector edges; +// { // create the edges +// edges.reserve(polygons.size()); +// for (size_t i = 0; i < polygons.size() - 1; i++) { +// edges.push_back(glm::ivec2(int(i), int(i + 1))); +// } - // last edge between start and end vertex - edges.push_back(glm::ivec2(polygons.size() - 1, 0)); - } +// // last edge between start and end vertex +// edges.push_back(glm::ivec2(polygons.size() - 1, 0)); +// } - // triangulation - CDT::Triangulation cdt; - cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); - cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); - cdt.eraseOuterTrianglesAndHoles(); +// // triangulation +// CDT::Triangulation cdt; +// cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); +// cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); +// cdt.eraseOuterTrianglesAndHoles(); - // fill our own data structures - for (size_t i = 0; i < cdt.triangles.size(); ++i) { - auto tri = cdt.triangles[i]; +// // fill our own data structures +// for (size_t i = 0; i < cdt.triangles.size(); ++i) { +// auto tri = cdt.triangles[i]; - std::vector tri_indices = { tri.vertices[0], tri.vertices[1], tri.vertices[2] }; +// std::vector tri_indices = { tri.vertices[0], tri.vertices[1], tri.vertices[2] }; - int top_index = (cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[1]].y) ? ((cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[2]].y) ? 0 : 2) - : ((cdt.vertices[tri.vertices[1]].y < cdt.vertices[tri.vertices[2]].y) ? 1 : 2); - // for middle and bottom index we first initialize them randomly with the values that still need to be tested - int middle_index; - int bottom_index; - if (top_index == 0) { - middle_index = 1; - bottom_index = 2; - } else if (top_index == 1) { - middle_index = 2; - bottom_index = 0; - } else { - middle_index = 0; - bottom_index = 1; - } +// int top_index = (cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[1]].y) ? ((cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[2]].y) ? 0 : 2) +// : ((cdt.vertices[tri.vertices[1]].y < cdt.vertices[tri.vertices[2]].y) ? 1 : 2); +// // for middle and bottom index we first initialize them randomly with the values that still need to be tested +// int middle_index; +// int bottom_index; +// if (top_index == 0) { +// middle_index = 1; +// bottom_index = 2; +// } else if (top_index == 1) { +// middle_index = 2; +// bottom_index = 0; +// } else { +// middle_index = 0; +// bottom_index = 1; +// } - // and now we test if we assigned them correctly - if (cdt.vertices[tri.vertices[middle_index]].y > cdt.vertices[tri.vertices[bottom_index]].y) { - // if not we have to interchange them - int tmp = middle_index; - middle_index = bottom_index; - bottom_index = tmp; - } +// // and now we test if we assigned them correctly +// if (cdt.vertices[tri.vertices[middle_index]].y > cdt.vertices[tri.vertices[bottom_index]].y) { +// // if not we have to interchange them +// int tmp = middle_index; +// middle_index = bottom_index; +// bottom_index = tmp; +// } - // lastly add the vertices to the vector in the correct order - processed_triangles.push_back({ cdt.vertices[tri.vertices[top_index]].x, cdt.vertices[tri.vertices[top_index]].y }); - processed_triangles.push_back({ cdt.vertices[tri.vertices[middle_index]].x, cdt.vertices[tri.vertices[middle_index]].y }); - processed_triangles.push_back({ cdt.vertices[tri.vertices[bottom_index]].x, cdt.vertices[tri.vertices[bottom_index]].y }); - } +// // lastly add the vertices to the vector in the correct order +// processed_triangles.push_back({ cdt.vertices[tri.vertices[top_index]].x, cdt.vertices[tri.vertices[top_index]].y }); +// processed_triangles.push_back({ cdt.vertices[tri.vertices[middle_index]].x, cdt.vertices[tri.vertices[middle_index]].y }); +// processed_triangles.push_back({ cdt.vertices[tri.vertices[bottom_index]].x, cdt.vertices[tri.vertices[bottom_index]].y }); +// } - return processed_triangles; -} +// return processed_triangles; +// } } // namespace nucleus::utils::rasterizer diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index b6458c69..27dfe442 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -505,7 +505,7 @@ namespace details { * triangulizes polygons and orders the vertices by y position per triangle * output: top, middle, bottom, top, middle,... */ -std::vector triangulize(std::vector polygons); +// std::vector triangulize(std::vector polygons); /* * ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index de118f54..92fa2a25 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -20,7 +20,7 @@ #include #include -#include +// #include #include "nucleus/Raster.h" // #include "nucleus/tile/conversion.h" @@ -36,61 +36,61 @@ TEST_CASE("nucleus/rasterizer") // but future changes might cause problems, when there shouldn't be problems // so if a test fails check the debug images to see what exactly went wrong. - SECTION("Triangulation") - { - // 5 point polygon - // basically a square where one side contains an inward facing triangle - // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape - const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; - const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; - - CDT::Triangulation cdt; - cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); - cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); - cdt.eraseOuterTrianglesAndHoles(); - - auto tri = cdt.triangles; - auto vert = cdt.vertices; - - // check if only 3 triangles have been found - CHECK(tri.size() == 3); - - // 1st triangle - CHECK(vert[tri[0].vertices[0]].x == 0.0); - CHECK(vert[tri[0].vertices[0]].y == 2.0); - CHECK(vert[tri[0].vertices[1]].x == 1.0); - CHECK(vert[tri[0].vertices[1]].y == 1.0); - CHECK(vert[tri[0].vertices[2]].x == 2.0); - CHECK(vert[tri[0].vertices[2]].y == 2.0); - - // 2nd triangle - CHECK(vert[tri[1].vertices[0]].x == 1.0); - CHECK(vert[tri[1].vertices[0]].y == 1.0); - CHECK(vert[tri[1].vertices[1]].x == 2.0); - CHECK(vert[tri[1].vertices[1]].y == 0.0); - CHECK(vert[tri[1].vertices[2]].x == 2.0); - CHECK(vert[tri[1].vertices[2]].y == 2.0); - - // 3rd triangle - CHECK(vert[tri[2].vertices[0]].x == 1.0); - CHECK(vert[tri[2].vertices[0]].y == 1.0); - CHECK(vert[tri[2].vertices[1]].x == 0.0); - CHECK(vert[tri[2].vertices[1]].y == 0.0); - CHECK(vert[tri[2].vertices[2]].x == 2.0); - CHECK(vert[tri[2].vertices[2]].y == 0.0); - - // DEBUG print out all the points of the triangles (to check what might have went wrong) - // for (std::size_t i = 0; i < tri.size(); i++) { - // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", - // vert[tri[i].vertices[0]].x, // x0 - // vert[tri[i].vertices[0]].y, // y0 - // vert[tri[i].vertices[1]].x, // x1 - // vert[tri[i].vertices[1]].y, // y1 - // vert[tri[i].vertices[2]].x, // x2 - // vert[tri[i].vertices[2]].y // y2 - // ); - // } - } + // SECTION("Triangulation") + // { + // // 5 point polygon + // // basically a square where one side contains an inward facing triangle + // // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape + // const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; + // const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; + + // CDT::Triangulation cdt; + // cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + // cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + // cdt.eraseOuterTrianglesAndHoles(); + + // auto tri = cdt.triangles; + // auto vert = cdt.vertices; + + // // check if only 3 triangles have been found + // CHECK(tri.size() == 3); + + // // 1st triangle + // CHECK(vert[tri[0].vertices[0]].x == 0.0); + // CHECK(vert[tri[0].vertices[0]].y == 2.0); + // CHECK(vert[tri[0].vertices[1]].x == 1.0); + // CHECK(vert[tri[0].vertices[1]].y == 1.0); + // CHECK(vert[tri[0].vertices[2]].x == 2.0); + // CHECK(vert[tri[0].vertices[2]].y == 2.0); + + // // 2nd triangle + // CHECK(vert[tri[1].vertices[0]].x == 1.0); + // CHECK(vert[tri[1].vertices[0]].y == 1.0); + // CHECK(vert[tri[1].vertices[1]].x == 2.0); + // CHECK(vert[tri[1].vertices[1]].y == 0.0); + // CHECK(vert[tri[1].vertices[2]].x == 2.0); + // CHECK(vert[tri[1].vertices[2]].y == 2.0); + + // // 3rd triangle + // CHECK(vert[tri[2].vertices[0]].x == 1.0); + // CHECK(vert[tri[2].vertices[0]].y == 1.0); + // CHECK(vert[tri[2].vertices[1]].x == 0.0); + // CHECK(vert[tri[2].vertices[1]].y == 0.0); + // CHECK(vert[tri[2].vertices[2]].x == 2.0); + // CHECK(vert[tri[2].vertices[2]].y == 0.0); + + // // DEBUG print out all the points of the triangles (to check what might have went wrong) + // // for (std::size_t i = 0; i < tri.size(); i++) { + // // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", + // // vert[tri[i].vertices[0]].x, // x0 + // // vert[tri[i].vertices[0]].y, // y0 + // // vert[tri[i].vertices[1]].x, // x1 + // // vert[tri[i].vertices[1]].y, // y1 + // // vert[tri[i].vertices[2]].x, // x2 + // // vert[tri[i].vertices[2]].y // y2 + // // ); + // // } + // } // SECTION("Triangle y ordering") // { From b0d70d3f8c410b7b3a1ad7390bbc7c444f88768b Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 07:38:39 +0100 Subject: [PATCH 13/33] Revert "Vector layer - rasterizer" This reverts commit 752095df39a3efc8482954b2e52b8186be68ed08. --- nucleus/CMakeLists.txt | 9 ++- nucleus/utils/rasterizer.cpp | 102 ++++++++++++++-------------- nucleus/utils/rasterizer.h | 2 +- unittests/nucleus/rasterizer.cpp | 112 +++++++++++++++---------------- 4 files changed, 112 insertions(+), 113 deletions(-) diff --git a/nucleus/CMakeLists.txt b/nucleus/CMakeLists.txt index 70830952..a503b7f7 100644 --- a/nucleus/CMakeLists.txt +++ b/nucleus/CMakeLists.txt @@ -29,7 +29,7 @@ if(ALP_ENABLE_LABELS) alp_add_git_repository(vector_tiles URL https://github.com/AlpineMapsOrg/vector-tile.git COMMITISH faba88257716c4bc01ebd44d8b8b98f711ecb78c) endif() alp_add_git_repository(goofy_tc URL https://github.com/AlpineMapsOrgDependencies/Goofy_slim.git COMMITISH 13b228784960a6227bb6ca704ff34161bbac1b91 DO_NOT_ADD_SUBPROJECT) -# alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT) add_library(zppbits INTERFACE) target_include_directories(zppbits SYSTEM INTERFACE ${zppbits_SOURCE_DIR}) @@ -41,8 +41,8 @@ set_target_properties(goofy_tc PROPERTIES SYSTEM true) add_library(tl_expected INTERFACE) target_include_directories(tl_expected INTERFACE ${tl_expected_SOURCE_DIR}/include) -# add_library(cdt INTERFACE) -# target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include) +add_library(cdt INTERFACE) +target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include) set(alp_version_out ${CMAKE_BINARY_DIR}/alp_version/nucleus/version.cpp) @@ -149,8 +149,7 @@ endif() target_include_directories(nucleus PUBLIC ${CMAKE_SOURCE_DIR}) # Please keep Qt::Gui outside the nucleus. If you need it optional via a cmake based switch -target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc) -# target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt) +target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt) qt_add_resources(nucleus "icons" PREFIX "/map_icons" diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index d78f0454..b572e203 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -18,68 +18,68 @@ #include "rasterizer.h" -// #include +#include namespace nucleus::utils::rasterizer { -// std::vector triangulize(std::vector polygons) -// { -// std::vector processed_triangles; +std::vector triangulize(std::vector polygons) +{ + std::vector processed_triangles; -// std::vector edges; -// { // create the edges -// edges.reserve(polygons.size()); -// for (size_t i = 0; i < polygons.size() - 1; i++) { -// edges.push_back(glm::ivec2(int(i), int(i + 1))); -// } + std::vector edges; + { // create the edges + edges.reserve(polygons.size()); + for (size_t i = 0; i < polygons.size() - 1; i++) { + edges.push_back(glm::ivec2(int(i), int(i + 1))); + } -// // last edge between start and end vertex -// edges.push_back(glm::ivec2(polygons.size() - 1, 0)); -// } + // last edge between start and end vertex + edges.push_back(glm::ivec2(polygons.size() - 1, 0)); + } -// // triangulation -// CDT::Triangulation cdt; -// cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); -// cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); -// cdt.eraseOuterTrianglesAndHoles(); + // triangulation + CDT::Triangulation cdt; + cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + cdt.eraseOuterTrianglesAndHoles(); -// // fill our own data structures -// for (size_t i = 0; i < cdt.triangles.size(); ++i) { -// auto tri = cdt.triangles[i]; + // fill our own data structures + for (size_t i = 0; i < cdt.triangles.size(); ++i) { + auto tri = cdt.triangles[i]; -// std::vector tri_indices = { tri.vertices[0], tri.vertices[1], tri.vertices[2] }; + std::vector tri_indices = { tri.vertices[0], tri.vertices[1], tri.vertices[2] }; -// int top_index = (cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[1]].y) ? ((cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[2]].y) ? 0 : 2) -// : ((cdt.vertices[tri.vertices[1]].y < cdt.vertices[tri.vertices[2]].y) ? 1 : 2); -// // for middle and bottom index we first initialize them randomly with the values that still need to be tested -// int middle_index; -// int bottom_index; -// if (top_index == 0) { -// middle_index = 1; -// bottom_index = 2; -// } else if (top_index == 1) { -// middle_index = 2; -// bottom_index = 0; -// } else { -// middle_index = 0; -// bottom_index = 1; -// } + int top_index = (cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[1]].y) ? ((cdt.vertices[tri.vertices[0]].y < cdt.vertices[tri.vertices[2]].y) ? 0 : 2) + : ((cdt.vertices[tri.vertices[1]].y < cdt.vertices[tri.vertices[2]].y) ? 1 : 2); + // for middle and bottom index we first initialize them randomly with the values that still need to be tested + int middle_index; + int bottom_index; + if (top_index == 0) { + middle_index = 1; + bottom_index = 2; + } else if (top_index == 1) { + middle_index = 2; + bottom_index = 0; + } else { + middle_index = 0; + bottom_index = 1; + } -// // and now we test if we assigned them correctly -// if (cdt.vertices[tri.vertices[middle_index]].y > cdt.vertices[tri.vertices[bottom_index]].y) { -// // if not we have to interchange them -// int tmp = middle_index; -// middle_index = bottom_index; -// bottom_index = tmp; -// } + // and now we test if we assigned them correctly + if (cdt.vertices[tri.vertices[middle_index]].y > cdt.vertices[tri.vertices[bottom_index]].y) { + // if not we have to interchange them + int tmp = middle_index; + middle_index = bottom_index; + bottom_index = tmp; + } -// // lastly add the vertices to the vector in the correct order -// processed_triangles.push_back({ cdt.vertices[tri.vertices[top_index]].x, cdt.vertices[tri.vertices[top_index]].y }); -// processed_triangles.push_back({ cdt.vertices[tri.vertices[middle_index]].x, cdt.vertices[tri.vertices[middle_index]].y }); -// processed_triangles.push_back({ cdt.vertices[tri.vertices[bottom_index]].x, cdt.vertices[tri.vertices[bottom_index]].y }); -// } + // lastly add the vertices to the vector in the correct order + processed_triangles.push_back({ cdt.vertices[tri.vertices[top_index]].x, cdt.vertices[tri.vertices[top_index]].y }); + processed_triangles.push_back({ cdt.vertices[tri.vertices[middle_index]].x, cdt.vertices[tri.vertices[middle_index]].y }); + processed_triangles.push_back({ cdt.vertices[tri.vertices[bottom_index]].x, cdt.vertices[tri.vertices[bottom_index]].y }); + } -// return processed_triangles; -// } + return processed_triangles; +} } // namespace nucleus::utils::rasterizer diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 27dfe442..b6458c69 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -505,7 +505,7 @@ namespace details { * triangulizes polygons and orders the vertices by y position per triangle * output: top, middle, bottom, top, middle,... */ -// std::vector triangulize(std::vector polygons); +std::vector triangulize(std::vector polygons); /* * ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 92fa2a25..de118f54 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -20,7 +20,7 @@ #include #include -// #include +#include #include "nucleus/Raster.h" // #include "nucleus/tile/conversion.h" @@ -36,61 +36,61 @@ TEST_CASE("nucleus/rasterizer") // but future changes might cause problems, when there shouldn't be problems // so if a test fails check the debug images to see what exactly went wrong. - // SECTION("Triangulation") - // { - // // 5 point polygon - // // basically a square where one side contains an inward facing triangle - // // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape - // const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; - // const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; - - // CDT::Triangulation cdt; - // cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); - // cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); - // cdt.eraseOuterTrianglesAndHoles(); - - // auto tri = cdt.triangles; - // auto vert = cdt.vertices; - - // // check if only 3 triangles have been found - // CHECK(tri.size() == 3); - - // // 1st triangle - // CHECK(vert[tri[0].vertices[0]].x == 0.0); - // CHECK(vert[tri[0].vertices[0]].y == 2.0); - // CHECK(vert[tri[0].vertices[1]].x == 1.0); - // CHECK(vert[tri[0].vertices[1]].y == 1.0); - // CHECK(vert[tri[0].vertices[2]].x == 2.0); - // CHECK(vert[tri[0].vertices[2]].y == 2.0); - - // // 2nd triangle - // CHECK(vert[tri[1].vertices[0]].x == 1.0); - // CHECK(vert[tri[1].vertices[0]].y == 1.0); - // CHECK(vert[tri[1].vertices[1]].x == 2.0); - // CHECK(vert[tri[1].vertices[1]].y == 0.0); - // CHECK(vert[tri[1].vertices[2]].x == 2.0); - // CHECK(vert[tri[1].vertices[2]].y == 2.0); - - // // 3rd triangle - // CHECK(vert[tri[2].vertices[0]].x == 1.0); - // CHECK(vert[tri[2].vertices[0]].y == 1.0); - // CHECK(vert[tri[2].vertices[1]].x == 0.0); - // CHECK(vert[tri[2].vertices[1]].y == 0.0); - // CHECK(vert[tri[2].vertices[2]].x == 2.0); - // CHECK(vert[tri[2].vertices[2]].y == 0.0); - - // // DEBUG print out all the points of the triangles (to check what might have went wrong) - // // for (std::size_t i = 0; i < tri.size(); i++) { - // // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", - // // vert[tri[i].vertices[0]].x, // x0 - // // vert[tri[i].vertices[0]].y, // y0 - // // vert[tri[i].vertices[1]].x, // x1 - // // vert[tri[i].vertices[1]].y, // y1 - // // vert[tri[i].vertices[2]].x, // x2 - // // vert[tri[i].vertices[2]].y // y2 - // // ); - // // } - // } + SECTION("Triangulation") + { + // 5 point polygon + // basically a square where one side contains an inward facing triangle + // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape + const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; + const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; + + CDT::Triangulation cdt; + cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + cdt.eraseOuterTrianglesAndHoles(); + + auto tri = cdt.triangles; + auto vert = cdt.vertices; + + // check if only 3 triangles have been found + CHECK(tri.size() == 3); + + // 1st triangle + CHECK(vert[tri[0].vertices[0]].x == 0.0); + CHECK(vert[tri[0].vertices[0]].y == 2.0); + CHECK(vert[tri[0].vertices[1]].x == 1.0); + CHECK(vert[tri[0].vertices[1]].y == 1.0); + CHECK(vert[tri[0].vertices[2]].x == 2.0); + CHECK(vert[tri[0].vertices[2]].y == 2.0); + + // 2nd triangle + CHECK(vert[tri[1].vertices[0]].x == 1.0); + CHECK(vert[tri[1].vertices[0]].y == 1.0); + CHECK(vert[tri[1].vertices[1]].x == 2.0); + CHECK(vert[tri[1].vertices[1]].y == 0.0); + CHECK(vert[tri[1].vertices[2]].x == 2.0); + CHECK(vert[tri[1].vertices[2]].y == 2.0); + + // 3rd triangle + CHECK(vert[tri[2].vertices[0]].x == 1.0); + CHECK(vert[tri[2].vertices[0]].y == 1.0); + CHECK(vert[tri[2].vertices[1]].x == 0.0); + CHECK(vert[tri[2].vertices[1]].y == 0.0); + CHECK(vert[tri[2].vertices[2]].x == 2.0); + CHECK(vert[tri[2].vertices[2]].y == 0.0); + + // DEBUG print out all the points of the triangles (to check what might have went wrong) + // for (std::size_t i = 0; i < tri.size(); i++) { + // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", + // vert[tri[i].vertices[0]].x, // x0 + // vert[tri[i].vertices[0]].y, // y0 + // vert[tri[i].vertices[1]].x, // x1 + // vert[tri[i].vertices[1]].y, // y1 + // vert[tri[i].vertices[2]].x, // x2 + // vert[tri[i].vertices[2]].y // y2 + // ); + // } + } // SECTION("Triangle y ordering") // { From 76881fd7eb4687bc03031275a793e2ce983c3f9c Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 07:44:30 +0100 Subject: [PATCH 14/33] reenabled ordering test in rasterizer.cpp --- unittests/nucleus/rasterizer.cpp | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index de118f54..aa0a3242 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -92,25 +92,25 @@ TEST_CASE("nucleus/rasterizer") // } } - // SECTION("Triangle y ordering") - // { - // // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value - // const std::vector triangle_points_012 = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; - // const std::vector triangle_points_021 = { glm::vec2(30, 10), glm::vec2(50, 50), glm::vec2(10, 30) }; - // const std::vector triangle_points_102 = { glm::vec2(10, 30), glm::vec2(30, 10), glm::vec2(50, 50) }; - // const std::vector triangle_points_201 = { glm::vec2(10, 30), glm::vec2(50, 50), glm::vec2(30, 10) }; - // const std::vector triangle_points_120 = { glm::vec2(50, 50), glm::vec2(30, 10), glm::vec2(10, 30) }; - // const std::vector triangle_points_210 = { glm::vec2(50, 50), glm::vec2(10, 30), glm::vec2(30, 10) }; - - // const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; - - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); - // CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); - // } + SECTION("Triangle y ordering") + { + // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value + const std::vector triangle_points_012 = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + const std::vector triangle_points_021 = { glm::vec2(30, 10), glm::vec2(50, 50), glm::vec2(10, 30) }; + const std::vector triangle_points_102 = { glm::vec2(10, 30), glm::vec2(30, 10), glm::vec2(50, 50) }; + const std::vector triangle_points_201 = { glm::vec2(10, 30), glm::vec2(50, 50), glm::vec2(30, 10) }; + const std::vector triangle_points_120 = { glm::vec2(50, 50), glm::vec2(30, 10), glm::vec2(10, 30) }; + const std::vector triangle_points_210 = { glm::vec2(50, 50), glm::vec2(10, 30), glm::vec2(30, 10) }; + + const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; + + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); + } SECTION("rasterize triangle") { From 1d9ff4c2e8e35a75700a1ea330c8430e3ce92130 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 14:48:18 +0100 Subject: [PATCH 15/33] added specific versions to deploy.yml to try and fix android builds --- .github/workflows/deploy.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1fb19946..4ea1b74c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,22 +22,25 @@ jobs: fail-fast: false matrix: qtarch: [wasm_singlethread, wasm_multithread, android_arm64_v8a, android_armv7] - qtversion: ['6.6.1'] include: - qtarch: wasm_singlethread qttarget: 'desktop' + qtversion: '6.6.1' qtmodules: '' additional_build_flags: '--target install' - qtarch: wasm_multithread qttarget: 'desktop' + qtversion: '6.6.1' qtmodules: '' additional_cmake_flags: '-DALP_ENABLE_THREADING=ON' additional_build_flags: '--target install' - qtarch: android_arm64_v8a qttarget: 'android' + qtversion: '6.8.1' qtmodules: 'qtcharts qtpositioning' - qtarch: android_armv7 qttarget: 'android' + qtversion: '6.8.1' qtmodules: 'qtcharts qtpositioning' steps: From 9571a237a10fe38bd3b971f26b840182113f9849 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 15:07:59 +0100 Subject: [PATCH 16/33] Revert "added specific versions to deploy.yml to try and fix android builds" This reverts commit 1d9ff4c2e8e35a75700a1ea330c8430e3ce92130. --- .github/workflows/deploy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4ea1b74c..1fb19946 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,25 +22,22 @@ jobs: fail-fast: false matrix: qtarch: [wasm_singlethread, wasm_multithread, android_arm64_v8a, android_armv7] + qtversion: ['6.6.1'] include: - qtarch: wasm_singlethread qttarget: 'desktop' - qtversion: '6.6.1' qtmodules: '' additional_build_flags: '--target install' - qtarch: wasm_multithread qttarget: 'desktop' - qtversion: '6.6.1' qtmodules: '' additional_cmake_flags: '-DALP_ENABLE_THREADING=ON' additional_build_flags: '--target install' - qtarch: android_arm64_v8a qttarget: 'android' - qtversion: '6.8.1' qtmodules: 'qtcharts qtpositioning' - qtarch: android_armv7 qttarget: 'android' - qtversion: '6.8.1' qtmodules: 'qtcharts qtpositioning' steps: From 46ee282071d7e3230dbac27a834075206adfcd49 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 15:09:30 +0100 Subject: [PATCH 17/33] git deploy without android --- .github/workflows/deploy.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1fb19946..c433927f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,12 +33,12 @@ jobs: qtmodules: '' additional_cmake_flags: '-DALP_ENABLE_THREADING=ON' additional_build_flags: '--target install' - - qtarch: android_arm64_v8a - qttarget: 'android' - qtmodules: 'qtcharts qtpositioning' - - qtarch: android_armv7 - qttarget: 'android' - qtmodules: 'qtcharts qtpositioning' + # - qtarch: android_arm64_v8a + # qttarget: 'android' + # qtmodules: 'qtcharts qtpositioning' + # - qtarch: android_armv7 + # qttarget: 'android' + # qtmodules: 'qtcharts qtpositioning' steps: - name: Install dependencies From 99d2db2926d8da07f053cb374021d7a61f3ab6c1 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 3 Dec 2024 15:32:29 +0100 Subject: [PATCH 18/33] second attempt to exclude android builds --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c433927f..d3b75456 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - qtarch: [wasm_singlethread, wasm_multithread, android_arm64_v8a, android_armv7] + qtarch: [wasm_singlethread, wasm_multithread] qtversion: ['6.6.1'] include: - qtarch: wasm_singlethread From bb7eececed6ac9b1990a3079338f8a1251112b6d Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Wed, 4 Dec 2024 12:47:53 +0100 Subject: [PATCH 19/33] Revert "second attempt to exclude android builds" This reverts commit 99d2db2926d8da07f053cb374021d7a61f3ab6c1. --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d3b75456..c433927f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - qtarch: [wasm_singlethread, wasm_multithread] + qtarch: [wasm_singlethread, wasm_multithread, android_arm64_v8a, android_armv7] qtversion: ['6.6.1'] include: - qtarch: wasm_singlethread From b5e6cc78554c5ab92d950206bc17df068a0a0717 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Wed, 4 Dec 2024 12:48:05 +0100 Subject: [PATCH 20/33] Revert "git deploy without android" This reverts commit 46ee282071d7e3230dbac27a834075206adfcd49. --- .github/workflows/deploy.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c433927f..1fb19946 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,12 +33,12 @@ jobs: qtmodules: '' additional_cmake_flags: '-DALP_ENABLE_THREADING=ON' additional_build_flags: '--target install' - # - qtarch: android_arm64_v8a - # qttarget: 'android' - # qtmodules: 'qtcharts qtpositioning' - # - qtarch: android_armv7 - # qttarget: 'android' - # qtmodules: 'qtcharts qtpositioning' + - qtarch: android_arm64_v8a + qttarget: 'android' + qtmodules: 'qtcharts qtpositioning' + - qtarch: android_armv7 + qttarget: 'android' + qtmodules: 'qtcharts qtpositioning' steps: - name: Install dependencies From de389522a6f3d19889010a2ca9364bc888b6e2ad Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Thu, 5 Dec 2024 08:44:07 +0100 Subject: [PATCH 21/33] improved rasterizer - added polygon convenience function - declared lambda signature and split signature into 1 and 2 parameter version - improved comments --- nucleus/utils/rasterizer.cpp | 10 +- nucleus/utils/rasterizer.h | 234 +++++++++++++------------------ unittests/nucleus/rasterizer.cpp | 218 ++++++++++++++++++++++------ 3 files changed, 277 insertions(+), 185 deletions(-) diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index b572e203..0e191732 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -22,24 +22,24 @@ namespace nucleus::utils::rasterizer { -std::vector triangulize(std::vector polygons) +std::vector triangulize(const std::vector& polygon_points) { std::vector processed_triangles; std::vector edges; { // create the edges - edges.reserve(polygons.size()); - for (size_t i = 0; i < polygons.size() - 1; i++) { + edges.reserve(polygon_points.size()); + for (size_t i = 0; i < polygon_points.size() - 1; i++) { edges.push_back(glm::ivec2(int(i), int(i + 1))); } // last edge between start and end vertex - edges.push_back(glm::ivec2(polygons.size() - 1, 0)); + edges.push_back(glm::ivec2(polygon_points.size() - 1, 0)); } // triangulation CDT::Triangulation cdt; - cdt.insertVertices(polygons.begin(), polygons.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertVertices(polygon_points.begin(), polygon_points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); cdt.eraseOuterTrianglesAndHoles(); diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index b6458c69..579827a3 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -22,20 +22,45 @@ #include namespace nucleus::utils::rasterizer { - /* * Possible future improvements: * - check how often each pixel is written to -> maybe we can improve performance here - * - sdf box filter instead by circle to only consider values of the current cell */ +/* PixelWriterFunctionConcept + * The functions use a PixelWriterFunction as a parameter. + * The pixelwriter function is a lambda with one glm::ivec2 position parameter and one optional unsigned int data argument + * glm::ivec2 position + * the position where a pixel should be rendered + * unsigned int data + * the triangle/line-segment index + */ +template +concept PixelWriterFunctionConcept = requires(Func f) { + { f(glm::ivec2(11, 22)) }; +} || requires(Func f) { + { f(glm::ivec2(11, 22), 123456u) }; +}; + namespace details { + /* + * This function calls either the 1 parameter or the 2 parameter pixelwriter (determined at runtime) + */ + template inline void invokePixelWriter(const PixelWriterFunction& pixel_writer, glm::ivec2 position, unsigned int data_index) + { + if constexpr (std::is_invocable_v) { + pixel_writer(position, data_index); + } else if constexpr (std::is_invocable_v) { + pixel_writer(position); + } + } + /* * Calculates the x value for a given y. * The resulting x value will be on the line. */ - inline float get_x_for_y_on_line(const glm::vec2 point_on_line, const glm::vec2 line, float y) + inline float get_x_for_y_on_line(const glm::vec2& point_on_line, const glm::vec2& line, float y) { // pos_on_line = point_on_line + t * line float t = (y - point_on_line.y) / line.y; @@ -68,12 +93,12 @@ namespace details { /* * fills a scanline in the intveral [x1,x2] (inclusive) */ - template inline void fill_between(const PixelWriterFunction& pixel_writer, int y, int x1, int x2, unsigned int data_index) + template inline void fill_between(const PixelWriterFunction& pixel_writer, int y, int x1, int x2, unsigned int data_index) { int fill_direction = (x1 < x2) ? 1 : -1; for (int j = x1; j != x2 + fill_direction; j += fill_direction) { - pixel_writer(glm::vec2(j, y), data_index); + invokePixelWriter(pixel_writer, glm::ivec2(j, y), data_index); } } @@ -83,12 +108,12 @@ namespace details { * if the other line is not located on the current scan line -> we fill to the given fall back lines * the given line should always go from top to bottom (only exception horizontal lines) */ - template + template void render_line(const PixelWriterFunction& pixel_writer, unsigned int data_index, const glm::vec2& line_point, const glm::vec2& line, - int fill_direction = 0, + const int fill_direction = 0, const glm::vec2& other_point = { 0, 0 }, const glm::vec2& other_line = { 0, 0 }, const glm::vec2& fallback_point_bottom = { 0, 0 }, @@ -201,7 +226,7 @@ namespace details { /* * renders a circle at the given position */ - template void add_circle_end_cap(const PixelWriterFunction& pixel_writer, const glm::vec2 position, unsigned int data_index, float distance) + template void add_circle_end_cap(const PixelWriterFunction& pixel_writer, const glm::vec2& position, unsigned int data_index, float distance) { distance += sqrt(0.5); float distance_test = ceil(distance); @@ -210,7 +235,7 @@ namespace details { for (int j = -distance_test; j < distance_test; j++) { const auto offset = glm::vec2(i + 0.0, j + 0.0); if (glm::length(offset) < distance) - pixel_writer(position + offset, data_index); + invokePixelWriter(pixel_writer, position + offset, data_index); } } } @@ -231,7 +256,8 @@ namespace details { * * lastly we have to add endcaps on each original vertice position with the given distance */ - template void render_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) + template + void render_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) { assert(triangle[0].y <= triangle[1].y); @@ -339,21 +365,22 @@ namespace details { // { // DEBUG visualize enlarged points // auto enlarged_top_bottom_end = triangle[2] + normal_top_bottom * distance; - // pixel_writer(triangle[0], 50); - // pixel_writer(enlarged_top_bottom_origin, 50); - // pixel_writer(enlarged_top_middle_origin, 50); + // invokePixelWriter(pixel_writer, triangle[0], 50); + // invokePixelWriter(pixel_writer, enlarged_top_bottom_origin, 50); + // invokePixelWriter(pixel_writer, enlarged_top_middle_origin, 50); - // pixel_writer(triangle[1], 50); - // pixel_writer(enlarged_middle_bottom_origin, 50); - // pixel_writer(enlarged_top_middle_end, 50); + // invokePixelWriter(pixel_writer, triangle[1], 50); + // invokePixelWriter(pixel_writer, enlarged_middle_bottom_origin, 50); + // invokePixelWriter(pixel_writer, enlarged_top_middle_end, 50); - // pixel_writer(triangle[2], 50); - // pixel_writer(enlarged_middle_bottom_end, 50); - // pixel_writer(enlarged_top_bottom_end, 50); + // invokePixelWriter(pixel_writer, triangle[2], 50); + // invokePixelWriter(pixel_writer, enlarged_middle_bottom_end, 50); + // invokePixelWriter(pixel_writer, enlarged_top_bottom_end, 50); // } } - template void render_line_preprocess(const PixelWriterFunction& pixel_writer, const std::array line_points, unsigned int line_index, float distance) + template + void render_line_preprocess(const PixelWriterFunction& pixel_writer, const std::array line_points, unsigned int line_index, float distance) { // edge from top to bottom glm::vec2 origin; @@ -371,8 +398,8 @@ namespace details { render_line(pixel_writer, line_index, origin, edge); // debug output for points - // pixel_writer(line_points[0], 50); - // pixel_writer(line_points[1], 50); + // invokePixelWriter(pixel_writer, line_points[0], 50); + // invokePixelWriter(pixel_writer, line_points[1], 50); return; // finished } @@ -436,147 +463,82 @@ namespace details { // { // DEBUG visualize enlarged points // // const auto enlarged_bottom_origin = origin + edge + normal * distance; - // pixel_writer(enlarged_top_origin, 50); - // pixel_writer(enlarged_middle_origin, 50); + // invokePixelWriter(pixel_writer, enlarged_top_origin, 50); + // invokePixelWriter(pixel_writer, enlarged_middle_origin, 50); - // pixel_writer(enlarged_middle_end, 50); - // pixel_writer(enlarged_bottom_origin, 50); + // invokePixelWriter(pixel_writer, enlarged_middle_end, 50); + // invokePixelWriter(pixel_writer, enlarged_bottom_origin, 50); - // pixel_writer(line_points[0], 50); - // pixel_writer(line_points[1], 50); + // invokePixelWriter(pixel_writer, line_points[0], 50); + // invokePixelWriter(pixel_writer, line_points[1], 50); // } } - /* - * calculates how far away the given position is from a triangle - * uses distance to shift the current triangle distance - * if it is within the triangle, the pixel writer function is called - * - * uses triangle distance calculation from: https://iquilezles.org/articles/distfunctions2d/ - */ - template - void triangle_sdf(const PixelWriterFunction& pixel_writer, - const glm::vec2 current_position, - const std::array points, - const std::array edges, - float s, - unsigned int data_index, - float distance) - { - glm::vec2 v0 = current_position - points[0], v1 = current_position - points[1], v2 = current_position - points[2]; - - // pq# = distance from edge # - glm::vec2 pq0 = v0 - edges[0] * glm::clamp(glm::dot(v0, edges[0]) / glm::dot(edges[0], edges[0]), 0.0f, 1.0f); - glm::vec2 pq1 = v1 - edges[1] * glm::clamp(glm::dot(v1, edges[1]) / glm::dot(edges[1], edges[1]), 0.0f, 1.0f); - glm::vec2 pq2 = v2 - edges[2] * glm::clamp(glm::dot(v2, edges[2]) / glm::dot(edges[2], edges[2]), 0.0f, 1.0f); - // d.x == squared distance to triangle edge/vertice - // d.y == are we inside or outside triangle - glm::vec2 d = min(min(glm::vec2(dot(pq0, pq0), s * (v0.x * edges[0].y - v0.y * edges[0].x)), glm::vec2(dot(pq1, pq1), s * (v1.x * edges[1].y - v1.y * edges[1].x))), - glm::vec2(dot(pq2, pq2), s * (v2.x * edges[2].y - v2.y * edges[2].x))); - float dist_from_tri = -sqrt(d.x) * glm::sign(d.y); - - if (dist_from_tri < distance) { - pixel_writer(current_position, data_index); - } - } - - /* - * calculates how far away the given position is from a line segment - * uses distance to shift the current triangle distance - * if it is within the triangle, the pixel writer function is called - * - * uses line segment distance calculation from: https://iquilezles.org/articles/distfunctions2d/ - */ - template - void line_sdf(const PixelWriterFunction& pixel_writer, const glm::vec2 current_position, const glm::vec2 origin, glm::vec2 edge, unsigned int data_index, float distance) - { - glm::vec2 v0 = current_position - origin; - - float dist_from_line = length(v0 - edge * glm::clamp(glm::dot(v0, edge) / glm::dot(edge, edge), 0.0f, 1.0f)); - - if (dist_from_line < distance) { - pixel_writer(current_position, data_index); - } - } - } // namespace details /* * triangulizes polygons and orders the vertices by y position per triangle * output: top, middle, bottom, top, middle,... */ -std::vector triangulize(std::vector polygons); +std::vector triangulize(const std::vector& polygon_points); /* - * ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size - * in this method we traverse through every triangle and generate the bounding box and traverse the bounding box + * Rasterize a triangle + * in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. + * this function determines the position where pixels should be set and calls the given pixel_writer lambda to actually draw the pixels + * + * NOTE: the triangle points have to be ordered correctly by y position + * -> input triangles: ordered by y position within each triangle (top_ypos_triangle_1, middle_ypos_triangle_1, bottom_ypos_triangle_1, top_ypos_triangle_2, middle_ypos_triangle_2, ...) + * + * example usage: + * const std::vector triangle_points = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5) }; + * nucleus::Raster output({ 64, 64 }, 0u); + * const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + * nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangle_points); */ -template void rasterize_triangle_sdf(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance) +template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) { - // we sample from the center - // and have a radius of a half pixel diagonal - // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! - distance += sqrt(0.5); - for (size_t i = 0; i < triangles.size() / 3; ++i) { - const std::array points = { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }; - const std::array edges = { points[1] - points[0], points[2] - points[1], points[0] - points[2] }; - - auto min_bound = min(min(points[0], points[1]), points[2]) - glm::vec2(distance); - auto max_bound = max(max(points[0], points[1]), points[2]) + glm::vec2(distance); - - float s = glm::sign(edges[0].x * edges[2].y - edges[0].y * edges[2].x); - - for (size_t x = min_bound.x; x < max_bound.x; x++) { - for (size_t y = min_bound.y; y < max_bound.y; y++) { - auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; - - details::triangle_sdf(pixel_writer, current_position, points, edges, s, i, distance); - } - } + details::render_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); } } -template void rasterize_line_sdf(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance) +/* + * Rasterize a line + * + * this function determines the position where pixels should be set and calls the given pixel_writer lambda to actually draw the pixels + * + * input line_points: a list of points that should form a line (line_start, line_point1, line_point2, ..., line_end) + * -> generates line segments between line_start-line_point1, line_point1 to line_point2, ... + * + * example usage: + * const std::vector line = { glm::vec2(30.5, 10.5), glm::vec2(50.5, 30.5), glm::vec2(30.5, 50.5), glm::vec2(10.5, 30.5), glm::vec2(30.5, 10.5) }; + * nucleus::Raster output({ 64, 64 }, 0u); + * const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + * nucleus::utils::rasterizer::rasterize_line(pixel_writer, line); + */ +template void rasterize_line(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance = 0.0) { - // we sample from the center - // and have a radius of a half pixel diagonal - // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! - distance += sqrt(0.5); - for (size_t i = 0; i < line_points.size() - 1; ++i) { - const std::array points = { line_points[i + 0], line_points[i + 1] }; - auto edge = points[1] - points[0]; - - auto min_bound = min(points[0], points[1]) - glm::vec2(distance); - auto max_bound = max(points[0], points[1]) + glm::vec2(distance); - - for (size_t x = min_bound.x; x < max_bound.x; x++) { - for (size_t y = min_bound.y; y < max_bound.y; y++) { - auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; - - details::line_sdf(pixel_writer, current_position, points[0], edge, i, distance); - } - } + details::render_line_preprocess(pixel_writer, { line_points[i + 0], line_points[i + 1] }, i, distance); } } /* - * ideal for many triangles. especially if each triangle only covers small parts of the raster - * in this method every triangle is traversed only once, and it only accesses pixels it needs for itself. + * Rasterize a polygon + * convenience function that really just calls rasterize_triangle after triangulizing the polygon + * this function determines the position where pixels should be set and calls the given pixel_writer lambda to actually draw the pixels + * + * example usage: + * const std::vector polygon_points = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5) }; + * nucleus::Raster output({ 64, 64 }, 0u); + * const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + * nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); */ -template void rasterize_triangle(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance = 0.0) +template void rasterize_polygon(const PixelWriterFunction& pixel_writer, const std::vector& polygon_points, float distance = 0.0) { - for (size_t i = 0; i < triangles.size() / 3; ++i) { - details::render_triangle(pixel_writer, { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }, i, distance); - } -} - -template void rasterize_line(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance = 0.0) -{ - for (size_t i = 0; i < line_points.size() - 1; ++i) { - details::render_line_preprocess(pixel_writer, { line_points[i + 0], line_points[i + 1] }, i, distance); - } + const auto triangles = triangulize(polygon_points); + rasterize_triangle(pixel_writer, triangles, distance); } } // namespace nucleus::utils::rasterizer diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index aa0a3242..40967e7f 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -28,6 +28,110 @@ #include +/* + * calculates how far away the given position is from a triangle + * uses distance to shift the current triangle distance + * if it is within the triangle, the pixel writer function is called + * * uses triangle distance calculation from: https://iquilezles.org/articles/distfunctions2d/ + */ +template +void triangle_sdf(const PixelWriterFunction& pixel_writer, + const glm::vec2 current_position, + const std::array points, + const std::array edges, + float s, + unsigned int data_index, + float distance) +{ + glm::vec2 v0 = current_position - points[0], v1 = current_position - points[1], v2 = current_position - points[2]; + + // pq# = distance from edge # + glm::vec2 pq0 = v0 - edges[0] * glm::clamp(glm::dot(v0, edges[0]) / glm::dot(edges[0], edges[0]), 0.0f, 1.0f); + glm::vec2 pq1 = v1 - edges[1] * glm::clamp(glm::dot(v1, edges[1]) / glm::dot(edges[1], edges[1]), 0.0f, 1.0f); + glm::vec2 pq2 = v2 - edges[2] * glm::clamp(glm::dot(v2, edges[2]) / glm::dot(edges[2], edges[2]), 0.0f, 1.0f); + // d.x == squared distance to triangle edge/vertice + // d.y == are we inside or outside triangle + glm::vec2 d = min(min(glm::vec2(dot(pq0, pq0), s * (v0.x * edges[0].y - v0.y * edges[0].x)), glm::vec2(dot(pq1, pq1), s * (v1.x * edges[1].y - v1.y * edges[1].x))), + glm::vec2(dot(pq2, pq2), s * (v2.x * edges[2].y - v2.y * edges[2].x))); + float dist_from_tri = -sqrt(d.x) * glm::sign(d.y); + + if (dist_from_tri < distance) { + nucleus::utils::rasterizer::details::invokePixelWriter(pixel_writer, current_position, data_index); + } +} + +/* + * calculates how far away the given position is from a line segment + * uses distance to shift the current triangle distance + * if it is within the triangle, the pixel writer function is called + * * uses line segment distance calculation from: https://iquilezles.org/articles/distfunctions2d/ + */ +template +void line_sdf(const PixelWriterFunction& pixel_writer, const glm::vec2 current_position, const glm::vec2 origin, glm::vec2 edge, unsigned int data_index, float distance) +{ + glm::vec2 v0 = current_position - origin; + + float dist_from_line = length(v0 - edge * glm::clamp(glm::dot(v0, edge) / glm::dot(edge, edge), 0.0f, 1.0f)); + + if (dist_from_line < distance) { + nucleus::utils::rasterizer::details::invokePixelWriter(pixel_writer, current_position, data_index); + } +} + +/* + * ideal if you want to rasterize only a few triangles, where every triangle covers a large part of the raster size + * in this method we traverse through every triangle and generate the bounding box and traverse the bounding box + */ +template void rasterize_triangle_sdf(const PixelWriterFunction& pixel_writer, const std::vector& triangles, float distance) +{ + // we sample from the center + // and have a radius of a half pixel diagonal + // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! + distance += sqrt(0.5); + + for (size_t i = 0; i < triangles.size() / 3; ++i) { + const std::array points = { triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2] }; + const std::array edges = { points[1] - points[0], points[2] - points[1], points[0] - points[2] }; + + auto min_bound = min(min(points[0], points[1]), points[2]) - glm::vec2(distance); + auto max_bound = max(max(points[0], points[1]), points[2]) + glm::vec2(distance); + + float s = glm::sign(edges[0].x * edges[2].y - edges[0].y * edges[2].x); + + for (size_t x = min_bound.x; x < max_bound.x; x++) { + for (size_t y = min_bound.y; y < max_bound.y; y++) { + auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; + + triangle_sdf(pixel_writer, current_position, points, edges, s, i, distance); + } + } + } +} + +template void rasterize_line_sdf(const PixelWriterFunction& pixel_writer, const std::vector& line_points, float distance) +{ + // we sample from the center + // and have a radius of a half pixel diagonal + // NOTICE: this still leads to false positives if the edge of a triangle is slighly in another pixel without every comming in this pixel!! + distance += sqrt(0.5); + + for (size_t i = 0; i < line_points.size() - 1; ++i) { + const std::array points = { line_points[i + 0], line_points[i + 1] }; + auto edge = points[1] - points[0]; + + auto min_bound = min(points[0], points[1]) - glm::vec2(distance); + auto max_bound = max(points[0], points[1]) + glm::vec2(distance); + + for (size_t x = min_bound.x; x < max_bound.x; x++) { + for (size_t y = min_bound.y; y < max_bound.y; y++) { + auto current_position = glm::vec2 { x + 0.5, y + 0.5 }; + + line_sdf(pixel_writer, current_position, points[0], edge, i, distance); + } + } + } +} + TEST_CASE("nucleus/rasterizer") { // NOTE: most tests compare the dda renderer with the sdf renderer @@ -112,18 +216,44 @@ TEST_CASE("nucleus/rasterizer") CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); } + SECTION("rasterize Polygon") + { + + const std::vector polygon_points = { + glm::vec2(10.5, 10.5), + glm::vec2(30.5, 10.5), + glm::vec2(50.5, 50.5), + glm::vec2(10.5, 30.5), + }; + + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); + + const auto triangles = nucleus::utils::rasterizer::triangulize(polygon_points); + nucleus::Raster output2({ 64, 64 }, 0u); + auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer2, triangles, 0); + + CHECK(output.buffer() == output2.buffer()); + + // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_polygon.png")); + } + SECTION("rasterize triangle") { // two triangles const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); nucleus::Raster output2({ 64, 64 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, 0); + auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer2, triangles, 0); CHECK(output.buffer() == output2.buffer()); @@ -136,12 +266,12 @@ TEST_CASE("nucleus/rasterizer") { const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; nucleus::Raster output({ 6, 6 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + const auto pixel_writer = [&output](glm::ivec2 pos, int) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); nucleus::Raster output2({ 6, 6 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_small, 0); + const auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer2, triangles_small, 0); CHECK(output.buffer() == output2.buffer()); @@ -155,12 +285,12 @@ TEST_CASE("nucleus/rasterizer") // less than one pixel const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::vec2 pos, int) { output.pixel(pos) = 255; }; + const auto pixel_writer = [&output](glm::ivec2 pos, int) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); nucleus::Raster output2({ 64, 64 }, 0u); - const auto pixel_writer2 = [&output2](glm::vec2 pos, int) { output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0); + const auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0); CHECK(output.buffer() == output2.buffer()); @@ -178,7 +308,7 @@ TEST_CASE("nucleus/rasterizer") float distance = 4.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; @@ -187,11 +317,11 @@ TEST_CASE("nucleus/rasterizer") nucleus::utils::rasterizer::details::add_circle_end_cap(pixel_writer, triangles[2], 1, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -215,18 +345,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 5.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -243,18 +373,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 4.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -272,18 +402,18 @@ TEST_CASE("nucleus/rasterizer") // float distance = 5.0; // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - // const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + // const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { // if (bounds.contains(pos)) // output.pixel(pos) = 255; // }; // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); // nucleus::Raster output2(size, 0u); - // const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + // const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { // if (bounds.contains(pos)) // output2.pixel(pos) = 255; // }; - // nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + // rasterize_triangle_sdf(pixel_writer2, triangles, distance); // CHECK(output.buffer() == output2.buffer()); @@ -329,18 +459,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_triangle_sdf(pixel_writer2, triangles, distance); + rasterize_triangle_sdf(pixel_writer2, triangles, distance); CHECK(output.buffer() == output2.buffer()); @@ -363,7 +493,7 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; @@ -375,16 +505,16 @@ TEST_CASE("nucleus/rasterizer") nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_right_to_left_2pixel, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_lower_min, distance); - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_upper_min, distance); - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_left_to_right_1pixel, distance); - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_right_to_left_1pixel, distance); - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_left_to_right_2pixel, distance); - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line_right_to_left_2pixel, distance); + rasterize_line_sdf(pixel_writer2, line_lower_min, distance); + rasterize_line_sdf(pixel_writer2, line_upper_min, distance); + rasterize_line_sdf(pixel_writer2, line_left_to_right_1pixel, distance); + rasterize_line_sdf(pixel_writer2, line_right_to_left_1pixel, distance); + rasterize_line_sdf(pixel_writer2, line_left_to_right_2pixel, distance); + rasterize_line_sdf(pixel_writer2, line_right_to_left_2pixel, distance); CHECK(output.buffer() == output2.buffer()); @@ -401,18 +531,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + rasterize_line_sdf(pixel_writer2, line, distance); CHECK(output.buffer() == output2.buffer()); @@ -429,18 +559,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + rasterize_line_sdf(pixel_writer2, line, distance); CHECK(output.buffer() == output2.buffer()); @@ -457,18 +587,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + rasterize_line_sdf(pixel_writer2, line, distance); CHECK(output.buffer() == output2.buffer()); @@ -486,18 +616,18 @@ TEST_CASE("nucleus/rasterizer") float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::vec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::vec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; - nucleus::utils::rasterizer::rasterize_line_sdf(pixel_writer2, line, distance); + rasterize_line_sdf(pixel_writer2, line, distance); CHECK(output.buffer() == output2.buffer()); From 320188abda254329850ad5efa60cd74ac67b8d61 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Thu, 5 Dec 2024 08:54:20 +0100 Subject: [PATCH 22/33] added rasterizer benchmarks --- unittests/nucleus/rasterizer.cpp | 96 +++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 40967e7f..19956a63 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -18,6 +18,7 @@ *****************************************************************************/ #include +#include #include #include @@ -134,6 +135,7 @@ template void rasterize_line_sdf(const PixelWrite TEST_CASE("nucleus/rasterizer") { + // NOTE: most tests compare the dda renderer with the sdf renderer // unfortunatly the sdf renderer can produce false positives (writing to cell that should be empty if near edge) // those errors have all been subverted by choosing parameters that work with both renderers @@ -232,7 +234,7 @@ TEST_CASE("nucleus/rasterizer") const auto triangles = nucleus::utils::rasterizer::triangulize(polygon_points); nucleus::Raster output2({ 64, 64 }, 0u); - auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; rasterize_triangle_sdf(pixel_writer2, triangles, 0); CHECK(output.buffer() == output2.buffer()); @@ -252,7 +254,7 @@ TEST_CASE("nucleus/rasterizer") nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); nucleus::Raster output2({ 64, 64 }, 0u); - auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; rasterize_triangle_sdf(pixel_writer2, triangles, 0); CHECK(output.buffer() == output2.buffer()); @@ -266,11 +268,11 @@ TEST_CASE("nucleus/rasterizer") { const std::vector triangles_small = { glm::vec2(2, 2), glm::vec2(1, 3), glm::vec2(3.8, 3.8) }; nucleus::Raster output({ 6, 6 }, 0u); - const auto pixel_writer = [&output](glm::ivec2 pos, int) { output.pixel(pos) = 255; }; + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_small); nucleus::Raster output2({ 6, 6 }, 0u); - const auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + const auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; rasterize_triangle_sdf(pixel_writer2, triangles_small, 0); CHECK(output.buffer() == output2.buffer()); @@ -285,11 +287,11 @@ TEST_CASE("nucleus/rasterizer") // less than one pixel const std::vector triangles_smallest = { glm::vec2(30.4, 30.4), glm::vec2(30.8, 30.6), glm::vec2(30.8, 30.8) }; nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::ivec2 pos, int) { output.pixel(pos) = 255; }; + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_smallest); nucleus::Raster output2({ 64, 64 }, 0u); - const auto pixel_writer2 = [&output2](glm::ivec2 pos, int) { output2.pixel(pos) = 255; }; + const auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; rasterize_triangle_sdf(pixel_writer2, triangles_smallest, 0); CHECK(output.buffer() == output2.buffer()); @@ -308,7 +310,7 @@ TEST_CASE("nucleus/rasterizer") float distance = 4.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; @@ -317,7 +319,7 @@ TEST_CASE("nucleus/rasterizer") nucleus::utils::rasterizer::details::add_circle_end_cap(pixel_writer, triangles[2], 1, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -345,14 +347,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 5.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -373,14 +375,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 4.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -402,14 +404,14 @@ TEST_CASE("nucleus/rasterizer") // float distance = 5.0; // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - // const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + // const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { // if (bounds.contains(pos)) // output.pixel(pos) = 255; // }; // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); // nucleus::Raster output2(size, 0u); - // const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + // const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { // if (bounds.contains(pos)) // output2.pixel(pos) = 255; // }; @@ -459,14 +461,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -493,7 +495,7 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; @@ -505,7 +507,7 @@ TEST_CASE("nucleus/rasterizer") nucleus::utils::rasterizer::rasterize_line(pixel_writer, line_right_to_left_2pixel, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -531,14 +533,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -559,14 +561,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 0.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -587,14 +589,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -616,14 +618,14 @@ TEST_CASE("nucleus/rasterizer") float distance = 2.0; radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - const auto pixel_writer = [&output, bounds](glm::ivec2 pos, int) { + const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_line(pixel_writer, line, distance); nucleus::Raster output2(size, 0u); - const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos, int) { + const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { if (bounds.contains(pos)) output2.pixel(pos) = 255; }; @@ -636,3 +638,45 @@ TEST_CASE("nucleus/rasterizer") // image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); } } +TEST_CASE("nucleus/utils/rasterizer benchmarks") +{ + BENCHMARK("triangulize polygons") + { + const std::vector polygon_points = { glm::vec2(10.5, 10.5), glm::vec2(30.5, 10.5), glm::vec2(50.5, 50.5), glm::vec2(10.5, 30.5) }; + nucleus::utils::rasterizer::triangulize(polygon_points); + }; + + BENCHMARK("Rasterize triangle") + { + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + }; + + BENCHMARK("Rasterize triangle (no raster write)") + { + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + }; + + BENCHMARK("Rasterize triangle SDF") + { + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + nucleus::Raster output2({ 64, 64 }, 0u); + auto pixel_writer = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer, triangles, 0); + }; + + BENCHMARK("Rasterize triangle SDF (no raster write)") + { + const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + + const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; + rasterize_triangle_sdf(pixel_writer, triangles, 0); + }; +} From e2ac7750d46fdb2e807c8ecbc095265259f107c2 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Thu, 5 Dec 2024 09:15:26 +0100 Subject: [PATCH 23/33] added rasterizer benchmarks with increased distance --- unittests/nucleus/rasterizer.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 19956a63..cf0554c0 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -650,33 +650,35 @@ TEST_CASE("nucleus/utils/rasterizer benchmarks") { const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; - nucleus::Raster output({ 64, 64 }, 0u); - const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + // we dont particular care how long it takes to write to raster -> so do nothing + const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); }; - BENCHMARK("Rasterize triangle (no raster write)") + BENCHMARK("Rasterize triangle distance") { const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + // we dont particular care how long it takes to write to raster -> so do nothing const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; - nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, 5.0); }; BENCHMARK("Rasterize triangle SDF") { const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; - nucleus::Raster output2({ 64, 64 }, 0u); - auto pixel_writer = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; + // we dont particular care how long it takes to write to raster -> so do nothing + const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; rasterize_triangle_sdf(pixel_writer, triangles, 0); }; - BENCHMARK("Rasterize triangle SDF (no raster write)") + BENCHMARK("Rasterize triangle SDF distance") { const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; + // we dont particular care how long it takes to write to raster -> so do nothing const auto pixel_writer = [](glm::ivec2) { /*do nothing*/ }; - rasterize_triangle_sdf(pixel_writer, triangles, 0); + rasterize_triangle_sdf(pixel_writer, triangles, 5.0); }; } From 1081cb426c58b6fe3f34061318ac88647339b75d Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Fri, 13 Dec 2024 07:39:32 +0100 Subject: [PATCH 24/33] rasterizer - added possibility to remove duplicate vertices --- nucleus/utils/rasterizer.cpp | 15 ++++- nucleus/utils/rasterizer.h | 2 +- unittests/nucleus/rasterizer.cpp | 103 +++++++++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index 0e191732..9449da14 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -22,7 +22,7 @@ namespace nucleus::utils::rasterizer { -std::vector triangulize(const std::vector& polygon_points) +std::vector triangulize(std::vector polygon_points, bool remove_duplicate_vertices) { std::vector processed_triangles; @@ -39,6 +39,19 @@ std::vector triangulize(const std::vector& polygon_points) // triangulation CDT::Triangulation cdt; + + if (remove_duplicate_vertices) { + CDT::RemoveDuplicatesAndRemapEdges( + polygon_points, + [](const glm::vec2& p) { return p.x; }, + [](const glm::vec2& p) { return p.y; }, + edges.begin(), + edges.end(), + [](const glm::ivec2& p) { return p.x; }, + [](const glm::ivec2& p) { return p.y; }, + [](CDT::VertInd start, CDT::VertInd end) { return glm::ivec2 { start, end }; }); + } + cdt.insertVertices(polygon_points.begin(), polygon_points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); cdt.eraseOuterTrianglesAndHoles(); diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 579827a3..9a5a5dd5 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -480,7 +480,7 @@ namespace details { * triangulizes polygons and orders the vertices by y position per triangle * output: top, middle, bottom, top, middle,... */ -std::vector triangulize(const std::vector& polygon_points); +std::vector triangulize(std::vector polygon_points, bool remove_duplicate_vertices = false); /* * Rasterize a triangle diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index cf0554c0..64c40dc8 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -132,6 +132,29 @@ template void rasterize_line_sdf(const PixelWrite } } } +std::pair>> triangulate(std::vector points, std::vector edges, bool remove_duplicate_vertices) +{ + + CDT::Triangulation cdt; + + if (remove_duplicate_vertices) { + CDT::RemoveDuplicatesAndRemapEdges( + points, + [](const glm::vec2& p) { return p.x; }, + [](const glm::vec2& p) { return p.y; }, + edges.begin(), + edges.end(), + [](const glm::ivec2& p) { return p.x; }, + [](const glm::ivec2& p) { return p.y; }, + [](CDT::VertInd start, CDT::VertInd end) { return glm::ivec2 { start, end }; }); + } + + cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); + cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); + cdt.eraseOuterTrianglesAndHoles(); + + return std::make_pair(cdt.triangles, cdt.vertices); +} TEST_CASE("nucleus/rasterizer") { @@ -150,13 +173,10 @@ TEST_CASE("nucleus/rasterizer") const std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; const std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; - CDT::Triangulation cdt; - cdt.insertVertices(points.begin(), points.end(), [](const glm::vec2& p) { return p.x; }, [](const glm::vec2& p) { return p.y; }); - cdt.insertEdges(edges.begin(), edges.end(), [](const glm::ivec2& p) { return p.x; }, [](const glm::ivec2& p) { return p.y; }); - cdt.eraseOuterTrianglesAndHoles(); + auto pair = triangulate(points, edges, false); - auto tri = cdt.triangles; - auto vert = cdt.vertices; + auto tri = pair.first; + auto vert = pair.second; // check if only 3 triangles have been found CHECK(tri.size() == 3); @@ -198,6 +218,53 @@ TEST_CASE("nucleus/rasterizer") // } } + SECTION("Triangulation - duplicate vertices") + { + // 6 point polygon + // basically a square where two sides contains an inward facing triangle that meets at the same middle vertice (so esentially two triangles that mirror at top of bottom triangle) + // a triangulation algorithm should be able to discern that 3 triangles are needed to construct this shape + std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(1, 1), glm::vec2(2, 0) }; + std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 5), glm::ivec2(5, 0) }; + + auto pair = triangulate(points, edges, true); + + auto tri = pair.first; + auto vert = pair.second; + + // check if only 3 triangles have been found + CHECK(tri.size() == 2); + + // 1st triangle + CHECK(vert[tri[0].vertices[0]].x == 0.0); + CHECK(vert[tri[0].vertices[0]].y == 2.0); + CHECK(vert[tri[0].vertices[1]].x == 1.0); + CHECK(vert[tri[0].vertices[1]].y == 1.0); + CHECK(vert[tri[0].vertices[2]].x == 2.0); + CHECK(vert[tri[0].vertices[2]].y == 2.0); + + // 2nd triangle + CHECK(vert[tri[1].vertices[0]].x == 1.0); + CHECK(vert[tri[1].vertices[0]].y == 1.0); + CHECK(vert[tri[1].vertices[1]].x == 0.0); + CHECK(vert[tri[1].vertices[1]].y == 0.0); + CHECK(vert[tri[1].vertices[2]].x == 2.0); + CHECK(vert[tri[1].vertices[2]].y == 0.0); + + // // DEBUG print out all the points of the triangles(to check what might have went wrong) for (std::size_t i = 0; i < tri.size(); i++) + // { + // for (std::size_t i = 0; i < tri.size(); i++) { + // printf("Triangle points: [[%f, %f], [%f, %f], [%f, %f]]\n", + // vert[tri[i].vertices[0]].x, // x0 + // vert[tri[i].vertices[0]].y, // y0 + // vert[tri[i].vertices[1]].x, // x1 + // vert[tri[i].vertices[1]].y, // y1 + // vert[tri[i].vertices[2]].x, // x2 + // vert[tri[i].vertices[2]].y // y2 + // ); + // } + // } + } + SECTION("Triangle y ordering") { // make sure that the triangle_order function correctly orders the triangle points from lowest y to highest y value @@ -640,12 +707,36 @@ TEST_CASE("nucleus/rasterizer") } TEST_CASE("nucleus/utils/rasterizer benchmarks") { + BENCHMARK("triangulize polygons") { const std::vector polygon_points = { glm::vec2(10.5, 10.5), glm::vec2(30.5, 10.5), glm::vec2(50.5, 50.5), glm::vec2(10.5, 30.5) }; nucleus::utils::rasterizer::triangulize(polygon_points); }; + BENCHMARK("triangulize polygons 2") + { + std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; + std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; + + nucleus::utils::rasterizer::triangulize(points); + }; + + BENCHMARK("triangulize polygons + remove duplicates (no duplicates)") + { + std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; + std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; + + nucleus::utils::rasterizer::triangulize(points, true); + }; + BENCHMARK("triangulize polygons + remove duplicates (with duplicates)") + { + std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(1, 1), glm::vec2(2, 0) }; + std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 5), glm::ivec2(5, 0) }; + + nucleus::utils::rasterizer::triangulize(points, true); + }; + BENCHMARK("Rasterize triangle") { const std::vector triangles = { glm::vec2(30.5, 10.5), glm::vec2(10.5, 30.5), glm::vec2(50.5, 50.5), glm::vec2(5.5, 5.5), glm::vec2(15.5, 10.5), glm::vec2(5.5, 15.5) }; From 8f88387f6cebbed385269cba9a387ed11af401c4 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Thu, 19 Dec 2024 09:02:23 +0100 Subject: [PATCH 25/33] Rasterizer - extracted edge generation to separate function --- nucleus/utils/rasterizer.cpp | 11 ++++++++--- nucleus/utils/rasterizer.h | 12 ++++++++++-- unittests/nucleus/rasterizer.cpp | 31 ++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index 9449da14..bbcb5bf4 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -22,10 +22,8 @@ namespace nucleus::utils::rasterizer { -std::vector triangulize(std::vector polygon_points, bool remove_duplicate_vertices) +std::vector generate_neighbour_edges(std::vector polygon_points) { - std::vector processed_triangles; - std::vector edges; { // create the edges edges.reserve(polygon_points.size()); @@ -37,6 +35,13 @@ std::vector triangulize(std::vector polygon_points, bool r edges.push_back(glm::ivec2(polygon_points.size() - 1, 0)); } + return edges; +} + +std::vector triangulize(std::vector polygon_points, std::vector edges, bool remove_duplicate_vertices) +{ + std::vector processed_triangles; + // triangulation CDT::Triangulation cdt; diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 9a5a5dd5..08352f3c 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -476,11 +476,17 @@ namespace details { } // namespace details +/* + * generates edges of a polygon + * this assumes that polygons neighbouring in the vector should form an edge and the first and last edge are also connected + */ +std::vector generate_neighbour_edges(std::vector polygon_points); + /* * triangulizes polygons and orders the vertices by y position per triangle * output: top, middle, bottom, top, middle,... */ -std::vector triangulize(std::vector polygon_points, bool remove_duplicate_vertices = false); +std::vector triangulize(std::vector polygon_points, std::vector edges, bool remove_duplicate_vertices = false); /* * Rasterize a triangle @@ -537,7 +543,9 @@ template void rasterize_line(co */ template void rasterize_polygon(const PixelWriterFunction& pixel_writer, const std::vector& polygon_points, float distance = 0.0) { - const auto triangles = triangulize(polygon_points); + const auto edges = generate_neighbour_edges(polygon_points); + const auto triangles = triangulize(polygon_points, edges); + rasterize_triangle(pixel_writer, triangles, distance); } diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 64c40dc8..df2bb02d 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -277,12 +277,19 @@ TEST_CASE("nucleus/rasterizer") const std::vector correct = { { glm::vec2(30, 10), glm::vec2(10, 30), glm::vec2(50, 50) } }; - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120)); - CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210)); + const auto edges_012 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_012); + const auto edges_021 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_021); + const auto edges_102 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_102); + const auto edges_201 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_201); + const auto edges_120 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_120); + const auto edges_210 = nucleus::utils::rasterizer::generate_neighbour_edges(triangle_points_210); + + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_012, edges_012)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_021, edges_021)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_102, edges_102)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_201, edges_201)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_120, edges_120)); + CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210, edges_210)); } SECTION("rasterize Polygon") @@ -299,7 +306,8 @@ TEST_CASE("nucleus/rasterizer") const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); - const auto triangles = nucleus::utils::rasterizer::triangulize(polygon_points); + const auto edges = nucleus::utils::rasterizer::generate_neighbour_edges(polygon_points); + const auto triangles = nucleus::utils::rasterizer::triangulize(polygon_points, edges); nucleus::Raster output2({ 64, 64 }, 0u); auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; rasterize_triangle_sdf(pixel_writer2, triangles, 0); @@ -711,7 +719,8 @@ TEST_CASE("nucleus/utils/rasterizer benchmarks") BENCHMARK("triangulize polygons") { const std::vector polygon_points = { glm::vec2(10.5, 10.5), glm::vec2(30.5, 10.5), glm::vec2(50.5, 50.5), glm::vec2(10.5, 30.5) }; - nucleus::utils::rasterizer::triangulize(polygon_points); + const auto edges = nucleus::utils::rasterizer::generate_neighbour_edges(polygon_points); + nucleus::utils::rasterizer::triangulize(polygon_points, edges); }; BENCHMARK("triangulize polygons 2") @@ -719,7 +728,7 @@ TEST_CASE("nucleus/utils/rasterizer benchmarks") std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; - nucleus::utils::rasterizer::triangulize(points); + nucleus::utils::rasterizer::triangulize(points, edges); }; BENCHMARK("triangulize polygons + remove duplicates (no duplicates)") @@ -727,14 +736,14 @@ TEST_CASE("nucleus/utils/rasterizer benchmarks") std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(2, 0) }; std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 0) }; - nucleus::utils::rasterizer::triangulize(points, true); + nucleus::utils::rasterizer::triangulize(points, edges, true); }; BENCHMARK("triangulize polygons + remove duplicates (with duplicates)") { std::vector points = { glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 2), glm::vec2(2, 2), glm::vec2(1, 1), glm::vec2(2, 0) }; std::vector edges = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 4), glm::ivec2(4, 5), glm::ivec2(5, 0) }; - nucleus::utils::rasterizer::triangulize(points, true); + nucleus::utils::rasterizer::triangulize(points, edges, true); }; BENCHMARK("Rasterize triangle") From a650f29d11d36bf16215e0c9d5ba6fad1bbd7971 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Sun, 22 Dec 2024 14:06:47 +0100 Subject: [PATCH 26/33] Rasterizer - Bugfix for specific triangles + small fixes - fixed problem where certain triangles were not rendered correctly - fixed render problem for triangles with integer vertices - added start_offset for generate_neighbour_edges function - added more triangle tests for rasterizer --- nucleus/utils/rasterizer.cpp | 6 +- nucleus/utils/rasterizer.h | 18 +- unittests/nucleus/CMakeLists.txt | 2 + .../rasterizer_output_random_triangle.png | Bin 0 -> 3281 bytes .../data/rasterizer_simple_triangle.png | Bin 0 -> 109 bytes unittests/nucleus/rasterizer.cpp | 245 ++++++++++++++---- 6 files changed, 207 insertions(+), 64 deletions(-) create mode 100644 unittests/nucleus/data/rasterizer_output_random_triangle.png create mode 100644 unittests/nucleus/data/rasterizer_simple_triangle.png diff --git a/nucleus/utils/rasterizer.cpp b/nucleus/utils/rasterizer.cpp index bbcb5bf4..cc621b93 100644 --- a/nucleus/utils/rasterizer.cpp +++ b/nucleus/utils/rasterizer.cpp @@ -22,17 +22,17 @@ namespace nucleus::utils::rasterizer { -std::vector generate_neighbour_edges(std::vector polygon_points) +std::vector generate_neighbour_edges(std::vector polygon_points, const size_t start_offset) { std::vector edges; { // create the edges edges.reserve(polygon_points.size()); for (size_t i = 0; i < polygon_points.size() - 1; i++) { - edges.push_back(glm::ivec2(int(i), int(i + 1))); + edges.push_back(glm::ivec2(start_offset + int(i), start_offset + int(i + 1))); } // last edge between start and end vertex - edges.push_back(glm::ivec2(polygon_points.size() - 1, 0)); + edges.push_back(glm::ivec2(start_offset + polygon_points.size() - 1, start_offset)); } return edges; diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 08352f3c..9fd33296 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -21,6 +21,8 @@ #include +#include + namespace nucleus::utils::rasterizer { /* * Possible future improvements: @@ -171,7 +173,8 @@ namespace details { } else { { // start of the line // const float line_top_y = line_point.y; - const float bottom_y = ceil(line_point.y) - epsilon; // top of the y step + // in case that the current point is exactly on an integer value, we first have to increase the value by a small amount then ceil it. + const float bottom_y = ceil(line_point.y + epsilon) - epsilon; // bottom of the y step // either use line start or calculate the x at the bottom of the pixel const auto x1 = (fill_current_from_top_x) ? line_point.x : get_x_for_y_on_line(line_point, line, bottom_y); @@ -202,10 +205,11 @@ namespace details { } // draw all the steps in between - for (int y = line_point.y + 1; y < line_point.y + y_steps - 1; y++) { + for (int y = line_point.y + 1; y < ceil(line_point.y + line.y + epsilon) - 1; y++) { // at a distinct y step draw the current line from floor to ceil if (!fill) { + // draw between line start and line end of current y level const auto x1 = get_x_for_y_on_line(line_point, line, y + epsilon); const auto x2 = get_x_for_y_on_line(line_point, line, y + 1 - epsilon); @@ -259,7 +263,6 @@ namespace details { template void render_triangle(const PixelWriterFunction& pixel_writer, const std::array triangle, unsigned int triangle_index, float distance) { - assert(triangle[0].y <= triangle[1].y); assert(triangle[1].y <= triangle[2].y); @@ -267,7 +270,9 @@ namespace details { auto edge_top_middle = triangle[1] - triangle[0]; auto edge_middle_bottom = triangle[2] - triangle[1]; - int fill_direction = (triangle[1].x < triangle[2].x) ? 1 : -1; + // by comparing the middle vertex with the middle position of the longest line, we can determine the direction of our fill algorithm + int x_middle_of_top_bottom_line = get_x_for_y_on_line(triangle[0], edge_top_bottom, triangle[1].y); + int fill_direction = (triangle[1].x < x_middle_of_top_bottom_line) ? 1 : -1; if (distance == 0.0) { // top middle @@ -279,7 +284,6 @@ namespace details { } // we need to render an enlarged triangle - auto normal_top_bottom = glm::normalize(glm::vec2(-edge_top_bottom.y, edge_top_bottom.x)); auto normal_top_middle = glm::normalize(glm::vec2(-edge_top_middle.y, edge_top_middle.x)); auto normal_middle_bottom = glm::normalize(glm::vec2(-edge_middle_bottom.y, edge_middle_bottom.x)); @@ -479,8 +483,10 @@ namespace details { /* * generates edges of a polygon * this assumes that polygons neighbouring in the vector should form an edge and the first and last edge are also connected + * start offset if you want to only create an edge ring list of a part of the bigger polygon you can only provide the part and an start_offset, + * that indicates by how much the vertice index has to be offset */ -std::vector generate_neighbour_edges(std::vector polygon_points); +std::vector generate_neighbour_edges(std::vector polygon_points, const size_t start_offset = 0); /* * triangulizes polygons and orders the vertices by y position per triangle diff --git a/unittests/nucleus/CMakeLists.txt b/unittests/nucleus/CMakeLists.txt index f097a660..c713e982 100644 --- a/unittests/nucleus/CMakeLists.txt +++ b/unittests/nucleus/CMakeLists.txt @@ -69,6 +69,8 @@ qt_add_resources(unittests_nucleus "test_data" data/eaws_0-0-0.mvt data/eaws_2-2-0.mvt data/eaws_10-236-299.mvt + data/rasterizer_simple_triangle.png + data/rasterizer_output_random_triangle.png ) target_link_libraries(unittests_nucleus PUBLIC nucleus Catch2::Catch2 Qt::Test Qt::Gui) target_compile_definitions(unittests_nucleus PUBLIC "ALP_TEST_DATA_DIR=\":/test_data/\"") diff --git a/unittests/nucleus/data/rasterizer_output_random_triangle.png b/unittests/nucleus/data/rasterizer_output_random_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..025e950de46afba82a1f05b895b6ebe967a14e0b GIT binary patch literal 3281 zcmV;?3@-DDP)*$FbFn&=lx$fbJ13_u>24bby_ZFdTPQZAq3E- z?Ou?#1dW1*ArOTp3RKbZ>%TC*|Dq7ZLFR6f!6=2)-$kHk% zHl$(|vR8kh>|*1xF1|2;(?FQ~`P^|zHlhMCWCv>h^weD{HjI8=-I+K7l8KO}?wEWqCho0TGW zwDEq_@=O`tl1SQS?a}waeoEs^#qRFlC0EV(Oz`NAsXw%-9! zOvj@BSPdAQRU>V*NLd|6NgKyFK6vYOD!^{(5}wGQ7WN#S2s<;7hdB8I;bckQ1nZKIpxPl$1%%5VZA{ILXG4sn3=`3ZI)Lcaq-0L%trztH zQI2b({8&Mu1yBJ|htav2G|9NRtrt{4P-1R&CNVLeh0tT^t_C>jFcQ7|G`;H-Fp08t zvEDSU831Fr_Wvm$vUdK1FMyDp{bm+tN>D_GHaF3O%?A_*X{Q7WHg^*kP4bf#zl9~+ z^V<{-8@xi{93{ViV@V6fI~ii2b@m6CQzHb3I1sJ|wd9z7REDgXkpu9eU4(@z1ddO7 zP3;Y7GrGUtTCfxkl2933h-{T#;_wgPizd+nfMsSonqi5RRp$-oipyp~X?|ePSk_HS z2{O7#+sg@p6(vo>mSm$w8L9w2*<^+#0YM8a&v0c( zkUA;Xw?=5KH3;OW=Rrb4^;T?~r#6T(Ai%H<5D2_!TU-Gm0ULFwf@=mv1x!BJU;(N5 zU%=f0n-fee77WZ; zqk+|6b%s&900xs=-&G>1|7cXem;ml0w-M3ja*u^O4dUR@KvZcNhs0{I%U-No0-F3; z_{&R;3YZ#hmMLI$#+CI1&G!o)oV+UG%&&uzpEXa8BJ#V68Ok_P1Hn%o=y0=V61O{Y z^n)M#;0J5r+x_4v|Ju{w4?b|Q1s3G5v4H7yVvYO>mnfhw;}}ZpA>>c}+q%VLKq+I* z=A*`Ofdx?VFJ2J^xz!I$^c+|q$5)jw1w@K}aRkz4CzdaPUVeN91n90qK`DTw6LjNl zaQ|y{6AH)^xg1ImPhpE*0_GkR5QJWxB|tPp_Bp})`ez1HV>&d%wlKqC;+;`e#S9t=P;VtYxd3t$?6+9WsO z4#ri?#ZF5GLAwO%cCK1D8pX%dloQ>e0HC-0nm*GhM2u{%6B`IIiyt64ClF9mvrX38 z`_lOl>F(l2R9bcW7JoA_uRL4Ng5NC$SilGqd;}WQCpdZbi6*(ci7>^&*kE`#w1$U3 zqHH@354rNo!U~b=m`2Gp^PO;=zfRdt4N9mik@)sr)e|&=Xu6R;7>ns3{gxxzOT4Aa5WT)lh4=)G|L<*P& z>QUAu;PN#7bNqZTrljpttdoX9MZ^S}l!EOzY@V{2B^ryBbq%!EXn%@jt|gD26*LWW zHxcy#@D^52axE8qh%BRts5vQb!0u>$(N(4yqO70(J|bs-yi(>cP-Y;QKqfxgADxB38!wp)j1~=$Um^-f-3JW@;zipK zHp>Wez|;om)(UeomoWq~8UQ<*_CJkI5{rb0+)cr}Z_hN!uVobaA+1fkFB40Ez@2oO zETIX&*1(SeJtG`jxB1CU*Z`tJ@pr&TIYHV4P{PICu|Hp$YN}2S0GaH#S+6?k6N3 zx%k0wGqegAR>74~od=3IAXoo6;c=^odT?@7h$k69!E8uJ|6LXU?up1ycu4+1pir1? z(l8#(2^wIyzE7dFslXCk9|N7$jF#}E{st3f)8V4GfGd|Ud!N&WF9h4+>NC{P0m2d( zHUKG&2L~w(7o1ycEkLD^^CsMk0P_(V9}p|x?*cuwv4D~4T#Nz8%`eh*kqOqgc8GS% z!?y9w32xACZns()96*Oq0o4*v_-{q7{|>4q37~60!$I7%y_l%U4xGNd{eg%w18wuup~4x^5ON8jv+f5&B+f2(4RqYN!Qeh+Yr zD}_0hz_9&}AZWd*iw?dGmlSr>OaWz>{3-~PP$mk3yKSb9Z~+7~byP1^4e{UW3?P`o z-?l~cLR>Gu;Wl==z{NSNDDpaj@VK(c zRj}~Q+qYZ-D-Fe^fCV>bnGT%Y;{Q)y51Bwj&%V;QffH72Tf72>40<(0LO^QB@I2>I z1@t}u3G9`;TbU=@sL%3rJs|2^01U8RU0)s^@4k`<<*iw_A#@Y)ezN*-31#KYY z!vf!hnJ;S|f02^0v*5hx(8LA5bLde)p#VRF2C|di*KJq9xec0~)X-`f1fe`+BycDo z&`HZMeI*!vwJDzp@UUgn*Sg~)rh?a~vO~no;gyR(p`rH@y-mJokXUwymK<|r6`WVk zUBDy1Z)=OyG}|~M>Y%UiYsyyqZ9``X$HdDUX=+$r8@x{{@x{q4 z)8dmM75}bPK)RCwykW5g+(Z7u`){ii5Gem(FmZ75jXt1)d)TBEmYv-03*Y|R;Xk-N zJjO(SyTBZ@&vDaVZvk@@@DvBIO;W&vr3V4R(ZETnJ+$js#kQy-MS_?!~aCKrdk!=xDnC{&%iKqlb{WVMz)1S9LEQp9%0-LBT^277~taxz-l4qxzkQ0#C4MfoF z1B$6}5;>df6u)74a@5sI>6jCWPYa##&HWjbt`1^M@KK5GAAA9fT4XW*E33n~(7^tf zR*nL4pi|ndZ*Be$AaBJY*nI)p{{#CgWSyLijfD_SK1lYhJrK>MdjlZPzG|)&8Zrjx znbUbD=;&0B4Ishp#CRCWCDw!8oHUt=z4%M9ad4$P0a7{=)TY$0DRa=5@bu@{3KnH1 z;%xDZ1c~IB#DG4GsJp^%rC)dSEJERz1|<2UOl||#x{oo@76;{7AgM<1)_~3gKVnz| zDurSUFsMEZsYY<_nlfeKyC@Cl05uXcr$N-n+&26d?Tgx~HU|M^P#WujK$^P!r$M zkFVR^06(K`krH6c4_MnM<#V?vDRB|)3QzzMg5l2GMk)OBi6PZ!32o&zKU=zc-p8(a*M=H_cZZ`IIg$J%UcMp{>kg77TEp)_m0C!>$hmBp4oAa_|Ew&H|6fVg?4jBOuH;Rhv&5C@Amg z;uyj)GxX?2UIqpZW`#@t>(@1RZ#r<1t1D+yXFkK499EW?{TDPr=6JgLxvX. *****************************************************************************/ +#define WRITE_DEBUG_IMAGE + #include #include #include @@ -24,7 +26,7 @@ #include #include "nucleus/Raster.h" -// #include "nucleus/tile/conversion.h" +#include "nucleus/tile/conversion.h" #include "nucleus/utils/rasterizer.h" #include @@ -156,6 +158,15 @@ std::pair>> triangulate(std::vect return std::make_pair(cdt.triangles, cdt.vertices); } +QImage example_rasterizer_image(QString filename) +{ + auto image_file = QFile(QString("%1%2").arg(ALP_TEST_DATA_DIR, filename)); + image_file.open(QFile::ReadOnly); + const auto image_bytes = image_file.readAll(); + REQUIRE(!QImage::fromData(image_bytes).isNull()); + return QImage::fromData(image_bytes); +} + TEST_CASE("nucleus/rasterizer") { @@ -292,9 +303,27 @@ TEST_CASE("nucleus/rasterizer") CHECK(correct == nucleus::utils::rasterizer::triangulize(triangle_points_210, edges_210)); } - SECTION("rasterize Polygon") + SECTION("rasterize simple triangle - integer values") { + // simple triangle with integer vertice values + // this tests if the visualization works with integer values + // test was added, since there was a case where the second to last row was never rendered + const std::vector polygon_points = { glm::vec2(5, 1), glm::vec2(5, 5), glm::vec2(1, 5) }; + + nucleus::Raster output({ 7, 7 }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); + + auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + CHECK(image == example_rasterizer_image("rasterizer_simple_triangle.png")); +#ifdef WRITE_DEBUG_IMAGE + image.save(QString("rasterizer_output_simple_triangle.png")); +#endif + } + + SECTION("rasterize Polygon") + { const std::vector polygon_points = { glm::vec2(10.5, 10.5), glm::vec2(30.5, 10.5), @@ -314,9 +343,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_polygon.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_polygon.png")); +#endif } SECTION("rasterize triangle") @@ -334,9 +365,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output.png")); +#endif } SECTION("rasterize triangle small") @@ -352,9 +385,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_small.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_small.png")); +#endif } SECTION("rasterize triangle smallest") @@ -371,9 +406,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_smallest.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_smallest.png")); +#endif } SECTION("rasterize triangle enlarged endcaps only") @@ -402,9 +439,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_endcap.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_endcap.png")); +#endif } SECTION("rasterize triangle enlarged") @@ -437,9 +476,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged.png")); +#endif } SECTION("rasterize triangle horizontal") @@ -465,39 +506,121 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_horizontal.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_horizontal.png")); +#endif } - // can't be verified exactly, since SDF is currently slightly inaccurate - // SECTION("rasterize triangle narrow") - // { - // const std::vector triangles = { glm::vec2(6.5, 16.5), glm::vec2(55.5, 18.5), glm::vec2(6.5, 20.5), glm::vec2(55.5, 46.5), glm::vec2(6.5, 48.5), glm::vec2(55.5, 50.5) }; - // auto size = glm::vec2(64, 64); - // nucleus::Raster output(size, 0u); - // float distance = 5.0; - // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; - - // const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { - // if (bounds.contains(pos)) - // output.pixel(pos) = 255; - // }; - // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); - - // nucleus::Raster output2(size, 0u); - // const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { - // if (bounds.contains(pos)) - // output2.pixel(pos) = 255; - // }; - // rasterize_triangle_sdf(pixel_writer2, triangles, distance); - - // CHECK(output.buffer() == output2.buffer()); - - // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_narrow.png")); + // // can't be verified exactly, since SDF is currently slightly inaccurate + // SECTION("rasterize triangle narrow") + // { + // const std::vector triangles = { glm::vec2(6.5, 16.5), glm::vec2(55.5, 18.5), glm::vec2(6.5, 20.5), glm::vec2(55.5, 46.5), glm::vec2(6.5, 48.5), glm::vec2(55.5, 50.5) // } + // ; + // auto size = glm::vec2(64, 64); + // nucleus::Raster output(size, 0u); + // float distance = 5.0; + // radix::geometry::Aabb2 bounds = { { 0, 0 }, size }; + + // const auto pixel_writer = [&output, bounds](glm::ivec2 pos) { + // if (bounds.contains(pos)) + // output.pixel(pos) = 255; + // }; + // nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles, distance); + + // nucleus::Raster output2(size, 0u); + // const auto pixel_writer2 = [&output2, bounds](glm::ivec2 pos) { + // if (bounds.contains(pos)) + // output2.pixel(pos) = 255; + // }; + // rasterize_triangle_sdf(pixel_writer2, triangles, distance); + + // CHECK(output.buffer() == output2.buffer()); + + // #ifdef WRITE_DEBUG_IMAGE + // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_narrow.png")); + // #endif + // } + + SECTION("rasterize donut") + { + const std::vector> polygon_points = { { glm::vec2(10.5, 10.5), glm::vec2(50.5, 10.5), glm::vec2(50.5, 50.5), glm::vec2(10.5, 50.5) }, + { glm::vec2(20.5, 20.5), glm::vec2(40.5, 20.5), glm::vec2(40.5, 40.5), glm::vec2(20.5, 40.5) } }; + + const std::vector correct_edges + = { glm::ivec2(0, 1), glm::ivec2(1, 2), glm::ivec2(2, 3), glm::ivec2(3, 0), glm::ivec2(4, 5), glm::ivec2(5, 6), glm::ivec2(6, 7), glm::ivec2(7, 4) }; + + std::vector vertices; + std::vector edges; + + for (size_t i = 0; i < polygon_points.size(); i++) { + auto current_edges = nucleus::utils::rasterizer::generate_neighbour_edges(polygon_points[i], vertices.size()); + edges.insert(edges.end(), current_edges.begin(), current_edges.end()); + vertices.insert(vertices.end(), polygon_points[i].begin(), polygon_points[i].end()); + } + + CHECK(edges.size() == correct_edges.size()); + + CHECK(edges == correct_edges); + + auto triangles = nucleus::utils::rasterizer::triangulize(vertices, edges); + + nucleus::Raster output({ 64, 64 }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles); + + nucleus::Raster output2({ 64, 64 }, 0u); + auto pixel_writer2 = [&output2](glm::ivec2 pos) { output2.pixel(pos) = 255; }; + rasterize_triangle_sdf(pixel_writer2, triangles, 0); + + CHECK(output.buffer() == output2.buffer()); + +#ifdef WRITE_DEBUG_IMAGE + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_donut.png")); +#endif + } + + SECTION("rasterize random triangles") + { + constexpr auto cells = 8u; + constexpr auto cell_size = 32u; + + nucleus::Raster output({ cells * cell_size, cells * cell_size }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + + const auto rand_pos = []() { return std::rand() % (cell_size * 10u) / 10.0f; }; + + std::srand(123458); // initialize rand -> we need to create consistent triangles + + for (size_t i = 0; i < cells; i++) { + for (size_t j = 0; j < cells; j++) { + const auto cell_offset = glm::vec2(j * cell_size, i * cell_size); + + const std::vector polygon_points + = { glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset }; + + nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); + + // DEBUG view the vertices in the image (uses different pixel intensity) + // const auto pixel_writer_points = [&output](glm::ivec2 pos) { output.pixel(pos) = 125; }; + // pixel_writer_points(polygon_points[0]); + // pixel_writer_points(polygon_points[1]); + // pixel_writer_points(polygon_points[2]); + } + } + + auto image = nucleus::tile::conversion::u8raster_to_qimage(output); + CHECK(image == example_rasterizer_image("rasterizer_output_random_triangle.png")); + +#ifdef WRITE_DEBUG_IMAGE + image.save(QString("rasterizer_output_random_triangle.png")); +#endif + } SECTION("rasterize triangle start/end y") { @@ -551,9 +674,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_triangle_start_end.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_triangle_start_end.png")); +#endif } SECTION("rasterize line start/end y") @@ -595,9 +720,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_start_end.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_start_end.png")); +#endif } SECTION("rasterize line straight") @@ -623,9 +750,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_straight.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_straight.png")); +#endif } SECTION("rasterize line diagonal") @@ -651,9 +780,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_diagonal.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_diagonal.png")); +#endif } SECTION("rasterize line straight enlarged") @@ -679,9 +810,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_straight_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_straight_enlarged.png")); +#endif } SECTION("rasterize line diagonal enlarged") @@ -708,9 +841,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); +#endif } } TEST_CASE("nucleus/utils/rasterizer benchmarks") From c1c9d1582ab4b1f4a5826622b0aa1adb5fac26d6 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Sun, 22 Dec 2024 14:23:36 +0100 Subject: [PATCH 27/33] Rasterizer - renamed rasterizer debug image macro and commented it out --- unittests/nucleus/rasterizer.cpp | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 417975de..35c31359 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -17,7 +17,7 @@ * along with this program. If not, see . *****************************************************************************/ -#define WRITE_DEBUG_IMAGE +// #define WRITE_RASTERIZER_DEBUG_IMAGE #include #include @@ -317,7 +317,7 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_simple_triangle.png")); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE image.save(QString("rasterizer_output_simple_triangle.png")); #endif } @@ -343,7 +343,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_polygon.png")); @@ -365,7 +365,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output.png")); @@ -385,7 +385,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_small.png")); @@ -406,7 +406,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_smallest.png")); @@ -439,7 +439,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_enlarged_endcap.png")); @@ -476,7 +476,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_enlarged.png")); @@ -506,7 +506,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_enlarged_horizontal.png")); @@ -579,7 +579,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_donut.png")); #endif @@ -617,7 +617,7 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_output_random_triangle.png")); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE image.save(QString("rasterizer_output_random_triangle.png")); #endif } @@ -674,7 +674,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_triangle_start_end.png")); @@ -720,7 +720,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_line_start_end.png")); @@ -750,7 +750,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_line_straight.png")); @@ -780,7 +780,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_line_diagonal.png")); @@ -810,7 +810,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_line_straight_enlarged.png")); @@ -841,7 +841,7 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_DEBUG_IMAGE +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); From e7982c5593935ddf040f636c2f3822efbb27d16c Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Sun, 22 Dec 2024 14:55:50 +0100 Subject: [PATCH 28/33] Rasterizer - removed macros as they likely cause github tests to fail --- unittests/nucleus/rasterizer.cpp | 101 ++++++++++--------------------- 1 file changed, 33 insertions(+), 68 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 35c31359..394c83f9 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -17,8 +17,6 @@ * along with this program. If not, see . *****************************************************************************/ -// #define WRITE_RASTERIZER_DEBUG_IMAGE - #include #include #include @@ -317,9 +315,7 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_simple_triangle.png")); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE - image.save(QString("rasterizer_output_simple_triangle.png")); -#endif + // image.save(QString("rasterizer_output_simple_triangle.png")); } SECTION("rasterize Polygon") @@ -343,11 +339,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_polygon.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_polygon.png")); } SECTION("rasterize triangle") @@ -365,11 +359,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output.png")); } SECTION("rasterize triangle small") @@ -385,11 +377,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_small.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_small.png")); } SECTION("rasterize triangle smallest") @@ -406,11 +396,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_smallest.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_smallest.png")); } SECTION("rasterize triangle enlarged endcaps only") @@ -439,11 +427,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged_endcap.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_endcap.png")); } SECTION("rasterize triangle enlarged") @@ -476,11 +462,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged.png")); } SECTION("rasterize triangle horizontal") @@ -506,11 +490,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_enlarged_horizontal.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_horizontal.png")); } // // can't be verified exactly, since SDF is currently slightly inaccurate @@ -539,11 +521,10 @@ TEST_CASE("nucleus/rasterizer") // CHECK(output.buffer() == output2.buffer()); - // #ifdef WRITE_DEBUG_IMAGE // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_narrow.png")); - // #endif + // // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // // image.save(QString("rasterizer_output_enlarged_narrow.png")); + // // } SECTION("rasterize donut") @@ -579,10 +560,8 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_donut.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_donut.png")); } SECTION("rasterize random triangles") @@ -617,9 +596,7 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_output_random_triangle.png")); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE - image.save(QString("rasterizer_output_random_triangle.png")); -#endif + // image.save(QString("rasterizer_output_random_triangle.png")); } SECTION("rasterize triangle start/end y") @@ -674,11 +651,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_triangle_start_end.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_triangle_start_end.png")); } SECTION("rasterize line start/end y") @@ -720,11 +695,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_start_end.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_start_end.png")); } SECTION("rasterize line straight") @@ -750,11 +723,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_straight.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_straight.png")); } SECTION("rasterize line diagonal") @@ -780,11 +751,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_diagonal.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_diagonal.png")); } SECTION("rasterize line straight enlarged") @@ -810,11 +779,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_straight_enlarged.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_straight_enlarged.png")); } SECTION("rasterize line diagonal enlarged") @@ -841,11 +808,9 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); -#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); -#endif + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); } } TEST_CASE("nucleus/utils/rasterizer benchmarks") From c6d52cffa67dd6dc496e7fabdc5a5a12c30e14dc Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 23 Dec 2024 09:43:04 +0100 Subject: [PATCH 29/33] Rasterizer Bugfix - wrong fill_direction due to int rounding --- nucleus/utils/rasterizer.h | 2 +- .../rasterizer_output_random_triangle.png | Bin 3281 -> 3286 bytes unittests/nucleus/rasterizer.cpp | 58 ++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/nucleus/utils/rasterizer.h b/nucleus/utils/rasterizer.h index 9fd33296..7168d795 100644 --- a/nucleus/utils/rasterizer.h +++ b/nucleus/utils/rasterizer.h @@ -271,7 +271,7 @@ namespace details { auto edge_middle_bottom = triangle[2] - triangle[1]; // by comparing the middle vertex with the middle position of the longest line, we can determine the direction of our fill algorithm - int x_middle_of_top_bottom_line = get_x_for_y_on_line(triangle[0], edge_top_bottom, triangle[1].y); + float x_middle_of_top_bottom_line = get_x_for_y_on_line(triangle[0], edge_top_bottom, triangle[1].y); int fill_direction = (triangle[1].x < x_middle_of_top_bottom_line) ? 1 : -1; if (distance == 0.0) { diff --git a/unittests/nucleus/data/rasterizer_output_random_triangle.png b/unittests/nucleus/data/rasterizer_output_random_triangle.png index 025e950de46afba82a1f05b895b6ebe967a14e0b..708de39aec5703dee56b581d53f3292afcfeab56 100644 GIT binary patch delta 3031 zcmV;|3n=u_8P*w)Ie&;rL_t(|ob6rNn(H74)_i^cSI%6tOuWeXw# zNprdv>?uQ|pkW9^;fVrOwEg*C7~hX5L~*cl!tFDt-OvIkrdrwK*nVPSmA)LFlIHH4 z8UoqfUR0)-mm7BS;{lPkYm*2`FiB&%#ZCwtYOdQs&SnI&Eq|uaz})4z6`u_%z_#m= zrBzI9NX05-tNudS#m4Qr_`(2A17Y&-=Z;gd5fz9r`&m1v8$ru0`UD@mXm^1Sup{d7 zL(;2;RIrl6BfvN9N6AMFc2IgG>@6!mPFDvYmdGU_EQ>XDt31VFS%;QdxA%I9M=|H>VmcP}+iJkztQu*fMat?pO4>Na@xfcKQvr5Mm+(XewXo;tMA(>tJjBT#2q#l= z_}$+^ivm20eHD~pv$j!tj~3AjAXt}t1l104Dj;0`Xn$jBZaf=eWM!C$KGXq3uO=mP zN^iZW4~TMH6XnMW3N3&Nh&qhU&7?`j&27D)0)i5AvondA`7DGUOLsNEQHPP}<)`Ug zr+`V6t&8=ham@f2!?pjXfXLeU5553GcJ`ZDoGC#O8QR=L4>liA9HgBREZE#lU^K~h zTKpE4Y=6&hQ#fq!42Ab7`2`$HS}8eX zLm5YEAo$4x z9d7naV!9(oKls59ey|q4-4CAfuRRU^;C~GlTVO%{8Vi_SC)UWHaESu?GLE6d9zy=q zzpYz529z?^Y(8on7gzu#|Kb%qk1q7j2X9*Auk$p~ZfBrLrsWBZIW`8ps zSri5~DLSE{jh4WwYnd^&SMa#jeA_JnzMX4y4PXtGz;uj8R~VoX2x;;Aykidrpc%2f zB-I5l4M1&@Nw|Y?6?3uEl0ncefx4Zm7LG>oHZ|o$wv`W z!npqcUjO`milbj%?{?Q)7cUGbDe82TzG*9eVM6i8CwT9QCxx@l0^ms4y?;}p6_!d* zc3M9E@PfcVq=0Fl9%WquE>Gh>$Il02O4>ffI%z0WL`+=AsfMI?tDB|VUZoxs3 zae@I)SW8c@uK-yD^)wi9KQ1)@6xdLBAw!ca1(V<|wZR$shH&0M0rOl1qJbME>%&8U zAN=44PWZ+qi_-ms#2zCTKNxO?Rsq8*xH78qKoJM*)qhU-xK%_wI5{fBlgb7?AalYO zg6(ki9%|?SVF?TyfRx6AgA|4f&MmeUpiuw42+lRt5*q zAyh!M1Qh;Tk?X&Ms!0Oq8qjbMH*G5>YVtz}y?|hX6buy3OMsK2jI4X{Vg3dR+#+j} zO;H(Aor}T>B0t;2h*pPDN5P|S_QS_p&3`mgKp7^#3IZjRiGtu}o2esQ00B)M)k{@F{P#Ko2&V8zDWVtRdif2vvD*bM z&S6E7*YQjf6uyV^-lpru98w;~HG7AIX$?2%4~T*Lsg{C|E1O&e3*Wqb%O$YVP+SUF zaD$fVz}YSSfBJgJ1R{F&mBtO6uwvWd6)67o+yy-H`?j`NO|y+N z!j4eHE&2Vo*PJc$q7M2Bzou-(BMqG;9DfrpZ=|VVd2R4Msl+EIw@iyqhFtu+Rss1= z2JnK#7H|*w5AUB=DOxKA^8g?D8MEEWpTsIUBD*OMH-0TthJmhKY#6# z!o=!0Kr(e}A6WzoIa~oUb9;oT56WMB&Y5VFi$mXW`ozssSW-1CK9{+{@_O78P+bca z`F(K}di-{P!M>4eBL)oI2x*1yz%X-@pbdydwv8I#rU84nnX3R=V#h1GGdI9TyZxf? z=i@10t!C_kRWjs(}UrfUN`U7ZBc)n+hO*pVxWfj_nay&?%*=byiiIrI~GSeHb$_!-SPF+^oPR zDvSK^yoVJp&Or9;bQ5v{^16WtntebqHBKUDlbzx>EKiQQS}7fKLh)&#GrqZhMy0ER z7!&-cME4KA07fmcnE#d4;l0qn_A#v-1?+)NX|uky`5(ZZibb&b0=EA`1KTrXot%x0 zg%D2ONVe1-i00D00gz{3HP;Fa83Xjp={ys3bgIV&kYIOWJPhR$>%neLnoPx3{H549 zxKcg=az2xM3n&(+1|<2UOl||#x{oo@76;{7Agf03(v!LiDSxCo5^JQOe`13tgI5Td z+En6AEYt?jxUI_vL})+-w+3JX*ufed^)oxrekfcgFGC0N+S)T^VuY zU1B{r&nI96G6NiK#M-tam;iC!R}oGHSJrYQw@B=CSJu#dS=$k3UCq6`h2ZL+y#CYz Z+aH}0$3k1f;B){0002ovPDHLkV1iCvjsXAw delta 3026 zcmV;@3oZ248POS#Ie&vmL_t(|ob6rNx}z`%Hh$;*UpaHpRsX zw%-9!Ovj@BSPdAQRU>V*NLd|6NgKyFK6vYOD!^{(5}wGQ7WN#S2s<;7hdB8I;bck< zzx!KgQGjQ$uYxjc);4PI(IR>Q1nZKIpxPl$1%%5VZGTM7jb}rQqzn_$hdO}h)ud!j z>8%&_0a1=?qWoAvp#@L@QHRmFnKa3`xvdveKu}_Cb|x_~pM}t4>8=Ji>M#<${4~Am z6flXhb+O(wt{DJhxc2`kAhLG;gD-%Po&9DOXG%~+hBi0RgUtsN2Wh7S3pRHX7)|n% z7Qclh+kf-h6b>7_Lg5@Gzkp*&3&uMcVxV>Q2bfbM1c*2gt_HQ_n0{1-teKGm@Sc;iIr974d;r>W1|7cXem;ml0w-M3ja*u^O4dUR@KvZcNhs0{I z%U-No0-F3;_{&R;3YZ#hmMLI$#+CI1&G!o)oV+UG%&&uzpEXa8BJ#V68Ok_P1Hn%o z=y0=V61O{Y^n)M#;0J5r+x_4v|Ju{w4}U&zu>}_7ud#sXbz+VD3706KFXI?W>>=b& z{oA_5V?Zfm&E})Vae)O;@-JQy1-aD^O!ORBAjemgFa<=4e{lrTW+#>}fnI)m1qA4> zLqREkq!V=GZgBr=brTB66S*8p5Km!?UIOMG6cB`7oh3jtMD{ts{Q74GQ)4={ zNvaEA8i3j)H{lM(Rm{asO9nx^1nPFKS~wcT$JCS)-J$@XxBQwu(v;zm?jb$|O7e={+!JX_C#-z^4Mzz7q31RB&QIC=JoCb_(c zFvY^yV0bvRhKE3+Y&#AQx$?`x3X$uWM#(kvop8v1eWT=(E;%EtW@&vzKsJDD3lz&h z3*-I=@cQTfr#SlM^=@~)b@9T0lA=yW>6^Cl7bX;ce1i9`cv3j)EC7y#-G4hJT4Aa5 zWT)lh4=)G|L<*P&>QUAu;PN#7bNqZTrljpttdoX9MZ^S}l!EOzY@V{2B^ryBbq%!E zXn%@jt|gD26*LWWHxcy#@D^52axE8qh%BRts5vQb!0u>$(N(4yqO70(J|bsV7m_Q~z+8>>Tz#A`_3yc;G zkY6GSNZkhw2I58A5H`yQbHLOF>DCH!GnX+0G8zCon)W}9P7;fRh}=!Vyl>Am%CBV< z`XQ}Nye|_=fWVz}nk=CSz}CQz0X-ueTetbiP1pdULh*ONNI5~;1bk;2=B3Kya-2fMtNhS)T`31q}0JK@l&%b_))Q zj1vra!diNIeFexOsHee*`*EoOpumR03mKYZDVPLzsSVE1H-z&B3Yh0A5DnZQSsxw( z{NM*aaKbk>S(NT4Bpx2Q_`z^9v+2Z}f#SN}QTajS@WaB@_Llgb7?Ak&5~ z1l!^2Gt|%l!V(xZ04a?J2Pq5}oLg)yK&6oLCX*)z9)EU-cFV)I@y!Wt&~9$GS{WQb zhfo345>WVWMXvu2swN4bYe2(6+_b%zsL2l@^a6qjQZP_BF9A-9GP364!~6{txJA|` zo1!wLIv0f%M1Ho35v>lRj)F(u?1z8HWto4gVvVB=Ej@k@aEvR3IhMe%{f;1Ly{U^1 zz73ZYc7M}M0cDu{DhQNNCJKVPZKjTJ0R%L4R4-Kx@!#tVAeh46wng+pTra=jHg>zf z#W}1f@;aVrg2MN3-rIEDm_y3rxMuH=FsKh;w3xU$Jru<*^>w_E}%4aKE^ z1vhA!4xHWM|4&~JnLtF(zS6jX6IN_nyaI*{dVe)ULO^QB@I2>I1@t}u3G9`;TbU=@ zsL%3rJs|2^01U8RU0)s^@4k`<<*iw_A#@Y)ezN*-31#KYY!vf!hnJ;S|f02^0 zvwz^c>CnUlzjNqOL7@OYg9fsb-`8zd!MP2Zoz&2383ds`WF&AXAkaz6FnuK$ezhr| z3h=OH)YrP>Bc_7asIo)E%;A-bK%t@c61`2nXpmTThn5_3WEGrO&t1SHzi(@c)im2U zBkTx8+>$(}UrfUN`U7Z5&^n+hO*o!5Edj_nay(A&0Et+T4yEX{1W^sc{lHo9q<7VR>@Y)k^7@6N*m@o$<~68I`UM zVodN+iS8eK0gPH?G5;&8!@1DF{+L#d0&<{J+O2PG{tqB;#Uj{!0o(sV1N$pvot%x0 zg%D0YNcOEg5Y44~10c`7YOWO;G6v|G(|IQ7=v0plAi?g$co@nh)`Q)gG?|LM_)D>I zaHTu}QaY1-3n&(s1|<2UOl||#x{oo@76;{7AgM<1)|0vmDSsp|6v0UU#0F0WuMje| zsf{+&26d?Tgx~HU|M^P#WujK$^ zP!r$MkFVR^0B=8|ZIKdS%nw-GDCKjvC@FCf?Fvu;5rW~)+(s%5S2VykQe0O?9C?>m z56<%mZtUUUXd~9P9l->M^S+93E_m9gM{^uKvmErxw`$0r!r> UN#lgNdH?_b07*qoM6N<$f@>>|t^fc4 diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 35c31359..1d5a5ab8 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -546,6 +546,64 @@ TEST_CASE("nucleus/rasterizer") // #endif // } + SECTION("validate small raster with large raster") + { + // Note: this test is necessary to validate the rasterizer for small rasterizations + // while larger rasterizations also encounter this problem it is more noticeable if you rasterize on a small raster and try to rasterize the actual triangle afterwards + // specific bug: we rasterized a triangle on a 16x16 raster and saw that on some pixels that should be filled, no triangle was rendered + // the problem was that we compared an integer with a float value, and for certain circumstances this caused the rasterizer to switch fill direction and render less than it should have + + // first render on the original 16x16 raster + // note orig_scale makes sure that we can easily test other scales without having to change the triangle coords + constexpr int orig_size = 16; + constexpr double orig_scale = orig_size / 16.0; + + const std::vector triangles_grid + = { glm::vec2(12.4023 * orig_scale, 0.2754 * orig_scale), glm::vec2(7.0312 * orig_scale, 10.8027 * orig_scale), glm::vec2(7.3633 * orig_scale, 11.0684 * orig_scale) }; + + nucleus::Raster output({ orig_size, orig_size }, 0u); + const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer, triangles_grid); + + // next write the same triangle to larger raster + constexpr int enlarged_size = 2048; + nucleus::Raster output_enlarged({ enlarged_size, enlarged_size }, 0u); + + { + constexpr double enlarged_scale = enlarged_size / 16.0; + const std::vector triangles = { + glm::vec2(12.4023 * enlarged_scale, 0.2754 * enlarged_scale), glm::vec2(7.0312 * enlarged_scale, 10.8027 * enlarged_scale), glm::vec2(7.3633 * enlarged_scale, 11.0684 * enlarged_scale) + }; + + const auto pixel_writer_enlarged = [&output_enlarged](glm::ivec2 pos) { output_enlarged.pixel(pos) = 255; }; + nucleus::utils::rasterizer::rasterize_triangle(pixel_writer_enlarged, triangles); + } + + // finally compare smaller with larger output -> everytime the enlarged output has a value, the smaller output should also have a value + constexpr double enlarged_to_orig = double(orig_size) / double(enlarged_size); + for (size_t i = 0; i < enlarged_size; i++) { + for (size_t j = 0; j < enlarged_size; j++) { + + const auto orig_value = output.pixel({ i * enlarged_to_orig, j * enlarged_to_orig }); + const auto enlarged_value = output_enlarged.pixel({ i, j }); + + if (enlarged_value != 0) { + CHECK(orig_value != 0); + } + + // debug output to visualize both values in one graphic at the end +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE + if (orig_value != 0 && enlarged_value == 0) + output_enlarged.pixel({ i, j }) = 125; +#endif + } + } +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE + auto image = nucleus::tile::conversion::u8raster_to_qimage(output_enlarged); + image.save(QString("rasterizer_problem.png")); +#endif + } + SECTION("rasterize donut") { const std::vector> polygon_points = { { glm::vec2(10.5, 10.5), glm::vec2(50.5, 10.5), glm::vec2(50.5, 50.5), glm::vec2(10.5, 50.5) }, From 27ee99e670679e2bfaaa7fcc3f7bfa5884e92da4 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Mon, 23 Dec 2024 09:44:40 +0100 Subject: [PATCH 30/33] Revert "Rasterizer - removed macros as they likely cause github tests to fail" This reverts commit e7982c5593935ddf040f636c2f3822efbb27d16c. --- unittests/nucleus/rasterizer.cpp | 101 +++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 394c83f9..35c31359 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -17,6 +17,8 @@ * along with this program. If not, see . *****************************************************************************/ +// #define WRITE_RASTERIZER_DEBUG_IMAGE + #include #include #include @@ -315,7 +317,9 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_simple_triangle.png")); - // image.save(QString("rasterizer_output_simple_triangle.png")); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE + image.save(QString("rasterizer_output_simple_triangle.png")); +#endif } SECTION("rasterize Polygon") @@ -339,9 +343,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_polygon.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_polygon.png")); +#endif } SECTION("rasterize triangle") @@ -359,9 +365,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output.png")); +#endif } SECTION("rasterize triangle small") @@ -377,9 +385,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_small.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_small.png")); +#endif } SECTION("rasterize triangle smallest") @@ -396,9 +406,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_smallest.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_smallest.png")); +#endif } SECTION("rasterize triangle enlarged endcaps only") @@ -427,9 +439,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_endcap.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_endcap.png")); +#endif } SECTION("rasterize triangle enlarged") @@ -462,9 +476,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged.png")); +#endif } SECTION("rasterize triangle horizontal") @@ -490,9 +506,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_enlarged_horizontal.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_enlarged_horizontal.png")); +#endif } // // can't be verified exactly, since SDF is currently slightly inaccurate @@ -521,10 +539,11 @@ TEST_CASE("nucleus/rasterizer") // CHECK(output.buffer() == output2.buffer()); + // #ifdef WRITE_DEBUG_IMAGE // // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // // image.save(QString("rasterizer_output_enlarged_narrow.png")); - // + // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + // image.save(QString("rasterizer_output_enlarged_narrow.png")); + // #endif // } SECTION("rasterize donut") @@ -560,8 +579,10 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_donut.png")); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_donut.png")); +#endif } SECTION("rasterize random triangles") @@ -596,7 +617,9 @@ TEST_CASE("nucleus/rasterizer") auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_output_random_triangle.png")); - // image.save(QString("rasterizer_output_random_triangle.png")); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE + image.save(QString("rasterizer_output_random_triangle.png")); +#endif } SECTION("rasterize triangle start/end y") @@ -651,9 +674,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_triangle_start_end.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_triangle_start_end.png")); +#endif } SECTION("rasterize line start/end y") @@ -695,9 +720,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_start_end.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_start_end.png")); +#endif } SECTION("rasterize line straight") @@ -723,9 +750,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_straight.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_straight.png")); +#endif } SECTION("rasterize line diagonal") @@ -751,9 +780,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_diagonal.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_diagonal.png")); +#endif } SECTION("rasterize line straight enlarged") @@ -779,9 +810,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_straight_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_straight_enlarged.png")); +#endif } SECTION("rasterize line diagonal enlarged") @@ -808,9 +841,11 @@ TEST_CASE("nucleus/rasterizer") CHECK(output.buffer() == output2.buffer()); +#ifdef WRITE_RASTERIZER_DEBUG_IMAGE // DEBUG: save image (image saved to build/Desktop-Profile/unittests/nucleus) - // auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); - // image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); + auto image = nucleus::tile::conversion::u8raster_2_to_qimage(output, output2); + image.save(QString("rasterizer_output_line_diagonal_enlarged.png")); +#endif } } TEST_CASE("nucleus/utils/rasterizer benchmarks") From ff847a3d992cb5264a929b77b46cbce2300c6077 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 14 Jan 2025 08:23:03 +0100 Subject: [PATCH 31/33] Rasterizer - removed randomness from test to be the same on all compilers --- .../rasterizer_output_random_triangle.png | Bin 3286 -> 3332 bytes unittests/nucleus/rasterizer.cpp | 103 +++++++++++++++--- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/unittests/nucleus/data/rasterizer_output_random_triangle.png b/unittests/nucleus/data/rasterizer_output_random_triangle.png index 708de39aec5703dee56b581d53f3292afcfeab56..b39875a18720dd73edb2dfb15493ada47c8a7b2a 100644 GIT binary patch delta 3307 zcmV

5R$8H5^;Ie)fEL_t(|ob6rNy5k@S*7^GWuk2j3SyYxEM3bh6o0(IUO;iMu zr1T9?g8QA|pMA)BC=)dhuYls1hCu4p0hIz`SkEC?8o*e~b{SqK0vAF-^$K@`@T+M6p20GSUvH0_9h z0h z8=GV>369GXzJHSfGBPCwei1~(UoGy!HL!39LTaFrzuE)=3i0xHxI#TleonU~Z1V%k zc_`Iv+(=S_1+&P1>f39enM2DSP^^rIh;~4hUYvK)3SqE-N%FTiDGFAqeE&`eTmdC% zXCJr%`U#hY6{TH)W@tZ&@DeCdc1Rh#yb5CFZ+=280DqDn;gl9N6aU+O@W`J@WRx|5 z3Mc>{hD&%4EcOwz1LTjqydhgaBY*Cjq8RE;mNsIuD=dIb{Kut&h_XuNBBLc!Gs#{=~BM}ShmcSjQkAZ8` z!J`m%wSWKC37#6;&<9?xwqp+%XUBALTfJ_$1Lhn>_sTE$x5|ix2Efg4-kbg0$N>(a z`^p8tNq8)QC~jmt(rsWmP|M(b9S$tQ5~Dk_PfP(nS2$oyji~|%(YThoDQQ$RC%VJy z^D`RFlRaJ`V2<}ah8R_ZC3YI5FN~Wr3cjz<(tk0h1a2Mh9;e^Q>JBf3o1Q{XA_Kl4 za@8H%An?+tJ}PWAHR(VFq3#{9H_G|Ix~nfQaN?3$(HdDj`!NKC{4dv;tPe1eGx|PS zmld@Z6$zq3WP~EdeKw1Mel1|DRV}heAHEaG|Hsu#M2Oorzaft@A)HP9nR!Rgx=IkN! z%>vr0+x0Oi3WW7ZH$gQAxvK)kZ-f0{vi-ga@PK@VkfmuWlcVzi zIzwvJJj()7O6nm(2SlC(q8ZFAk+~JXr+>&(6%gpU{lOnR8m6q0p;DjdYB`x$zSS|f zI%!!tLvk@#!TL*$!=2k-jG@CdusBZPBUT&%qwObz?}Oc0JHIu$5*ES zu-ME4j$%QguUm`s(e)xb%>YBCpnnM4FiY$^apJcMSSEjffq9&GVoa@oc~9;Qxj#YV z>Cz;sC)at9fq1kuB|0z6kZw1@IX199+|g2Ji7yI?U3vMJFXx>jG=Vz+>)8-Eiheht~+pLK@|d<-s$>?86MXK+s7vB`{uV8AayP1G>` zR6a+iL2cvc5?J7uAwqEToPlc!J=U@Z+*bUQ-YG3Hsm}~1hEQm158yO&0qjr^e&rm} z%C>6QI#Qb;sVl?V4eV}wG2v$*3$**)Z~K8<0{xU(uMDONlc*SUE`NzJH(^ScV=(qE zrio{reYQ9+rDUzdVbwKOK{;o~go<>zc4iZ>l+!>k(_unzOVdYHsR{~k$uMwZfRQ7| zJ=doz)!~u3wx-FYK+x*c)Kt%JESr>##p4J4C2XhoiVrXEfa&zpOM1WNaqfgpQ$ul* z2_R_keLRxM1+{>%OMeb)AXov@!?-Ywb#djj@ZGPnkAR<7z#lAw>F>c-8CXJv3yIl6 zKr>*G`e_A(RCf=ZioN)gKEcC3G&M+X%!H`v57xkEembN<_xUlWh`jW~6=s}eD>Z6y zCnzoM17qiy^QYK=G7YT_bk7ZH)SYx`B$@u1g*W#=5*Rnb*MZkIkRpo?2htWYwUJG@ zF@ZWNY{*bozkhzzKIExE@BcTHOFeF%mo(KB7iw3Wvu>0I#n= ztf0$IyMK9856@{vev7|w4S%kqf&r;E0P_c6H8iOr<4!=yL6ga6*-N9DJW?LTf5 zKKp7h??LfhB&A`CwGV4;7~hTH3pIgl(y@>jQhy%p1azoWFXN;6HUr=-GPz)jWb!uh zMvvAKN>Ft$d~k8r!DX7pMh!zxk?f_iAn?S-_AsLg-a`bknhDew21EfH#%YJ8Lj~_) zLp0xD%L`2NRzfvvDyFu+PLH>OOUq3C)F2C}xFd|%Pbn@r`laIkI@}~IcNvr-FM+44r=%n*;0NT-k|V^+&ph<@WuRs2q#&Q= zIm5fcCLgy1hT;@o0Q>w(HA1X#Q>0#cBptMbcZkfQ?OJugJfxkJ`O&nhhl5`}#JcW@ ztn-Z+ey?y1Gnm|P7WfTD&Q()I1jatG;eRp!9$TrpX~-p1(%r0%hH4$Y88Ob9esX4@{1dR zLZKS(-iwccBVV5Uy6$%h?dVe_R z?C_p=6Vm5b6iX)WDENCIO>z3*jfA0U$ddFjjBh?x5o+bxy&Vqu`eS@ zi1Y}Fx;-K+ZgR=tG{2J>>rnRN>%%@7C*z7@Jc&lr_Pmd;r<794@UA@m@pW^_V@_#) z$Qh1ivB3$WSd>6F2>s|}!*YWbLw~r_8Vly7G6XrKwq6S)(cq7+<`h-JH3bNUCOkWg ztcLrjW*#TXPIo~?1#6K{&w`5ZsTTin_~k;0z074LH1IT^jbQ_dAXY)bj+|@85VTWdO9ahj9sQRBO z0El4XLl@4o0@Oud(14!9)5h}6;Ot`;?#coX4WPtX4DQjNHgpcafP?)-N(cje8ov1f z$qrM$<4Ye6xDX#$;A@yRE`N053JMiS43>?rm!BEkSb%yGe6TCTd1P)4*j%wres;92 zP!iFA!l+LJjuW^V%@1!C0PgiXMt_M3i^$`LpNPw&=>iFD8{oUd=e-@$R|`NXDdQ?> zfD$aFO3-+(5d22#nc8ds@-2gvLe6V3gU_r=2qHXWVT$O=FI)jYEo2gX(1)#mjjlw4 zbO3n!;;l}Tq1V!g`)dKt&H6OjerJh5dr#GAaa5Drg^gTDo828^#a1w<9D`#(SA#v} p%BROCg-Itv?3{g}0{*}X{{cj#zrvO}k!An@002ovPDHLkV1hq@Ex`Z) delta 3261 zcmV;u3_|mS8rB(*Ie&;rL_t(|ob6rNn(H74)_i^cSI%6tOuWeXw# zNprdv>?uQ|pkW9^;fVrOwEg*C7~hX5L~*cl!tFDt-OvIkrdrwK*nVPSmA)LFlIHH4 z8UoqfUR0)-mm7BS;{lPkYm*2`FiB&%#ZCwtYOdQs&SnI&Eq|uaz})4z6`u_%z_#m= zrBzI9NX05-tNudS#m4Qr_`(2A17Y&-=Z;gd5fz9r`&m1v8$ru0`UD@mXm^1Sup{d7 zL(;2;RIrl6BfvN9N6AMFc2IgG>@6!mPFDvYmdGU_EQ>XDt31VFS%;QdxA%I9M=|H>VmcP}+iJkztQu*fMat?pO4>Na@xfcKQvr5Mm+(XewXo;tMA(>tJjBT#2q#l= z_}$+^ivm20eHD~pv$j!tj~3AjAXt}t1l104Dj;0`Xn$jBZaf=eWM!C$KGXq3uO=mP zN^iZW4~TMH6XnMW3N3&Nh&qhU&7?`j&27D)0)i5AvondA`7DGUOLsNEQHPP}<)`Ug zr+`V6t&8=ham@f2!?pjXfXLeU5553GcJ`ZDoGC#O8QR=L4>liA9HgBREZE#lU^K~h zTKpE4Y=6&hQ#fq!42Ab7`2`$HS}8eX zLm5YEAo$4x z9d7naV!9(oKls59ey|q4-4CAfuRRU^;C~GlTVO%{8Vi_SC)UWHaESu?GLE6d9zy=q zzpYz529z?^Y(8on7gzu#|Kb%qk1q7j2X9*Auk$p~ZfBrLrsWBZIW`8ps zSri5~DLSE{jh4WwYnd^&SMa#jeA_JnzMX4y4PXtGz;uj8R~VoX2x;;Aykidrpc%2f zB-I5l4M1&@Nw|Y?6?3uEl0ncefx4Zm7LG>oHZ|o$wv`W z!npqcUjO`milbj%?{?Q)7cUGbDe82TzG*9eVM6i8CwT9QCxx@l0^ms4y?;}p6_!d* zc3M9E@PfcVq=0Fl9%WquE>Gh>$Il02O4>ffI%z0WL`+=AsfMI?tDB|VUZoxs3 zae@I)SW8c@uK-yD^)wi9KQ1)@6xdLBAw!ca1(V<|wZR$shH&0M0rOl1qJbME>%&8U zAN=44PWZ+qi_-ms#D60fKNxO?Rsq8*xH78qKoJM*)qhU-xK%_wI5{fBCmBG&Y)D7{ zT^0cDiO5iRNd7^fP?&AfFdoba8eq7-PocD_z!F>^1D(~3mhhzh1`}q};i9*IE0-{P zpL4<&g6(ki9%|?SVF?TyfRx6AgA|4f&MmeUpiO*SD1SpskKY3v<4R$UB`|EiBM4e=>Y{^h!zG2yG*dtsCcg>-C6tMR;AWet zBU}IhO&!%sRYUyuIs*u%@JK157vg&P4Y#q|1uo8EMUmI>OcNBohx6X1>&6^X9>+C% zhlFVjH|P(Df%~bJf{!bkTm=i?ynV|hu+mUm3RrN1mVfEM*)9Hm`g+I&B6{|f#todX zV%y>sFl5lHArb;oLx$%$mnxw50Z3r4!SJh1`>6mATSk4YJ3eA6c#bMNM9dssxd;>* zdN0w-P zeLw~Gut_T{JGtE#zWukue{g&F7!&>N0&~!Qj+_2^3z(yTPjLWgk^(+R{+UbQGt@9c z0e_z%`3oi}z$O1>al_1Az$Vj08i?PlwVW(J?UKU8>Nr3$b!#741PeJ_0Wx!YgsBh8 zUwqD)Xp@UW-*NiH%~M!XH7q`txxw;!+!Ih;3l{l(aTR*}c7VaYk!vFc4BQB5h3~*H zbCaMAh(@-J8sMe@d$^ga09sFd8+*~) zB93d{i_Q%mv;lq*(=KQuuJ?Zi1*)|Ud?B-PnP0@mSM`m^-Sz&G#X<8(h(U}=D5Za> zGk{xQ-37M4qQ8i_g$5BbiDU=RfT)0qbf&L$lk3;$h=8pF>=zK;i-ATNu}6@*4u8QM z#|N`JfSUt2p}C*>CIp3P#LX5=IG@*f;*RYRS~s@y0`j_n2%3FBF*QyiXOo@cH!M$%x>_k6b3*ZHp)5JtsDjHflg_&zP0%uz@Calu=xVE{{!1IWSyLi zjfD_S-bl989*E}By#bJCUp3bX4H*OU%;`K6babl629RKPVmu7x66?WkPMS=`R{W*d zIJizN?wT@X;kzge=m0emG^at- z$mJ2ftH$)CIudK7pnqb6CxcfAnc7t1O)S&~(73J32SjK<1-AxZ1K7bD9rZIi(0(Xf zCof6G_ky-U732V1{wXElmw%>M{J@z3fVr!{&`0I?tuY{4)PX}1fgE&yB0>4!BZS0E zQPfadlHzUXz-T+R3O~N?DJketlnTOYInWr?!nk-mF<*4l(P=D-c?lG(ZItr4+o}Py zD?kB62!=az8>uv0(E#5_aa|d4~vSw(0y6k5ocY^y}X6s>Yu#+)B@Wdof5}FTf^XV00000NkvXXu0mjf=^g^d diff --git a/unittests/nucleus/rasterizer.cpp b/unittests/nucleus/rasterizer.cpp index 1d5a5ab8..46323a52 100644 --- a/unittests/nucleus/rasterizer.cpp +++ b/unittests/nucleus/rasterizer.cpp @@ -651,26 +651,99 @@ TEST_CASE("nucleus/rasterizer") nucleus::Raster output({ cells * cell_size, cells * cell_size }, 0u); const auto pixel_writer = [&output](glm::ivec2 pos) { output.pixel(pos) = 255; }; - const auto rand_pos = []() { return std::rand() % (cell_size * 10u) / 10.0f; }; + const std::vector> polygon_points = { { { 11.5, 16 }, { 12.9, 3.9 }, { 4.8, 5.5 } }, + { { 59.3, 11.9 }, { 58.9, 6.9 }, { 38.5, 24.7 } }, + { { 79.1, 14.3 }, { 82, 2.4 }, { 72.7, 31.9 } }, + { { 125.2, 24.7 }, { 105.2, 30.8 }, { 125.5, 12.1 } }, + { { 145.9, 17.1 }, { 128, 15.5 }, { 141.6, 29.5 } }, + { { 160.7, 23.1 }, { 174.3, 12.4 }, { 165.1, 0.8 } }, + { { 223.9, 19.1 }, { 205.2, 0.4 }, { 217.1, 26.8 } }, + { { 252.4, 6.9 }, { 233.3, 27.4 }, { 251.3, 14.4 } }, + { { 7.1, 37.2 }, { 4, 43.7 }, { 11.1, 35.5 } }, + { { 39.9, 52.7 }, { 55.5, 48.2 }, { 45.7, 39.9 } }, + { { 82.3, 37.1 }, { 68.7, 46.5 }, { 66.5, 32.6 } }, + { { 102.9, 41.8 }, { 125.5, 34.5 }, { 112.5, 52.2 } }, + { { 155.1, 41.8 }, { 133.3, 44.9 }, { 155.3, 55.7 } }, + { { 188.9, 51.8 }, { 178.8, 53.6 }, { 172.4, 52.2 } }, + { { 220.1, 61.9 }, { 206.1, 39.5 }, { 194.6, 51.6 } }, + { { 236, 59.8 }, { 253.5, 52.9 }, { 232.7, 35.9 } }, + { { 13.8, 83.3 }, { 21.8, 79.7 }, { 23.1, 94.5 } }, + { { 40.3, 70.3 }, { 51.3, 69.4 }, { 61.1, 77.6 } }, + { { 84.7, 65.8 }, { 87.5, 77.3 }, { 84.7, 71.5 } }, + { { 101.4, 87.1 }, { 126.6, 80.8 }, { 119.7, 70.7 } }, + { { 149.7, 65.2 }, { 137.4, 67.7 }, { 135.6, 70.5 } }, + { { 185.8, 82.1 }, { 181, 72.6 }, { 167.1, 79.6 } }, + { { 213.9, 76.1 }, { 209.6, 79.5 }, { 208.3, 92.4 } }, + { { 254.3, 65.9 }, { 226.4, 88.2 }, { 255.7, 73 } }, + { { 19.3, 119.1 }, { 27.2, 120.3 }, { 18.2, 113.9 } }, + { { 38.3, 114.9 }, { 54.6, 104 }, { 46.5, 98.9 } }, + { { 85.1, 113.4 }, { 90.1, 123.5 }, { 75.1, 125.3 } }, + { { 105.5, 97.2 }, { 99.9, 116.2 }, { 99.9, 110.3 } }, + { { 131.4, 116.3 }, { 159.7, 98.2 }, { 158.4, 101.9 } }, + { { 176.2, 127.5 }, { 171, 113.7 }, { 182.8, 107.4 } }, + { { 222.3, 125.3 }, { 216.5, 125.1 }, { 192.1, 104.1 } }, + { { 249.6, 122.2 }, { 245.7, 104.4 }, { 229.7, 115.7 } }, + { { 8.1, 148 }, { 27.4, 130.4 }, { 3.9, 140.1 } }, + { { 51.6, 159.3 }, { 52.7, 135.4 }, { 45.3, 147.3 } }, + { { 70, 147.1 }, { 68.8, 144.7 }, { 92.2, 132.3 } }, + { { 116.8, 142.8 }, { 127.2, 141.7 }, { 117.9, 141.3 } }, + { { 154.7, 146.5 }, { 154.9, 130.7 }, { 137.6, 144.4 } }, + { { 183.6, 147.8 }, { 169.5, 132.9 }, { 177, 147 } }, + { { 210.4, 141.4 }, { 200, 151.8 }, { 222.4, 135.1 } }, + { { 237.5, 136.5 }, { 249.3, 151.6 }, { 251.9, 133.5 } }, + { { 20.4, 168.7 }, { 22.4, 163.9 }, { 4.4, 179.6 } }, + { { 38.1, 191.5 }, { 53.4, 191.1 }, { 34.7, 180.2 } }, + { { 72, 191 }, { 68, 186.3 }, { 77.4, 164.7 } }, + { { 101.4, 168.2 }, { 115.3, 179 }, { 122.1, 173.4 } }, + { { 137.2, 177.7 }, { 137.3, 186.8 }, { 128.4, 181.7 } }, + { { 190.4, 184.5 }, { 188.4, 180.8 }, { 168.4, 180.8 } }, + { { 199.6, 160.9 }, { 211.2, 161.7 }, { 213.9, 189 } }, + { { 252.1, 182 }, { 240.3, 189.9 }, { 245.9, 179.3 } }, + { { 14.7, 208.9 }, { 23.1, 219.3 }, { 27.9, 194 } }, + { { 51.7, 196.5 }, { 63.3, 197.1 }, { 58.8, 208.3 } }, + { { 92, 223.7 }, { 84.5, 204.4 }, { 65.3, 216.4 } }, + { { 108.5, 208.1 }, { 113.8, 200.9 }, { 121.1, 223.8 } }, + { { 137, 199.7 }, { 152.9, 213.2 }, { 155.7, 204.5 } }, + { { 189.5, 206.8 }, { 189.3, 202.5 }, { 172.5, 212.6 } }, + { { 217.1, 217.2 }, { 209.6, 211.5 }, { 215, 203.7 } }, + { { 235.4, 223.6 }, { 236.1, 211 }, { 254.6, 192 } }, + { { 3.3, 224.6 }, { 28.7, 254.4 }, { 17.4, 245.2 } }, + { { 48.1, 245.8 }, { 62.3, 250.4 }, { 38.9, 233 } }, + { { 75, 250 }, { 87.7, 247.6 }, { 95.5, 232.3 } }, + { { 116.8, 247.5 }, { 107, 235.8 }, { 119.5, 230.4 } }, + { { 153.2, 245.2 }, { 136.2, 246.2 }, { 137.4, 229.3 } }, + { { 185.1, 250 }, { 184.4, 236.7 }, { 161.9, 245.8 } }, + { { 222.9, 233.8 }, { 215.4, 242.1 }, { 206.3, 253.2 } }, + { { 234.4, 241.6 }, { 233.2, 249.4 }, { 225.7, 245.4 } } }; + + for (const auto& tri : polygon_points) { + nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, tri); + + // DEBUG view the vertices in the image(uses different pixel intensity) + // const auto pixel_writer_points = [&output](glm::ivec2 pos) { output.pixel(pos) = 125; }; + // pixel_writer_points(tri[0]); + // pixel_writer_points(tri[1]); + // pixel_writer_points(tri[2]); + } - std::srand(123458); // initialize rand -> we need to create consistent triangles + // DEBUG how the random points were generated + // const auto rand_pos = []() { return std::rand() % (cell_size * 10u) / 10.0f; }; + // std::srand(123458); // initialize rand -> we need to create consistent triangles - for (size_t i = 0; i < cells; i++) { - for (size_t j = 0; j < cells; j++) { - const auto cell_offset = glm::vec2(j * cell_size, i * cell_size); + // for (size_t i = 0; i < cells; i++) { + // for (size_t j = 0; j < cells; j++) { + // const auto cell_offset = glm::vec2(j * cell_size, i * cell_size); - const std::vector polygon_points - = { glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset }; + // const std::vector polygon_points + // = { glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset, glm::vec2(rand_pos(), rand_pos()) + cell_offset }; - nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); + // nucleus::utils::rasterizer::rasterize_polygon(pixel_writer, polygon_points); - // DEBUG view the vertices in the image (uses different pixel intensity) - // const auto pixel_writer_points = [&output](glm::ivec2 pos) { output.pixel(pos) = 125; }; - // pixel_writer_points(polygon_points[0]); - // pixel_writer_points(polygon_points[1]); - // pixel_writer_points(polygon_points[2]); - } - } + // qDebug() << "{{" << polygon_points[0].x << "," << polygon_points[0].y << "}, " << "{" << polygon_points[1].x << "," << polygon_points[1].y << "}, " << "{" << polygon_points[2].x << + // "," + // << polygon_points[2].y << "}}, "; + // } + // } auto image = nucleus::tile::conversion::u8raster_to_qimage(output); CHECK(image == example_rasterizer_image("rasterizer_output_random_triangle.png")); From 4a4f8e5deb969c829e99bcc4386580a644213874 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 14 Jan 2025 16:01:50 +0100 Subject: [PATCH 32/33] Added support for RGB32UI textures + RG32UI and RGB32UI array textures --- gl_engine/Texture.cpp | 27 +++++++++++ gl_engine/Texture.h | 7 +++ unittests/gl_engine/texture.cpp | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/gl_engine/Texture.cpp b/gl_engine/Texture.cpp index f1e9d9a4..75cecd76 100644 --- a/gl_engine/Texture.cpp +++ b/gl_engine/Texture.cpp @@ -54,6 +54,8 @@ GlParams gl_tex_params(gl_engine::Texture::Format format) return { GL_RG8, GL_RG, GL_UNSIGNED_BYTE, 2, 1, true }; case F::RG32UI: return { GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, 2, 4 }; + case F::RGB32UI: + return { GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT, 3, 4 }; case F::R16UI: return { GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, 1, 2 }; case F::R32UI: @@ -211,6 +213,27 @@ void gl_engine::Texture::upload(const nucleus::Raster& texture, unsign f->glTexSubImage3D(GLenum(m_target), 0, 0, 0, GLint(array_index), width, height, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT, texture.bytes()); } +template void gl_engine::Texture::upload(const nucleus::Raster& texture, unsigned int array_index) +{ + const auto p = gl_tex_params(m_format); + + assert(m_format != Format::CompressedRGBA8); + assert(m_format != Format::Invalid); + assert(m_mag_filter == Filter::Nearest); // not filterable according to + assert(m_min_filter == Filter::Nearest); // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml + assert(array_index < m_n_layers); + assert(texture.width() == m_width); + assert(texture.height() == m_height); + + const auto width = GLsizei(texture.width()); + const auto height = GLsizei(texture.height()); + + auto* f = QOpenGLContext::currentContext()->extraFunctions(); + f->glBindTexture(GLenum(m_target), m_id); + f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + f->glTexSubImage3D(GLenum(m_target), 0, 0, 0, GLint(array_index), width, height, 1, p.format, p.type, texture.bytes()); +} + void gl_engine::Texture::upload(const nucleus::Raster& texture, unsigned int array_index) { assert(m_format == Format::RG8); @@ -250,9 +273,13 @@ template void gl_engine::Texture::upload(const nucleus::Raster& template void gl_engine::Texture::upload(const nucleus::Raster&); template void gl_engine::Texture::upload(const nucleus::Raster&); template void gl_engine::Texture::upload>(const nucleus::Raster>&); +template void gl_engine::Texture::upload>(const nucleus::Raster>&); template void gl_engine::Texture::upload>(const nucleus::Raster>&); template void gl_engine::Texture::upload>(const nucleus::Raster>&); +template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); +template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); + GLenum gl_engine::Texture::compressed_texture_format() { // select between diff --git a/gl_engine/Texture.h b/gl_engine/Texture.h index ba3c0f39..34c6c2b5 100644 --- a/gl_engine/Texture.h +++ b/gl_engine/Texture.h @@ -37,6 +37,7 @@ class Texture { RGBA8UI, RG8, // normalised on gpu RG32UI, + RGB32UI, R16UI, R32UI, Invalid @@ -59,6 +60,8 @@ class Texture { void upload(const nucleus::utils::MipmappedColourTexture& mipped_texture, unsigned array_index); void upload(const nucleus::Raster& texture, unsigned int array_index); void upload(const nucleus::Raster& texture, unsigned int array_index); + template void upload(const nucleus::Raster& texture, unsigned int array_index); + template void upload(const nucleus::Raster& texture); static GLenum compressed_texture_format(); @@ -82,7 +85,11 @@ class Texture { extern template void gl_engine::Texture::upload(const nucleus::Raster&); extern template void gl_engine::Texture::upload(const nucleus::Raster&); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); +extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); +extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); +extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); + } // namespace gl_engine diff --git a/unittests/gl_engine/texture.cpp b/unittests/gl_engine/texture.cpp index 0bce240d..3fc41afb 100644 --- a/unittests/gl_engine/texture.cpp +++ b/unittests/gl_engine/texture.cpp @@ -115,6 +115,84 @@ void test_unsigned_texture_with(const TexelType& texel_value, gl_engine::Texture CHECK(qAlpha(render_result.pixel(0, 0)) == 126); } +template > void test_unsigned_texture_array_with(const std::array& texel_value, gl_engine::Texture::Format format) +{ + Framebuffer b(Framebuffer::DepthFormat::None, { Framebuffer::ColourFormat::RGBA8, Framebuffer::ColourFormat::RGBA8 }, { 1, 1 }); + + b.bind(); + + gl_engine::Texture opengl_texture(gl_engine::Texture::Target::_2dArray, format); + opengl_texture.setParams(gl_engine::Texture::Filter::Nearest, gl_engine::Texture::Filter::Nearest); + opengl_texture.allocate_array(1, 1, 2); + + const auto tex0 = nucleus::Raster({ 1, 1 }, texel_value[0]); + const auto tex1 = nucleus::Raster({ 1, 1 }, texel_value[1]); + opengl_texture.upload(tex0, 0); + opengl_texture.upload(tex1, 1); + + const auto precision = []() -> QString { + if (sizeof(Type) == 1) + return "lowp"; + if (sizeof(Type) == 2) + return "mediump"; + if (sizeof(Type) == 4) + return "highp"; + assert(false); + return "Type has unexpected size"; + }; + + ShaderProgram shader = create_debug_shader(QString(R"( + uniform %1 usampler2DArray texture_sampler; + layout (location = 0) out lowp vec4 out_color0; + layout (location = 1) out lowp vec4 out_color1; + void main() { + %1 uvec4 v0 = texelFetch(texture_sampler, ivec3(0, 0, 0), 0); + out_color0 = vec4((v0.r == %2) ? 123.0 / 255.0 : 9.0 / 255.0, + (%10 < 2 || v0.g == %3) ? 124.0 / 255.0 : 9.0 / 255.0, + (%10 < 3 || v0.b == %4) ? 125.0 / 255.0 : 9.0 / 255.0, + (%10 < 4 || v0.a == %5) ? 126.0 / 255.0 : 9.0 / 255.0); + + %1 uvec4 v1 = texelFetch(texture_sampler, ivec3(0, 0, 1), 0); + out_color1 = vec4((v1.r == %6) ? 127.0 / 255.0 : 9.0 / 255.0, + (%10 < 2 || v1.g == %7) ? 128.0 / 255.0 : 9.0 / 255.0, + (%10 < 3 || v1.b == %8) ? 129.0 / 255.0 : 9.0 / 255.0, + (%10 < 4 || v1.a == %9) ? 130.0 / 255.0 : 9.0 / 255.0); + } + )") + .arg(precision()) + .arg(texel_component(texel_value[0], 0)) + .arg(texel_component(texel_value[0], 1)) + .arg(texel_component(texel_value[0], 2)) + .arg(texel_component(texel_value[0], 3)) + .arg(texel_component(texel_value[1], 0)) + .arg(texel_component(texel_value[1], 1)) + .arg(texel_component(texel_value[1], 2)) + .arg(texel_component(texel_value[1], 3)) + .arg(length)); + shader.bind(); + opengl_texture.bind(0); + shader.set_uniform("texture_sampler", 0); + gl_engine::helpers::create_screen_quad_geometry().draw(); + + // render_result.save("render_result.png"); + { + const QImage render_result = b.read_colour_attachment(0); + CHECK(qRed(render_result.pixel(0, 0)) == 123); + CHECK(qGreen(render_result.pixel(0, 0)) == 124); + CHECK(qBlue(render_result.pixel(0, 0)) == 125); + CHECK(qAlpha(render_result.pixel(0, 0)) == 126); + } + { + const QImage render_result = b.read_colour_attachment(1); + CHECK(qRed(render_result.pixel(0, 0)) == 127); + CHECK(qGreen(render_result.pixel(0, 0)) == 128); + CHECK(qBlue(render_result.pixel(0, 0)) == 129); + CHECK(qAlpha(render_result.pixel(0, 0)) == 130); + } + + Framebuffer::unbind(); +} + QImage create_test_rgba_qimage(unsigned width, unsigned height) { QImage test_texture(width, height, QImage::Format_RGBA8888); @@ -300,8 +378,14 @@ TEST_CASE("gl texture") SECTION("rgba8ui") { test_unsigned_texture_with<4, unsigned char>({ 1, 2, 255, 140 }, gl_engine::Texture::Format::RGBA8UI); } SECTION("rg32ui") { test_unsigned_texture_with<2, uint32_t>({ 3000111222, 4000111222 }, gl_engine::Texture::Format::RG32UI); } + SECTION("rgb32ui") { test_unsigned_texture_with<3, uint32_t>({ 3000111222, 4000111222, 2500111222 }, gl_engine::Texture::Format::RGB32UI); } SECTION("red16ui") { test_unsigned_texture_with<1, uint16_t, uint16_t>(uint16_t(60123), gl_engine::Texture::Format::R16UI); } SECTION("red32ui") { test_unsigned_texture_with<1, uint32_t, uint32_t>(uint32_t(4000111222), gl_engine::Texture::Format::R32UI); } + SECTION("rg32ui_array") { test_unsigned_texture_array_with<2, uint32_t>({ glm::uvec2 { 3000111222u, 4000111222u }, glm::uvec2 { 3000111222, 4000111222 } }, gl_engine::Texture::Format::RG32UI); } + SECTION("rgb32ui_array") + { + test_unsigned_texture_array_with<3, uint32_t>({ glm::uvec3 { 3000111222u, 4000111222u, 2500111222 }, glm::uvec3 { 3000114422, 4000114422, 2500114422 } }, gl_engine::Texture::Format::RGB32UI); + } SECTION("rgba array (compressed and uncompressed, mipmapped and not)") { From 6b47ca4c3e17e352edcb85aa599290a9bdc640e6 Mon Sep 17 00:00:00 2001 From: Lucas Dworschak Date: Tue, 14 Jan 2025 16:26:00 +0100 Subject: [PATCH 33/33] added R32UI array test and texture upload; changed clang format AlwaysBreakTemplateDeclarations to always break --- .clang-format | 2 +- gl_engine/Texture.cpp | 7 +++++-- gl_engine/Texture.h | 7 +++++-- unittests/gl_engine/texture.cpp | 8 +++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.clang-format b/.clang-format index 0eda6a7e..d281d629 100644 --- a/.clang-format +++ b/.clang-format @@ -23,7 +23,7 @@ AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: MultiLine +AlwaysBreakTemplateDeclarations: Yes AttributeMacros: - __capability BinPackArguments: false diff --git a/gl_engine/Texture.cpp b/gl_engine/Texture.cpp index 75cecd76..7550dd37 100644 --- a/gl_engine/Texture.cpp +++ b/gl_engine/Texture.cpp @@ -213,7 +213,8 @@ void gl_engine::Texture::upload(const nucleus::Raster& texture, unsign f->glTexSubImage3D(GLenum(m_target), 0, 0, 0, GLint(array_index), width, height, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT, texture.bytes()); } -template void gl_engine::Texture::upload(const nucleus::Raster& texture, unsigned int array_index) +template +void gl_engine::Texture::upload(const nucleus::Raster& texture, unsigned int array_index) { const auto p = gl_tex_params(m_format); @@ -253,7 +254,8 @@ void gl_engine::Texture::upload(const nucleus::Raster& texture, uns f->glGenerateMipmap(GLenum(m_target)); } -template void gl_engine::Texture::upload(const nucleus::Raster& texture) +template +void gl_engine::Texture::upload(const nucleus::Raster& texture) { assert(m_target == Target::_2d); @@ -277,6 +279,7 @@ template void gl_engine::Texture::upload>(const nucleus::R template void gl_engine::Texture::upload>(const nucleus::Raster>&); template void gl_engine::Texture::upload>(const nucleus::Raster>&); +template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned int); template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); diff --git a/gl_engine/Texture.h b/gl_engine/Texture.h index 34c6c2b5..c9ee5489 100644 --- a/gl_engine/Texture.h +++ b/gl_engine/Texture.h @@ -60,9 +60,11 @@ class Texture { void upload(const nucleus::utils::MipmappedColourTexture& mipped_texture, unsigned array_index); void upload(const nucleus::Raster& texture, unsigned int array_index); void upload(const nucleus::Raster& texture, unsigned int array_index); - template void upload(const nucleus::Raster& texture, unsigned int array_index); + template + void upload(const nucleus::Raster& texture, unsigned int array_index); - template void upload(const nucleus::Raster& texture); + template + void upload(const nucleus::Raster& texture); static GLenum compressed_texture_format(); static nucleus::utils::ColourTexture::Format compression_algorithm(); @@ -89,6 +91,7 @@ extern template void gl_engine::Texture::upload>(const nuc extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&); +extern template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned int); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int); diff --git a/unittests/gl_engine/texture.cpp b/unittests/gl_engine/texture.cpp index 3fc41afb..7d9fbe26 100644 --- a/unittests/gl_engine/texture.cpp +++ b/unittests/gl_engine/texture.cpp @@ -115,7 +115,8 @@ void test_unsigned_texture_with(const TexelType& texel_value, gl_engine::Texture CHECK(qAlpha(render_result.pixel(0, 0)) == 126); } -template > void test_unsigned_texture_array_with(const std::array& texel_value, gl_engine::Texture::Format format) +template > +void test_unsigned_texture_array_with(const std::array& texel_value, gl_engine::Texture::Format format) { Framebuffer b(Framebuffer::DepthFormat::None, { Framebuffer::ColourFormat::RGBA8, Framebuffer::ColourFormat::RGBA8 }, { 1, 1 }); @@ -381,10 +382,11 @@ TEST_CASE("gl texture") SECTION("rgb32ui") { test_unsigned_texture_with<3, uint32_t>({ 3000111222, 4000111222, 2500111222 }, gl_engine::Texture::Format::RGB32UI); } SECTION("red16ui") { test_unsigned_texture_with<1, uint16_t, uint16_t>(uint16_t(60123), gl_engine::Texture::Format::R16UI); } SECTION("red32ui") { test_unsigned_texture_with<1, uint32_t, uint32_t>(uint32_t(4000111222), gl_engine::Texture::Format::R32UI); } - SECTION("rg32ui_array") { test_unsigned_texture_array_with<2, uint32_t>({ glm::uvec2 { 3000111222u, 4000111222u }, glm::uvec2 { 3000111222, 4000111222 } }, gl_engine::Texture::Format::RG32UI); } + SECTION("r32ui_array") { test_unsigned_texture_array_with<1, uint32_t, uint32_t>({ uint32_t { 3000111222 }, uint32_t { 3000114422 } }, gl_engine::Texture::Format::R32UI); } + SECTION("rg32ui_array") { test_unsigned_texture_array_with<2, uint32_t>({ glm::uvec2 { 3000111222, 4000111222 }, glm::uvec2 { 3000114422, 4000114422 } }, gl_engine::Texture::Format::RG32UI); } SECTION("rgb32ui_array") { - test_unsigned_texture_array_with<3, uint32_t>({ glm::uvec3 { 3000111222u, 4000111222u, 2500111222 }, glm::uvec3 { 3000114422, 4000114422, 2500114422 } }, gl_engine::Texture::Format::RGB32UI); + test_unsigned_texture_array_with<3, uint32_t>({ glm::uvec3 { 3000111222, 4000111222, 2500111222 }, glm::uvec3 { 3000114422, 4000114422, 2500114422 } }, gl_engine::Texture::Format::RGB32UI); } SECTION("rgba array (compressed and uncompressed, mipmapped and not)")