Pythagoras Tree

The construction of the Pythagoras tree begins with a square. Upon this square are constructed two squares, each scaled down by a linear factor of ½√2, such that the corners of the squares coincide pairwise. The same procedure is then applied recursively to the two smaller squares,ad infinitum. The illustration below shows the first few iterations in the construction process.[2][3]

Order 0

Order 1

Order 2

Order 3

My approach was to begin a with a line (root), and then just scale and rotate the line relative to the parent.

#include "SFML/Graphics.hpp"

#include <math.h>
#include <iostream>
#include <utility>
#include <queue>

inline void pause() { std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); }

#define WIDTH 1920
#define HEIGHT 1080

namespace CONFIG {
    double line_scaler = .7071067812; // sqrt(2)/2
    const int total_orders = 10;
    double initial_angle_seed = -90;
    double rotation = 45;
    double thickness = 10.f;
    const double thickness_scaler = .87;

    namespace GRADIENT {
        const float r = .85;
        const float g = .85;
        const float b = .85;
    }
}

class PythagorusLine : public sf::Drawable
{
public:
    PythagorusLine(const sf::Vector2f& point1, const sf::Vector2f& point2,
        int rotation, float thickness, sf::Color color) :
        thickness(thickness), p1(point1), p2(point2),
        color(color), left(nullptr), right(nullptr), rotation(rotation)
    {
        sf::Vector2f direction = point2 - point1;
        sf::Vector2f unitDirection = direction / std::sqrt(direction.x*direction.x + direction.y*direction.y);
        sf::Vector2f unitPerpendicular(-unitDirection.y, unitDirection.x);
        sf::Vector2f offset = (thickness / 2.f)*unitPerpendicular;

        vertices[0].position = point1 + offset;
        vertices[1].position = point2 + offset;
        vertices[2].position = point2 - offset;
        vertices[3].position = point1 - offset;

        for (int i = 0; i<4; ++i)
            vertices[i].color = color;
    }

    ~PythagorusLine() {
        if (left) delete left;
        if (right) delete right;
    }

    sf::Vector2f getBottom() { return p1; }
    sf::Vector2f getTop() { return p2; }

    static PythagorusLine* getRotatedCopy(PythagorusLine &line, const int rotation, const sf::Color &color) {
        sf::Vector2f bottom_point = line.getTop();
        const float dist = distance(line.getBottom(), line.getTop());
        const float rad = radians(rotation);

        float top_x = bottom_point.x + (cos(rad) * dist * CONFIG::line_scaler);
        float top_y = bottom_point.y + (sin(rad) * dist * CONFIG::line_scaler);
        return new PythagorusLine(bottom_point, { top_x, top_y }, rotation, line.thickness * CONFIG::thickness_scaler, color);
    }

    static float radians(int degrees) {
        return 3.14159 / 180 * degrees;
    }

    void draw(sf::RenderTarget &target, sf::RenderStates states) const
    {
        target.draw(vertices, 4, sf::Quads);
    }

    void draw(sf::RenderWindow &window) const
    {
        window.draw(vertices, 4, sf::Quads);
    }

    static float distance(const sf::Vector2f &p1, const sf::Vector2f &p2) {
        float sumOfSquares = pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2);
        return sqrt(sumOfSquares);
    }

    PythagorusLine *left, *right;
    int rotation;
    float thickness;
    sf::Color color;

private:
    sf::Vector2f p1, p2;
    sf::Vertex vertices[4];
};


class PythagorusTree {
public:
    PythagorusTree() {}

    static void draw(sf::RenderWindow &window, PythagorusLine *root, int num_orders) {

        if (!root) return;
        num_orders = std::min(num_orders, CONFIG::total_orders);

        std::queue<PythagorusLine*> q;
        q.push(root);
        q.push(nullptr);

        while (!q.empty()) {
            PythagorusLine *parent = q.front(); q.pop();
            if (parent) parent->draw(window);

            if (parent && num_orders) {
                if (parent->right)
                    q.push(parent->right);

                if (parent->left)
                    q.push(parent->left);
            }

            else if (!parent && num_orders) {
                num_orders--;
                q.push(nullptr);
            }
        }
    }

    PythagorusLine* generatePythagorusTree(PythagorusLine *root) {
        if (!root) return nullptr;

        int order_count_down = CONFIG::total_orders;
        std::queue<PythagorusLine*> q;
        q.push(root);
        q.push(nullptr);

        while (!q.empty()) {
            PythagorusLine *parent = q.front(); q.pop();

            if (parent && order_count_down) {
                sf::Color darker = parent->color;
                darker.r = int(darker.r * CONFIG::GRADIENT::r);
                darker.b = int(darker.b * CONFIG::GRADIENT::b);
                darker.g = int(darker.g * CONFIG::GRADIENT::g);

                parent->right = PythagorusLine::getRotatedCopy(*parent, int(parent->rotation + CONFIG::rotation) % 360, darker);
                parent->left = PythagorusLine::getRotatedCopy(*parent, int(parent->rotation - CONFIG::rotation) % 360, darker);
                q.push(parent->right); q.push(parent->left);
            }

            else if (!parent && order_count_down && !q.empty()) {
                order_count_down--;
                q.push(nullptr);
            }
        }

        return root;
    }
};


