<commit_before>#include <termbox.h>
#include <cstdint>

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <regex>

class CocoClient {
  std::vector<std::string> inputs;
  std::vector<std::string> render_items;
  std::vector<std::string> filtered;

  std::string query;

  long selected;
  long cursor;

  size_t offset;
  size_t query_offset;

  bool flag_selected = false;

public:
  CocoClient()
  {
    for (std::string line; std::getline(std::cin, line);) {
      inputs.push_back(line);
    }

    if (int error = tb_init()) {
      std::cerr << "tb_init() failed with error code " << error << std::endl;
      std::exit(error);
    }

    apply_filter();
  }

  ~CocoClient()
  {
    try {
      tb_shutdown();

      if (flag_selected) {
        std::cout << render_items[offset + selected] << std::endl;
      }
    }
    catch (...) {
    }
  }

  bool poll_event()
  {
    update_items();

    tb_event ev;
    tb_poll_event(&ev);

    if (ev.key == TB_KEY_ENTER) {
      flag_selected = true;
      return true;
    }
    else if (ev.key == TB_KEY_ESC) {
      return true;
    }
    else if (ev.key == TB_KEY_BACKSPACE) {
      if (!query.empty()) {
        query.pop_back();
        apply_filter();
      }
    }
    else if (ev.key == TB_KEY_ARROW_UP) {
      if (selected > -1) {
        selected -= 1;
      }
      if (cursor > 0) {
        cursor -= 1;
      }

      if (selected == -1) {
        selected += 1;
        if (offset > 0) {
          offset -= 1;

          render_items.resize(filtered.size() - offset);
          std::copy(filtered.begin() + offset, filtered.end(), render_items.begin());
        }
      }
    }
    else if (ev.key == TB_KEY_ARROW_DOWN) {
      if (cursor < render_items.size() - 1) {
        cursor += 1;
      }
      if ((render_items.size() < tb_height() - 1) && (selected < render_items.size() - 1)) {
        selected += 1;
      }
      else if ((render_items.size() > tb_height() - 1) && (selected < tb_height() - 1)) {
        selected += 1;
      }

      if (selected == tb_height() - 1) {
        selected -= 1;
        if (offset < filtered.size() - 1) {
          offset += 1;

          render_items.resize(filtered.size() - offset);
          std::copy(filtered.begin() + offset, filtered.end(), render_items.begin());
        }
      }
    }
    else {
      if ((ev.ch != 0) || (ev.key == 32 && ev.ch == 0)) {
        query.push_back(ev.ch);
        apply_filter();
      }
    }
    return false;
  }

private:
  void apply_filter()
  {
    filtered = filter();
    render_items = filtered;
    selected = 0;
    cursor = 0;
    offset = 0;
  }

  std::vector<std::string> filter() const
  {
    std::regex re(query);

    if (query.empty()) {
      return inputs;
    }
    else {
      std::vector<std::string> ret;
      for (auto&& elem : inputs) {
        if (std::regex_search(elem, re)) {
          ret.push_back(elem);
        }
      }
      return ret;
    }
  }

  void update_items() const
  {
    tb_clear();

    print_query();
    for (int y = 0; y < (int)render_items.size(); ++y) {
      print_line(render_items[y], y, y == selected);
    }

    tb_present();
  }

  void print_query() const
  {
    std::string const query_header = "QUERY> ";
    std::string query_str = query_header + query;

    for (int x = 0; x < tb_width(); ++x) {
      auto const c = static_cast<uint32_t>(x < query_str.length() ? query_str[x] : ' ');
      if (x == query_str.length()) {
        tb_change_cell(x, 0, c, TB_WHITE, TB_WHITE);
      }
      else {
        tb_change_cell(x, 0, c, TB_WHITE, TB_BLACK);
      }
    }
  }

  void print_line(std::string line, unsigned y, bool selected) const
  {
    constexpr unsigned y_offset = 1;

    for (int x = 0; x < tb_width(); ++x) {
      auto const c = static_cast<uint32_t>(x < line.length() ? line[x] : ' ');
      if (selected) {
        tb_change_cell(x, y + y_offset, c, TB_RED, TB_WHITE);
      }
      else {
        tb_change_cell(x, y + y_offset, c, TB_WHITE, TB_BLACK);
      }
    }
  }
};

int main()
{
  CocoClient cli{};
  while (!cli.poll_event())
    ;
  return 0;
}
<commit_msg>additional bs-key<commit_after>#include <termbox.h>
#include <cstdint>

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <regex>

class CocoClient {
  std::vector<std::string> inputs;
  std::vector<std::string> render_items;
  std::vector<std::string> filtered;

  std::string query;

  long selected;
  long cursor;

  size_t offset;
  size_t query_offset;

  bool flag_selected = false;

public:
  CocoClient()
  {
    for (std::string line; std::getline(std::cin, line);) {
      inputs.push_back(line);
    }

    if (int error = tb_init()) {
      std::cerr << "tb_init() failed with error code " << error << std::endl;
      std::exit(error);
    }

    apply_filter();
  }

  ~CocoClient()
  {
    try {
      tb_shutdown();

      if (flag_selected) {
        std::cout << render_items[offset + selected] << std::endl;
      }
    }
    catch (...) {
    }
  }

  bool poll_event()
  {
    update_items();

    tb_event ev;
    tb_poll_event(&ev);

    if (ev.key == TB_KEY_ENTER) {
      flag_selected = true;
      return true;
    }
    else if (ev.key == TB_KEY_ESC) {
      return true;
    }
    else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
      if (!query.empty()) {
        query.pop_back();
        apply_filter();
      }
    }
    else if (ev.key == TB_KEY_ARROW_UP) {
      if (selected > -1) {
        selected -= 1;
      }
      if (cursor > 0) {
        cursor -= 1;
      }

      if (selected == -1) {
        selected += 1;
        if (offset > 0) {
          offset -= 1;

          render_items.resize(filtered.size() - offset);
          std::copy(filtered.begin() + offset, filtered.end(), render_items.begin());
        }
      }
    }
    else if (ev.key == TB_KEY_ARROW_DOWN) {
      if (cursor < render_items.size() - 1) {
        cursor += 1;
      }
      if ((render_items.size() < tb_height() - 1) && (selected < render_items.size() - 1)) {
        selected += 1;
      }
      else if ((render_items.size() > tb_height() - 1) && (selected < tb_height() - 1)) {
        selected += 1;
      }

      if (selected == tb_height() - 1) {
        selected -= 1;
        if (offset < filtered.size() - 1) {
          offset += 1;

          render_items.resize(filtered.size() - offset);
          std::copy(filtered.begin() + offset, filtered.end(), render_items.begin());
        }
      }
    }
    else {
      if ((ev.ch != 0) || (ev.key == 32 && ev.ch == 0)) {
        query.push_back(ev.ch);
        apply_filter();
      }
    }
    return false;
  }

private:
  void apply_filter()
  {
    filtered = filter();
    render_items = filtered;
    selected = 0;
    cursor = 0;
    offset = 0;
  }

  std::vector<std::string> filter() const
  {
    std::regex re(query);

    if (query.empty()) {
      return inputs;
    }
    else {
      std::vector<std::string> ret;
      for (auto&& elem : inputs) {
        if (std::regex_search(elem, re)) {
          ret.push_back(elem);
        }
      }
      return ret;
    }
  }

  void update_items() const
  {
    tb_clear();

    print_query();
    for (int y = 0; y < (int)render_items.size(); ++y) {
      print_line(render_items[y], y, y == selected);
    }

    tb_present();
  }

  void print_query() const
  {
    std::string const query_header = "QUERY> ";
    std::string query_str = query_header + query;

    for (int x = 0; x < tb_width(); ++x) {
      auto const c = static_cast<uint32_t>(x < query_str.length() ? query_str[x] : ' ');
      if (x == query_str.length()) {
        tb_change_cell(x, 0, c, TB_WHITE, TB_WHITE);
      }
      else {
        tb_change_cell(x, 0, c, TB_WHITE, TB_BLACK);
      }
    }
  }

  void print_line(std::string line, unsigned y, bool selected) const
  {
    constexpr unsigned y_offset = 1;

    for (int x = 0; x < tb_width(); ++x) {
      auto const c = static_cast<uint32_t>(x < line.length() ? line[x] : ' ');
      if (selected) {
        tb_change_cell(x, y + y_offset, c, TB_RED, TB_WHITE);
      }
      else {
        tb_change_cell(x, y + y_offset, c, TB_WHITE, TB_BLACK);
      }
    }
  }
};

int main()
{
  CocoClient cli{};
  while (!cli.poll_event())
    ;
  return 0;
}
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by Daniel Eichhorn
 * Copyright (c) 2016 by Fabrice Weinberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "OLEDDisplayUi.h"

OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) {
  this->display = display;
}

void OLEDDisplayUi::init() {
  this->display->init();
}

void OLEDDisplayUi::setTargetFPS(uint8_t fps){
  float oldInterval = this->updateInterval;
  this->updateInterval = ((float) 1.0 / (float) fps) * 1000;

  // Calculate new ticksPerFrame
  float changeRatio = oldInterval / (float) this->updateInterval;
  this->ticksPerFrame *= changeRatio;
  this->ticksPerTransition *= changeRatio;
}

// -/------ Automatic controll ------\-

void OLEDDisplayUi::enableAutoTransition(){
  this->autoTransition = true;
}
void OLEDDisplayUi::disableAutoTransition(){
  this->autoTransition = false;
}
void OLEDDisplayUi::setAutoTransitionForwards(){
  this->state.frameTransitionDirection = 1;
  this->lastTransitionDirection = 1;
}
void OLEDDisplayUi::setAutoTransitionBackwards(){
  this->state.frameTransitionDirection = -1;
  this->lastTransitionDirection = -1;
}
void OLEDDisplayUi::setTimePerFrame(uint16_t time){
  this->ticksPerFrame = (int) ( (float) time / (float) updateInterval);
}
void OLEDDisplayUi::setTimePerTransition(uint16_t time){
  this->ticksPerTransition = (int) ( (float) time / (float) updateInterval);
}

// -/------ Customize indicator position and style -------\-
void OLEDDisplayUi::enableIndicator(){
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::disableIndicator(){
  this->state.isIndicatorDrawen = false;
}

void OLEDDisplayUi::enableAllIndicators(){
  this->shouldDrawIndicators = true;
}

void OLEDDisplayUi::disableAllIndicators(){
  this->shouldDrawIndicators = false;
}

void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) {
  this->indicatorPosition = pos;
}
void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) {
  this->indicatorDirection = dir;
}
void OLEDDisplayUi::setActiveSymbol(const char* symbol) {
  this->activeSymbol = symbol;
}
void OLEDDisplayUi::setInactiveSymbol(const char* symbol) {
  this->inactiveSymbol = symbol;
}

