Compare commits

...

6 Commits

Author SHA1 Message Date
Simon Bruder 4f3c184519 u02: Add readme 2023-05-13 22:58:56 +02:00
Simon Bruder 9239c2904a u02/tests: Fix inverted logic in comment 2023-05-13 22:55:17 +02:00
Simon Bruder 9871b11bfe flake: Build variants without tests
Tests set the C++ version to 17, however only up to 11 is allowed in the
other files.
2023-05-13 22:20:43 +02:00
Simon Bruder f330960b78 u02/tests: Fix edge case for invalid points 2023-05-13 22:08:02 +02:00
Simon Bruder 2657f6c2a4 u02/tests: Fix skipping behaviour
When the test runner only tests one case, it exits with a non-zero exit
code should that test invoke a skip (even if there are 1000+ successful
iterations).

This fixes this by falling back to a cheap message to stderr.
2023-05-13 22:08:02 +02:00
Simon Bruder 1dcdf09b2a u02/tests: Fix floating point comparison
I didn’t really understand what WithinRel and WithinAbs do. Now I know
that for this use case WithinAbs is the better choice.

This also increases the allowed deviance for the average point of all
points of a star, because this would now fail for larger values.
2023-05-13 22:08:00 +02:00
3 changed files with 107 additions and 10 deletions

View File