struct ControlHandler
{
    ControlHandler() {}

    static void processEvents(sf::RenderWindow &window, sf::View &view, int &current_order_count) {
        sf::Event event;
        while (window.pollEvent(event))
        {
            // close
            if (event.type == sf::Event::Closed) window.close();
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) &&
                sf::Keyboard::isKeyPressed(sf::Keyboard::C)) window.close();

            // camera
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) view.move(-20, 0);
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) view.move(+20, 0);
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) view.move(0, -20);
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) view.move(0, +20);

            // zoom
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) view.zoom(1.05);
            else if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) view.zoom(.95);

            // click generator
            else if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) current_order_count++;
            window.setView(view);
        }
    }
};

enum ANIMATE { length, rotation, branch_angle, thickness, all, none,
               l_r_b};

class Game
{
public:
    Game(const ANIMATE &generator)
        : window(sf::VideoMode(WIDTH, HEIGHT), "Pythagorus Tree") {

        window.setFramerateLimit(60);
        view.setCenter(WIDTH / 2, HEIGHT / 2);
        view.setSize(WIDTH, HEIGHT);
        handler = new ControlHandler();
        create_new_tree(generator);

        if (generator == ANIMATE::none) {
            run();
        }
        else {
            current_order_count = CONFIG::total_orders;
            run(generator);
        }
    }

private:
    PythagorusTree *tree;
    PythagorusLine *root;
    ControlHandler *handler;

    int current_order_count = 0;

    sf::RenderWindow window;
    sf::View view;
    sf::Color colorWindow = sf::Color(80, 49, 42);
    sf::Color branch_init = sf::Color(111, 193, 185);

    void render() {
        window.clear(colorWindow);
        tree->draw(window, this->root, current_order_count);
        window.display();
    }

    void run(const ANIMATE &generator) {
        while (window.isOpen())
        {
            handler->processEvents(window, view, current_order_count);
            render();
            create_new_tree(generator);
        }
    }

    void run() {
        while (window.isOpen())
        {
            handler->processEvents(window, view, current_order_count);
            render();
        }
    }

    const double line_scaler = 1.0005;
    const double initial_angle_seed_scaler = 1.1;
    const double rotation_scaler = 1.01;
    const double thickness_scaler = 1.01;

    void create_new_tree(const ANIMATE &generator) {
        if (this->root) delete root;
        root = nullptr;

        tree = new PythagorusTree();

        switch (generator) {
        case length:
            CONFIG::line_scaler *= line_scaler;
            break;
        case rotation:
            CONFIG::initial_angle_seed =
                fmod(CONFIG::initial_angle_seed * initial_angle_seed_scaler, 360);
            break;
        case branch_angle:
            CONFIG::rotation =
                fmod(CONFIG::rotation * rotation_scaler, 360);
            break;
        case thickness:
            CONFIG::thickness *= thickness_scaler;
            break;
        case l_r_b:
            CONFIG::line_scaler *= line_scaler;
            CONFIG::initial_angle_seed =
                fmod(CONFIG::initial_angle_seed * initial_angle_seed_scaler, 360);
            CONFIG::rotation =
                fmod(CONFIG::rotation * rotation_scaler, 360);
            break;
        case all:
            CONFIG::line_scaler *= line_scaler;
            CONFIG::initial_angle_seed =
                fmod(CONFIG::initial_angle_seed * initial_angle_seed_scaler, 360);
            CONFIG::rotation =
                fmod(CONFIG::rotation * rotation_scaler, 360);
            CONFIG::thickness *= thickness_scaler;
            break;
        case none:
            break;
        }

        root = new PythagorusLine({ WIDTH / 2, WIDTH / 2 }, { WIDTH / 2, HEIGHT / 2 },
            CONFIG::initial_angle_seed, CONFIG::thickness, branch_init);
        tree->generatePythagorusTree(root);
    }
};


int main()
{
    //Game game(ANIMATE::none);
    //Game game(ANIMATE::rotation);
    //Game game(ANIMATE::thickness);
    //Game game(ANIMATE::branch_angle);
    //Game game(ANIMATE::length);
    //Game game(ANIMATE::l_r_b);
    Game game(ANIMATE::all);
}

Last updated