// -/----- Frame settings -----\-
void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) {
  this->frameAnimationDirection = dir;
}
void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
  this->frameFunctions = frameFunctions;
  this->frameCount     = frameCount;
  this->resetState();
}

// -/----- Overlays ------\-
void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
  this->overlayFunctions = overlayFunctions;
  this->overlayCount     = overlayCount;
}

// -/----- Loading Process -----\-

void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) {
  this->loadingDrawFunction = loadingDrawFunction;
}

void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
  uint8_t progress = 0;
  uint8_t increment = 100 / stagesCount;

  for (uint8_t i = 0; i < stagesCount; i++) {
    display->clear();
    this->loadingDrawFunction(this->display, &stages[i], progress);
    display->display();

    stages[i].callback();

    progress += increment;
    yield();
  }

  display->clear();
  this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
  display->display();

  delay(150);
}

// -/----- Manuel control -----\-
void OLEDDisplayUi::nextFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = 1;
  }
}
void OLEDDisplayUi::previousFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = -1;
  }
}

void OLEDDisplayUi::switchToFrame(uint8_t frame) {
  if (frame >= this->frameCount) return;
  this->state.ticksSinceLastStateSwitch = 0;
  if (frame == this->state.currentFrame) return;
  this->state.frameState = FIXED;
  this->state.currentFrame = frame;
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::transitionToFrame(uint8_t frame) {
  if (frame >= this->frameCount) return;
  this->state.ticksSinceLastStateSwitch = 0;
  if (frame == this->state.currentFrame) return;
  this->nextFrameNumber = frame;
  this->lastTransitionDirection = this->state.frameTransitionDirection;
  this->state.manuelControll = true;
  this->state.frameState = IN_TRANSITION;
  this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1;
}

// -/----- State information -----\-
OLEDDisplayUiState* OLEDDisplayUi::getUiState(){
  return &this->state;
}

int8_t OLEDDisplayUi::update(){
  long frameStart = millis();
  int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate);
  if ( timeBudget <= 0) {
    // Implement frame skipping to ensure time budget is keept
    if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);

    this->state.lastUpdate = frameStart;
    this->tick();
  }
  return this->updateInterval - (millis() - frameStart);
}

void OLEDDisplayUi::tick() {
  this->state.ticksSinceLastStateSwitch++;

  switch (this->state.frameState) {
    case IN_TRANSITION:
        if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
          this->state.frameState = FIXED;
          this->state.currentFrame = getNextFrameNumber();
          this->state.ticksSinceLastStateSwitch = 0;
          this->nextFrameNumber = -1;
        }
      break;
    case FIXED:
      // Revert manuelControll
      if (this->state.manuelControll) {
        this->state.frameTransitionDirection = this->lastTransitionDirection;
        this->state.manuelControll = false;
      }
      if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
          if (this->autoTransition){
            this->state.frameState = IN_TRANSITION;
          }
          this->state.ticksSinceLastStateSwitch = 0;
      }
      break;
  }

  this->display->clear();
  this->drawFrame();
  if (shouldDrawIndicators) {
    this->drawIndicator();
  }
  this->drawOverlays();
  this->display->display();
}

void OLEDDisplayUi::resetState() {
  this->state.lastUpdate = 0;
  this->state.ticksSinceLastStateSwitch = 0;
  this->state.frameState = FIXED;
  this->state.currentFrame = 0;
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::drawFrame(){
  switch (this->state.frameState){
     case IN_TRANSITION: {
       float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
       int16_t x, y, x1, y1;
       switch(this->frameAnimationDirection){
        case SLIDE_LEFT:
          x = -128 * progress;
          y = 0;
          x1 = x + 128;
          y1 = 0;
          break;
        case SLIDE_RIGHT:
          x = 128 * progress;
          y = 0;
          x1 = x - 128;
          y1 = 0;
          break;
        case SLIDE_UP:
          x = 0;
          y = -64 * progress;
          x1 = 0;
          y1 = y + 64;
          break;
        case SLIDE_DOWN:
          x = 0;
          y = 64 * progress;
          x1 = 0;
          y1 = y - 64;
          break;
       }

       // Invert animation if direction is reversed.
       int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
       x *= dir; y *= dir; x1 *= dir; y1 *= dir;

       bool drawenCurrentFrame;

       // Prope each frameFunction for the indicator Drawen state
       this->enableIndicator();
       (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
       drawenCurrentFrame = this->state.isIndicatorDrawen;

       this->enableIndicator();
       (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);

       // Build up the indicatorDrawState
       if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Drawen now but not next
         this->indicatorDrawState = 2;
       } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
         // Not drawen now but next
         this->indicatorDrawState = 1;
       } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Not drawen in both frames
         this->indicatorDrawState = 3;
       }

       // If the indicator isn't draw in the current frame
       // reflect it in state.isIndicatorDrawen
       if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;

       break;
     }
     case FIXED:
      // Always assume that the indicator is drawn!
      // And set indicatorDrawState to "not known yet"
      this->indicatorDrawState = 0;
      this->enableIndicator();
      (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
      break;
  }
}

void OLEDDisplayUi::drawIndicator() {

    // Only draw if the indicator is invisible
    // for both frames or
    // the indiactor is shown and we are IN_TRANSITION
    if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
      return;
    }

    uint8_t posOfHighlightFrame;
    float indicatorFadeProgress = 0;

    // if the indicator needs to be slided in we want to
    // highlight the next frame in the transition
    uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;

    // Calculate the frame that needs to be highlighted
    // based on the Direction the indiactor is drawn
    switch (this->indicatorDirection){
      case LEFT_RIGHT:
        posOfHighlightFrame = frameToHighlight;
        break;
      case RIGHT_LEFT:
        posOfHighlightFrame = this->frameCount - frameToHighlight;
        break;
    }

    switch (this->indicatorDrawState) {
      case 1: // Indicator was not drawn in this frame but will be in next
        // Slide IN
        indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
      case 2: // Indicator was drawn in this frame but not in next
        // Slide OUT
        indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
    }

    uint16_t frameStartPos = (12 * frameCount / 2);
    const char *image;
    uint16_t x,y;
    for (byte i = 0; i < this->frameCount; i++) {

      switch (this->indicatorPosition){
        case TOP:
          y = 0 - (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case BOTTOM:
          y = 56 + (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case RIGHT:
          x = 120 + (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 2 + 12 * i;
          break;
        case LEFT:
          x = 0 - (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 2 + 12 * i;
          break;
      }

      if (posOfHighlightFrame == i) {
         image = this->activeSymbol;
      } else {
         image = this->inactiveSymbol;
      }

      this->display->drawFastImage(x, y, 8, 8, image);
    }
}

void OLEDDisplayUi::drawOverlays() {
 for (uint8_t i=0;i<this->overlayCount;i++){
    (this->overlayFunctions[i])(this->display, &this->state);
 }
}

uint8_t OLEDDisplayUi::getNextFrameNumber(){
  if (this->nextFrameNumber != -1) return this->nextFrameNumber;
  return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount;
}
<commit_msg>change image data to be unsigned<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by Daniel Eichhorn
 * Copyright (c) 2016 by Fabrice Weinberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "OLEDDisplayUi.h"

OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) {
  this->display = display;
}

void OLEDDisplayUi::init() {
  this->display->init();
}

void OLEDDisplayUi::setTargetFPS(uint8_t fps){
  float oldInterval = this->updateInterval;
  this->updateInterval = ((float) 1.0 / (float) fps) * 1000;

  // Calculate new ticksPerFrame
  float changeRatio = oldInterval / (float) this->updateInterval;
  this->ticksPerFrame *= changeRatio;
  this->ticksPerTransition *= changeRatio;
}

// -/------ Automatic controll ------\-

void OLEDDisplayUi::enableAutoTransition(){
  this->autoTransition = true;
}
void OLEDDisplayUi::disableAutoTransition(){
  this->autoTransition = false;
}
void OLEDDisplayUi::setAutoTransitionForwards(){
  this->state.frameTransitionDirection = 1;
  this->lastTransitionDirection = 1;
}
void OLEDDisplayUi::setAutoTransitionBackwards(){
  this->state.frameTransitionDirection = -1;
  this->lastTransitionDirection = -1;
}
void OLEDDisplayUi::setTimePerFrame(uint16_t time){
  this->ticksPerFrame = (int) ( (float) time / (float) updateInterval);
}
void OLEDDisplayUi::setTimePerTransition(uint16_t time){
  this->ticksPerTransition = (int) ( (float) time / (float) updateInterval);
}

// -/------ Customize indicator position and style -------\-
void OLEDDisplayUi::enableIndicator(){
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::disableIndicator(){
  this->state.isIndicatorDrawen = false;
}

void OLEDDisplayUi::enableAllIndicators(){
  this->shouldDrawIndicators = true;
}

void OLEDDisplayUi::disableAllIndicators(){
  this->shouldDrawIndicators = false;
}

void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) {
  this->indicatorPosition = pos;
}
void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) {
  this->indicatorDirection = dir;
}
void OLEDDisplayUi::setActiveSymbol(const char* symbol) {
  this->activeSymbol = symbol;
}
void OLEDDisplayUi::setInactiveSymbol(const char* symbol) {
  this->inactiveSymbol = symbol;
}

// -/----- Frame settings -----\-
void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) {
  this->frameAnimationDirection = dir;
}
void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
  this->frameFunctions = frameFunctions;
  this->frameCount     = frameCount;
  this->resetState();
}

// -/----- Overlays ------\-
void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
  this->overlayFunctions = overlayFunctions;
  this->overlayCount     = overlayCount;
}

// -/----- Loading Process -----\-

void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) {
  this->loadingDrawFunction = loadingDrawFunction;
}

void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
  uint8_t progress = 0;
  uint8_t increment = 100 / stagesCount;

  for (uint8_t i = 0; i < stagesCount; i++) {
    display->clear();
    this->loadingDrawFunction(this->display, &stages[i], progress);
    display->display();

    stages[i].callback();

    progress += increment;
    yield();
  }

  display->clear();
  this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
  display->display();

  delay(150);
}

// -/----- Manuel control -----\-
void OLEDDisplayUi::nextFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = 1;
  }
}
void OLEDDisplayUi::previousFrame() {
  if (this->state.frameState != IN_TRANSITION) {
    this->state.manuelControll = true;
    this->state.frameState = IN_TRANSITION;
    this->state.ticksSinceLastStateSwitch = 0;
    this->lastTransitionDirection = this->state.frameTransitionDirection;
    this->state.frameTransitionDirection = -1;
  }
}

