Compare commits
10 Commits
0fae793f28
...
b63edb305e
Author | SHA1 | Date |
---|---|---|
Simon Bruder | b63edb305e | |
Simon Bruder | f3a197930f | |
Simon Bruder | 6125ed2c72 | |
Simon Bruder | 65d7ce6e73 | |
Simon Bruder | a935e0fd22 | |
Simon Bruder | e720dc6ebc | |
Simon Bruder | 2532925813 | |
Simon Bruder | b4dcb52bde | |
Simon Bruder | 24adbcfbea | |
Simon Bruder | edb9e7400c |
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.
|
||||
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` 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
|
||||
|
||||
### Build
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/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 switch -
|
|
@ -25,6 +25,7 @@ set(HEADERS
|
|||
include/preview_renderer.h
|
||||
include/rectangle_tool.h
|
||||
include/recursive_fill_tool.h
|
||||
include/sweep_line_tool.h
|
||||
include/tiny_vec.h
|
||||
include/util.h
|
||||
)
|
||||
|
@ -47,6 +48,7 @@ set(SOURCES
|
|||
src/preview_renderer.cpp
|
||||
src/rectangle_tool.cpp
|
||||
src/recursive_fill_tool.cpp
|
||||
src/sweep_line_tool.cpp
|
||||
src/util.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ enum MenuActions {
|
|||
MA_FILL_RECURSIVE, // use recursive fill tool
|
||||
MA_FILL_NONRECURSIVE, // use non-recursive fill tool
|
||||
MA_FILL_LINE, // use line-fill tool
|
||||
MA_SWEEP_LINE, // use sweep line tool
|
||||
MA_CLEAR_CANVAS, // clear the canvas
|
||||
MA_TEST_SHAPE, // draw the test shape
|
||||
MA_RESET_VIEW // reset translation and zoom
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -41,6 +41,8 @@ public:
|
|||
virtual void draw(int x, int y);
|
||||
// Draw with two points provided
|
||||
virtual void draw(int x0, int y0, int x1, int y1);
|
||||
// Draw with three points provided
|
||||
virtual void draw(int x0, int y0, int x1, int y1, int x2, int y2);
|
||||
|
||||
// Get the shape that this tool will draw
|
||||
const ToolShape get_shape() const;
|
||||
|
|
|
@ -30,3 +30,22 @@ std::pair<int, int> transform_inv(Transformation transformation, int x, int y);
|
|||
// to make the given endpoints of a line conform
|
||||
// to the standard case for rasterization.
|
||||
Transformation transformation_to_standard_case(int x0, int y0, int x1, int y1);
|
||||
|
||||
// Returns the barycentric coordinates of the point given by (xp, yp)
|
||||
// in the triangle given by the three points (x0, y0), (x1, y1), (x2, y2).
|
||||
// May return non-real values when the points are on a straight line.
|
||||
std::tuple<float, float, float> barycentric_coordinates(int x0, int y0, int x1,
|
||||
int y1, int x2, int y2,
|
||||
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).
|
||||
void sort_triangle_points(int &x0, int &y0, int &x1, int &y1, int &x2, int &y2);
|
||||
|
||||
// This calculates the slope of a line from (x0, y0) to (x1, y1).
|
||||
// It handles special cases to ensure the return value will be real.
|
||||
float slope(int x0, int y0, int x1, int y1);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "pen_tool.h"
|
||||
#include "rectangle_tool.h"
|
||||
#include "recursive_fill_tool.h"
|
||||
#include "sweep_line_tool.h"
|
||||
#include <math.h>
|
||||
#include <sstream>
|
||||
|
||||
|
@ -127,6 +128,11 @@ void application::context_menu_select(int item) {
|
|||
set_tool(new line_fill_tool(*canvas));
|
||||
break;
|
||||
|
||||
// Set the sweep line tool
|
||||
case MA_SWEEP_LINE:
|
||||
set_tool(new sweep_line_tool(*canvas));
|
||||
break;
|
||||
|
||||
// Clear the canvas
|
||||
case MA_CLEAR_CANVAS:
|
||||
canvas->clear_canvas();
|
||||
|
@ -174,6 +180,7 @@ void application::setup_context_menu() {
|
|||
glutAddMenuEntry(" Recursive Fill (r) ", MA_FILL_RECURSIVE);
|
||||
glutAddMenuEntry(" Non-recursive Fill (f) ", MA_FILL_NONRECURSIVE);
|
||||
glutAddMenuEntry(" Line-Fill (l) ", MA_FILL_LINE);
|
||||
glutAddMenuEntry(" Sweep-Line (fixed shape) (w) ", MA_SWEEP_LINE);
|
||||
glutAddMenuEntry(" ------- Miscellaneous ------- ", -1);
|
||||
glutAddMenuEntry(" Clear canvas (c) ", MA_CLEAR_CANVAS);
|
||||
glutAddMenuEntry(" Draw test shape (t) ", MA_TEST_SHAPE);
|
||||
|
@ -252,6 +259,9 @@ void application::key_down(unsigned char key, int x, int y) {
|
|||
case 'l':
|
||||
context_menu_select(MA_FILL_LINE);
|
||||
break;
|
||||
case 'w':
|
||||
context_menu_select(MA_SWEEP_LINE);
|
||||
break;
|
||||
// Space or "c" clears the canvas
|
||||
case 'c':
|
||||
case ' ':
|
||||
|
|
|
@ -19,18 +19,7 @@ void dda_line_tool::draw(int x0, int y0, int x1, int y1) {
|
|||
transformation_to_standard_case(x0, y0, x1, y1);
|
||||
transform_mut(transformation, x0, y0);
|
||||
transform_mut(transformation, x1, y1);
|
||||
float m = ((float)(y1 - y0)) / ((float)(x1 - x0));
|
||||
if (std::isinf(m) || std::isnan(m)) {
|
||||
// This is a special case for two things:
|
||||
//
|
||||
// IEEE 754 specifies ∞ × 0 / 0 × ∞ to be an invalid operation,
|
||||
// and therefore return NaN.
|
||||
// That makes the computation of Δy fail when x0 == x1.
|
||||
//
|
||||
// In the case that additionally y0 == y1,
|
||||
// the expression is 0/0, also defined in IEEE 754 as invalid.
|
||||
m = 0;
|
||||
}
|
||||
float m = slope(x0, y0, x1, y1);
|
||||
float y = y0;
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
int tx, ty;
|
||||
|
|
|
@ -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";
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/generators/catch_generators_adapters.hpp>
|
||||
#include <catch2/generators/catch_generators_random.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include "bresenham_circle_tool.h"
|
||||
#include "bresenham_line_tool.h"
|
||||
|
@ -12,8 +13,11 @@
|
|||
#include "non_recursive_fill_tool.h"
|
||||
#include "rectangle_tool.h"
|
||||
#include "recursive_fill_tool.h"
|
||||
#include "sweep_line_tool.h"
|
||||
#include "util.h"
|
||||
|
||||
using Catch::Matchers::WithinRel;
|
||||
|
||||
TEST_CASE("Transform Mirror") {
|
||||
// elementary operations
|
||||
REQUIRE(transform(TRANSFORM_MIRROR_X, 10, 20) == std::make_pair(10, -20));
|
||||
|
@ -370,3 +374,151 @@ TEST_CASE("Bresenham circle (prop: √(x²+y²)-r<ε)") {
|
|||
}
|
||||
REQUIRE(pass);
|
||||
}
|
||||
|
||||
TEST_CASE("Barycentric coordinates: Edge cases") {
|
||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 10, x2 = 10, y2 = 0;
|
||||
float b1, b2, b3;
|
||||
|
||||
// point on vertex
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 0, 0);
|
||||
REQUIRE(b1 == 1);
|
||||
REQUIRE(b2 == 0);
|
||||
REQUIRE(b3 == 0);
|
||||
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 0, 10);
|
||||
REQUIRE(b1 == 0);
|
||||
REQUIRE(b2 == 1);
|
||||
REQUIRE(b3 == 0);
|
||||
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 10, 0);
|
||||
REQUIRE(b1 == 0);
|
||||
REQUIRE(b2 == 0);
|
||||
REQUIRE(b3 == 1);
|
||||
|
||||
// point on edge
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 0, 5);
|
||||
REQUIRE(b1 == 0.5);
|
||||
REQUIRE(b2 == 0.5);
|
||||
REQUIRE(b3 == 0);
|
||||
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 5, 0);
|
||||
REQUIRE(b1 == 0.5);
|
||||
REQUIRE(b2 == 0);
|
||||
REQUIRE(b3 == 0.5);
|
||||
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, y0, x1, y1, x2, y2, 5, 5);
|
||||
REQUIRE(b1 == 0);
|
||||
REQUIRE(b2 == 0.5);
|
||||
REQUIRE(b3 == 0.5);
|
||||
|
||||
// All points on straight line
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(0, y0, 0, y1, 0, y2, 0, 0);
|
||||
REQUIRE(std::isnan(b1));
|
||||
REQUIRE(std::isnan(b2));
|
||||
REQUIRE(std::isnan(b3));
|
||||
|
||||
std::tie(b1, b2, b3) = barycentric_coordinates(x0, 0, x1, 0, x2, 0, 0, 0);
|
||||
REQUIRE(std::isnan(b1));
|
||||
REQUIRE(std::isnan(b2));
|
||||
REQUIRE(std::isnan(b3));
|
||||
}
|
||||
|
||||
TEST_CASE("Barycentric coordinates (prop: Σ = 1)") {
|
||||
int x0 = GENERATE(take(2, random(-100, 100)));
|
||||
int y0 = GENERATE(take(2, random(-100, 100)));
|
||||
int x1 = GENERATE(take(2, random(-100, 100)));
|
||||
int y1 = GENERATE(take(2, random(-100, 100)));
|
||||
int x2 = GENERATE(take(2, random(-100, 100)));
|
||||
int y2 = GENERATE(take(2, random(-100, 100)));
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Sort triangle points") {
|
||||
int x0 = 10, y0 = 60, x1 = 50, y1 = 90, x2 = 40, y2 = 30;
|
||||
sort_triangle_points(x0, y0, x1, y1, x2, y2);
|
||||
REQUIRE(x0 == 40);
|
||||
REQUIRE(y0 == 30);
|
||||
REQUIRE(x1 == 10);
|
||||
REQUIRE(y1 == 60);
|
||||
REQUIRE(x2 == 50);
|
||||
REQUIRE(y2 == 90);
|
||||
}
|
||||
|
||||
TEST_CASE("Sort triangle points (prop: y0 < y1 < y2)") {
|
||||
int x0 = GENERATE(take(3, random(-100, 100)));
|
||||
int y0 = GENERATE(take(3, random(-100, 100)));
|
||||
int x1 = GENERATE(take(3, random(-100, 100)));
|
||||
int y1 = GENERATE(take(3, random(-100, 100)));
|
||||
int x2 = GENERATE(take(3, random(-100, 100)));
|
||||
int y2 = GENERATE(take(3, random(-100, 100)));
|
||||
|
||||
sort_triangle_points(x0, y0, x1, y1, x2, y2);
|
||||
|
||||
REQUIRE(y0 <= y1);
|
||||
REQUIRE(y1 <= y2);
|
||||
}
|
||||
|
||||
TEST_CASE("Slope") {
|
||||
REQUIRE(slope(5, 10, 20, 10) == 0.0);
|
||||
REQUIRE(slope(0, 0, 10, 10) == 1.0);
|
||||
REQUIRE(slope(0, 0, 10, -10) == -1.0);
|
||||
REQUIRE(slope(0, 0, 10, 5) == 0.5);
|
||||
REQUIRE(slope(0, 0, 10, -5) == -0.5);
|
||||
REQUIRE(slope(0, 10, 10, 40) == 3.0);
|
||||
REQUIRE(slope(0, 10, 10, -40) == -5.0);
|
||||
|
||||
// Special case: Infinite slope, must be normalized
|
||||
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 fail.
|
||||
// This ist not accurate (false positives 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));
|
||||
}
|
||||
|
|
|
@ -29,6 +29,12 @@ void tool_base::draw(int from_x, int from_y, int to_x, int to_y) {
|
|||
// this method if needed
|
||||
}
|
||||
|
||||
// Draw with three points provided
|
||||
void tool_base::draw(int x0, int y0, int x1, int y1, int x2, int y2) {
|
||||
// Nothing implemented here. Children of this class can implement
|
||||
// this method if needed
|
||||
}
|
||||
|
||||
// Get the shape that this tool will draw
|
||||
const ToolShape tool_base::get_shape() const { return shape; }
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
#include <cmath>
|
||||
|
||||
void transform_mut(Transformation transformation, int &x, int &y) {
|
||||
if ((transformation & TRANSFORM_ROTATE_CW) != 0) {
|
||||
if (transformation & TRANSFORM_ROTATE_CW) {
|
||||
std::swap(x, y);
|
||||
x = -x;
|
||||
}
|
||||
if ((transformation & TRANSFORM_ROTATE_CCW) != 0) {
|
||||
if (transformation & TRANSFORM_ROTATE_CCW) {
|
||||
std::swap(x, y);
|
||||
y = -y;
|
||||
}
|
||||
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
|
||||
if (transformation & TRANSFORM_MIRROR_X) {
|
||||
y = -y;
|
||||
}
|
||||
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
|
||||
if (transformation & TRANSFORM_MIRROR_Y) {
|
||||
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) {
|
||||
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
|
||||
if (transformation & TRANSFORM_MIRROR_Y) {
|
||||
x = -x;
|
||||
}
|
||||
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
|
||||
if (transformation & TRANSFORM_MIRROR_X) {
|
||||
y = -y;
|
||||
}
|
||||
if (transformation & TRANSFORM_ROTATE_CCW) {
|
||||
|
@ -139,3 +139,69 @@ Transformation transformation_to_standard_case(int x0, int y0, int x1, int y1) {
|
|||
|
||||
return transformation;
|
||||
}
|
||||
|
||||
std::tuple<float, float, float> barycentric_coordinates(int x0, int y0, int x1,
|
||||
int y1, int x2, int y2,
|
||||
int xp, int yp) {
|
||||
// Source:
|
||||
// https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Vertex_approach
|
||||
float b1 = x1 * y2 - x2 * y1 + xp * (y1 - y2) + yp * (x2 - x1);
|
||||
float b2 = x2 * y0 - x0 * y2 + xp * (y2 - y0) + yp * (x0 - x2);
|
||||
float b3 = x0 * y1 - x1 * y0 + xp * (y0 - y1) + yp * (x1 - x0);
|
||||
|
||||
// reciprocal computed directly for performance
|
||||
float area_factor =
|
||||
1 / static_cast<float>(x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1));
|
||||
|
||||
b1 *= area_factor;
|
||||
b2 *= area_factor;
|
||||
b3 *= area_factor;
|
||||
|
||||
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,
|
||||
int &y2) {
|
||||
// Bubble sort is not really ideal in general.
|
||||
// It could be changed to use a more efficient algorithm,
|
||||
// but for only 3 values, it should suffice.
|
||||
// Moreover, implementing sorting on an array/vector of tuples
|
||||
// is probably more overhead.
|
||||
if (y0 > y1) {
|
||||
std::swap(x0, x1);
|
||||
std::swap(y0, y1);
|
||||
}
|
||||
if (y0 > y2) {
|
||||
std::swap(x0, x2);
|
||||
std::swap(y0, y2);
|
||||
}
|
||||
if (y1 > y2) {
|
||||
std::swap(x1, x2);
|
||||
std::swap(y1, y2);
|
||||
}
|
||||
}
|
||||
|
||||
float slope(int x0, int y0, int x1, int y1) {
|
||||
float m = ((float)(y1 - y0)) / ((float)(x1 - x0));
|
||||
if (std::isinf(m) || std::isnan(m)) {
|
||||
// This is a special case for two things:
|
||||
//
|
||||
// IEEE 754 specifies ∞ × 0 / 0 × ∞ to be an invalid operation,
|
||||
// and therefore return NaN.
|
||||
// That makes the computation of Δy fail when x0 == x1.
|
||||
//
|
||||
// In the case that additionally y0 == y1,
|
||||
// the expression is 0/0, also defined in IEEE 754 as invalid.
|
||||
m = 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
|
Reference in New Issue