u02: Implement bresenham circle tool

This commit is contained in:
Simon Bruder 2023-05-07 13:25:57 +02:00
parent 6b63595986
commit e942c62ad6

View file

@ -5,6 +5,7 @@
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_random.hpp>
#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);
}