Pythagoras Tree

The construction of the Pythagoras tree begins with a squarearrow-up-right. 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 recursivelyarrow-up-right to the two smaller squares,ad infinitum. The illustration below shows the first few iterationsarrow-up-right in the construction process.[2]arrow-up-right[3]arrow-up-right

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);
}

Results

Last updated

Was this helpful?