Skip to content

Commit

Permalink
Merge pull request #4 from stevenewald/zoom
Browse files Browse the repository at this point in the history
Add zoom functionality
  • Loading branch information
stevenewald authored Nov 24, 2024
2 parents 6df06ef + 0e9f626 commit 0e35e21
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 100 deletions.
6 changes: 3 additions & 3 deletions source/coordinates.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace fractal {
using display_coordinate = std::pair<uint16_t, uint16_t>;
using complex_coordinate = std::complex<float>;
using complex_coordinate = std::complex<double>;

struct display_domain {
display_coordinate start_coordinate;
Expand Down Expand Up @@ -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();
}
Expand Down
115 changes: 59 additions & 56 deletions source/graphics/basic_display.cpp
Original file line number Diff line number Diff line change
@@ -1,66 +1,73 @@
#include "basic_display.hpp"

#include "coordinates.hpp"
#include "graphics/color_conversions.hpp"

#include <fmt/format.h>
#include <SFML/Graphics.hpp>
#include <SFML/Graphics/RectangleShape.hpp>

#include <cmath>

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<int, int, int> interpolateColor(
float t, std::tuple<int, int, int> color1, std::tuple<int, int, int> 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<unsigned int>(coordinate.first),
static_cast<unsigned int>(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<float>(std::abs(current.first - start.first));
auto height = static_cast<float>(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<float>(std::min(current.first, start.first));
auto y = static_cast<float>(std::min(current.second, start.second));

image.setPixel(
static_cast<unsigned int>(x_pos), static_cast<unsigned int>(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<float>(start.first) - width;
}
if (current.second < start.second) {
y = static_cast<float>(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<float>(event.mouseMove.x);
Expand All @@ -75,50 +82,46 @@ void BasicDisplay::display_window()
selection_start_x = static_cast<float>(event.mouseButton.x);
selection_start_y = static_cast<float>(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
17 changes: 11 additions & 6 deletions source/graphics/basic_display.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@
#include "config.hpp"
#include "coordinates.hpp"

#include <SFML/Graphics.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/System/Vector2.hpp>

#include <cstdint>

#include <array>
#include <functional>
#include <utility>

namespace fractal {
class BasicDisplay {
std::array<std::array<uint16_t, WINDOW_HEIGHT>, WINDOW_WIDTH> pixels_{};
sf::RenderWindow window_{sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "SFML Window"};
sf::Image image_;
sf::Texture texture_;
std::function<void(sf::Vector2f, sf::Vector2f)> on_resize_;

public:
explicit BasicDisplay(
std::function<void(sf::Vector2f, sf::Vector2f)> on_resize = [](sf::Vector2f,
sf::Vector2f) {}
explicit BasicDisplay(std::function<void(sf::Vector2f, sf::Vector2f)> 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();
Expand Down
21 changes: 11 additions & 10 deletions source/graphics/display_to_complex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(display_top_right.first);
double real_d_size = real_domain_size(complex_domain);
return real_d_size / static_cast<double>(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<float>(display_top_right.second);
double imaginary_d_size = imaginary_domain_size(complex_domain);
return imaginary_d_size / static_cast<double>(display_top_right.second);
}

static complex_coordinate to_complex(display_coordinate coordinate)
{
return {
static_cast<float>(coordinate.first), static_cast<float>(coordinate.second)
static_cast<double>(coordinate.first),
static_cast<double>(coordinate.second)
};
}

Expand All @@ -48,7 +49,7 @@ class DisplayToComplexCoordinates {

complex_coordinate to_complex_projection(display_coordinate display_coord)
{
std::complex<float> raw_complex_coord = to_complex(display_coord);
std::complex<double> raw_complex_coord = to_complex(display_coord);
std::complex offset = {
real_scaling_factor_ * raw_complex_coord.real(),
imaginary_scaling_factor_ * raw_complex_coord.imag()
Expand Down
62 changes: 37 additions & 25 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@
#include <algorithm>
#include <limits>

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<float> step(std::complex<float> z_n, std::complex<float> constant)
std::complex<double> step(std::complex<double> z_n, std::complex<double> constant)
{
return z_n * z_n + constant;
}

std::size_t compute_iterations(
std::complex<float> z_0, std::complex<float> constant, std::size_t max_iters
std::complex<double> z_0, std::complex<double> constant, std::size_t max_iters
)
{
std::size_t iterations = 0;
std::complex<float> z_n = z_0;
std::complex<double> z_n = z_0;

while (iterations < max_iters && std::norm(z_n) < DIVERGENCE_NORM) {
z_n = step(z_n, constant);
Expand All @@ -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<uint16_t>(
(static_cast<float>(iterations) / static_cast<float>(MAX_ITERATIONS))
* std::numeric_limits<uint16_t>::max()
)
);
};
display.set_pixel(
coord, static_cast<uint16_t>(
(static_cast<float>(iterations)
/ static_cast<float>(MAX_ITERATIONS))
* std::numeric_limits<uint16_t>::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()
{
Expand Down Expand Up @@ -98,7 +110,7 @@ int main()
auto height = program.get<int>("height");
*/

display_mandelbrot();
fractal::display_mandelbrot();

return 0;
}

0 comments on commit 0e35e21

Please sign in to comment.