void OLEDDisplayUi::switchToFrame(uint8_t frame) {
  if (frame >= this->frameCount) return;
  this->state.ticksSinceLastStateSwitch = 0;
  if (frame == this->state.currentFrame) return;
  this->state.frameState = FIXED;
  this->state.currentFrame = frame;
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::transitionToFrame(uint8_t frame) {
  if (frame >= this->frameCount) return;
  this->state.ticksSinceLastStateSwitch = 0;
  if (frame == this->state.currentFrame) return;
  this->nextFrameNumber = frame;
  this->lastTransitionDirection = this->state.frameTransitionDirection;
  this->state.manuelControll = true;
  this->state.frameState = IN_TRANSITION;
  this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1;
}

// -/----- State information -----\-
OLEDDisplayUiState* OLEDDisplayUi::getUiState(){
  return &this->state;
}

int8_t OLEDDisplayUi::update(){
  long frameStart = millis();
  int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate);
  if ( timeBudget <= 0) {
    // Implement frame skipping to ensure time budget is keept
    if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);

    this->state.lastUpdate = frameStart;
    this->tick();
  }
  return this->updateInterval - (millis() - frameStart);
}

void OLEDDisplayUi::tick() {
  this->state.ticksSinceLastStateSwitch++;

  switch (this->state.frameState) {
    case IN_TRANSITION:
        if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
          this->state.frameState = FIXED;
          this->state.currentFrame = getNextFrameNumber();
          this->state.ticksSinceLastStateSwitch = 0;
          this->nextFrameNumber = -1;
        }
      break;
    case FIXED:
      // Revert manuelControll
      if (this->state.manuelControll) {
        this->state.frameTransitionDirection = this->lastTransitionDirection;
        this->state.manuelControll = false;
      }
      if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
          if (this->autoTransition){
            this->state.frameState = IN_TRANSITION;
          }
          this->state.ticksSinceLastStateSwitch = 0;
      }
      break;
  }

  this->display->clear();
  this->drawFrame();
  if (shouldDrawIndicators) {
    this->drawIndicator();
  }
  this->drawOverlays();
  this->display->display();
}

void OLEDDisplayUi::resetState() {
  this->state.lastUpdate = 0;
  this->state.ticksSinceLastStateSwitch = 0;
  this->state.frameState = FIXED;
  this->state.currentFrame = 0;
  this->state.isIndicatorDrawen = true;
}

void OLEDDisplayUi::drawFrame(){
  switch (this->state.frameState){
     case IN_TRANSITION: {
       float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
       int16_t x, y, x1, y1;
       switch(this->frameAnimationDirection){
        case SLIDE_LEFT:
          x = -128 * progress;
          y = 0;
          x1 = x + 128;
          y1 = 0;
          break;
        case SLIDE_RIGHT:
          x = 128 * progress;
          y = 0;
          x1 = x - 128;
          y1 = 0;
          break;
        case SLIDE_UP:
          x = 0;
          y = -64 * progress;
          x1 = 0;
          y1 = y + 64;
          break;
        case SLIDE_DOWN:
          x = 0;
          y = 64 * progress;
          x1 = 0;
          y1 = y - 64;
          break;
       }

       // Invert animation if direction is reversed.
       int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
       x *= dir; y *= dir; x1 *= dir; y1 *= dir;

       bool drawenCurrentFrame;

       // Prope each frameFunction for the indicator Drawen state
       this->enableIndicator();
       (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
       drawenCurrentFrame = this->state.isIndicatorDrawen;

       this->enableIndicator();
       (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);

       // Build up the indicatorDrawState
       if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Drawen now but not next
         this->indicatorDrawState = 2;
       } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
         // Not drawen now but next
         this->indicatorDrawState = 1;
       } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
         // Not drawen in both frames
         this->indicatorDrawState = 3;
       }

       // If the indicator isn't draw in the current frame
       // reflect it in state.isIndicatorDrawen
       if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;

       break;
     }
     case FIXED:
      // Always assume that the indicator is drawn!
      // And set indicatorDrawState to "not known yet"
      this->indicatorDrawState = 0;
      this->enableIndicator();
      (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
      break;
  }
}

void OLEDDisplayUi::drawIndicator() {

    // Only draw if the indicator is invisible
    // for both frames or
    // the indiactor is shown and we are IN_TRANSITION
    if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
      return;
    }

    uint8_t posOfHighlightFrame;
    float indicatorFadeProgress = 0;

    // if the indicator needs to be slided in we want to
    // highlight the next frame in the transition
    uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;

    // Calculate the frame that needs to be highlighted
    // based on the Direction the indiactor is drawn
    switch (this->indicatorDirection){
      case LEFT_RIGHT:
        posOfHighlightFrame = frameToHighlight;
        break;
      case RIGHT_LEFT:
        posOfHighlightFrame = this->frameCount - frameToHighlight;
        break;
    }

    switch (this->indicatorDrawState) {
      case 1: // Indicator was not drawn in this frame but will be in next
        // Slide IN
        indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
      case 2: // Indicator was drawn in this frame but not in next
        // Slide OUT
        indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
        break;
    }

    uint16_t frameStartPos = (12 * frameCount / 2);
    const unsigned char *image;
    uint16_t x,y;
    for (byte i = 0; i < this->frameCount; i++) {

      switch (this->indicatorPosition){
        case TOP:
          y = 0 - (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case BOTTOM:
          y = 56 + (8 * indicatorFadeProgress);
          x = 64 - frameStartPos + 12 * i;
          break;
        case RIGHT:
          x = 120 + (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 2 + 12 * i;
          break;
        case LEFT:
          x = 0 - (8 * indicatorFadeProgress);
          y = 32 - frameStartPos + 2 + 12 * i;
          break;
      }

      if (posOfHighlightFrame == i) {
         image = this->activeSymbol;
      } else {
         image = this->inactiveSymbol;
      }

      this->display->drawFastImage(x, y, 8, 8, image);
    }
}

void OLEDDisplayUi::drawOverlays() {
 for (uint8_t i=0;i<this->overlayCount;i++){
    (this->overlayFunctions[i])(this->display, &this->state);
 }
}

uint8_t OLEDDisplayUi::getNextFrameNumber(){
  if (this->nextFrameNumber != -1) return this->nextFrameNumber;
  return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount;
}
<|endoftext|>


<commit_before>#include "ImageChecker.h"

#include <QJsonArray>
#include <QJsonDocument>
#include <QProcess>
#include <QFile>
#include <QDir>

#include "../FKUtility/selectBestSizeset.h"
#include "../FKUtility/sizeString.h"

const int ImageChecker::SelfIndexRole=Qt::UserRole+1;
const int ImageChecker::ImageCropRole=Qt::UserRole+2;
const int ImageChecker::AutoSizeRole=Qt::UserRole+3;
const int ImageChecker::SourceSizesRole=Qt::UserRole+4;
const int ImageChecker::PackageSizeRole=Qt::UserRole+5;
const int ImageChecker::CustomSizeRole=Qt::UserRole+6;
const int ImageChecker::AutoSizeValueRole=Qt::UserRole+7;

class CustomImageItem:public QStandardItem{
    virtual QVariant data(int role = Qt::UserRole + 1) const override{
        if(role==Qt::DisplayRole){
            if(data(ImageChecker::AutoSizeRole).toBool()){
                return data(ImageChecker::AutoSizeValueRole);
            }else{
                return data(ImageChecker::CustomSizeRole);
            }
        }else if(role==ImageChecker::AutoSizeValueRole){
            QJsonArray sourseImageSizes=parent()->data(ImageChecker::SourceSizesRole).toJsonArray();
            QList<QSize> sourceSizes;
            for(auto s=sourseImageSizes.constBegin();s!=sourseImageSizes.constEnd();++s){
                sourceSizes.append(FKUtility::stringToSize((*s).toString()));
            }
            QSize bestSize=FKUtility::selectBestSizeset(sourceSizes,FKUtility::stringToSize(data(ImageChecker::PackageSizeRole).toString()));
            return FKUtility::sizeToString(bestSize);
        }
        return QStandardItem::data(role);
    }
public:
    CustomImageItem(const QString& text):QStandardItem(text){}
};

ImageChecker::ImageChecker(QObject *parent) : QObject(parent),_model(nullptr)
{
    _rebuildPackageProcess=new QProcess(this);
    connect(_rebuildPackageProcess,SIGNAL(finished(int)),SLOT(rebuildModel(int)));
    connect(_rebuildPackageProcess,SIGNAL(error(QProcess::ProcessError)),SLOT(refreshError()));
}

QString ImageChecker::packageFolder() const
{
    return _packageFolder;
}

void ImageChecker::setPackageFolder(const QString& arg)
{
    if(_packageFolder!=arg){
        _packageFolder=arg;
        _packageLoaded=false;
        emit packageFolderChanged();
        runManager();
    }
}

QStringList ImageChecker::sizes() const
{
    return _packageMap.value("sizes").toVariant().toStringList();
}

ImagesetModel* ImageChecker::model() const
{
    return _model;
}

bool ImageChecker::isRefreshing() const
{
    return _rebuildPackageProcess->state()!=QProcess::NotRunning;
}

void ImageChecker::refreshPackage(){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable save package, load package first");
        return;
    }
    applySettings();
    runManager();
}

void ImageChecker::rebuildModel(int returnCode)
{
    if(returnCode){
        emit packageManagerOutput(QString("Package manager error %1 has occured").arg(QString::number(returnCode)));
    }

    QString output(_rebuildPackageProcess->readAllStandardOutput());
    emit packageManagerOutput(output);

    if(_model)_model->deleteLater();

    _packageMap=readPackageMap();

    _model=new ImagesetModel(this);
    QHash<int,QByteArray> roleNames=_model->roleNames();
    roleNames[SelfIndexRole]="selfIndex";
    roleNames[ImageCropRole]="imageCrop";
    roleNames[AutoSizeRole]="autoSize";
    roleNames[SourceSizesRole]="sourceSizes";
    roleNames[CustomSizeRole]="customSize";
    roleNames[AutoSizeValueRole]="autoSizeValue";
    _model->setItemRoleNames(roleNames);

    QStringList targetImageSizes(sizes());
    qint32 sizeCount=targetImageSizes.count();
    QJsonArray images=_packageMap.value("images").toArray();

    for(auto img=images.constBegin();img!=images.constEnd();++img){
        QJsonObject image=img->toObject();
        QStandardItem* row=new QStandardItem(image.value("path").toString());
        row->setData(image.value("crop").toBool(),ImageCropRole);

        QJsonArray customImageSizes=image.value("usedSizes").toArray();
        QJsonArray sourseImageSizes=image.value("sourceSizes").toArray();
        row->setData(sourseImageSizes,SourceSizesRole);

        QList<QSize> sourceSizes;
        for(auto s=sourseImageSizes.constBegin();s!=sourseImageSizes.constEnd();++s){
            sourceSizes.append(FKUtility::stringToSize((*s).toString()));
        }
        for(qint32 i=0;i<sizeCount;++i){
            QString imageSize=customImageSizes.at(i).toString();
            QStandardItem* imageSizeItem=new CustomImageItem(imageSize);
            row->appendRow(imageSizeItem);
            imageSizeItem->setData(targetImageSizes.at(i),PackageSizeRole);
            imageSizeItem->setData(imageSize.isEmpty(),AutoSizeRole);
            if(imageSize.isEmpty())imageSize=imageSizeItem->data(Qt::DisplayRole).toString();
            imageSizeItem->setData(imageSize,CustomSizeRole);
        }
        _model->appendRow(row);
        row->setData(row->index(),SelfIndexRole);
    }

    emit sizesChanged();
    emit modelChanged();
    emit isRefreshingChanged();
}

