u02: Implement star tool
This commit is contained in:
parent
7e06373aa1
commit
2226fb32af
|
@ -2,6 +2,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "tool_base.h"
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
// Return the points of a regular n-gon
|
||||
// with the centre at (x, y)
|
||||
// that is modified in the sense that it takes rf,
|
||||
// which is used to set the radius for each individual point.
|
||||
// It is rotated by base_angle,
|
||||
// where 0 starts drawing at the top.
|
||||
std::vector<std::pair<float, float>>
|
||||
regular_polygon_mod(int n, std::function<float(int)> rf, int x = 0, int y = 0,
|
||||
float base_angle = 0);
|
||||
|
||||
// Return the points of a star
|
||||
// with n spikes,
|
||||
// the inner radius r1,
|
||||
// the outer radius r2
|
||||
// and the centre (x, y),
|
||||
// rotated by base_angle.
|
||||
std::vector<std::pair<float, float>> star(int n, float r1, float r2, int x = 0,
|
||||
int y = 0, float base_angle = 0);
|
||||
|
||||
class star_tool : public tool_base {
|
||||
public:
|
||||
|
@ -10,4 +31,12 @@ public:
|
|||
void draw(int x0, int y0, int x1, int y1);
|
||||
|
||||
void set_text(std::stringstream &stream);
|
||||
|
||||
void set_spikes(int spikes);
|
||||
|
||||
void set_radius_factor(int radius_factor);
|
||||
|
||||
private:
|
||||
int spikes = 5;
|
||||
float radius_factor = 1.0 / 3.0;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,74 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#include "star_tool.h"
|
||||
#include "bresenham_line_tool.h"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <util.h>
|
||||
|
||||
/*
|
||||
* The implementation is modeled after one in Haskell
|
||||
* I created for EMI in the winter semester 2022/2023
|
||||
* to output SVGs of regular polygons varied in different ways.
|
||||
* It can be found here:
|
||||
* https://git.sbruder.de/simon/emi5/src/branch/master/genstar/Main.hs
|
||||
*/
|
||||
|
||||
std::vector<std::pair<float, float>>
|
||||
regular_polygon_mod(int n, std::function<float(int)> rf, int x, int y,
|
||||
float base_angle) {
|
||||
if (n < 3) {
|
||||
std::cerr << "Polygons must have at least 3 vertices (regular_polygon_mod "
|
||||
"was called with n="
|
||||
<< n << ")." << std::endl;
|
||||
return {};
|
||||
}
|
||||
std::vector<std::pair<float, float>> points = {};
|
||||
for (int step = 0; step < n; step++) {
|
||||
const float angle = base_angle + (step * 2.0 * atan(1) * 4.0) / n;
|
||||
const float r = rf(step);
|
||||
// Changed from Haskell implementation:
|
||||
// It behaves like the unit circle,
|
||||
// in that it starts at (1, 0) instead of (0, 1).
|
||||
points.push_back({round(x + r * cosf(angle)), round(y + r * sinf(angle))});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
std::vector<std::pair<float, float>> star(int n, float r1, float r2, int x,
|
||||
int y, float base_angle) {
|
||||
return regular_polygon_mod(
|
||||
n * 2, [r1, r2](int i) { return i % 2 == 0 ? r2 : r1; }, x, y,
|
||||
base_angle);
|
||||
}
|
||||
|
||||
star_tool::star_tool(canvas_buffer &canvas) : tool_base(canvas) {
|
||||
shape = TS_CIRCLE; // TODO: Use star for preview
|
||||
}
|
||||
|
||||
void star_tool::draw(int x0, int y0, int x1, int y1) {}
|
||||
void star_tool::draw(int x0, int y0, int x1, int y1) {
|
||||
const int r =
|
||||
round(std::sqrt(std::pow((x1 - x0), 2) + std::pow((y1 - y0), 2)));
|
||||
|
||||
void star_tool::set_text(std::stringstream &stream) { stream << "Tool: Star"; }
|
||||
const float angle = atan2(y1 - y0, x1 - x0);
|
||||
|
||||
bresenham_line_tool *blt = new bresenham_line_tool(canvas);
|
||||
|
||||
const std::vector<std::pair<float, float>> points =
|
||||
star(spikes, radius_factor * r, r, x0, y0, angle);
|
||||
|
||||
for (int i = 1; i <= points.size(); i++) {
|
||||
blt->draw(round(points[i - 1].first), round(points[i - 1].second),
|
||||
round(points[i % points.size()].first),
|
||||
round(points[i % points.size()].second));
|
||||
}
|
||||
}
|
||||
|
||||
void star_tool::set_text(std::stringstream &stream) {
|
||||
stream << "Tool: Star (" << spikes << " spikes)";
|
||||
}
|
||||
|
||||
void star_tool::set_spikes(int spikes) { this->spikes = spikes; }
|
||||
|
||||
void star_tool::set_radius_factor(int radius_factor) {
|
||||
this->radius_factor = radius_factor;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "non_recursive_fill_tool.h"
|
||||
#include "rectangle_tool.h"
|
||||
#include "recursive_fill_tool.h"
|
||||
#include "star_tool.h"
|
||||
#include "sweep_line_tool.h"
|
||||
#include "util.h"
|
||||
|
||||
|
@ -522,3 +523,45 @@ TEST_CASE("Sweep line (prop: Barycentric coordinates)") {
|
|||
REQUIRE(deviating < abs(y1 - y0) + abs(y2 - y1) + abs(y0 - y2) +
|
||||
abs(x1 - x0) + abs(x2 - x1) + abs(x0 - x2));
|
||||
}
|
||||
|
||||
TEST_CASE("Star (prop: all points inside circle)") {
|
||||
// See test “Bresenham circle (prop: √(x²+y²)-r<ε)” for what this does.
|
||||
const int size = 100;
|
||||
const int max_c = std::floor(size / (3 - std::sqrt(2)));
|
||||
const int min_c = std::ceil(((4 - std::sqrt(2)) / 7) * size);
|
||||
|
||||
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 float r = std::sqrt(std::pow((x1 - x0), 2) + std::pow((y1 - y0), 2));
|
||||
|
||||
// correction factor for very small stars
|
||||
const float correction = r < 2 ? 1 : 0;
|
||||
|
||||
canvas_buffer *canvas = new canvas_buffer(size, size);
|
||||
star_tool *tool = new star_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++) {
|
||||
if (canvas->get_pixel(x, y) &&
|
||||
(std::sqrt(std::pow(x0 - x, 2) + std::pow(y0 - y, 2)) >
|
||||
r * r + correction)) {
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
REQUIRE(pass);
|
||||
}
|
||||
|
||||
// The Haskell implementation of regular_polygon_mod has many tests,
|
||||
// but they are not implemented here for brevity.
|
||||
|
|
Reference in a new issue