diff --git a/source/coordinates.hpp b/source/coordinates.hpp index 197be27..ba82451 100644 --- a/source/coordinates.hpp +++ b/source/coordinates.hpp @@ -4,7 +4,7 @@ namespace fractal { using display_coordinate = std::pair; -using complex_coordinate = std::complex; +using complex_coordinate = std::complex; struct display_domain { display_coordinate start_coordinate; @@ -52,12 +52,12 @@ struct complex_domain { complex_coordinate end_coordinate; }; -inline float real_domain_size(const complex_domain& domain) +inline double real_domain_size(const complex_domain& domain) { return domain.end_coordinate.real() - domain.start_coordinate.real(); } -inline float imaginary_domain_size(const complex_domain& domain) +inline double imaginary_domain_size(const complex_domain& domain) { return domain.end_coordinate.imag() - domain.start_coordinate.imag(); } diff --git a/source/graphics/basic_display.cpp b/source/graphics/basic_display.cpp index c65c53d..cf8c3c3 100644 --- a/source/graphics/basic_display.cpp +++ b/source/graphics/basic_display.cpp @@ -1,66 +1,73 @@ #include "basic_display.hpp" +#include "coordinates.hpp" #include "graphics/color_conversions.hpp" #include -#include -#include #include namespace fractal { void BasicDisplay::set_pixel(display_coordinate coordinate, uint16_t value) { - pixels_.at(coordinate.first).at(coordinate.second) = value; -} + auto tuple = number_to_rgb(value); -std::tuple interpolateColor( - float t, std::tuple color1, std::tuple color2 -) -{ - int r = std::get<0>(color1) + t * (std::get<0>(color2) - std::get<0>(color1)); - int g = std::get<1>(color1) + t * (std::get<1>(color2) - std::get<1>(color1)); - int b = std::get<2>(color1) + t * (std::get<2>(color2) - std::get<2>(color1)); - return {r, g, b}; + image_.setPixel( + static_cast(coordinate.first), + static_cast(coordinate.second), + sf::Color(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple)) + ); } -void BasicDisplay::display_window() +display_coordinate calculate_end_points( + display_coordinate start, display_coordinate current, + float target_aspect_ratio = 800.0f / 600.0f +) { - sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "SFML Window"); + auto width = static_cast(std::abs(current.first - start.first)); + auto height = static_cast(std::abs(current.second - start.second)); - window.setFramerateLimit(FRAME_RATE); + // Adjust the dimensions to maintain the target aspect ratio + if (width / height > target_aspect_ratio) { + // Too wide, adjust width + width = height * target_aspect_ratio; + } + else { + // Too tall, adjust height + height = width / target_aspect_ratio; + } - sf::Image image; - image.create(WINDOW_WIDTH, WINDOW_HEIGHT); - for (std::size_t x_pos = 0; x_pos < WINDOW_WIDTH; ++x_pos) { - for (std::size_t y_pos = 0; y_pos < WINDOW_HEIGHT; ++y_pos) { - std::uint16_t pixel_value = pixels_.at(x_pos).at(y_pos); - auto tuple = number_to_rgb(pixel_value); + auto x = static_cast(std::min(current.first, start.first)); + auto y = static_cast(std::min(current.second, start.second)); - image.setPixel( - static_cast(x_pos), static_cast(y_pos), - sf::Color(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple)) - ); - } + // Adjust the top-left corner based on new dimensions + if (current.first < start.first) { + x = static_cast(start.first) - width; + } + if (current.second < start.second) { + y = static_cast(start.second) - height; } - sf::Texture texture; - texture.loadFromImage(image); - - sf::Sprite sprite(texture); + // Return the top-left and bottom-right corners as a pair of sf::Vector2f + return {x + width, y + height}; +} +void BasicDisplay::display_window() +{ + texture_.loadFromImage(image_); + sf::Sprite sprite{texture_}; bool left_mouse_down{}; float selection_start_x{}; float selection_start_y{}; float mouse_x{}; float mouse_y{}; - while (window.isOpen()) { - sf::Event event; - while (window.pollEvent(event)) { + while (window_.isOpen()) { + sf::Event event{}; + while (window_.pollEvent(event)) { switch (event.type) { case sf::Event::Closed: - window.close(); + window_.close(); break; case sf::Event::MouseMoved: mouse_x = static_cast(event.mouseMove.x); @@ -75,50 +82,46 @@ void BasicDisplay::display_window() selection_start_x = static_cast(event.mouseButton.x); selection_start_y = static_cast(event.mouseButton.y); break; + default: + break; case sf::Event::MouseButtonReleased: if (event.mouseButton.button != sf::Mouse::Left) { break; } + auto ends = calculate_end_points( + {selection_start_x, selection_start_y}, {mouse_x, mouse_y} + ); on_resize_( sf::Vector2f( std::min(mouse_x, selection_start_x), std::min(mouse_y, selection_start_y) ), - sf::Vector2f( - std::max(mouse_x, selection_start_x), - std::max(mouse_y, selection_start_y) - ) + sf::Vector2f(ends.first, ends.second) ); left_mouse_down = false; - break; - - default: - break; - } - if (event.type == sf::Event::Closed) { - window.close(); + return; } } - window.clear(sf::Color::Black); + window_.clear(sf::Color::Black); - window.draw(sprite); + window_.draw(sprite); if (left_mouse_down) { - sf::RectangleShape selection_rectangle(sf::Vector2f( - std::abs(mouse_x - selection_start_x), - std::abs(mouse_y - selection_start_y) - )); - selection_rectangle.setPosition( - std::min(mouse_x, selection_start_x), - std::min(mouse_y, selection_start_y) + display_coordinate end_point = calculate_end_points( + {selection_start_x, selection_start_y}, {mouse_x, mouse_y} ); + sf::RectangleShape selection_rectangle( + {end_point.first - selection_start_x, + end_point.second - selection_start_y} + ); + selection_rectangle.setPosition(selection_start_x, selection_start_y); selection_rectangle.setFillColor(sf::Color(255, 0, 0, 127)); - window.draw(selection_rectangle); + window_.draw(selection_rectangle); } - window.display(); + window_.display(); } } } // namespace fractal diff --git a/source/graphics/basic_display.hpp b/source/graphics/basic_display.hpp index 0a99b71..df471d6 100644 --- a/source/graphics/basic_display.hpp +++ b/source/graphics/basic_display.hpp @@ -3,25 +3,30 @@ #include "config.hpp" #include "coordinates.hpp" +#include +#include #include #include -#include #include #include namespace fractal { class BasicDisplay { - std::array, WINDOW_WIDTH> pixels_{}; + sf::RenderWindow window_{sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "SFML Window"}; + sf::Image image_; + sf::Texture texture_; std::function on_resize_; public: - explicit BasicDisplay( - std::function on_resize = [](sf::Vector2f, - sf::Vector2f) {} + explicit BasicDisplay(std::function on_resize + ) : on_resize_(std::move(on_resize)) - {} + { + window_.setFramerateLimit(FRAME_RATE); + image_.create(WINDOW_WIDTH, WINDOW_HEIGHT); + } void set_pixel(display_coordinate coordinate, uint16_t value); void display_window(); diff --git a/source/graphics/display_to_complex.hpp b/source/graphics/display_to_complex.hpp index 9714e8a..6adbb00 100644 --- a/source/graphics/display_to_complex.hpp +++ b/source/graphics/display_to_complex.hpp @@ -6,32 +6,33 @@ namespace fractal { class DisplayToComplexCoordinates { - float real_scaling_factor_; - float imaginary_scaling_factor_; + double real_scaling_factor_; + double imaginary_scaling_factor_; complex_coordinate complex_domain_start_; - static float real_scaling_factor( + static double real_scaling_factor( const display_coordinate& display_top_right, const complex_domain& complex_domain ) { - float real_d_size = real_domain_size(complex_domain); - return real_d_size / static_cast(display_top_right.first); + double real_d_size = real_domain_size(complex_domain); + return real_d_size / static_cast(display_top_right.first); } - static float imaginary_scaling_factor( + static double imaginary_scaling_factor( const display_coordinate& display_top_right, const complex_domain& complex_domain ) { - float imaginary_d_size = imaginary_domain_size(complex_domain); - return imaginary_d_size / static_cast(display_top_right.second); + double imaginary_d_size = imaginary_domain_size(complex_domain); + return imaginary_d_size / static_cast(display_top_right.second); } static complex_coordinate to_complex(display_coordinate coordinate) { return { - static_cast(coordinate.first), static_cast(coordinate.second) + static_cast(coordinate.first), + static_cast(coordinate.second) }; } @@ -48,7 +49,7 @@ class DisplayToComplexCoordinates { complex_coordinate to_complex_projection(display_coordinate display_coord) { - std::complex raw_complex_coord = to_complex(display_coord); + std::complex raw_complex_coord = to_complex(display_coord); std::complex offset = { real_scaling_factor_ * raw_complex_coord.real(), imaginary_scaling_factor_ * raw_complex_coord.imag() diff --git a/source/main.cpp b/source/main.cpp index 2a44372..d1e2343 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -10,29 +10,31 @@ #include #include -constexpr float DIVERGENCE_NORM = 4; -constexpr fractal::display_domain DISPLAY_DOMAIN{ +namespace fractal { + +constexpr double DIVERGENCE_NORM = 4; +constexpr display_domain DISPLAY_DOMAIN{ {0, 0 }, {799, 599} }; -constexpr fractal::complex_domain COMPLEX_DOMAIN{ +constexpr complex_domain COMPLEX_DOMAIN{ {-2, -1.5}, {1, 1.5 } }; constexpr std::size_t MAX_ITERATIONS = 50; // https://en.wikipedia.org/wiki/Mandelbrot_set#Formal_definition -std::complex step(std::complex z_n, std::complex constant) +std::complex step(std::complex z_n, std::complex constant) { return z_n * z_n + constant; } std::size_t compute_iterations( - std::complex z_0, std::complex constant, std::size_t max_iters + std::complex z_0, std::complex constant, std::size_t max_iters ) { std::size_t iterations = 0; - std::complex z_n = z_0; + std::complex z_n = z_0; while (iterations < max_iters && std::norm(z_n) < DIVERGENCE_NORM) { z_n = step(z_n, constant); @@ -44,31 +46,41 @@ std::size_t compute_iterations( void display_mandelbrot() { - fractal::BasicDisplay display; - - fractal::DisplayToComplexCoordinates to_complex{ - DISPLAY_DOMAIN.end_coordinate, COMPLEX_DOMAIN + complex_domain domain = COMPLEX_DOMAIN; + DisplayToComplexCoordinates to_complex{DISPLAY_DOMAIN.end_coordinate, domain}; + + auto on_resize = [&](sf::Vector2f first, sf::Vector2f second) { + complex_coordinate top = to_complex.to_complex_projection({first.x, first.y}); + complex_coordinate bottom = + to_complex.to_complex_projection({second.x, second.y}); + to_complex = { + DISPLAY_DOMAIN.end_coordinate, {top, bottom} + }; }; + BasicDisplay display(on_resize); - auto process_coordinate = [&](const fractal::display_coordinate& coord) { - auto complex_coord = to_complex.to_complex_projection(coord); + while (true) { + auto process_coordinate = [&](const display_coordinate& coord) { + auto complex_coord = to_complex.to_complex_projection(coord); - // Compute the number of iterations - auto iterations = compute_iterations({0, 0}, complex_coord, MAX_ITERATIONS); + // Compute the number of iterations + auto iterations = compute_iterations({0, 0}, complex_coord, MAX_ITERATIONS); - display.set_pixel( - coord, - static_cast( - (static_cast(iterations) / static_cast(MAX_ITERATIONS)) - * std::numeric_limits::max() - ) - ); - }; + display.set_pixel( + coord, static_cast( + (static_cast(iterations) + / static_cast(MAX_ITERATIONS)) + * std::numeric_limits::max() + ) + ); + }; - std::for_each(DISPLAY_DOMAIN.begin(), DISPLAY_DOMAIN.end(), process_coordinate); + std::for_each(DISPLAY_DOMAIN.begin(), DISPLAY_DOMAIN.end(), process_coordinate); - display.display_window(); + display.display_window(); + } } +} // namespace fractal int main() { @@ -98,7 +110,7 @@ int main() auto height = program.get("height"); */ - display_mandelbrot(); + fractal::display_mandelbrot(); return 0; }