void ImageChecker::setPackageUrl(const QUrl url){
    QDir dir;
    setPackageFolder(dir.relativeFilePath(url.toLocalFile()));
}

void ImageChecker::addSizeset(const QString size){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable add sizeset, load package first");
        return;
    }
    applySettings();
    runManager(QStringList(size));
}

void ImageChecker::removeSizeset(const QString size){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable remove sizeset, load package first");
        return;
    }
    applySettings();
    runManager(QStringList(),QStringList(size));
}

void ImageChecker::spawnPackage(const QUrl url){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable spawn package, load package first");
        return;
    }
    runGenerator(url.toLocalFile(),QStringList("--add"));
}

void ImageChecker::applySettings(){
    qint32 rows=_model->rowCount();
    QJsonArray images;
    for(qint32 r=0;r<rows;++r){
        QStandardItem* item=_model->item(r);
        QJsonObject image;
        image["path"]=item->data(Qt::DisplayRole).toString();
        if(item->data(ImageCropRole).toBool()){
            image["crop"]=true;
        }
        image["sourceSizes"]=item->data(SourceSizesRole).toJsonArray();
        QJsonArray customImageSizes;
        qint32 usedSizesCount=item->rowCount();
        for(qint32 s=0;s<usedSizesCount;++s){
            customImageSizes.append(item->child(s)->data(AutoSizeRole).toBool() ?
                                        "" :
                                        item->child(s)->data(Qt::DisplayRole).toString());
        }
        image["usedSizes"]=customImageSizes;
        images.append(image);
    }
    _packageMap["images"]=images;

    writePackageMap();
}

void ImageChecker::refreshError(){
    QString output=QString("Refresh error: %1").arg(QString::number(_rebuildPackageProcess->error()));
    emit packageManagerOutput(output);

    rebuildModel(-1);
}

void ImageChecker::runManager(const QStringList& addSizes, const QStringList& removeSizes){
    if(_packageFolder.isEmpty()){
        emit packageManagerOutput("Unable run package manager, select package first");
        return;
    }
    if(!isRefreshing()){
        QString platformSuffix;
#ifdef Q_OS_WIN32
        platformSuffix=".exe";
#endif
        QStringList arguments(_packageFolder);
        foreach(QString size,addSizes){
            arguments.append("-a");
            arguments.append(size);
        }
        foreach(QString size,removeSizes){
            arguments.append("-r");
            arguments.append(size);
        }
        _rebuildPackageProcess->start(QString("PackageManager%1").arg(platformSuffix),arguments);
        emit isRefreshingChanged();
    }
}

void ImageChecker::runGenerator(const QString& targetPath, const QStringList& args)
{
    QString platformSuffix;
#ifdef Q_OS_WIN32
    platformSuffix=".exe";
#endif
    QStringList commandArgs(_packageFolder);
    commandArgs<<targetPath<<args;
    emit packageManagerOutput(commandArgs.join(' '));
    bool a=QProcess::startDetached(QString("PackageGenerator%1").arg(platformSuffix),commandArgs);
    if(!a)packageManagerOutput("failed");
}

QJsonObject ImageChecker::readPackageMap(){

    QFile dataSource(QString("%1/package.json").arg(_packageFolder));

    if(!dataSource.open(QIODevice::ReadOnly)){
        QString error("Unable open package.json");
        emit packageManagerOutput(error);
        return QJsonObject();
    }

    QByteArray data(dataSource.readAll());

/*
    QByteArray data(""
                    "{"
                    ""
                    ""
                    "   \"sizes\":[\"2732x1536\",\"1024x768\"],"
                    ""
                    "   \"images\":["
                    "       {\"path\":\"myImg.png\","
                    "        \"crop\":false,"
                    "        \"sourceSizes\":["
                    "           \"2732x1536\","
                    "           \"400x400\""
                    "        ],"
                    "        \"usedSizes\":["
                    "           \"\","
                    "           \"400x400\""
                    "        ]},"
                    "       {\"path\":\"compressed.jpg\","
                    "        \"crop\":false,"
                    "        \"sourceSizes\":["
                    "           \"2732x1536\""
                    "        ],"
                    "        \"usedSizes\":["
                    "           \"\","
                    "           \"\""
                    "        ]}"
                    "   ]"
                    "}"
                    "");
*/

    QJsonDocument doc(QJsonDocument::fromJson(data));
    _packageLoaded=true;
    return doc.object();
}

bool ImageChecker::writePackageMap(){
    QFile dataSource(QString("%1/package.json").arg(_packageFolder));

    if(!dataSource.open(QIODevice::WriteOnly)){
        QString error("Unable open package.json for write");
        emit packageManagerOutput(error);
        return false;
    }

    QJsonDocument doc(_packageMap);
    QByteArray data(doc.toJson());
    return dataSource.write(data);
}

<commit_msg>removed debug log<commit_after>#include "ImageChecker.h"

#include <QJsonArray>
#include <QJsonDocument>
#include <QProcess>
#include <QFile>
#include <QDir>

#include "../FKUtility/selectBestSizeset.h"
#include "../FKUtility/sizeString.h"

const int ImageChecker::SelfIndexRole=Qt::UserRole+1;
const int ImageChecker::ImageCropRole=Qt::UserRole+2;
const int ImageChecker::AutoSizeRole=Qt::UserRole+3;
const int ImageChecker::SourceSizesRole=Qt::UserRole+4;
const int ImageChecker::PackageSizeRole=Qt::UserRole+5;
const int ImageChecker::CustomSizeRole=Qt::UserRole+6;
const int ImageChecker::AutoSizeValueRole=Qt::UserRole+7;

class CustomImageItem:public QStandardItem{
    virtual QVariant data(int role = Qt::UserRole + 1) const override{
        if(role==Qt::DisplayRole){
            if(data(ImageChecker::AutoSizeRole).toBool()){
                return data(ImageChecker::AutoSizeValueRole);
            }else{
                return data(ImageChecker::CustomSizeRole);
            }
        }else if(role==ImageChecker::AutoSizeValueRole){
            QJsonArray sourseImageSizes=parent()->data(ImageChecker::SourceSizesRole).toJsonArray();
            QList<QSize> sourceSizes;
            for(auto s=sourseImageSizes.constBegin();s!=sourseImageSizes.constEnd();++s){
                sourceSizes.append(FKUtility::stringToSize((*s).toString()));
            }
            QSize bestSize=FKUtility::selectBestSizeset(sourceSizes,FKUtility::stringToSize(data(ImageChecker::PackageSizeRole).toString()));
            return FKUtility::sizeToString(bestSize);
        }
        return QStandardItem::data(role);
    }
public:
    CustomImageItem(const QString& text):QStandardItem(text){}
};

ImageChecker::ImageChecker(QObject *parent) : QObject(parent),_model(nullptr)
{
    _rebuildPackageProcess=new QProcess(this);
    connect(_rebuildPackageProcess,SIGNAL(finished(int)),SLOT(rebuildModel(int)));
    connect(_rebuildPackageProcess,SIGNAL(error(QProcess::ProcessError)),SLOT(refreshError()));
}

QString ImageChecker::packageFolder() const
{
    return _packageFolder;
}

void ImageChecker::setPackageFolder(const QString& arg)
{
    if(_packageFolder!=arg){
        _packageFolder=arg;
        _packageLoaded=false;
        emit packageFolderChanged();
        runManager();
    }
}

QStringList ImageChecker::sizes() const
{
    return _packageMap.value("sizes").toVariant().toStringList();
}

ImagesetModel* ImageChecker::model() const
{
    return _model;
}

bool ImageChecker::isRefreshing() const
{
    return _rebuildPackageProcess->state()!=QProcess::NotRunning;
}

void ImageChecker::refreshPackage(){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable save package, load package first");
        return;
    }
    applySettings();
    runManager();
}

void ImageChecker::rebuildModel(int returnCode)
{
    if(returnCode){
        emit packageManagerOutput(QString("Package manager error %1 has occured").arg(QString::number(returnCode)));
    }

    QString output(_rebuildPackageProcess->readAllStandardOutput());
    emit packageManagerOutput(output);

    if(_model)_model->deleteLater();

    _packageMap=readPackageMap();

    _model=new ImagesetModel(this);
    QHash<int,QByteArray> roleNames=_model->roleNames();
    roleNames[SelfIndexRole]="selfIndex";
    roleNames[ImageCropRole]="imageCrop";
    roleNames[AutoSizeRole]="autoSize";
    roleNames[SourceSizesRole]="sourceSizes";
    roleNames[CustomSizeRole]="customSize";
    roleNames[AutoSizeValueRole]="autoSizeValue";
    _model->setItemRoleNames(roleNames);

    QStringList targetImageSizes(sizes());
    qint32 sizeCount=targetImageSizes.count();
    QJsonArray images=_packageMap.value("images").toArray();

    for(auto img=images.constBegin();img!=images.constEnd();++img){
        QJsonObject image=img->toObject();
        QStandardItem* row=new QStandardItem(image.value("path").toString());
        row->setData(image.value("crop").toBool(),ImageCropRole);

        QJsonArray customImageSizes=image.value("usedSizes").toArray();
        QJsonArray sourseImageSizes=image.value("sourceSizes").toArray();
        row->setData(sourseImageSizes,SourceSizesRole);

        QList<QSize> sourceSizes;
        for(auto s=sourseImageSizes.constBegin();s!=sourseImageSizes.constEnd();++s){
            sourceSizes.append(FKUtility::stringToSize((*s).toString()));
        }
        for(qint32 i=0;i<sizeCount;++i){
            QString imageSize=customImageSizes.at(i).toString();
            QStandardItem* imageSizeItem=new CustomImageItem(imageSize);
            row->appendRow(imageSizeItem);
            imageSizeItem->setData(targetImageSizes.at(i),PackageSizeRole);
            imageSizeItem->setData(imageSize.isEmpty(),AutoSizeRole);
            if(imageSize.isEmpty())imageSize=imageSizeItem->data(Qt::DisplayRole).toString();
            imageSizeItem->setData(imageSize,CustomSizeRole);
        }
        _model->appendRow(row);
        row->setData(row->index(),SelfIndexRole);
    }

    emit sizesChanged();
    emit modelChanged();
    emit isRefreshingChanged();
}

