Program Listing for File lightninganimation.cpp

Return to documentation for file (src/animation/lightninganimation.cpp)

#include "../utility.h"
#include "lightninganimation.h"

namespace PixelMaestro {
    LightningAnimation::LightningAnimation(Section& section) : Animation(section) {
        type_ = AnimationType::Lightning;
        map();
    }

    void LightningAnimation::map() {
        // Clear the grid
        for (uint16_t x = 0; x < section_.get_dimensions().x; x++) {
            for (uint16_t y = 0; y < section_.get_dimensions().y; y++) {
                set_map_color_index(x, y, 255);
            }
        }

        /*
         * Assume horizontal movement. Choose a random point on the y-axis starting at 0, then move from left to right.
         * "102" is the maximum length of the grid that a single fork can cover (102 equates to 40%).
         */
        Point start = {0, 0};
        for (uint8_t bolt = 0; bolt < num_bolts_; bolt++) {
            if (orientation_ == Orientation::Vertical || orientation_ == Orientation::VerticalFlipped) {
                start.set((uint16_t)Utility::rand(section_.get_dimensions().x), 0);
                draw_bolt_vertical(bolt, &start, drift_, fork_chance_, 102);
            }
            else {
                start.set(0, (uint16_t)Utility::rand(section_.get_dimensions().y));
                draw_bolt_horizontal(bolt, &start, drift_, fork_chance_, 102);
            }
        }
    }

    void LightningAnimation::update() {
        map();
        update_cycle(0, palette_->get_num_colors());
    }

    void LightningAnimation::draw_bolt_horizontal(uint8_t bolt_num, Point* start, int8_t drift, uint8_t fork_chance, uint8_t max_fork_length) {
        Point cursor = {start->x, start->y};

        /*
         * Calculate the maximum length of the bolt.
         * For the main bolt, we set the length equal to the length of the grid.
         * For forks, we cap the distance at a percentage of the grid length (calculated using max_fork_length).
         */
        uint16_t length;
        if (cursor.x == 0) {
            length = section_.get_dimensions().x;
        }
        else {
            if ((cursor.x + (section_.get_dimensions().x * (max_fork_length / (float)100))) > section_.get_dimensions().x) {
                length = section_.get_dimensions().x - cursor.x;
            }
            else {
                length = cursor.x + (section_.get_dimensions().x * (max_fork_length / (float)100));
            }
        }

        /*
         * For each step along the grid, generate a random number and compare it to the drift threshold.
         * This determines the direction that the bolt moves in.
         */
        for (uint16_t x = cursor.x; x < length; x++) {
            int8_t drift_roll = Utility::rand(UINT8_MAX) - INT8_MAX;
            if (drift_roll > drift) {
                if (cursor.y + 1 < section_.get_dimensions().y) {
                    cursor.y += 1;
                }
            }
            else {
                if (cursor.y - 1 >= 0) {
                    cursor.y -= 1;
                }
            }
            cursor.x++;

            set_map_color_index(x, cursor.y, cycle_index_ + bolt_num);

            // Check to see if we should fork the bolt.
            if (x < (uint16_t)section_.get_dimensions().x) {
                uint8_t fork_roll = Utility::rand(UINT8_MAX);
                if (fork_roll < fork_chance) {

                    /*
                     * If we forked...
                     *  1) Change the drift so that it sends the bolt away from the parent. We do this by increasing the drift threshold to 85% in the opposite direciton, making it extremely likely that the bolt will move that way.
                     *  2) Reduce the chance of another fork by 50%.
                     *  3) Reduce the length of the next fork by a random amount. We don't want forks longer than their parents.
                     */
                    if (drift_roll < drift) {
                        draw_bolt_horizontal(bolt_num, &cursor, 90, fork_chance / 2, Utility::rand(max_fork_length));
                    }
                    else {
                        // Invert threshold
                        draw_bolt_horizontal(bolt_num, &cursor, -90, fork_chance / 2, Utility::rand(max_fork_length));
                    }
                }
            }
        }
    }

    void LightningAnimation::draw_bolt_vertical(uint8_t bolt_num, Point* start, int8_t drift, uint8_t fork_chance, uint8_t max_fork_length) {
        Point cursor = {start->x, start->y};

        uint32_t length;
        if (cursor.y == 0) {
            length = section_.get_dimensions().y;
        }
        else {
            if (cursor.y + ((section_.get_dimensions().y * (max_fork_length / (float)100))) > section_.get_dimensions().y) {
                length = section_.get_dimensions().y - cursor.y;
            }
            else {
                length = cursor.y + (section_.get_dimensions().y * (max_fork_length / (float)100));
            }
        }

        for (uint16_t y = cursor.y; y < length; y++) {
            int8_t drift_roll = Utility::rand(UINT8_MAX) - INT8_MAX;
            if (drift_roll < drift) {   // Intentionally inverted from draw_bolt_horizontal.
                if (cursor.x + 1 < section_.get_dimensions().x) {
                    cursor.x += 1;
                }
            }
            else {
                if (cursor.x - 1 >= 0) {
                    cursor.x -= 1;
                }
            }
            cursor.y++;

            set_map_color_index(cursor.x, y, cycle_index_ + bolt_num);

            if (y < (uint16_t)section_.get_dimensions().y) {
                uint8_t fork_roll = Utility::rand(UINT8_MAX);
                if (fork_roll < fork_chance) {
                    if (drift_roll > drift) {
                        draw_bolt_vertical(bolt_num, &cursor, 90, fork_chance / 2, Utility::rand(max_fork_length));
                    }
                    else {
                        draw_bolt_vertical(bolt_num, &cursor, -90, fork_chance / 2, Utility::rand(max_fork_length));
                    }
                }
            }
        }
    }

    uint8_t LightningAnimation::get_bolt_count() const {
        return num_bolts_;
    }

    uint8_t LightningAnimation::get_fork_chance() const {
        return fork_chance_;
    }

    int8_t LightningAnimation::get_drift() const {
        return drift_;
    }

    void LightningAnimation::set_bolt_count(uint8_t bolt_count) {
        this->num_bolts_ = bolt_count;
    }

    void LightningAnimation::set_fork_chance(uint8_t fork_chance) {
        this->fork_chance_ = fork_chance;
    }

    void LightningAnimation::set_drift(int8_t drift) {
        this->drift_ = drift;
    }
}