Program Listing for File section.cpp

Return to documentation for file (src/core/section.cpp)

/*
    Section.cpp - Class for controlling multiple Pixels.
    Requires Pixel and Colors classes.
*/

#include "../animation/blinkanimation.h"
#include "../animation/cycleanimation.h"
#include "../animation/fireanimation.h"
#include "../animation/lightninganimation.h"
#include "../animation/mandelbrotanimation.h"
#include "../animation/plasmaanimation.h"
#include "../animation/radialanimation.h"
#include "../animation/randomanimation.h"
#include "../animation/solidanimation.h"
#include "../animation/sparkleanimation.h"
#include "../animation/vuanimation.h"
#include "../animation/waveanimation.h"
#include "../canvas/canvas.h"
#include "../utility.h"
#include "colors.h"
#include "pixel.h"
#include "section.h"

namespace PixelMaestro {

    Section::Section() : Section(0, 0) { }

    Section::Section(uint16_t x, uint16_t y, Section* parent) {
        set_dimensions(x, y);
        parent_section_ = parent;
    }

    Animation* Section::get_animation() const {
        return animation_;
    }

    uint8_t Section::get_brightness() const {
        return brightness_ * 255;
    }

    Canvas* Section::get_canvas() const {
        return canvas_;
    }

    Point& Section::get_dimensions() const {
        return const_cast<Point&>(dimensions_);
    }

    Maestro& Section::get_maestro() const {
        return *maestro_;
    }

    Section::Layer* Section::get_layer() const {
        return layer_;
    }

    Section::Mirror* Section::get_mirror() const {
        return mirror_;
    }

    Point& Section::get_offset() {
        return offset_;
    }

    Section* Section::get_parent_section() const {
        return parent_section_;
    }

    Pixel& Section::get_pixel(uint16_t x, uint16_t y) const {
        return pixels_[dimensions_.get_inline_index(x, y)];
    }

    Colors::RGB Section::get_pixel_color(uint16_t x, uint16_t y, Colors::RGB* base_color) {

        // Adjust coordinates based on offset
        uint16_t offset_x = x + offset_.x;
        uint16_t offset_y = y + offset_.y;

        /*
         * If enabled, use the remainder to wrap the Section around the grid.
         * Otherwise, return black for any out of bound Pixels.
         */
        if (wrap_) {
            offset_x %= dimensions_.x;
            offset_y %= dimensions_.y;
        }

        if (!dimensions_.in_bounds(offset_x, offset_y)) return Colors::RGB(0, 0, 0);

        // If mirroring is enabled, mirror across the axes center.
        if (mirror_ != nullptr) {
            if (mirror_->x) {
                if (offset_y > mirror_->mid_y) {
                    offset_y = mirror_->mid_y - (offset_y - mirror_->mid_y);
                }
            }

            if (mirror_->y) {
                if (offset_x > mirror_->mid_x) {
                    offset_x = mirror_->mid_x - (offset_x - mirror_->mid_x);
                }
            }
        }

        Colors::RGB final_color = pixels_[dimensions_.get_inline_index(offset_x, offset_y)].get_color();

        // If we have a Canvas and it returns a color, use that color instead of the Pixel's actual color.
        if (canvas_ != nullptr) {
            Colors::RGB* canvas_color = canvas_->get_pixel_color(offset_x, offset_y);
            if (canvas_color != nullptr) {
                final_color = *canvas_color;
            }
        }

        /*
         * If this Section a Layer, combine this Pixel's color with its parent Pixel's color.
         * This is done recursively, with each Layer building on top of its parent Section.
         * We check both the parent Section and Layer to ensure colors are layered correctly.
         */
        if (parent_section_ != nullptr && base_color != nullptr) {
            final_color = Colors::mix_colors(
            *base_color,
            final_color,
            parent_section_->get_layer()->mix_mode,
            parent_section_->get_layer()->alpha);
        }

        // If this Section has a Layer, merge in the Layer's color output.
        if (layer_ != nullptr) {
            final_color = layer_->section->get_pixel_color(x, y, &final_color);
        }

        // Return the final color after applying brightness
        return final_color * brightness_;
    }

    Section::Scroll* Section::get_scroll() const {
        return scroll_;
    }

    void Section::remove_animation(bool clear_pixels) {
        delete animation_;
        animation_ = nullptr;

        // Reset Pixel colors
        if (clear_pixels) {
            for (uint32_t pixel = 0; pixel < dimensions_.size(); pixel++) {
                pixels_[pixel].clear();
            }
        }
    }