void ImageChecker::setPackageUrl(const QUrl url){
    QDir dir;
    setPackageFolder(dir.relativeFilePath(url.toLocalFile()));
}

void ImageChecker::addSizeset(const QString size){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable add sizeset, load package first");
        return;
    }
    applySettings();
    runManager(QStringList(size));
}

void ImageChecker::removeSizeset(const QString size){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable remove sizeset, load package first");
        return;
    }
    applySettings();
    runManager(QStringList(),QStringList(size));
}

void ImageChecker::spawnPackage(const QUrl url){
    if(!_packageLoaded){
        emit packageManagerOutput("Unable spawn package, load package first");
        return;
    }
    QStringList args("--add");
    runGenerator(url.toLocalFile(),args);
}

void ImageChecker::applySettings(){
    qint32 rows=_model->rowCount();
    QJsonArray images;
    for(qint32 r=0;r<rows;++r){
        QStandardItem* item=_model->item(r);
        QJsonObject image;
        image["path"]=item->data(Qt::DisplayRole).toString();
        if(item->data(ImageCropRole).toBool()){
            image["crop"]=true;
        }
        image["sourceSizes"]=item->data(SourceSizesRole).toJsonArray();
        QJsonArray customImageSizes;
        qint32 usedSizesCount=item->rowCount();
        for(qint32 s=0;s<usedSizesCount;++s){
            customImageSizes.append(item->child(s)->data(AutoSizeRole).toBool() ?
                                        "" :
                                        item->child(s)->data(Qt::DisplayRole).toString());
        }
        image["usedSizes"]=customImageSizes;
        images.append(image);
    }
    _packageMap["images"]=images;

    writePackageMap();
}

void ImageChecker::refreshError(){
    QString output=QString("Refresh error: %1").arg(QString::number(_rebuildPackageProcess->error()));
    emit packageManagerOutput(output);

    rebuildModel(-1);
}

void ImageChecker::runManager(const QStringList& addSizes, const QStringList& removeSizes){
    if(_packageFolder.isEmpty()){
        emit packageManagerOutput("Unable run package manager, select package first");
        return;
    }
    if(!isRefreshing()){
        QString platformSuffix;
#ifdef Q_OS_WIN32
        platformSuffix=".exe";
#endif
        QStringList arguments(_packageFolder);
        foreach(QString size,addSizes){
            arguments.append("-a");
            arguments.append(size);
        }
        foreach(QString size,removeSizes){
            arguments.append("-r");
            arguments.append(size);
        }
        _rebuildPackageProcess->start(QString("PackageManager%1").arg(platformSuffix),arguments);
        emit isRefreshingChanged();
    }
}

void ImageChecker::runGenerator(const QString& targetPath, const QStringList& args)
{
    QString platformSuffix;
#ifdef Q_OS_WIN32
    platformSuffix=".exe";
#endif
    QStringList commandArgs(_packageFolder);
    commandArgs<<targetPath<<args;
    QProcess::startDetached(QString("PackageGenerator%1").arg(platformSuffix),commandArgs);
}

QJsonObject ImageChecker::readPackageMap(){

    QFile dataSource(QString("%1/package.json").arg(_packageFolder));

    if(!dataSource.open(QIODevice::ReadOnly)){
        QString error("Unable open package.json");
        emit packageManagerOutput(error);
        return QJsonObject();
    }

    QByteArray data(dataSource.readAll());

/*
    QByteArray data(""
                    "{"
                    ""
                    ""
                    "   \"sizes\":[\"2732x1536\",\"1024x768\"],"
                    ""
                    "   \"images\":["
                    "       {\"path\":\"myImg.png\","
                    "        \"crop\":false,"
                    "        \"sourceSizes\":["
                    "           \"2732x1536\","
                    "           \"400x400\""
                    "        ],"
                    "        \"usedSizes\":["
                    "           \"\","
                    "           \"400x400\""
                    "        ]},"
                    "       {\"path\":\"compressed.jpg\","
                    "        \"crop\":false,"
                    "        \"sourceSizes\":["
                    "           \"2732x1536\""
                    "        ],"
                    "        \"usedSizes\":["
                    "           \"\","
                    "           \"\""
                    "        ]}"
                    "   ]"
                    "}"
                    "");
*/

    QJsonDocument doc(QJsonDocument::fromJson(data));
    _packageLoaded=true;
    return doc.object();
}

bool ImageChecker::writePackageMap(){
    QFile dataSource(QString("%1/package.json").arg(_packageFolder));

    if(!dataSource.open(QIODevice::WriteOnly)){
        QString error("Unable open package.json for write");
        emit packageManagerOutput(error);
        return false;
    }

    QJsonDocument doc(_packageMap);
    QByteArray data(doc.toJson());
    return dataSource.write(data);
}

<|endoftext|>


<commit_before>//
// Created by jannis on 07.09.18.
//

#ifndef NOVA_RENDERER_GENERAL_TEST_SETUP_HPP
#define NOVA_RENDERER_GENERAL_TEST_SETUP_HPP

#include <iostream>

#include "../../src/settings/nova_settings.hpp"
#include "../../src/loading/zip_folder_accessor.hpp"
#include "../../src/nova_renderer.hpp"
#include "../../src/platform.hpp"
#ifdef _WIN32
    #include <direct.h>
    #include "../../src/render_engine/dx12/dx12_render_engine.hpp"
    #define getcwd _getcwd
#else
    #include <unistd.h>
    #include "../../src/render_engine/vulkan/vulkan_render_engine.hpp"
#endif

#ifndef CMAKE_DEFINED_RESOURCES_PREFIX
    #define CMAKE_DEFINED_RESOURCES_PREFIX ""
#endif

#include "../../src/util/logger.hpp"

