From f439ae911923ee70937592b1ee535e8e8e133808 Mon Sep 17 00:00:00 2001 From: Jonas Gunz Date: Wed, 6 Mar 2019 15:04:57 +0100 Subject: Directory updates Moved source files to ./src and exmaple and test to ./example Updated Makefile and .doxygen to use those directorys --- src/cInput.cpp | 73 ++++++++++ src/cInput.h | 58 ++++++++ src/cObject.cpp | 82 +++++++++++ src/cObject.h | 95 +++++++++++++ src/cObjectHandler.cpp | 368 +++++++++++++++++++++++++++++++++++++++++++++++ src/cObjectHandler.h | 118 +++++++++++++++ src/cRender.cpp | 380 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cRender.h | 177 +++++++++++++++++++++++ src/cWiremesh.cpp | 132 +++++++++++++++++ src/cWiremesh.h | 104 ++++++++++++++ 10 files changed, 1587 insertions(+) create mode 100644 src/cInput.cpp create mode 100644 src/cInput.h create mode 100644 src/cObject.cpp create mode 100644 src/cObject.h create mode 100644 src/cObjectHandler.cpp create mode 100644 src/cObjectHandler.h create mode 100644 src/cRender.cpp create mode 100644 src/cRender.h create mode 100644 src/cWiremesh.cpp create mode 100644 src/cWiremesh.h (limited to 'src') diff --git a/src/cInput.cpp b/src/cInput.cpp new file mode 100644 index 0000000..6384c91 --- /dev/null +++ b/src/cInput.cpp @@ -0,0 +1,73 @@ +#include "cInput.h" + +cInput::cInput() +{ + // Save original serial communication configuration for stdin + tcgetattr( STDIN_FILENO, &original); + + // Put stdin in raw mode so keys get through directly without + // requiring pressing enter. + cfmakeraw (&raw); + tcsetattr (STDIN_FILENO, TCSANOW, &raw); + + // Enable mouse tracking + write (STDOUT_FILENO, "\e[?1000h", 8); +} + +cInput::~cInput() +{ + //revert changes to console + write (STDOUT_FILENO, "\e[?1000l", 8); + tcsetattr (STDIN_FILENO, TCSANOW, &original); +} + +sInputEvent cInput::poll() +{ + sInputEvent ret; + unsigned char buff [6]; + + //setup for select + fd_set rfds; + struct timeval tv; + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + + ret.type = _EVENT_NULL; + + //Check for Input. return of none + if(!select(1, &rfds, NULL, NULL, &tv)) + return ret; + + read (STDIN_FILENO, &buff, 1); + if (buff[0] == 3) { + // User pressd Ctr+C + ret.type = _EVENT_TERM; + } + else if (buff[0] == '\x1B') //Escape sequence + { + read (STDIN_FILENO, &buff, 5); + if(buff[0] == '[') + { + if(buff[1] == 'M') //Mouse Event + { + ret.b = buff[2] - 32; + ret.x = buff[3] - 32 - 1; //Console sees origin at 1,1 + ret.y = buff[4] - 32 - 1; //Program at 0,0 + ret.type = _EVENT_MOUSE; + } + else //e.g. Arrow Keys + { + ret.c = buff[1]; + ret.type = _EVENT_KEY; + } + } + } + else + { + ret.type = _EVENT_CHAR; + ret.c = buff[0]; + } + return ret; +} diff --git a/src/cInput.h b/src/cInput.h new file mode 100644 index 0000000..00ddb7d --- /dev/null +++ b/src/cInput.h @@ -0,0 +1,58 @@ +#ifndef CINPUT_H_ +#define CINPUT_H_ + +#include +#include +#include +#include + +#ifdef __linux__ +#elif _WIN32 + #error "Platforn not supported" +#else + #error "Platforn not supported" +#endif + +#define _EVENT_NULL 0 +#define _EVENT_CHAR 1 +#define _EVENT_KEY 2 +#define _EVENT_MOUSE 3 +#define _EVENT_TERM 4 + +struct sInputEvent +{ + unsigned int type; + unsigned char c; + unsigned int b; + int x, y; +}; +/** +* ##cInput +* * puts STDIN in raw mode +* * activates mouse tracking +* * reverts console back to normal operation on destruction. +* Compatible with xterm compatible terminal emulators +*/ +class cInput +{ +public: + cInput(); + + ~cInput(); + + /** Reads inputevents + * returns event struct + * ### sInputEvent.type + * * _EVENT_NULL: No input recorded + * * _EVENT_CHAR: A Key was pressed, stored in .c + * * _EVENT_KEY: Escape sequence recorded, stored in .c without escape char + * * _EVENT_MOUSE: Console registered click at (.x, .y) with origin at (0,0) (top left). Mouse button stored in b. + * * _EVENT_TERM: Console registered Ctrl+C + */ + sInputEvent poll(); + +private: + struct termios original, raw; +}; + +#endif /* end of include guard: */ diff --git a/src/cObject.cpp b/src/cObject.cpp new file mode 100644 index 0000000..7dc194c --- /dev/null +++ b/src/cObject.cpp @@ -0,0 +1,82 @@ +#include "cObject.h" + +cObject::cObject(int _sx, int _sy) : pos({0,0}) , bSizeSet(false) +{ + setSize(_sx, _sy); +} + +cObject::~cObject() +{ + destruct(); +} + +sPos cObject::getPosition() +{ + return pos; +} + +void cObject::setPosition(sPos _pos) +{ + pos = _pos; +} + + +void cObject::setPosition(int _x, int _y) +{ + pos.x = _x; + pos.y = _y; +} + + +sObject cObject::getObject() +{ + return sObject{pos, wColor, cScreen, sizeX, sizeY}; +} + +//protected +cObject::cObject() : pos({0,0}) , bSizeSet(false){} + +void cObject::setSize(int _sx, int _sy) +{ + if(bSizeSet) + return; + + bBlockRender = true; //Block inherited render capabilities of parent + + sizeX = _sx; + sizeY = _sy; + + //Initialize 2D array + cScreen = (char**) malloc(sizeof *cScreen * _sx); + for (int i = 0; i < _sx; i++) + cScreen[i] = (char*)malloc(sizeof *cScreen[i] * _sy); + + wColor = (WORD**)malloc(sizeof *wColor * _sx); + for (int i = 0; i < _sx; i++) + wColor[i] = (WORD*)malloc(sizeof *wColor[i] * _sy); + + for (int i = 0; i < sizeY; i++) { + for (int o = 0; o < sizeX; o++) { + cScreen[o][i] = NULL; + wColor[o][i] = _COL_DEFAULT; + } + } + + bSizeSet = true; +} + +void cObject::destruct() +{ + if(!bSizeSet) + return; + + for (int i = 0; i < sizeX; i++) { + free(cScreen[i]); + free(wColor[i]); + } + + free(cScreen); + free(wColor); + + bSizeSet = false; +} diff --git a/src/cObject.h b/src/cObject.h new file mode 100644 index 0000000..50b9d18 --- /dev/null +++ b/src/cObject.h @@ -0,0 +1,95 @@ +#pragma once +#include + +#include "cRender.h" + +#define _HIT_TOP 1 +#define _HIT_BOTTOM 2 +#define _HIT_LEFT 3 +#define _HIT_RIGHT 4 + +struct sObject +{ + sPos pos; + WORD **wColor; + char **cScreen; + int sizeX; + int sizeY; +}; + +/** cObject can be used standalone as well as inherited +* every cObject has its own framebuffer as well as position viariables to be moveable. +* cObject is used by cObjectHandler to manage all objects to be displayed. +* +* Minimal example for inheriting class +* +* +* class example : cObject +* { +* public: +* example() { setSize(10,5); } +* ~example() { destruct(); } +* }; +* +*/ +class cObject : public cRender +{ +public: + /** Sets the size to _sx x _sy + */ + cObject(int _sx, int _sy); + + virtual ~cObject(); + + /** Returns current position + */ + sPos getPosition(); + + /** Sets position to _pos + */ + void setPosition(sPos _pos); + /** Sets position by coordinates + */ + void setPosition(int _x, int _y); + + /** Returns sObject with framebuffer and current position + */ + sObject getObject(); + + /** Called by cObjecthandler if cObject is clicked + */ + virtual void onClick(sPos _pos, unsigned int _button){} + /** Called by cObjecthandler if cObject is active on keyboard input + * _pos decribes the relative position of mousepointer to origin of object + */ + virtual void onChar(unsigned char _c){} + + + /** Called by cObjectHandler if Object hits another during move operation + * return true to abort move, false to continue and allow overlap + */ + virtual bool onCollisionActive(unsigned int _hit, int _passiveObject) { return false; } + + /** Called by cObjectHandler if Object is hit by another object + * return any integer value to be identified by hitting object + */ + virtual int onCollisionPassive(unsigned int _hit) { return 0; } + + + + +protected: //For child classes + cObject(); + /** For inheriting classes: sets size of framebuffer + */ + void setSize(int _sx, int _sy); + + /** For inheriting classes: frees the framebuffer + */ + void destruct(); + +private: + //wColor, cScreen, sizeX and sizeY are inherited from cRender + sPos pos; + bool bSizeSet; +}; diff --git a/src/cObjectHandler.cpp b/src/cObjectHandler.cpp new file mode 100644 index 0000000..11c02e6 --- /dev/null +++ b/src/cObjectHandler.cpp @@ -0,0 +1,368 @@ +#include "cObjectHandler.h" + +cObjectHandler::cObjectHandler(cRender *_render, bool _enableInputMapping, bool _enableCollision) : cameraPosition ({0,0}), iActiveObject(0) +{ + render = _render; + + enableInputMapping = _enableInputMapping; + enableCollision = enableInputMapping ? _enableCollision : false; // Collision requires input mapping + + objects.push_back(NULL); //Create first Object as Catcher for Events + + buildHitmap(); +} + +int cObjectHandler::createObject(cObject *_object) +{ + objects.push_back(_object); + + buildHitmap(); + return objects.size() - 1; +} + +int cObjectHandler::moveObject(int _object, sPos _pos, int _mode) +{ + if (_object >= objects.size()) //prevent segmentation faults + return 1; + + if (!objects[_object]) + return 1; + + sPos objPosition = objects[_object]->getPosition(); + sPos newPosition; + + if (_mode == _MOVE_RELATIVE) + newPosition = { objPosition.x + _pos.x, objPosition.y + _pos.y }; + else if (_mode == _MOVE_ABSOLUTE) + newPosition = _pos; + else if (_mode == _MOVE_FORCE_ABSOLUTE) + { + objects[_object]->setPosition(_pos); + return 0; + } + + sCollision coll; + + coll = checkCollision(newPosition, objects[_object]->getSize()); + + bool abort = false; + + if(coll.idc) + { + for(int i = 0; i < coll.idc; i++) + { + if(coll.idv[i] != _object) + abort += objects[_object]->onCollisionActive(0, objects[coll.idv[0]]->onCollisionPassive(0)); + } + } + + if(!abort) + objects[_object]->setPosition(newPosition); + + if(coll.idv) + free (coll.idv); + if(coll.hitv) + free (coll.hitv); + + buildHitmap(); + return 0; +} + +int cObjectHandler::destroyObject(int _object) +{ + if(!objects[_object]) + return 1; + + delete objects[_object]; + objects[_object] = NULL; + + buildHitmap(); + return 0; +} + +int cObjectHandler::write() +{ + render->clear(); + + for (unsigned long int i = 0; i < meshes.size(); i++) + { + if(meshes[i]) + { + moveWiremesh(i,{-cameraPosition.x, -cameraPosition.y, 0} ,_MOVE_RELATIVE); + meshes[i]->write(render); + moveWiremesh(i,{cameraPosition.x, cameraPosition.y, 0},_MOVE_RELATIVE); + } + } + + for (unsigned long int i = 0; i < objects.size(); i++) + { + if (objects[i]) // Check if objects[i] is existent + { + //Draw every Object + sObject obj = objects[i]->getObject(); //get Object #i + + for (int o = 0; o < obj.sizeY; o++) { //y axis + for (int p = 0; p < obj.sizeX; p++) { //x axis + if (obj.cScreen[p][o]) { //Dont overwrite empty pixels + sPos pos{ obj.pos.x + p - cameraPosition.x, + obj.pos.y + o - cameraPosition.y }; + render->drawPoint(obj.cScreen[p][o], pos, true, obj.wColor[p][o]); + } + } + } + } + } + + return 0; +} + +int cObjectHandler::clickEvent(sPos _pos, unsigned int _button) +{ + if(_pos.x >= iHitMap.size()) + return 1; + if(_pos.y >= iHitMap[_pos.x].size()) + return 1; + + + if(objects[ iHitMap[_pos.x][_pos.y] ]) + { + sPos rel_pos; + sPos obj_pos = objects[ iHitMap[_pos.x][_pos.y] ]->getPosition(); + rel_pos.x = _pos.x - obj_pos.x + cameraPosition.x; + rel_pos.y = _pos.y - obj_pos.y + cameraPosition.y; + + iActiveObject = iHitMap[_pos.x][_pos.y]; //Set active object + objects[ iHitMap[_pos.x][_pos.y] ]->onClick(rel_pos, _button); + } + else + return 1; + + return 0; +} + +int cObjectHandler::charEvent(unsigned char _c) +{ + if(objects.size() > iActiveObject) + { + if(objects[iActiveObject]) + { + objects[iActiveObject]->onChar(_c); + } + else + return 1; + } + + return 0; +} + +void cObjectHandler::buildHitmap() +{ + if(!enableInputMapping) + return; + + //Rebuild 2D vector + sPos size = render->getSize(); + + vector cp; + + while(size.y > cp.size()) + { + cp.push_back(0); + } + + while (size.x > iHitMap.size()) + { + iHitMap.push_back(cp); + } + + while (size.x <= iHitMap.size()) + { + iHitMap.pop_back(); + } + for(unsigned int x = 0; x < iHitMap.size(); x++) + { + for(unsigned int y = 0; y < iHitMap[x].size(); y++) + { + iHitMap[x][y] = 0; + } + } + //Write object IDs to iHitMap + for(unsigned int i = 0; i < objects.size(); i++) + { + if(objects[i]) + { + sPos oPos = objects[i]->getPosition(); + sPos oSize = objects[i]->getSize(); + + oPos.x -= cameraPosition.x; + oPos.y -= cameraPosition.y; + + for(int x = oPos.x; x < oPos.x + oSize.x; x++) + { + for(int y = oPos.y; y < oPos.y + oSize.y; y++) + { + if((x < size.x && y < size.y) && (x >= 0 && y >= 0)) //Objects can be outside the screen. + iHitMap[x][y] = i; + }//for + }//for + }//if + }//for +}//buildHitmap + +void cObjectHandler::focusNext() +{ + iActiveObject++; + + if(iActiveObject >= objects.size()) + iActiveObject = 0; +} + +void cObjectHandler::focus(unsigned int _id) +{ + if(_id >= objects.size()) + iActiveObject = objects.size(); + else + iActiveObject = _id; +} + +int cObjectHandler::createWiremesh(cWiremesh *_mesh) +{ + meshes.push_back(_mesh); + + return meshes.size() - 1; +} + +int cObjectHandler::moveWiremesh(int _mesh, sCoord3d _pos, int _mode) +{ + if (_mesh >= meshes.size()) //prevent segmentation faults + return 1; + + if (!meshes[_mesh]) + return 1; + + sCoord3d meshPosition = meshes[_mesh]->getPosition(); + + if (_mode == _MOVE_RELATIVE) + meshes[_mesh]->setPosition(meshPosition + _pos); + else if (_mode == _MOVE_ABSOLUTE) + meshes[_mesh]->setPosition(_pos); + + return 0; +} + +int cObjectHandler::destroyWiremesh(int _mesh) +{ + if(!meshes[_mesh]) + return 1; + + delete meshes[_mesh]; + meshes[_mesh] = NULL; + + return 0; +} + +int cObjectHandler::rotateWiremesh(int _mesh, sCoord3d _angle) +{ + if (_mesh >= meshes.size()) //prevent segmentation faults + return 1; + + if (!meshes[_mesh]) + return 1; + + meshes[_mesh]->rotate(_angle); + + return 0; +} + +void cObjectHandler::setCameraPosition(sPos _pos, int _mode) +{ + if(_mode == _MOVE_ABSOLUTE) + cameraPosition = _pos; + else if(_mode == _MOVE_RELATIVE) + { + cameraPosition.x += _pos.x; + cameraPosition.y += _pos.y; + } + + buildHitmap(); +} + +sPos cObjectHandler::getCameraPosition() +{ + return cameraPosition; +} + +sCollision cObjectHandler::checkCollision(sPos _pos, sPos _size) +{ + sCollision ret; + vector collisions; + vector hitTypes; + ret.idc = 0; + ret.idv = NULL; + ret.hitv = NULL; + + if(!enableCollision) + return ret; + + int sizeX, sizeY; + + sizeX = render->getSize().x; + sizeY = render->getSize().y; + + //The mother of if-statements + //No collision for offscreen objects + if( (_pos.x < cameraPosition.x && _pos.x + _size.x + cameraPosition.x < 0) || + (_pos.x - cameraPosition.x >= iHitMap.size() && _pos.x + _size.x - cameraPosition.x >= iHitMap.size()) || + (_pos.y < cameraPosition.y && _pos.y + _size.y + cameraPosition.y < 0) || + (_pos.y - cameraPosition.y >= iHitMap[0].size() && _pos.y + _size.y - cameraPosition.y >= iHitMap[0].size()) ) + return ret; + + for(int x = _pos.x - cameraPosition.x; x < _pos.x + _size.x - cameraPosition.x; x++) + { + for(int y = _pos.y - cameraPosition.y; y < _pos.y + _size.y - cameraPosition.y; y++) + { + if(!(x >= sizeX || x < 0 || y >= sizeY || y < 0)) + { + if(iHitMap[x][y]) + collisions.push_back(iHitMap[x][y]); + } + } + } + + //Since Object can hit on multiple Pixels, duplications can occur. + //Sort and set duplicates to zero + //-> zeros are at front of vector + for(unsigned int swaps = 1; swaps > 0;) + { + swaps = 0; + + for(unsigned int i = 0; i < collisions.size() - 1; i++) + { + if(collisions[i] > collisions[i + 1]) + { + swaps ++; + unsigned int tmp = 0; + + tmp = collisions[i]; + collisions[i] = collisions[i + 1]; + collisions[i + 1] = tmp; + } + if(collisions[i] == collisions[i + 1]) + collisions[i] = 0; + } + } + + //Since every empty entry is in front, pop them + while(!collisions.front()) + collisions.erase(collisions.begin()); + + ret.idc = collisions.size(); + ret.idv = (unsigned int*) malloc( sizeof(*ret.idv) * ret.idc ); + + for(unsigned int i = 0; i < ret.idc; i++) + { + ret.idv[i] = collisions[i]; + } + + return ret; +} diff --git a/src/cObjectHandler.h b/src/cObjectHandler.h new file mode 100644 index 0000000..4a0306b --- /dev/null +++ b/src/cObjectHandler.h @@ -0,0 +1,118 @@ +#pragma once + +#include + +#include "cObject.h" +#include "cWiremesh.h" + +//movemodes +#define _MOVE_RELATIVE 0 +#define _MOVE_ABSOLUTE 1 +#define _MOVE_FORCE_ABSOLUTE 2 + +using namespace std; + +struct sCollision +{ + unsigned int *idv; + int *hitv; + unsigned int idc; +}; + +/** +* Manages cObject and cWiremesh and writes them to a cRender framebuffer (Also works on cObject, since it inherits from cRender!). +* forwards input events to corresponding cObject. +* Runs collision checking for every move operation. This is very expensive, so deactivate if not needed (eg. for background animations etc)! +*/ +class cObjectHandler +{ +public: + /** + * *_render: pointer to instance of cRender all objects will be written to + * _enableCollision: activate collision checking globally. CAUTION: Collision requires InputMapping. If InputMapping is disabled, Collision will NOT WORK! + * _enableInputMapping: activate Input mapping for mouse and keyboard events + */ + explicit cObjectHandler(cRender *_render, bool _enableInputMapping = true, bool _enableCollision = true); + + /** + * Adds _object to managed objects vector + * returns Identifier for newly created vector + */ + int createObject(cObject *_object); + + /** + * Alters position of _object by _pos either relative to old position or Absolute + * Depending on selected _mode (_MOVE_RELATIVE / _MOVE_ABSOLUTE / _MOVE_ABSOLUTE). + * _MOVE_ABSOLUTE not recommended: Collision is only checked at destination. To ensure initialisation, use _MOVE_FORCE_ABSOLUTE! + */ + int moveObject(int _object, sPos _pos, int _mode); + + /** + * removes _object from vector after deleting it + */ + int destroyObject(int _object); + + /** + * Analog to createObject() + */ + int createWiremesh(cWiremesh *_mesh); + + int moveWiremesh(int _mesh, sCoord3d _pos, int _mode); + + int rotateWiremesh(int _mesh, sCoord3d _angle); + + int destroyWiremesh(int _mesh); + + + void setCameraPosition(sPos _pos, int _mode); + + sPos getCameraPosition(); + + + /** + * writes all objects in objects[] to render buffer + */ + int write(); + + + /** + * Calls onClick of cObject at _pos, focuses Object + * returns 0 if successfull, 1 if no Object is at _pos + */ + int clickEvent(sPos _pos, unsigned int _button); + + /** + * Calls onChar of active cObject, default 0 + * returns 0 if successfull, 1 if no Object or destroyed Object is empty + */ + int charEvent(unsigned char _c); + + /** + * Focuses next Object + */ + void focusNext(); + + /** + * Focuses Object by id. + * 0 is empty by default and can be used to unfocus + */ + void focus(unsigned int _id); + + +private: + /** + * This function is very expensive! Only use when needed! + */ + sCollision checkCollision(sPos _pos, sPos _size); + + void buildHitmap(); + + vector objects; + vector meshes; + vector< vector > iHitMap; + cRender *render; + unsigned long int iActiveObject; + sPos cameraPosition; + bool enableCollision; + bool enableInputMapping; +}; diff --git a/src/cRender.cpp b/src/cRender.cpp new file mode 100644 index 0000000..9fbdc8f --- /dev/null +++ b/src/cRender.cpp @@ -0,0 +1,380 @@ +#include "cRender.h" + + +cRender::cRender(char _backound, WORD _color, int _sx, int _sy) +{ + bBlockRender = false; //If this Constructor is used, this instance is not inherited, thus render() doesn't need to be blocked + iLastError = _OK_; + sizeX = 0; + sizeY = 0; + + cBackound = _backound; + wBackColor = _color; + +#ifdef __linux__ //In Linux, setting Console size is not supported, so it gets Size of Console (Window) instead. + + wDefColor = _COL_DEFAULT; + + //Set up console + setAlternateBufferScreen(true); + setConsoleCursor(false); + + setBufferSize( getConsoleWindowSize() ); + + if(sizeX < _sx || sizeY < _sy) //Notify Program tha screen is too small for desired Size + iLastError = _ERR_SCREEN_TOO_SMALL_; + +#elif _WIN32 //Windows Specific Code + hstdout = GetStdHandle(STD_OUTPUT_HANDLE); //get handle + + GetConsoleScreenBufferInfo(hstdout, &csbi); //get current console settings + wDefColor = csbi.wAttributes; //Get default console color + + SetConsoleWindowSize(_sx + 1, _sy + 1); //set the windows size to _sx * _sy (+1 so no scrolling accurs) + + setBufferSize({_sx,_sy}); +#endif //_WIN32 + + setConsoleEcho(false); + clear(true); //Init backround array + +}//render() + + +cRender::cRender() {} + +cRender::~cRender() +{ + if(bBlockRender) //Don't run destructor if inherited + return; + + for (int i = 0; i < sizeX; i++) { + free(cScreen[i]); + free(wColor[i]); + free(bChanged[i]); + } + + free(cScreen); + free(wColor); + free(bChanged); + + setConsoleEcho(true); + + #ifdef __linux__ + setConsoleCursor(true); + setAlternateBufferScreen(false); + #endif //__linux__ +} + +int cRender::drawPoint(char _c, sPos _pos, bool _overrideCollision, WORD _color) +{ + if (_pos.x >= sizeX || _pos.y >= sizeY || _pos.x < 0 || _pos.y < 0) + return _ERR_COORDINATES_INVALID_; + + if (cScreen[_pos.x][_pos.y] != cBackound && _overrideCollision != true) //detect Collsision + return _COLLISION_; + + cScreen[_pos.x][_pos.y] = _c; + if (_color == _COL_DEFAULT) //_COL_DEFAULT is NOT a proper colorcode! + wColor[_pos.x][_pos.y] = wDefColor; + else + wColor[_pos.x][_pos.y] = _color; + + if(!bBlockRender) //Changemap is not allocated in inherited Classes + bChanged[_pos.x][_pos.y] = true; + + return 0; +} + +int cRender::drawLine(char _c, sPos _pos1, sPos _pos2, bool _overrideCollision, WORD _color) +{ + if(_pos1.x > _pos2.x) + { + //Shit WILL go wrong + return drawLine(_c, _pos2, _pos1, _overrideCollision, _color); + } + + if (_pos1.x == _pos2.x) { //Horizontal line + for (int i = _pos1.y; i <= _pos2.y; i++) + { + drawPoint(_c, sPos{_pos1.x, i}, _overrideCollision, _color); + } + } + else if (_pos1.y == _pos2.y) { //Vertical line + for (int i = _pos1.x; i <= _pos2.x; i++) + { + drawPoint(_c, sPos{ i, _pos1.y }, _overrideCollision, _color); + } + } + else { //Diagonal Line + int dX = _pos1.x - _pos2.x; + int dY = _pos1.y - _pos2.y; + float fGradient = (float)dY / (float)dX; + + for (int i = 0; i <= abs(dX); i++) + { + drawPoint(_c, sPos{i + _pos1.x, (int)(i * fGradient + _pos1.y + 0.5)}, _overrideCollision, _color); //+0.5 for rounding error + + if(std::abs(fGradient) > 1.0) + { + int dy = (int)(((i + 1) * fGradient + _pos1.y + 0.5) - (i * fGradient + _pos1.y + 0.5)); + + if(dy > 0 && ((int)(i * fGradient + _pos1.y + 0.5) + dy) <= _pos2.y) + { + drawLine(_c, + sPos{i + _pos1.x, (int)(i * fGradient + _pos1.y + 0.5)}, + sPos{i + _pos1.x, (int)(i * fGradient + _pos1.y + 0.5) + dy }, + _overrideCollision, _color); + }//if + else if(dy < 0 && ((int)(i * fGradient + _pos1.y + 0.5) + dy) >= (_pos2.y) ) + { + drawLine(_c, + sPos{i + _pos1.x, (int)(i * fGradient + _pos1.y + 0.5) + dy }, + sPos{i + _pos1.x, (int)(i * fGradient + _pos1.y + 0.5)}, + _overrideCollision, _color); + }//else if + }//if + }//for + }//else + + return 0; +}//drawLine + +int cRender::drawText(string _s, sPos _pos, WORD _color) +{ + for (int i = 0; i < _s.length(); i++) + { + drawPoint(_s[i], sPos{ i + _pos.x,_pos.y }, true, _color); + } + return 0; +} + +int cRender::drawRectangle(char _border, char _fill, sPos _pos1, sPos _pos2, WORD _borderColor, WORD _fillColor) +{ + //Draw the four outside lines + drawLine(_border, _pos1, sPos{ _pos1.x, _pos2.y }, true, _borderColor); + drawLine(_border, _pos1, sPos{ _pos2.x, _pos1.y }, true, _borderColor); + drawLine(_border, sPos{ _pos1.x, _pos2.y }, _pos2, true, _borderColor); + drawLine(_border, sPos{ _pos2.x, _pos1.y }, _pos2, true, _borderColor); + + //Fill rectangle if _fill isn't NULL + if (_fill) { + for (int i = _pos1.y + 1; i < _pos2.y; i++) { + for (int o = _pos1.x + 1; o < _pos2.x; o++) { + drawPoint(_fill, sPos{ o,i }, true, _fillColor); + } + } + } + + return 0; +} + +int cRender::render(void) +{ + if (bBlockRender) + return _ERR_RENDER_BLOCKED_BY_CHILD_; + + //Resize screenbuffer if needed + setBufferSize( getConsoleWindowSize( ) ); + + for (int i = 0; i < sizeY; i++) { + for (int o = 0; o < sizeX; o++) { + if(bChanged[o][i]) + { + #ifdef _WIN32 + + gotoxy(o,i); + SetConsoleTextAttribute(hstdout, wColor[o][i] | _COL_INTENSITY); + //cout << cScreen[o][i]; + printf("%c", cScreen[o][i]); + + #elif __linux__ + //gotoxy(x,y) now included!! + char buffer[20]; + int cbuf = sprintf(buffer,"\e[%i;%iH\e[%im%c", i + 1, o + 1, wColor[o][i], cScreen[o][i]); + // Position Color Origin is at 1,1 + write (STDOUT_FILENO, buffer, cbuf); + + #endif //__linux__ + } + bChanged[o][i] = false; + } + } + return 0; +} + +int cRender::clear(bool _forceReRender) +{ + for (int i = 0; i < sizeY; i++) { + for (int o = 0; o < sizeX; o++) { + if(((cScreen[o][i] == cBackound) && (wColor[o][i] == wBackColor)) && !_forceReRender) + bChanged[o][i] = false; + else + { + cScreen[o][i] = cBackound; + wColor[o][i] = wBackColor; + bChanged[o][i] = true; + } + } + } + return 0; +} + +int cRender::clear() +{ + return clear(false); +} + + +#ifdef _WIN32 +//Source: http://www.cplusplus.com/forum/windows/121444/ +int cRender::SetConsoleWindowSize(int x, int y) +{ + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + + if (h == INVALID_HANDLE_VALUE) + return 1; + + CONSOLE_SCREEN_BUFFER_INFO bufferInfo; + if (!GetConsoleScreenBufferInfo(h, &bufferInfo)) + return 1; + + SMALL_RECT& winInfo = bufferInfo.srWindow; + COORD windowSize = { winInfo.Right - winInfo.Left + 1, winInfo.Bottom - winInfo.Top + 1 }; + + if (windowSize.X > x || windowSize.Y > y) + { + // window size needs to be adjusted before the buffer size can be reduced. + SMALL_RECT info = + { + 0, 0, + x < windowSize.X ? x - 1 : windowSize.X - 1, + y < windowSize.Y ? y - 1 : windowSize.Y - 1 + }; + + if (!SetConsoleWindowInfo(h, TRUE, &info)) + return 1; + } + + COORD size = { x, y }; + if (!SetConsoleScreenBufferSize(h, size)) + return 1; + + SMALL_RECT info = { 0, 0, x - 1, y - 1 }; + if (!SetConsoleWindowInfo(h, TRUE, &info)) + return 1; +} +#endif //_WIN32 + +int cRender::getLastError() +{ + return iLastError; +} + +#ifdef _WIN32 +void cRender::gotoxy( int x, int y ) +{ + COORD p = { x, y }; + SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), p ); +} + +#elif __linux__ + +sPos cRender::getConsoleWindowSize() +{ + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + return {w.ws_col, w.ws_row}; +} + +void cRender::setAlternateBufferScreen(bool _enable) +{ + _enable ? write (STDOUT_FILENO, "\e[?47h", 6):write (STDOUT_FILENO, "\e[?47l", 6); +} + +void cRender::setConsoleCursor(bool _enable) +{ + _enable ? write (STDOUT_FILENO, "\e[?25h", 6) : write (STDOUT_FILENO, "\e[?25l", 6); +} + +#endif // __linux__ + +void cRender::setBufferSize(sPos _size) +{ + if(_size.x == sizeX && _size.y == sizeY) + return; + + if(sizeX!=0 && sizeY!=0) //resize. delete first + { + for (int i = 0; i < sizeX; i++) { + free(cScreen[i]); + free(wColor[i]); + free(bChanged[i]); + } + + free(cScreen); + free(wColor); + free(bChanged); + } + + sizeX = _size.x; + sizeY = _size.y; + + //Initialize 2D array + cScreen = (char**)malloc(sizeof *cScreen * sizeX); + for (int i = 0; i < sizeX; i++) + cScreen[i] = (char*)malloc(sizeof *cScreen[i] * sizeY); + + wColor = (WORD**)malloc(sizeof *wColor * sizeX); + for (int i = 0; i < sizeX; i++) + wColor[i] = (WORD*)malloc(sizeof *wColor[i] * sizeY); + + bChanged = (bool**)malloc(sizeof *bChanged * sizeX); + for (int i = 0; i < sizeX; i++) + bChanged[i] = (bool*)malloc(sizeof *bChanged[i] * sizeY); + + clear(true); +} + +sPos cRender::getSize() +{ + return {sizeX, sizeY}; +} + +void cRender::forceReRender() +{ + for (int i = 0; i < sizeY; i++) { + for (int o = 0; o < sizeX; o++) { + bChanged[o][i] = true; + } + } +} + +void cRender::setConsoleEcho(bool _enable) +{ +#ifdef WIN32 + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + DWORD mode; + GetConsoleMode(hStdin, &mode); + + if( !_enable ) + mode &= ~ENABLE_ECHO_INPUT; + else + mode |= ENABLE_ECHO_INPUT; + + SetConsoleMode(hStdin, mode ); + +#elif __linux__ + /*struct termios tty; + tcgetattr(STDIN_FILENO, &tty); + if( !_enable ) + tty.c_lflag &= ~ECHO; + else + tty.c_lflag |= ECHO; + + (void) tcsetattr(STDIN_FILENO, TCSANOW, &tty);*/ + + _enable ? write (STDOUT_FILENO, "\e[?8h", 5) : write (STDOUT_FILENO, "\e[?8l", 5); +#endif //__linux__ +} diff --git a/src/cRender.h b/src/cRender.h new file mode 100644 index 0000000..415db6e --- /dev/null +++ b/src/cRender.h @@ -0,0 +1,177 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __linux__ + #include + #include + + typedef int WORD; +#elif _WIN32 + #include +#else + #error "Platforn not supported" +#endif + +//errors +#define _OK_ 0 +#define _ERR_ 1 +#define _ERR_COORDINATES_INVALID_ 2 +#define _ERR_RENDER_BLOCKED_BY_CHILD_ 3 +#define _ERR_SCREEN_TOO_SMALL_ 4 + +#define _COLLISION_ 255 + +//Colors +#ifdef _WIN32 + #define _COL_BLACK 0x00 + #define _COL_BLUE 0x01 + #define _COL_GREEN 0x02 + #define _COL_YELLOW 0x0E + #define _COL_RED 0x04 + #define _COL_WHITE 0x0F + #define _COL_DARK_WHITE 0x07 + #define _COL_INTENSITY 0x08 + #define _COL_DEFAULT 0xFF +#elif __linux__ + #define _COL_BLACK 30 + #define _COL_BLUE 34 + #define _COL_GREEN 32 + #define _COL_YELLOW 33 + #define _COL_RED 31 + #define _COL_WHITE 37 + #define _COL_DEFAULT 0 + + //Linux Specific + #define _COL_BOLD 1 + #define _COL_BOLD_OFF 21 + #define _COL_UNDERLINE 4 + #define _COL_UNDERLINE_OFF 24 + #define _COL_INVERSE 7 + #define _COL_INVERSE_OFF 27 +#endif + +using namespace std; + +struct sPos +{ + int x; + int y; +}; +/** cRender manages a framebuffer the size of the console (window) it is run in. +* +* puts console in alternate screen mode +*/ +class cRender +{ +public: + /** Constructor + * sets cBackround[][] to _backround & wColor[][] to _color + * Resizes console window for Windows + * Sets Size to Console Window Size for Linux. Writes Error for _sx or _sy smaller than Screen. Get by getLastError() + */ + cRender(char _backound, WORD _color, int _sx, int _sy); + + virtual ~cRender(); + + /** Draws _c @ _pos in screenbuffer + * Returns _COLLOSION_ if _pos is already set to !cBackround && _overrideCollision isnt set + */ + int drawPoint(char _c, sPos _pos, bool _overrideCollision, WORD _color); + + /** draws Line from _pos1 to _pos2 in screenbuffer + * x Value of pos1 MUSTNT exceed the x value of pos2! + */ + int drawLine(char _c, sPos _pos1, sPos _pos2, bool _overrideCollision, WORD _color); + + /** Draws Text _s @ _pos in screenbuffer + * First char is @ _pos + */ + int drawText(string _s, sPos _pos, WORD _color); + + /** writes rectangle to screenbuffer + * x Value of pos1 MUSTNT exceed the x value of pos2! + */ + int drawRectangle(char _border, char _fill, sPos _pos1, sPos _pos2, WORD _borderColor, WORD _fillColor); + + /** Dumps screenbuffer to stdout + * prints changed pixels + */ + int render(void); + + /** clears cScreen + wColor + * for _forceReRender == true, the bChanged[][] is set to true to force Re-Render of whole Screen + * clear(void) calls clear(_forceReRender = false) + */ + int clear(); + int clear(bool _forceReRender); + + /** Returns last Error that was not returnable + */ + int getLastError(); + + /** Returns size of screenbuffer + */ + sPos getSize(); + + +protected: + /** Empty Constructor for being inheritable + */ + cRender(); + + /** Sets screenbuffer size + * Uses sizeX, sizeY to determine previous screenbuffer size. Do NOT change sizeX, sizeY manually!! + */ + void setBufferSize(sPos _size); + + bool bBlockRender; + //* Used by children to block render function + + char **cScreen; + //* Pixel Map + WORD **wColor; + //* Color Map + bool **bChanged; + //* Pixel Change Map + + char cBackound; + //* Default backround + WORD wBackColor; + //* Default backround color + int sizeX, sizeY; + //* Size of screen array + +#ifdef _WIN32 + HANDLE hstdout; + CONSOLE_SCREEN_BUFFER_INFO csbi; +#endif + + WORD wDefColor; + //* Default Color + + int iLastError; + +private: +#ifdef _WIN32 + int SetConsoleWindowSize(int x, int y); + //Slightly adapted from: http://www.cplusplus.com/forum/windows/121444/ + + void gotoxy( int x, int y ); +#endif + + void forceReRender(); + + void setConsoleEcho(bool _enable); +#ifdef __linux__ + sPos getConsoleWindowSize(); + + void setConsoleCursor(bool _enable); + + void setAlternateBufferScreen(bool _enable); +#endif +}; diff --git a/src/cWiremesh.cpp b/src/cWiremesh.cpp new file mode 100644 index 0000000..62fee0d --- /dev/null +++ b/src/cWiremesh.cpp @@ -0,0 +1,132 @@ +#include "cWiremesh.h" + +cWiremesh::cWiremesh() : position({0,0,0}), angle({0,0,0}) { } + +cWiremesh::~cWiremesh() { } + +void cWiremesh::addVector(sCoord3d _origin, sCoord3d _vector, char _char, WORD _color) +{ + vectors.push_back(sVector{_origin, _vector, _char, _color}); +} + +void cWiremesh::rotate(sCoord3d _val) +{ + angle = angle + _val; +} + +void cWiremesh::reset() +{ + vectors.clear(); +} + +void cWiremesh::write(cRender *_render) +{ + if(!_render) + return; + + sPos porigin = _render->getSize(); + sCoord3d origin = {porigin.x / 2, porigin.y / 2, 0}; + + for(long unsigned int i = 0; i < vectors.size(); i++) + { + sCoord3d vorigin = applyRotation(vectors[i].origin, angle); + sCoord3d vdirection = applyRotation(vectors[i].direction, angle); + + _render->drawLine(vectors[i].c, + translate(vorigin + position, origin), + translate(vorigin + vdirection + position, origin), + true, vectors[i].color); + } +} + +sPos cWiremesh::translate(sCoord3d _coord, sCoord3d _origin) +{ + sPos ret; + + ret.x = (int)((float)_coord.x - ((float)_coord.z / (float)_DEPTH * (float)(_coord.x - _origin.x))); + ret.y = (int)((float)_coord.y - ((float)_coord.z / (float)_DEPTH * (float)(_coord.y - _origin.y))); + + return ret; +} + +sCoord3d cWiremesh::getPosition() +{ + return position; +} + +void cWiremesh::setPosition(int _x, int _y, int _z) +{ + position = {_x, _y, _z}; +} + +void cWiremesh::setPosition(sCoord3d _pos) +{ + position = _pos; +} + +void cWiremesh::scale(float _scalar) +{ + for(unsigned long int i = 0; i < vectors.size(); i++) + { + vectors[i].origin.x = (int)((float)vectors[i].origin.x * _scalar); + vectors[i].origin.y = (int)((float)vectors[i].origin.y * _scalar); + vectors[i].origin.z = (int)((float)vectors[i].origin.z * _scalar); + + vectors[i].direction.x = (int)((float)vectors[i].direction.x * _scalar); + vectors[i].direction.y = (int)((float)vectors[i].direction.y * _scalar); + vectors[i].direction.z = (int)((float)vectors[i].direction.z * _scalar); + } +} + +sCoord3d cWiremesh::applyRotation(sCoord3d _vector, sCoord3d _angle) +{ + sCoord3d ret = _vector; + + //Perform some algebra-magic + //couldn't be bothered to implement or use a matrix class + + if(_angle.x) + { + float rads = (float)_angle.x * PI / 180.0; + + ret.y = (int)( + (float)_vector.y * cos(rads) - + (float)_vector.z * sin(rads) + ); + ret.z = (int)( + (float)_vector.y * sin(rads) + + (float)_vector.z * cos(rads) + ); + } + if(_angle.y) + { + float rads = (float)_angle.y * PI / 180.0; + sCoord3d tmp = ret; + + ret.x = (int)( + (float)tmp.x * cos(rads) + + (float)tmp.z * sin(rads) + ); + + ret.z = (int)( + - (float)tmp.x * sin(rads) + + (float)tmp.z * cos(rads) + ); + } + if(_angle.z) + { + float rads = (float)_angle.z * PI / 180.0; + sCoord3d tmp = ret; + + ret.x = (int) ( + (float)tmp.x * cos(rads) - + (float)tmp.y * sin(rads) + ); + ret.y = (int) ( + (float)tmp.x * sin(rads) + + (float)tmp.y * cos(rads) + ); + } + + return ret; +} diff --git a/src/cWiremesh.h b/src/cWiremesh.h new file mode 100644 index 0000000..82d3bd6 --- /dev/null +++ b/src/cWiremesh.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +#include "cRender.h" + +#define _DEPTH 99 +#define PI 3.14159265 + +struct sCoord3d +{ + int x; + int y; + int z; + + sCoord3d operator+(sCoord3d p) + { + sCoord3d ret; + ret.x = x + p.x; + ret.y = y + p.y; + ret.z = z + p.z; + return ret; + } + + sCoord3d operator-(sCoord3d p) + { + sCoord3d ret; + ret.x = x - p.x; + ret.y = y - p.y; + ret.z = z - p.z; + return ret; + } +}; + +struct sVector +{ + sCoord3d origin; + sCoord3d direction; + + char c; + WORD color; +}; + +/** +* cWiremesh stores 3D objects as multiple vectors. it can write itself on a cRender framebuffer. +*/ +class cWiremesh +{ +public: + + cWiremesh(); + + virtual ~cWiremesh(); + + /** + * Add a line from _origin to (_origin + _vector) in 3D space. + */ + void addVector(sCoord3d _origin, sCoord3d _vector, char _char, WORD _color); + + /** + * Rotates by (x,y,z) degrees around the corresponding axis. + * Rotation is stored seperatately from original vectors while they stay untouched to prevent growing rounding errors by repeated rotation. + * + * Rotation is applied relative to the origin of this wiremsh. + */ + void rotate(sCoord3d _val); + + /** + * Scales by _scalar. The scalar is directly applied to all vectors. Be wary of growing rounding errors! + */ + void scale(float _scalar); + + sCoord3d getPosition(); + + void setPosition(int _x, int _y, int _z); + + void setPosition(sCoord3d _pos); + + /** + * clear this wiremesh + */ + void reset(); + + /** + * Translates wiremesh into 2D space after applying rotation to each vector. + * The vanishing point is set to the center of _render with depth _DEPTH. Alter _DEPTH to achieve optimal resultst. + */ + void write(cRender *_render); + +protected: + +private: + + sPos translate(sCoord3d _coord, sCoord3d _origin); + + sCoord3d applyRotation(sCoord3d _vector, sCoord3d _angle); + + sCoord3d position; + + sCoord3d angle; + + std::vector vectors; +}; -- cgit v1.2.3