    bool Section::get_wrap() const {
        return wrap_;
    }

    void Section::remove_canvas() {
        delete canvas_;
        canvas_ = nullptr;
    }

    void Section::remove_layer() {
        delete layer_;
        layer_ = nullptr;
    }

    void Section::remove_scroll() {
        delete scroll_;
        scroll_ = nullptr;
    }

    Animation& Section::set_animation(AnimationType animation_type, bool preserve_settings) {
        Animation* new_animation = nullptr;
        switch(animation_type) {
            case AnimationType::Blink:
                new_animation = new BlinkAnimation(*this);
                break;
            case AnimationType::Cycle:
                new_animation = new CycleAnimation(*this);
                break;
            case AnimationType::Fire:
                new_animation = new FireAnimation(*this);
                break;
            case AnimationType::Lightning:
                new_animation = new LightningAnimation(*this);
                break;
            case AnimationType::Mandelbrot:
                new_animation = new MandelbrotAnimation(*this);
                break;
            case AnimationType::Plasma:
                new_animation = new PlasmaAnimation(*this);
                break;
            case AnimationType::Radial:
                new_animation = new RadialAnimation(*this);
                break;
            case AnimationType::Random:
                new_animation = new RandomAnimation(*this);
                break;
            case AnimationType::Solid:
                new_animation = new SolidAnimation(*this);
                break;
            case AnimationType::Sparkle:
                new_animation = new SparkleAnimation(*this);
                break;
            //case AnimationType::VUMeter:
                //new_animation = new VUAnimation(*this);
                //break;
            case AnimationType::Wave:
                new_animation = new WaveAnimation(*this);
                break;
        }

        /*
         * Check for an existing Animation.
         * If one exists and preserve_settings is true, copy the old Animation's settings to the new Animation.
         */
        if (this->animation_ != nullptr) {
            if (preserve_settings) {
                new_animation->set_center(this->animation_->get_center().x, this->animation_->get_center().y);
                new_animation->set_palette(*this->animation_->get_palette());
                new_animation->set_cycle_index(this->animation_->get_cycle_index());
                new_animation->set_fade(this->animation_->get_fade());
                new_animation->set_orientation(this->animation_->get_orientation());
                new_animation->set_reverse(this->animation_->get_reverse());

                if (this->animation_->get_timer() != nullptr) {
                    new_animation->set_timer(this->animation_->get_timer()->get_interval(), this->animation_->get_timer()->get_delay());
                }
            }
            remove_animation(false);
        }

        this->animation_ = new_animation;
        return *animation_;
    }

    void Section::set_brightness(uint8_t brightness) {
        this->brightness_ = static_cast<float>(brightness / static_cast<float>(255));
    }

    Canvas& Section::set_canvas(uint16_t num_frames) {
        remove_canvas();
        canvas_ = new Canvas(*this, num_frames);
        return *canvas_;
    }

    void Section::set_dimensions(uint16_t x, uint16_t y) {
        dimensions_.x = x;
        dimensions_.y = y;

        // Resize the Pixel grid
        delete [] pixels_;
        pixels_ = new Pixel[dimensions_.size()];

        // Resize the Animation
        if (animation_ != nullptr) {
            animation_->rebuild_map();
        }

        // Resize the Canvas
        if (canvas_ != nullptr) {
            canvas_->initialize();
        }

        // Resize the Layer
        if (layer_ != nullptr) {
            layer_->section->set_dimensions(dimensions_.x, dimensions_.y);
        }

        // If mirroring is enabled, recalculate the midway points
        if (mirror_ != nullptr) {
            mirror_->set(mirror_->x, mirror_->y, this->dimensions_);
        }
    }

    Section::Layer& Section::set_layer(Colors::MixMode mix_mode, uint8_t alpha) {
        if (layer_ == nullptr) {
            layer_ = new Layer(*this, mix_mode, alpha);
        }
        else {
            layer_->mix_mode = mix_mode;
            layer_->alpha = alpha;
        }

        return *layer_;
    }

    void Section::set_maestro(Maestro& maestro) {
        this->maestro_ = &maestro;
    }

    Section::Mirror* Section::set_mirror(bool x, bool y) {
        if (x == false && y == false) {
            delete mirror_;
            mirror_ = nullptr;
        }
        else {
            if (mirror_ == nullptr) {
                mirror_ = new Mirror();
            }

            mirror_->set(x, y, this->dimensions_);
        }

        return mirror_;
    }

    Point& Section::set_offset(uint16_t x, uint16_t y) {
        offset_.set(x, y);
        return offset_;
    }