#ifndef TEST_SETUP_LOGGER // Tests are weird... this is done to avoid some linking errors
#define TEST_SETUP_LOGGER \
[]{ \
    auto &log = nova::logger::instance; \
    log.add_log_handler(nova::log_level::TRACE, [](auto msg) { std::cout << "TRACE: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::DEBUG, [](auto msg) { std::cout << "DEBUG: " << msg << std::endl;}); \
    log.add_log_handler(nova::log_level::INFO, [](auto msg) { std::cout << "INFO: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::WARN, [](auto msg) { std::cerr << "WARN: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::ERROR, [](auto msg) { std::cerr << "ERROR: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::FATAL, [](auto msg) { std::cerr << "FATAL: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::MAX_LEVEL, [](auto msg) { std::cerr << "MAX_LEVEL: " << msg << std::endl; }); \
}
#endif

#endif //NOVA_RENDERER_GENERAL_TEST_SETUP_HPP
<commit_msg>Log errors into a file<commit_after>//
// Created by jannis on 07.09.18.
//

#ifndef NOVA_RENDERER_GENERAL_TEST_SETUP_HPP
#define NOVA_RENDERER_GENERAL_TEST_SETUP_HPP

#include <iostream>
#include <fstream>

#include "../../src/settings/nova_settings.hpp"
#include "../../src/loading/zip_folder_accessor.hpp"
#include "../../src/nova_renderer.hpp"
#include "../../src/platform.hpp"
#ifdef _WIN32
    #include <direct.h>
    #include "../../src/render_engine/dx12/dx12_render_engine.hpp"
    #define getcwd _getcwd
#else
    #include <unistd.h>
    #include "../../src/render_engine/vulkan/vulkan_render_engine.hpp"
#endif

#ifndef CMAKE_DEFINED_RESOURCES_PREFIX
    #define CMAKE_DEFINED_RESOURCES_PREFIX ""
#endif

#include "../../src/util/logger.hpp"

#ifndef TEST_SETUP_LOGGER // Tests are weird... this is done to avoid some linking errors
#define TEST_SETUP_LOGGER \
[]{ \
    auto error_log = std::make_shared<std::ofstream>(); \
    error_log->open("test_error_log.txt");\
    auto &log = nova::logger::instance; \
    log.add_log_handler(nova::log_level::TRACE, [](auto msg) { std::cout << "TRACE: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::DEBUG, [](auto msg) { std::cout << "DEBUG: " << msg << std::endl;}); \
    log.add_log_handler(nova::log_level::INFO, [](auto msg) { std::cout << "INFO: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::WARN, [](auto msg) { std::cerr << "WARN: " << msg << std::endl; }); \
    log.add_log_handler(nova::log_level::ERROR, [error_log](auto msg) { std::cerr << "ERROR: " << msg << std::endl; *error_log << "ERROR: " << msg << std::endl << std::flush;}); \
    log.add_log_handler(nova::log_level::FATAL, [error_log](auto msg) { std::cerr << "FATAL: " << msg << std::endl; *error_log << "FATAL: " << msg << std::endl << std::flush;}); \
    log.add_log_handler(nova::log_level::MAX_LEVEL, [error_log](auto msg) { std::cerr << "MAX_LEVEL: " << msg << std::endl; *error_log << "MAX_LEVEL: " << msg << std::endl << std::flush;}); \
}
#endif

#endif //NOVA_RENDERER_GENERAL_TEST_SETUP_HPP
<|endoftext|>


<commit_before>//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------

#include "PIDController.h"

//------------------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------------------

// Default constructor
PIDController::PIDController() {
    this->setGains(0, 0, 0);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(-1, -1);
    this->reset();
}

// Just gains, no limits
PIDController::PIDController(double kp, double ki, double kd) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(-1, -1);
    this->reset();
}
// Gains and output limits
PIDController::PIDController(double kp, double ki, double kd, double lowerOutputLimit, double upperOutputLimit) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(lowerOutputLimit, upperOutputLimit);
    this->reset();
}

// All gains and limits
PIDController::PIDController(double kp, double ki, double kd, double lowerInputLimit, double upperInputLimit, double lowerOutputLimit, double upperOutputLimit) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(lowerInputLimit, upperInputLimit);
    this->setOutputLimits(lowerOutputLimit, upperOutputLimit);
    this->reset();
}

// Copy constructor
PIDController::PIDController(const PIDController& orig) {
    this->setGains(orig.kp, orig.ki, orig.kd);
    this->setInputLimits(orig.lowerInputLimit, orig.upperInputLimit);
    this->setOutputLimits(orig.lowerOutputLimit, orig.lowerInputLimit);
    integrator = orig.integrator;
    lastError = orig.lastError;
    peakTime = orig.peakTime;
    settlingTime = orig.settlingTime;
    percentOvershoot = orig.percentOvershoot;
}

// Destructor
PIDController::~PIDController() {
}

//------------------------------------------------------------------------------
// Accessors
//------------------------------------------------------------------------------

double PIDController::getSetpoint() {
    return setpoint;
}

double PIDController::getKp() {
    return kp;
}

double PIDController::getKi() {
    return ki;
}

double PIDController::getKd() {
    return kd;
}

//------------------------------------------------------------------------------
// Mutators
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// targetSetpoint
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : setpoint
//
// This function sets the desired setpoint that the PID controller will
// attempt to track.
//------------------------------------------------------------------------------

void PIDController::targetSetpoint(double setpoint) {
    this->setpoint = limiter(setpoint, lowerInputLimit, upperInputLimit);
    peakTime = -1;
    settlingTime = -1;
    percentOvershoot = 0;
    sample_timer.start();
    performance_timer.start();
}

//------------------------------------------------------------------------------
// setGains
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : kp, ki, kd
//
// This function sets the control gains of the PID controller in order to
// achieve the desired performance characteristics.
//------------------------------------------------------------------------------

void PIDController::setGains(double kp, double ki, double kd) {
    this->kp = kp;
    this->ki = ki;
    this->kd = kd;
}

//------------------------------------------------------------------------------
// setInputLimits
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : lowerInputLimit, upperInputLimit
//
// This function allows the user to set bounds on the setpoint to prevent
// undesirable behavior of the system.
//------------------------------------------------------------------------------

void PIDController::setInputLimits(double lowerInputLimit, double upperInputLimit) {
    this->lowerInputLimit = lowerInputLimit;
    this->upperInputLimit = upperInputLimit;
}

//------------------------------------------------------------------------------
// setOutputLimits
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : lowerOutputLimit, upperOutputLimit
//
// This function allows the user to set bounds on the control variable to
// prevent undesirable behavior of the system.
//------------------------------------------------------------------------------

void PIDController::setOutputLimits(double lowerOutputLimit, double upperOutputLimit) {
    this->lowerOutputLimit = lowerOutputLimit;
    this->upperOutputLimit = upperOutputLimit;
}

//------------------------------------------------------------------------------
// Other Functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// limiter
//------------------------------------------------------------------------------
//
// Return Value : double
// Parameters   : value, lowerLimit, upperLimit
//
// This function compares the input 'value' to the specified limits. If the
// value exceeds the limits, the value is capped to one of the limits.
//------------------------------------------------------------------------------

double PIDController::limiter(double value, double lowerLimit, double upperLimit) {
    if (lowerLimit == upperLimit) {
        return value;
    }
    else if(value < lowerLimit) {
        return lowerLimit;
    }
    else if(value > upperLimit) {
        return upperLimit;
    }
    else
        return value;
}

//------------------------------------------------------------------------------
// reset
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// This function initializes the PID controller to the default values.
//------------------------------------------------------------------------------

void PIDController::reset() {
    setpoint = 0;
    integrator = 0;
    lastError = 0;
    peakTime = -1;
    settlingTime = -1;
    percentOvershoot = 0;
    sample_timer.stop();
    performance_timer.stop();
}

//------------------------------------------------------------------------------
// hasSettled
//------------------------------------------------------------------------------
//
// Return Value : bool
// Parameters   : None
//
// This function returns true only when the PID controller has stabilized to
// within 5% of its final value.
//------------------------------------------------------------------------------

bool PIDController::hasSettled() {
    if(settlingTime != -1) {
        return true;
    }

    else {
        return false;
    }
}

//------------------------------------------------------------------------------
// calc
//------------------------------------------------------------------------------
//
// Return Value : double
// Parameters   : processVariable
//
// This function calculates the next output value of the PID controller, given
// the current setpoint, elasped time, and feedback (processVariable). It also
// keeps track of peak time, settling time, and percent overshoot for quickly
// assessing the current performance of the controller.
//------------------------------------------------------------------------------

double PIDController::calc(double processVariable) {
    sample_timer.stop();

    double percent = (processVariable/setpoint) - 1;

    if(percent > percentOvershoot && percent > 0)
    {
        percentOvershoot = processVariable/setpoint;
        peakTime = (performance_timer.elapsed().wall)/1e9;
    }
    if(abs(percent) < 0.05 && settlingTime == -1)
    {
        performance_timer.stop();
        settlingTime = (performance_timer.elapsed().wall)/1e9;
        std::cout << "Peak Time Tp: " << peakTime << std::endl;
        std::cout << "Percent Overshoot \%OS: " << percentOvershoot << std::endl;
        std::cout << "Settling Time Ts" << settlingTime << std::endl;
    }

    double error = setpoint - processVariable;
    double samplingTime = (sample_timer.elapsed().wall)/1e9;
    double differentiator = (error - lastError)/samplingTime;
    integrator += (error * samplingTime);
    double controlVariable = kp * error + ki * integrator + kd * differentiator;

    controlVariable = limiter(controlVariable, lowerOutputLimit, upperOutputLimit);

    lastError = error;
    sample_timer.start();

    return controlVariable;
}
<commit_msg>Add compensation for reset windup<commit_after>//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------

#include "PIDController.h"

//------------------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------------------

// Default constructor
PIDController::PIDController() {
    this->setGains(0, 0, 0);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(-1, -1);
    this->reset();
}

// Just gains, no limits
PIDController::PIDController(double kp, double ki, double kd) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(-1, -1);
    this->reset();
}
// Gains and output limits
PIDController::PIDController(double kp, double ki, double kd, double lowerOutputLimit, double upperOutputLimit) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(-1, -1);
    this->setOutputLimits(lowerOutputLimit, upperOutputLimit);
    this->reset();
}

// All gains and limits
PIDController::PIDController(double kp, double ki, double kd, double lowerInputLimit, double upperInputLimit, double lowerOutputLimit, double upperOutputLimit) {
    this->setGains(kp, ki, kd);
    this->setInputLimits(lowerInputLimit, upperInputLimit);
    this->setOutputLimits(lowerOutputLimit, upperOutputLimit);
    this->reset();
}

// Copy constructor
PIDController::PIDController(const PIDController& orig) {
    this->setGains(orig.kp, orig.ki, orig.kd);
    this->setInputLimits(orig.lowerInputLimit, orig.upperInputLimit);
    this->setOutputLimits(orig.lowerOutputLimit, orig.lowerInputLimit);
    integrator = orig.integrator;
    lastError = orig.lastError;
    peakTime = orig.peakTime;
    settlingTime = orig.settlingTime;
    percentOvershoot = orig.percentOvershoot;
}

// Destructor
PIDController::~PIDController() {
}

//------------------------------------------------------------------------------
// Accessors
//------------------------------------------------------------------------------

double PIDController::getSetpoint() {
    return setpoint;
}

double PIDController::getKp() {
    return kp;
}

double PIDController::getKi() {
    return ki;
}

double PIDController::getKd() {
    return kd;
}

//------------------------------------------------------------------------------
// Mutators
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// targetSetpoint
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : setpoint
//
// This function sets the desired setpoint that the PID controller will
// attempt to track.
//------------------------------------------------------------------------------

void PIDController::targetSetpoint(double setpoint) {
    this->setpoint = limiter(setpoint, lowerInputLimit, upperInputLimit);
    peakTime = -1;
    settlingTime = -1;
    percentOvershoot = 0;
    sample_timer.start();
    performance_timer.start();
}

//------------------------------------------------------------------------------
// setGains
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : kp, ki, kd
//
// This function sets the control gains of the PID controller in order to
// achieve the desired performance characteristics.
//------------------------------------------------------------------------------

void PIDController::setGains(double kp, double ki, double kd) {
    this->kp = kp;
    this->ki = ki;
    this->kd = kd;
}

//------------------------------------------------------------------------------
// setInputLimits
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : lowerInputLimit, upperInputLimit
//
// This function allows the user to set bounds on the setpoint to prevent
// undesirable behavior of the system.
//------------------------------------------------------------------------------

void PIDController::setInputLimits(double lowerInputLimit, double upperInputLimit) {
    this->lowerInputLimit = lowerInputLimit;
    this->upperInputLimit = upperInputLimit;
}

//------------------------------------------------------------------------------
// setOutputLimits
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : lowerOutputLimit, upperOutputLimit
//
// This function allows the user to set bounds on the control variable to
// prevent undesirable behavior of the system.
//------------------------------------------------------------------------------

void PIDController::setOutputLimits(double lowerOutputLimit, double upperOutputLimit) {
    this->lowerOutputLimit = lowerOutputLimit;
    this->upperOutputLimit = upperOutputLimit;
}

//------------------------------------------------------------------------------
// Other Functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// limiter
//------------------------------------------------------------------------------
//
// Return Value : double
// Parameters   : value, lowerLimit, upperLimit
//
// This function compares the input 'value' to the specified limits. If the
// value exceeds the limits, the value is capped to one of the limits.
//------------------------------------------------------------------------------

double PIDController::limiter(double value, double lowerLimit, double upperLimit) {
    if (lowerLimit == upperLimit) {
        return value;
    }
    else if(value < lowerLimit) {
        return lowerLimit;
    }
    else if(value > upperLimit) {
        return upperLimit;
    }
    else
        return value;
}

//------------------------------------------------------------------------------
// reset
//------------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// This function initializes the PID controller to the default values.
//------------------------------------------------------------------------------

void PIDController::reset() {
    setpoint = 0;
    integrator = 0;
    lastError = 0;
    peakTime = -1;
    settlingTime = -1;
    percentOvershoot = 0;
    sample_timer.stop();
    performance_timer.stop();
}

//------------------------------------------------------------------------------
// hasSettled
//------------------------------------------------------------------------------
//
// Return Value : bool
// Parameters   : None
//
// This function returns true only when the PID controller has stabilized to
// within 5% of its final value.
//------------------------------------------------------------------------------

bool PIDController::hasSettled() {
    if(settlingTime != -1) {
        return true;
    }

    else {
        return false;
    }
}

//------------------------------------------------------------------------------
// calc
//------------------------------------------------------------------------------
//
// Return Value : double
// Parameters   : processVariable
//
// This function calculates the next output value of the PID controller, given
// the current setpoint, elasped time, and feedback (processVariable). It also
// keeps track of peak time, settling time, and percent overshoot for quickly
// assessing the current performance of the controller.
//------------------------------------------------------------------------------

double PIDController::calc(double processVariable) {
    sample_timer.stop();

    double percent = (processVariable/setpoint) - 1;

    if(percent > percentOvershoot && percent > 0)
    {
        percentOvershoot = processVariable/setpoint;
        peakTime = (performance_timer.elapsed().wall)/1e9;
    }
    if(abs(percent) < 0.05 && settlingTime == -1)
    {
        performance_timer.stop();
        settlingTime = (performance_timer.elapsed().wall)/1e9;
        std::cout << "Peak Time Tp: " << peakTime << std::endl;
        std::cout << "Percent Overshoot \%OS: " << percentOvershoot << std::endl;
        std::cout << "Settling Time Ts" << settlingTime << std::endl;
    }

    double error = setpoint - processVariable;
    double samplingTime = (sample_timer.elapsed().wall)/1e9;
    double differentiator = (error - lastError)/samplingTime;
    integrator += (error * samplingTime);
    integrator = limiter(integrator, lowerOutputLimit, upperOutputLimit);
    double controlVariable = kp * error + ki * integrator + kd * differentiator;

    controlVariable = limiter(controlVariable, lowerOutputLimit, upperOutputLimit);

    lastError = error;
    sample_timer.start();

    return controlVariable;
}
<|endoftext|>


<commit_before>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2012 Scientific Computing and Imaging Institute,
   University of Utah.

   License for the specific language governing rights and limitations under
   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <iostream>
#include <QtGui>
#include <Dataflow/Network/Port.h>
#include <Interface/Application/Port.h>
#include <Interface/Application/GuiLogger.h>
#include <Interface/Application/Connection.h>
#include <Interface/Application/PositionProvider.h>
#include <Interface/Application/Utility.h>
#include <Interface/Application/ClosestPortFinder.h>

using namespace SCIRun::Gui;
using namespace SCIRun::Dataflow::Networks;

std::map<PortWidget::Key, PortWidget*> PortWidget::portWidgetMap_;

PortWidget::PortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  bool isInput,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder, QWidget* parent /* = 0 */)
  : QWidget(parent),
  name_(name), color_(color), moduleId_(moduleId), index_(index), isInput_(isInput), isConnected_(false), lightOn_(false), currentConnection_(0),
  connectionFactory_(connectionFactory),
  closestPortFinder_(closestPortFinder)
{
  setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  setAcceptDrops(true);
  setToolTip(name_);
  portWidgetMap_[boost::make_tuple(moduleId_.toStdString(), index_, isInput_)] = this;
}