@ -7,6 +7,10 @@
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
mkNocheck = drv: drv.overrideAttrs (o: {
doCheck = false;
nativeBuildInputs = pkgs.lib.filter (p: p != pkgs.catch2_3) o.nativeBuildInputs;
});
in
{
packages = rec {
@ -33,6 +37,9 @@
doCheck = true;
})
{ };
u01-nocheck = mkNocheck u01;
u02-nocheck = mkNocheck u02;
};
devShells.default = pkgs.mkShell {

74
u02/readme.txt Normal file
View File

@ -0,0 +1,74 @@
<!-- vim: set ft=markdown: -->
<!-- LTeX: language=de-DE -->
<!-- SPDX-License-Identifier: LGPL-3.0-or-later -->
# Praxisaufgabe 2 Einführung in die Computergrafik
## Team
* Simon Bruder, Matrikelnummer: 5075324
## Bearbeitete Zusatzaufgaben
* Rechteck-Werkzeug (`rectangle_tool.cpp`)
* Kreisrasterisierer (`bresenham_circle_tool.cpp`)
* Rasterisierer für Sternform (`star_tool.cpp`)
* Sweepline-Algorithmus (`sweep_line_tool.cpp`)
## Hinweise
### Projektaufbau
Die vorgegebene Ordnerstruktur wurde beibehalten.
Es wurde jedoch die `CMakeLists.txt`-Datei in das Wurzelverzeichnis verschoben,
um einen üblichen Aufbau des Projektes zu erhalten,
und es wurden die nicht benötigten Verzeichnisse (`build`, `data`, `dependencies`, `src_solution`) entfernt,
um einen Stand zu erhalten, der tauglich für Versionskontrolle ist.
Das Projekt kann (abweichend von der Ausgangskonfiguration)
mit folgenden (für CMake-Projekte übliche) Befehlen gebaut werden:
```bash
# in `u02`
mkdir -p build
cd build
cmake ..
make -j$(nproc)
```
Für Tests der Implementation wurde [Catch2](https://github.com/catchorg/Catch2) eingebunden,
was jedoch optional ist und bei Nichtvorhandensein lediglich eine Nachricht beim Aufruf von CMake ausgibt,
welche aber keinen Fehler darstellt.
Zu einer Auslagerung von grundlegenden Funktionalitäten,
die nicht einem bestimmten Werkzeug zuzuordnen sind,
wurde die Hilfsdatei `util.cpp` (und der zugehörige Header `util.h`) angelegt,
welche in CMake eingebunden ist.
### Sternrasterisierer
Der Sternrasterisierer ermöglicht theoretisch
die Rasterisierung von Sternen mit beliebigen Zackenanzahlen
(wobei ein hartes Limit von mindestens 2 Zacken besteht,
jedoch erst ab 3 eine Art Stern vorliegt).
In dem Beispielprogramm kann jedoch aus Komplexitätsgründen nur aus bestimmten Zackenanzahlen gewählt werden,
was jedoch per Kontextmenü möglich ist und damit der Aufgabenstellung entspricht.
Darüber hinaus wurde rudimentär eine Vorschau der Sternform hinzugefügt,
was jedoch nur für eine feste Zackenanzahl (hier: 5) möglich ist,
ohne die Architektur der Applikation zu ändern.
### Sweepline-Algorithmus
Der Sweepline-Algorithmus, der im Programm aufrufbar ist,
hat für ein Beispiel-Dreieck 3 Eckpunkte fest im Code definiert.
Er ist jedoch allgemein ausgelegt und kann mit drei beliebigen Punkten aufgerufen werden,
die ein valides Dreieck bilden.
Dafür wurde `tool_base` so modifiziert,
dass es Werkzeugen möglich ist,
eine `draw`-Methode mit keinem (für die fest definierten Eckpunkte)
oder drei Punkten (für beliebige Dreiecke)
anzubieten.
Damit das Werkzeug kompatibel mit der Architektur des Hauptprogramms ist,
bietet es jedoch zusätzlich die `draw`-Methode mit zwei Punkten an,
welche jedoch die Punkte ignoriert und die `draw`-Methode ohne Punkte aufruft.

View File

@ -5,6 +5,7 @@
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_random.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <iostream>
#include "bresenham_circle_tool.h"
#include "bresenham_line_tool.h"
@ -17,7 +18,7 @@
#include "sweep_line_tool.h"
#include "util.h"
using Catch::Matchers::WithinRel;
using Catch::Matchers::WithinAbs;
TEST_CASE("Transform Mirror") {
// elementary operations
@ -349,7 +350,12 @@ TEST_CASE("Bresenham circle (prop: √(x²+y²)-r<ε)") {
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)");
// catch2s SKIP macro does not exactly do what I want here,
// so this just returns from the function and prints a message.
std::cerr
<< "All coordinates have extreme value, skipping (avoid rounding error)"
<< std::endl;
return;
}
const float r =
@ -432,16 +438,22 @@ TEST_CASE("Barycentric coordinates (prop: Σ = 1)") {
int x2 = GENERATE(take(2, random(-100, 100)));
int y2 = GENERATE(take(2, random(-100, 100)));
// If all points are on a straight line, the property does not hold.
// Checking this is equivalent to checking if the area of the triangle is 0.
if ((x0 - x1) * (y0 - y2) - (y0 - y1) * (x0 - x2) == 0) {
// see above
std::cerr << "Points do not form reasonable triangle, skipping"
<< std::endl;
return;
}
int x = GENERATE(take(5, random(-100, 100)));
int y = GENERATE(take(5, random(-100, 100)));
float b1, b2, b3;
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, x, y);
// If all points are on a straight line, the property does not hold
if (!(x0 == x1 && x1 == x2) && !(y0 == y1 && y1 == y2)) {
REQUIRE_THAT(b1 + b2 + b3, WithinRel(1.0, 0.01));
}
REQUIRE_THAT(b1 + b2 + b3, WithinAbs(1.0, 0.01));
}
TEST_CASE("Sort triangle points") {
@ -502,7 +514,7 @@ TEST_CASE("Sweep line (prop: Barycentric coordinates)") {
for (int y = 0; y < size; y++) {
if (point_in_triangle(x0, y0, x1, y1, x2, y2, x, y)) {
if (!canvas->get_pixel(x, y)) {
// Barycentric coordinates say, point is not in triangle,
// Barycentric coordinates say, point is in triangle,
// but point is not set.
// This must not happen → fail test.
pass = false;
@ -537,7 +549,11 @@ TEST_CASE("Star (prop: all points inside circle)") {
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)");
// see above
std::cerr
<< "All coordinates have extreme value, skipping (avoid rounding error)"
<< std::endl;
return;
}
const float r = std::sqrt(std::pow((x1 - x0), 2) + std::pow((y1 - y0), 2));
@ -580,8 +596,8 @@ TEST_CASE("Star (prop: average of all points is centre)") {
y += point.second;
}
REQUIRE_THAT(x / points.size(), WithinRel(x0, 0.1));
REQUIRE_THAT(y / points.size(), WithinRel(y0, 0.1));
REQUIRE_THAT(x / points.size(), WithinAbs(x0, 0.5));
REQUIRE_THAT(y / points.size(), WithinAbs(y0, 0.5));
}
// The Haskell implementation of regular_polygon_mod has many more tests,