Compare commits
4 Commits
dbf298940e
...
0d316d7aeb
Author | SHA1 | Date |
---|---|---|
Simon Bruder | 0d316d7aeb | |
Simon Bruder | a1f8720e74 | |
Simon Bruder | 25e159d96b | |
Simon Bruder | 7e8dbfabea |
13
README.md
13
README.md
|
@ -8,6 +8,19 @@ Each exercise includes a separate `readme.txt`
|
||||||
that describes things specific to how I implemented the task.
|
that describes things specific to how I implemented the task.
|
||||||
However, those documents are in German, which is a requirement.
|
However, those documents are in German, which is a requirement.
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
Due to copyright restrictions,
|
||||||
|
I am not allowed to publish many of my solutions.
|
||||||
|
The repository `ecg-prog-filtered` only includes files solely written by me.
|
||||||
|
It is filtered with the [`filter.sh`](./filter.sh) script.
|
||||||
|
Please **don’t** rely on the history of this repository,
|
||||||
|
as rewriting is part of how it can exist.
|
||||||
|
See [Licence](#licence) for more information on the licencing.
|
||||||
|
|
||||||
|
If you want access to all of my solutions,
|
||||||
|
please write me an email (or contact me in another way),
|
||||||
|
I’ll see what I can do.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
set -euo pipefail
|
||||||
|
export FILTER_BRANCH_SQUELCH_WARNING=1
|
||||||
|
git branch -D filtered
|
||||||
|
git switch -c filtered
|
||||||
|
git filter-branch -f --tree-filter "rm -f $(rg --files-without-match 'SPDX-License-Identifier: (L)?GPL-3.0-or-later' -g '/u??/**/*' | tr '\n' ' ')" HEAD
|
||||||
|
git push -f -u filtered filtered
|
||||||
|
git switch -
|
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tool_base.h"
|
||||||
|
|
||||||
|
class sweep_line_tool : public tool_base {
|
||||||
|
public:
|
||||||
|
sweep_line_tool(canvas_buffer &canvas);
|
||||||
|
|
||||||
|
// Draw example triangle
|
||||||
|
void draw();
|
||||||
|
// Compatibility for main application (only handles draw methods with one or two points)
|
||||||
|
void draw(int _x, int _y);
|
||||||
|
// Draw triangle provided by three given points
|
||||||
|
void draw(int x0, int y0, int x1, int y1, int x2, int y2);
|
||||||
|
|
||||||
|
void set_text(std::stringstream &stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Draw every pixel on the specified y coordinate,
|
||||||
|
// in the interval given by the boundaries b1 and b2.
|
||||||
|
// The boundaries do not need to be sorted.
|
||||||
|
void draw_interval(int b1, int b2, int y);
|
||||||
|
};
|
|
@ -38,6 +38,11 @@ std::tuple<float, float, float> barycentric_coordinates(int x0, int y0, int x1,
|
||||||
int y1, int x2, int y2,
|
int y1, int x2, int y2,
|
||||||
int xp, int yp);
|
int xp, int yp);
|
||||||
|
|
||||||
|
// Checks if the point given by (xp, yp) is inside the triangle
|
||||||
|
// given by the three points (x0, y0), (x1, y1), (x2, y2).
|
||||||
|
bool point_in_triangle(int x0, int y0, int x1, int y1, int x2, int y2, int xp,
|
||||||
|
int yp);
|
||||||
|
|
||||||
// Sorts the points of a triangle to be in ascending order (y0 < y1 < y2).
|
// Sorts the points of a triangle to be in ascending order (y0 < y1 < y2).
|
||||||
void sort_triangle_points(int &x0, int &y0, int &x1, int &y1, int &x2, int &y2);
|
void sort_triangle_points(int &x0, int &y0, int &x1, int &y1, int &x2, int &y2);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
#include "sweep_line_tool.h"
|
||||||
|
#include "dda_line_tool.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Calculate the inverse of the DDA function.
|
||||||
|
int dda_inv(int x0, int y0, float m, int y) {
|
||||||
|
// This uses the regular function that is the basis of DDA
|
||||||
|
//
|
||||||
|
// y_i = y_0 + m·(x_i - x_0)
|
||||||
|
//
|
||||||
|
// but rearranges it to be the inverse function:
|
||||||
|
//
|
||||||
|
// y_i = y_0 + m·(x_i - x_0)
|
||||||
|
// ⇔ y_i - y_0 + x_0·m = x_i·m
|
||||||
|
// ⇔ x_i = (y_i - y_0)/m + x_0
|
||||||
|
//
|
||||||
|
// This returns a valid x coordinate on the line
|
||||||
|
// starting from (x0, y0) with the slope m.
|
||||||
|
|
||||||
|
// Handle special case of flat line
|
||||||
|
if (m == 0)
|
||||||
|
return x0;
|
||||||
|
else
|
||||||
|
return round((y - y0) / m + x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sweep_line_tool::sweep_line_tool(canvas_buffer &canvas) : tool_base(canvas) {
|
||||||
|
shape = TS_NONE;
|
||||||
|
is_draggable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sweep_line_tool::draw_interval(int b1, int b2, int y) {
|
||||||
|
for (int x = std::min(b1, b2); x <= std::max(b1, b2); x++) {
|
||||||
|
canvas.set_pixel(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sweep_line_tool::draw() { draw(10, 10, 90, 30, 30, 90); }
|
||||||
|
|
||||||
|
void sweep_line_tool::draw(int _x, int _y) { draw(); }
|
||||||
|
|
||||||
|
void sweep_line_tool::draw(int x0, int y0, int x1, int y1, int x2, int y2) {
|
||||||
|
// Terminology:
|
||||||
|
//
|
||||||
|
// (x0, y0)
|
||||||
|
// +
|
||||||
|
// | \
|
||||||
|
// | \ m_1
|
||||||
|
// |first\
|
||||||
|
// | pass \
|
||||||
|
// m_shared |---------+ (x1, y1)
|
||||||
|
// |second /
|
||||||
|
// |pass /
|
||||||
|
// | / m_2
|
||||||
|
// | /
|
||||||
|
// +
|
||||||
|
// (x2, y2)
|
||||||
|
|
||||||
|
// Sort triangle points (in place) so that y0 < y1 < y2
|
||||||
|
sort_triangle_points(x0, y0, x1, y1, x2, y2);
|
||||||
|
|
||||||
|
// Slope of the side limiting the first pass (only)
|
||||||
|
float m_1 = slope(x0, y0, x1, y1);
|
||||||
|
// Slope of the side limiting the second pass (only)
|
||||||
|
float m_2 = slope(x1, y1, x2, y2);
|
||||||
|
// Slope of the side limiting both passes
|
||||||
|
float m_shared = slope(x0, y0, x2, y2);
|
||||||
|
|
||||||
|
// First pass
|
||||||
|
if (y0 == y1) {
|
||||||
|
// If the first two points are on the same height, only draw one line.
|
||||||
|
// This is only needed for the first interval,
|
||||||
|
// because in the case that y1 == y2,
|
||||||
|
// the problematic line would have already been handled in the first pass.
|
||||||
|
draw_interval(x0, x1, y0);
|
||||||
|
} else {
|
||||||
|
for (int y = y0; y <= y1; y++) {
|
||||||
|
int b1 = dda_inv(x0, y0, m_1, y);
|
||||||
|
int b2 = dda_inv(x0, y0, m_shared, y);
|
||||||
|
draw_interval(b1, b2, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass
|
||||||
|
// it can start iterating at y1 + 1,
|
||||||
|
// because y1 is already included in the first pass.
|
||||||
|
for (int y = y1 + 1; y <= y2; y++) {
|
||||||
|
int b1 = dda_inv(x1, y1, m_2, y);
|
||||||
|
int b2 = dda_inv(x0, y0, m_shared, y);
|
||||||
|
draw_interval(b1, b2, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sweep_line_tool::set_text(std::stringstream &stream) {
|
||||||
|
stream << "Tool: Sweep-Line";
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
#include "non_recursive_fill_tool.h"
|
#include "non_recursive_fill_tool.h"
|
||||||
#include "rectangle_tool.h"
|
#include "rectangle_tool.h"
|
||||||
#include "recursive_fill_tool.h"
|
#include "recursive_fill_tool.h"
|
||||||
|
#include "sweep_line_tool.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using Catch::Matchers::WithinRel;
|
using Catch::Matchers::WithinRel;
|
||||||
|
@ -479,3 +480,45 @@ TEST_CASE("Slope") {
|
||||||
// Special case: Infinite slope, must be normalized
|
// Special case: Infinite slope, must be normalized
|
||||||
REQUIRE(slope(10, 10, 10, 40) == 0.0);
|
REQUIRE(slope(10, 10, 10, 40) == 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Sweep line (prop: Barycentric coordinates)") {
|
||||||
|
const int size = 100;
|
||||||
|
int x0 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
int y0 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
int x1 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
int y1 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
int x2 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
int y2 = GENERATE(take(3, random(0, size - 1)));
|
||||||
|
|
||||||
|
canvas_buffer *canvas = new canvas_buffer(size, size);
|
||||||
|
sweep_line_tool *tool = new sweep_line_tool(*canvas);
|
||||||
|
|
||||||
|
tool->draw(x0, y0, x1, y1, x2, y2);
|
||||||
|
|
||||||
|
int deviating = 0;
|
||||||
|
bool pass = true;
|
||||||
|
for (int x = 0; x < size; x++) {
|
||||||
|
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,
|
||||||
|
// but point is not set.
|
||||||
|
// This must not happen → fail test.
|
||||||
|
pass = false;
|
||||||
|
}
|
||||||
|
} else if (canvas->get_pixel(x, y)) {
|
||||||
|
// Barycentric coordinates say, point is not in triangle,
|
||||||
|
// but point is set.
|
||||||
|
// The point is most likely on edge → mark it as deviating.
|
||||||
|
deviating++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUIRE(pass);
|
||||||
|
// Crude heuristic:
|
||||||
|
// No more than differences of all edge point coordinates can deviate.
|
||||||
|
// This ist not accurate (false negatives possible) on small/spiky triangles,
|
||||||
|
// but overall it gives an okayish result.
|
||||||
|
REQUIRE(deviating < abs(y1 - y0) + abs(y2 - y1) + abs(y0 - y2) +
|
||||||
|
abs(x1 - x0) + abs(x2 - x1) + abs(x0 - x2));
|
||||||
|
}
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
void transform_mut(Transformation transformation, int &x, int &y) {
|
void transform_mut(Transformation transformation, int &x, int &y) {
|
||||||
if ((transformation & TRANSFORM_ROTATE_CW) != 0) {
|
if (transformation & TRANSFORM_ROTATE_CW) {
|
||||||
std::swap(x, y);
|
std::swap(x, y);
|
||||||
x = -x;
|
x = -x;
|
||||||
}
|
}
|
||||||
if ((transformation & TRANSFORM_ROTATE_CCW) != 0) {
|
if (transformation & TRANSFORM_ROTATE_CCW) {
|
||||||
std::swap(x, y);
|
std::swap(x, y);
|
||||||
y = -y;
|
y = -y;
|
||||||
}
|
}
|
||||||
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
|
if (transformation & TRANSFORM_MIRROR_X) {
|
||||||
y = -y;
|
y = -y;
|
||||||
}
|
}
|
||||||
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
|
if (transformation & TRANSFORM_MIRROR_Y) {
|
||||||
x = -x;
|
x = -x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ std::pair<int, int> transform(Transformation transformation, int x, int y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void transform_inv_mut(Transformation transformation, int &x, int &y) {
|
void transform_inv_mut(Transformation transformation, int &x, int &y) {
|
||||||
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
|
if (transformation & TRANSFORM_MIRROR_Y) {
|
||||||
x = -x;
|
x = -x;
|
||||||
}
|
}
|
||||||
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
|
if (transformation & TRANSFORM_MIRROR_X) {
|
||||||
y = -y;
|
y = -y;
|
||||||
}
|
}
|
||||||
if (transformation & TRANSFORM_ROTATE_CCW) {
|
if (transformation & TRANSFORM_ROTATE_CCW) {
|
||||||
|
@ -160,6 +160,15 @@ std::tuple<float, float, float> barycentric_coordinates(int x0, int y0, int x1,
|
||||||
return {b1, b2, b3};
|
return {b1, b2, b3};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool point_in_triangle(int x0, int y0, int x1, int y1, int x2, int y2, int xp,
|
||||||
|
int yp) {
|
||||||
|
float b1, b2, b3;
|
||||||
|
std::tie(b1, b2, b3) =
|
||||||
|
barycentric_coordinates(x0, y0, x1, y1, x2, y2, xp, yp);
|
||||||
|
return b1 >= 0.0 && b1 <= 1.0 && b2 >= 0.0 && b2 <= 1.0 && b3 >= 0.0 &&
|
||||||
|
b3 <= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
void sort_triangle_points(int &x0, int &y0, int &x1, int &y1, int &x2,
|
void sort_triangle_points(int &x0, int &y0, int &x1, int &y1, int &x2,
|
||||||
int &y2) {
|
int &y2) {
|
||||||
// Bubble sort is not really ideal in general.
|
// Bubble sort is not really ideal in general.
|
||||||
|
|
Reference in New Issue