PortWidget::~PortWidget()
{
  portWidgetMap_[boost::make_tuple(moduleId_.toStdString(), index_, isInput_)] = 0;
}

QSize PortWidget::sizeHint() const
{
  const int width = 11;
  const int coloredHeight = isInput() ? 5 : 4;
  const int blackHeight = 2;
  return QSize(width, coloredHeight + blackHeight);
}

void PortWidget::toggleLight()
{
  lightOn_ = !lightOn_;
}

void PortWidget::turn_off_light()
{
  lightOn_ = false;
}

void PortWidget::turn_on_light()
{
  lightOn_ = true;
}

void PortWidget::paintEvent(QPaintEvent* event)
{
  QSize size = sizeHint();
  QPainter painter(this);
  painter.fillRect(QRect(QPoint(), size), color());
  QPoint lightStart = isInput() ? QPoint(0,5) : QPoint(0,0);
  QColor lightColor = isLightOn() ? Qt::red : Qt::black;
  painter.fillRect(QRect(lightStart, QSize(size.width(), 2)), lightColor);
}

void PortWidget::mousePressEvent(QMouseEvent* event)
{
  doMousePress(event->button(), event->pos());
}

void PortWidget::doMousePress(Qt::MouseButton button, const QPointF& pos)
{
  if (button == Qt::LeftButton && !isConnected())
  {
    toggleLight();
    startPos_ = pos;
    update();
  }
}

void PortWidget::mouseMoveEvent(QMouseEvent* event)
{
  doMouseMove(event->buttons(), event->pos());
}

void PortWidget::doMouseMove(Qt::MouseButtons buttons, const QPointF& pos)
{
  if (buttons & Qt::LeftButton)
  {
    int distance = (pos - startPos_).manhattanLength();
    if (distance >= QApplication::startDragDistance())
      performDrag(pos);
  }
}

void PortWidget::mouseReleaseEvent(QMouseEvent* event)
{
  doMouseRelease(event->button(), event->pos());
}

namespace
{
  const int PORT_CONNECTION_THRESHOLD = 12;
}

namespace SCIRun {
  namespace Gui {
    struct DeleteCurrentConnectionAtEndOfBlock
    {
      explicit DeleteCurrentConnectionAtEndOfBlock(PortWidget* p) : p_(p) {}
      ~DeleteCurrentConnectionAtEndOfBlock()
      {
        p_->cancelConnectionsInProgress();
      }
      PortWidget* p_;
    };
  }
}

void PortWidget::cancelConnectionsInProgress()
{
  delete currentConnection_;
  currentConnection_ = 0;
}

void PortWidget::doMouseRelease(Qt::MouseButton button, const QPointF& pos)
{
  if (button == Qt::LeftButton && !isConnected())
  {
    toggleLight();
    update();

    if (currentConnection_)
    {
      makeConnection(pos);
    }
  }
}

void PortWidget::makeConnection(const QPointF& pos)
{
  //std::cout << "-----------makeConnection" << std::endl;
  DeleteCurrentConnectionAtEndOfBlock deleter(this);

  auto port = closestPortFinder_->closestPort(pos);
  if (port)
    tryConnectPort(pos, port);

  //else
  //  std::cout << "null port from ClosestPortFinder" << std::endl;
}

bool PortWidget::tryConnectPort(const QPointF& pos, PortWidget* port)
{
  //std::cout << "tryConnectPort" << std::endl;
  int distance = (pos - port->position()).manhattanLength();
  //std::cout << "distance: " << distance << std::endl;
  if (distance <= PORT_CONNECTION_THRESHOLD)
  {
    if (canBeConnected(port))
    {
      PortWidget* out = this->isInput() ? port : this;
      PortWidget* in = this->isInput() ? this : port;

      SCIRun::Dataflow::Networks::ConnectionDescription cd(
        SCIRun::Dataflow::Networks::OutgoingConnectionDescription(out->moduleId_.toStdString(), out->index_),
        SCIRun::Dataflow::Networks::IncomingConnectionDescription(in->moduleId_.toStdString(), in->index_));

      Q_EMIT needConnection(cd);

      return true;
    }
    else
    {
      GuiLogger::Instance().log("input port is full, or ports are different datatype or same i/o type: should not be connected.  this message should come from the domain layer!");
    }
  }
  //std::cout << "---tryConnectPort returns false" << std::endl;
  return false;
}

void PortWidget::MakeTheConnection(const SCIRun::Dataflow::Networks::ConnectionDescription& cd)
{
  if (matches(cd))
  {
    PortWidget* out = portWidgetMap_[boost::make_tuple(cd.out_.moduleId_, cd.out_.port_, false)];
    PortWidget* in = portWidgetMap_[boost::make_tuple(cd.in_.moduleId_, cd.in_.port_, true)];
    SCIRun::Dataflow::Networks::ConnectionId id = SCIRun::Dataflow::Networks::ConnectionId::create(cd);
    ConnectionLine* c = connectionFactory_->makeFinishedConnection(out, in, id);
    connect(c, SIGNAL(deleted(const SCIRun::Dataflow::Networks::ConnectionId&)), this, SIGNAL(connectionDeleted(const SCIRun::Dataflow::Networks::ConnectionId&)));
  }
}

bool PortWidget::matches(const SCIRun::Dataflow::Networks::ConnectionDescription& cd) const
{
  return (isInput() && cd.in_.moduleId_ == moduleId_.toStdString() && cd.in_.port_ == index_)
    || (!isInput() && cd.out_.moduleId_ == moduleId_.toStdString() && cd.out_.port_ == index_);
}

bool PortWidget::sharesParentModule(const PortWidget& other) const
{
  return moduleId_ == other.moduleId_;
}

bool PortWidget::isFullInputPort() const
{
  return isInput() && !connections_.empty();
}

bool PortWidget::canBeConnected(PortWidget* other) const
{
  if (!other)
    return false;

  PortConnectionDeterminer q;
  return q.canBeConnected(*this, *other);
}

void PortWidget::performDrag(const QPointF& endPos)
{
  if (!currentConnection_)
  {
    currentConnection_ = connectionFactory_->makeConnectionInProgress(this);
  }
  currentConnection_->update(endPos);
}

void PortWidget::addConnection(ConnectionLine* c)
{
  connections_.insert(c);
}

void PortWidget::removeConnection(ConnectionLine* c)
{
  connections_.erase(c);
}

void PortWidget::deleteConnections()
{
  Q_FOREACH (ConnectionLine* c, connections_)
    delete c;
  connections_.clear();
}

void PortWidget::trackConnections()
{
  Q_FOREACH (ConnectionLine* c, connections_)
    c->trackNodes();
}

QPointF PortWidget::position() const
{
  if (positionProvider_)
    return positionProvider_->currentPosition();
  return pos();
}

size_t PortWidget::nconnections() const
{
  return connections_.size();
}

std::string PortWidget::get_colorname() const
{
  return color().name().toStdString();
}

std::string PortWidget::get_portname() const
{
  return name_.toStdString();
}

std::string PortWidget::getUnderlyingModuleId() const
{
  return moduleId_.toStdString();
}

InputPortWidget::InputPortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder,
  QWidget* parent /* = 0 */)
  : PortWidget(name, color, moduleId, index, true, connectionFactory, closestPortFinder, parent)
{
}

OutputPortWidget::OutputPortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder,
  QWidget* parent /* = 0 */)
  : PortWidget(name, color, moduleId, index, false, connectionFactory, closestPortFinder, parent)
{
}
<commit_msg>Annotating Port.cc refactoring for #32<commit_after>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2012 Scientific Computing and Imaging Institute,
   University of Utah.

   License for the specific language governing rights and limitations under
   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <iostream>
#include <QtGui>
#include <Dataflow/Network/Port.h>
#include <Interface/Application/Port.h>
#include <Interface/Application/GuiLogger.h>
#include <Interface/Application/Connection.h>
#include <Interface/Application/PositionProvider.h>
#include <Interface/Application/Utility.h>
#include <Interface/Application/ClosestPortFinder.h>

using namespace SCIRun::Gui;
using namespace SCIRun::Dataflow::Networks;

std::map<PortWidget::Key, PortWidget*> PortWidget::portWidgetMap_;

PortWidget::PortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  bool isInput,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder, QWidget* parent /* = 0 */)
  : QWidget(parent),
  name_(name), color_(color), moduleId_(moduleId), index_(index), isInput_(isInput), isConnected_(false), lightOn_(false), currentConnection_(0),
  connectionFactory_(connectionFactory),
  closestPortFinder_(closestPortFinder)
{
  setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  setAcceptDrops(true);
  setToolTip(name_);
  portWidgetMap_[boost::make_tuple(moduleId_.toStdString(), index_, isInput_)] = this;
}

PortWidget::~PortWidget()
{
  portWidgetMap_[boost::make_tuple(moduleId_.toStdString(), index_, isInput_)] = 0;
}

QSize PortWidget::sizeHint() const
{
  const int width = 11;
  const int coloredHeight = isInput() ? 5 : 4;
  const int blackHeight = 2;
  return QSize(width, coloredHeight + blackHeight);
}

void PortWidget::toggleLight()
{
  lightOn_ = !lightOn_;
}