    void Section::set_pixel_color(uint16_t x, uint16_t y, const Colors::RGB& color) {
        // Only continue if the Pixel is within the bounds of the array.
        if (dimensions_.in_bounds(x, y)) {
            pixels_[dimensions_.get_inline_index(x, y)].set_next_color(color, step_count_);
        }
    }

    Section::Scroll& Section::set_scroll(uint16_t x, uint16_t y, bool reverse_x, bool reverse_y) {
        if (scroll_ == nullptr) {
            scroll_ = new Scroll();
        }

        scroll_->set(maestro_->get_timer().get_interval(), &dimensions_, x, y, reverse_x, reverse_y);

        return *scroll_;
    }

    void Section::set_step_count(uint8_t step_count) {
        this->step_count_ = step_count;
    }

    void Section::set_wrap(bool wrap) {
        this->wrap_ = wrap;
    }

    void Section::sync(const uint32_t &new_time) {
        if (layer_ != nullptr) {
            layer_->section->sync(new_time);
        }
        if (animation_ != nullptr) {
            animation_->get_timer()->set_last_time(new_time);
        }
        if (canvas_ != nullptr && canvas_->get_frame_timer() != nullptr) {
            canvas_->get_frame_timer()->set_last_time(new_time);
        }
        if (scroll_ != nullptr) {
            if (scroll_->timer_x != nullptr) scroll_->timer_x->set_last_time(new_time);
            if (scroll_->timer_y != nullptr) scroll_->timer_y->set_last_time(new_time);
        }
    }

    void Section::update(const uint32_t& current_time) {

        /*
         * Run the update.
         * Since the Animation directly modifies Pixel color, it must be run first.
         */
        if (animation_ != nullptr) {
            animation_->update(current_time);
        }

        if (canvas_ != nullptr) {
            canvas_->update(current_time);
        }

#ifndef PIXEL_DISABLE_FADING
        if (step_count_ > 0) {
            for (uint32_t pixel = 0; pixel < dimensions_.size(); pixel++) {
                pixels_[pixel].update(step_count_ == 1);
            }

            step_count_--;
        }
#endif

        if (scroll_ != nullptr) {
            update_scroll(current_time);
        }

        if (layer_ != nullptr) {
            layer_->section->update(current_time);
        }
    }

    void Section::update_scroll(const uint32_t &current_time) {

        // Scroll x axis
        if (scroll_->timer_x != nullptr || scroll_->step_x != 0) {
            uint16_t x_step = 0;
            // If a timer is used (scrolling < 1 pixel per update), determine whether it's time to scroll.
            if (scroll_->timer_x != nullptr) {
                if (scroll_->timer_x->update(current_time)) {
                    x_step = 1;
                }
            }
            // If a timer is not used (scrolling > 1 pixel per update), apply the scroll amount directly to the offset.
            else if (scroll_->step_x > 0) {
                x_step = scroll_->step_x;
            }

            // Check to see if we need to reset the offset, but only when wrapping is enabled
            if (wrap_) {
                if (!scroll_->reverse_x && offset_.x >= dimensions_.x) {
                    offset_.x = 0;
                }
                else if (scroll_->reverse_x && offset_.x == 0) {
                    offset_.x = dimensions_.x;
                }
            }

            // Finally, apply the movement
            if (!scroll_->reverse_x) {
                offset_.x += x_step;
            }
            else {
                offset_.x -= x_step;
            }
        }


        // Repeat for y axis
        if (scroll_->timer_y != nullptr || scroll_->step_y != 0) {
            uint16_t y_step = 0;
            if (scroll_->timer_y != nullptr) {
                if (scroll_->timer_y->update(current_time)) {
                    y_step = 1;
                }
            }
            else if (scroll_->step_y > 0) {
                y_step = scroll_->step_y;
            }

            // Check to see if we need to reset the offset, but only when wrapping is enabled
            if (wrap_) {
                if (!scroll_->reverse_y && offset_.y >= dimensions_.y) {
                    offset_.y = 0;
                }
                else if (scroll_->reverse_y && offset_.y == 0) {
                    offset_.y = dimensions_.y;
                }
            }

            // Finally, apply the movement
            if (!scroll_->reverse_y) {
                offset_.y += y_step;
            }
            else {
                offset_.y -= y_step;
            }
        }
    }

    Section::~Section() {
        remove_animation(false);
        remove_canvas();
        remove_layer();
        remove_scroll();

        delete mirror_;
        delete [] pixels_;
    }
}