diff --git a/u02/src/tests.cpp b/u02/src/tests.cpp index a2611dd..90dda1e 100644 --- a/u02/src/tests.cpp +++ b/u02/src/tests.cpp @@ -5,6 +5,7 @@ #include #include +#include "bresenham_circle_tool.h" #include "bresenham_line_tool.h" #include "canvas_buffer.h" #include "dda_line_tool.h" @@ -298,3 +299,71 @@ TEST_CASE("Rectangle (prop)") { } REQUIRE(pass); } + +TEST_CASE("Bresenham circle (prop: √(x²+y²)-r<ε)") { + // Let s be the size of the canvas (s,s). + // Let m be the smallest coordinate (x and y) for random points + // and M be the largest coordinate (x and y) for random points. + // + // The largest radius that can fit on canvas (for arbitrary centres) is m. + // The largest radius that is possible to create is √(2)(M-m). + // + // ⇒ √(2)(M-m) ≥ m + // ⇔ √(2)M-√(2)m ≥ m + // ⇔ √(2)M ≥ (1+√(2))m + // ⇔ m ≤ (√(2)/(1+√(2)))m + // ⇔ m ≤ (2-√(2))M (1) + // + // Additionally, to have a centered point field, + // s-M=m must hold + // + // s-M = m + // ⇒ s-M ≤ (2-√(2))M + // ⇔ s ≤ (3-√(2))M + // ⇔ M ≥ s/(3-√(2)) (2) + // + // With this, it now is possible to express M and m in terms of s: + // + // m ≤ (2-√(2))M (1) + // ⇔ m ≤ ((2-√(2))/(3-√(2)))s + // ⇔ m ≤ ((4-√(2))/7)s (3) + // + // When the points are rounded to the nearest integer, + // M must be rounded down and m rounded down. + const int size = 100; // s + const int max_c = std::floor(size / (3 - std::sqrt(2))); // M (2) + const int min_c = std::ceil(((4 - std::sqrt(2)) / 7) * size); // m (3) + + const int x0 = GENERATE_COPY(take(10, random(min_c, max_c))); + const int y0 = GENERATE_COPY(take(10, random(min_c, max_c))); + const int x1 = GENERATE_COPY(take(10, random(min_c, max_c))); + const int y1 = GENERATE_COPY(take(10, random(min_c, max_c))); + + if ((x0 == min_c || x0 == max_c) && (x1 == min_c || x1 == max_c) && + (y0 == min_c || y0 == max_c) && (y1 == min_c || y1 == max_c)) { + SKIP("All coordinates have extreme value, skipping (avoid rounding error)"); + } + + const int r = + round(std::sqrt(std::pow((x1 - x0), 2) + std::pow((y1 - y0), 2))); + + canvas_buffer *canvas = new canvas_buffer(size, size); + bresenham_circle_tool *tool = new bresenham_circle_tool(*canvas); + + tool->draw(x0, y0, x1, y1); + + bool pass = true; + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + double distance = + std::abs(std::sqrt(std::pow(x0 - x, 2) + std::pow(y0 - y, 2)) - r); + // Because of rounding errors, an exact test (for all pixels) is not + // feasible. + // Therefore, it is only tested if set pixels have a distance <= 1. + if (canvas->get_pixel(x, y) && distance > 1) { + pass = false; + } + } + } + REQUIRE(pass); +}