void PortWidget::turn_off_light()
{
  lightOn_ = false;
}

void PortWidget::turn_on_light()
{
  lightOn_ = true;
}

void PortWidget::paintEvent(QPaintEvent* event)
{
  QSize size = sizeHint();
  QPainter painter(this);
  painter.fillRect(QRect(QPoint(), size), color());
  QPoint lightStart = isInput() ? QPoint(0,5) : QPoint(0,0);
  QColor lightColor = isLightOn() ? Qt::red : Qt::black;
  painter.fillRect(QRect(lightStart, QSize(size.width(), 2)), lightColor);
}

void PortWidget::mousePressEvent(QMouseEvent* event)
{
  doMousePress(event->button(), event->pos());
}

void PortWidget::doMousePress(Qt::MouseButton button, const QPointF& pos)
{
  if (button == Qt::LeftButton && !isConnected())
  {
    toggleLight();
    startPos_ = pos;
    update();
  }
}

void PortWidget::mouseMoveEvent(QMouseEvent* event)
{
  doMouseMove(event->buttons(), event->pos());
}

void PortWidget::doMouseMove(Qt::MouseButtons buttons, const QPointF& pos)
{
  if (buttons & Qt::LeftButton)
  {
    int distance = (pos - startPos_).manhattanLength();
    if (distance >= QApplication::startDragDistance())
      performDrag(pos);
  }
}

void PortWidget::mouseReleaseEvent(QMouseEvent* event)
{
  doMouseRelease(event->button(), event->pos());
}

namespace
{
  const int PORT_CONNECTION_THRESHOLD = 12;
}

namespace SCIRun {
  namespace Gui {
    struct DeleteCurrentConnectionAtEndOfBlock
    {
      explicit DeleteCurrentConnectionAtEndOfBlock(PortWidget* p) : p_(p) {}
      ~DeleteCurrentConnectionAtEndOfBlock()
      {
        p_->cancelConnectionsInProgress();
      }
      PortWidget* p_;
    };
  }
}

void PortWidget::cancelConnectionsInProgress()
{
  delete currentConnection_;
  currentConnection_ = 0;
}

void PortWidget::doMouseRelease(Qt::MouseButton button, const QPointF& pos)
{
  if (button == Qt::LeftButton && !isConnected())
  {
    toggleLight();
    update();

    if (currentConnection_)
    {
      makeConnection(pos);
    }
  }
}

void PortWidget::makeConnection(const QPointF& pos)
{
  DeleteCurrentConnectionAtEndOfBlock deleter(this);  //GUI concern: could go away if we got a NO-CONNECT signal from service layer

  auto port = closestPortFinder_->closestPort(pos);  //GUI concern
  if (port)
    tryConnectPort(pos, port);
}

bool PortWidget::tryConnectPort(const QPointF& pos, PortWidget* port)
{
  int distance = (pos - port->position()).manhattanLength();     //GUI concern
  if (distance <= PORT_CONNECTION_THRESHOLD)                 //GUI concern
  {
    if (canBeConnected(port))   //NONE gui, can reuse
    {
      PortWidget* out = this->isInput() ? port : this;     //NONE gui, can reuse
      PortWidget* in = this->isInput() ? this : port;      //NONE gui, can reuse

      SCIRun::Dataflow::Networks::ConnectionDescription cd(          //NONE gui, can reuse
        SCIRun::Dataflow::Networks::OutgoingConnectionDescription(out->moduleId_.toStdString(), out->index_),
        SCIRun::Dataflow::Networks::IncomingConnectionDescription(in->moduleId_.toStdString(), in->index_));

      Q_EMIT needConnection(cd);       //NONE gui, can reuse

      return true;
    }
    else   //NONE gui, can reuse
    {    //NONE gui, can reuse
      GuiLogger::Instance().log("input port is full, or ports are different datatype or same i/o type: should not be connected.  this message should come from the domain layer!");
    }
  }
  return false;
}

void PortWidget::MakeTheConnection(const SCIRun::Dataflow::Networks::ConnectionDescription& cd)
{
  if (matches(cd))
  {
    PortWidget* out = portWidgetMap_[boost::make_tuple(cd.out_.moduleId_, cd.out_.port_, false)];
    PortWidget* in = portWidgetMap_[boost::make_tuple(cd.in_.moduleId_, cd.in_.port_, true)];
    SCIRun::Dataflow::Networks::ConnectionId id = SCIRun::Dataflow::Networks::ConnectionId::create(cd);
    ConnectionLine* c = connectionFactory_->makeFinishedConnection(out, in, id);
    connect(c, SIGNAL(deleted(const SCIRun::Dataflow::Networks::ConnectionId&)), this, SIGNAL(connectionDeleted(const SCIRun::Dataflow::Networks::ConnectionId&)));
  }
}

bool PortWidget::matches(const SCIRun::Dataflow::Networks::ConnectionDescription& cd) const
{
  return (isInput() && cd.in_.moduleId_ == moduleId_.toStdString() && cd.in_.port_ == index_)
    || (!isInput() && cd.out_.moduleId_ == moduleId_.toStdString() && cd.out_.port_ == index_);
}

bool PortWidget::sharesParentModule(const PortWidget& other) const
{
  return moduleId_ == other.moduleId_;
}

bool PortWidget::isFullInputPort() const
{
  return isInput() && !connections_.empty();
}

bool PortWidget::canBeConnected(PortWidget* other) const
{
  if (!other)
    return false;

  PortConnectionDeterminer q;
  return q.canBeConnected(*this, *other);
}

void PortWidget::performDrag(const QPointF& endPos)
{
  if (!currentConnection_)
  {
    currentConnection_ = connectionFactory_->makeConnectionInProgress(this);
  }
  currentConnection_->update(endPos);
}

void PortWidget::addConnection(ConnectionLine* c)
{
  connections_.insert(c);
}

void PortWidget::removeConnection(ConnectionLine* c)
{
  connections_.erase(c);
}

void PortWidget::deleteConnections()
{
  Q_FOREACH (ConnectionLine* c, connections_)
    delete c;
  connections_.clear();
}

void PortWidget::trackConnections()
{
  Q_FOREACH (ConnectionLine* c, connections_)
    c->trackNodes();
}

QPointF PortWidget::position() const
{
  if (positionProvider_)
    return positionProvider_->currentPosition();
  return pos();
}

size_t PortWidget::nconnections() const
{
  return connections_.size();
}

std::string PortWidget::get_colorname() const
{
  return color().name().toStdString();
}

std::string PortWidget::get_portname() const
{
  return name_.toStdString();
}

std::string PortWidget::getUnderlyingModuleId() const
{
  return moduleId_.toStdString();
}

InputPortWidget::InputPortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder,
  QWidget* parent /* = 0 */)
  : PortWidget(name, color, moduleId, index, true, connectionFactory, closestPortFinder, parent)
{
}

OutputPortWidget::OutputPortWidget(const QString& name, const QColor& color, const QString& moduleId, size_t index,
  boost::shared_ptr<ConnectionFactory> connectionFactory,
  boost::shared_ptr<ClosestPortFinder> closestPortFinder,
  QWidget* parent /* = 0 */)
  : PortWidget(name, color, moduleId, index, false, connectionFactory, closestPortFinder, parent)
{
}
<|endoftext|>


<commit_before>#ifndef _CAMERA_HPP
#define _CAMERA_HPP

#include "data_structures.hpp"

class Camera {
private:
    Point position;
    Vector direction, right, up;

public:
    /**
     * Constructs a default camera.
     * - position is 0
     * - direction is z
     * - right is x
     * - up is y
     *
     * Takes the width and height of the image and auto-generates the
     * aspect ratio and such.
     */
    Camera(int width, int height);

    /**
     * Constructs a camera from the given vectors.
     */
    Camera(Point position, Vector direction, Vector right, Vector up);

    /**
     * Constructs a camera from the given point.
     */
    Camera(Point position);

    /**
     * Constructs a camera from the given point. Auto-generates the
     * aspect ration from the width and height
     */
    Camera(Point position, int width, int height);

    /**
     * Computes a camera based on the given parameters.
     */
    static Camera lookAt(Point position, Vector sky, Point look_at, float angle, int width, int height);

    /**
     * Computes a camera ray.
     * @param x X coordinate in range [-1, 1]
     * @param y Y coordinate in range [-1, 1]
     * @return The camera ray
     */
    Ray get_ray(float x, float y) const;
};

#endif
<commit_msg>Thoroughly documented camera<commit_after>#ifndef _CAMERA_HPP
#define _CAMERA_HPP

#include "data_structures.hpp"

/**
 * An implementation of a rectilinear camera. The camera is controlled
 * by three vectors (direction, right, and up) and one point (position).
 * A convenience function lookAt() is provided so the parameters don't
 * have to be computed by hand.
 */
class Camera {
private:
    Point position;
    Vector direction, right, up;

public:
    /**
     * Constructs a default camera. A left handed coordinate system is
     * used, where x is right, y is up, and z is forward, and the camera
     * is sitting at the origin looking forward with a focal length
     * equal to the image height.
     *
     * - position is 0
     * - direction is <0,0,1>
     * - right is <1,0,0>
     * - up is <0,1,0>
     *
     * \param width Image width, used to determine the aspect ratio
     * \param height Image width, used to determine the aspect ratio
     */
    Camera(int width, int height);

    /**
     * Constructs a camera from the given vectors.
     */
    Camera(Point position, Vector direction, Vector right, Vector up);

    /**
     * Constructs a camera whose position is the given point.
     *
     * The other parameters use the default values from Camera(int,int).
     */
    Camera(Point position);

    /**
     * Constructs a camera from the given point and image size.
     *
     * The other parameters use the default values from Camera(int,int).
     *
     * \param width Image width, used to determine the aspect ratio
     * \param height Image width, used to determine the aspect ratio
     */
    Camera(Point position, int width, int height);

    /**
     * Computes a camera based on the given parameters.
     *
     * The following formulas are used:
     *
     * - right_length = width/height
     * - direction_length = 0.5 * right_length / tan(angle/2)
     *
     * - direction = Vector pointing from position to look_at whose
     *   length is direction_length
     * - right = (sky × direction) with length of right_length
     * - up = (direction × right) with unit length
     *
     * \see Vector.normalized
     * \see × is Vector.crossProduct
     */
    static Camera lookAt(Point position, Vector sky, Point look_at, float angle, int width, int height);

    /**
     * Computes a camera ray from the given point in the image.
     *
     * The ray is determined as follows:
     *
     * - Ray's position = The Camera's position
     * - Ray's direction = direction + x*right + y*up
     *
     * @param x X coordinate in range [-1, 1]
     * @param y Y coordinate in range [-1, 1]
     * @return The camera ray
     */
    Ray get_ray(float x, float y) const;
};

#endif
<|endoftext|>