As the title suggests I'm having an issue with my windows console application in c++. So far I've created a class, Console, to represent all of the functions I repeatedly use to construct a windows console.
I implemented a line drawing algorithm and had up to 500 fps when filling the screen with magenta characters and drawing a line in white.
However, I implemented a triangle drawing algorithm next (just three line drawing calls consecutively) and was surprised to find out that the frame rate dropped to about 20. I removed this code again but the bad frame rate persisted. I really don't know why this has happend because I've essentially ended up not changing anything.
For reference, here is the Console code (without header):
Console::Console(int width, int height):
m_handle(GetStdHandle(STD_OUTPUT_HANDLE)),
m_width(width),
m_height(height),
m_screen({ 0, 0, 1, 1 }),
m_title(L"Demo"),
m_buffer(new CHAR_INFO[(size_t)m_width * (size_t)m_height])
{
memset(m_buffer, 0, sizeof(CHAR_INFO) * m_width * m_height);
}
Console::~Console()
{
if (m_buffer)
delete[] m_buffer;
}
int Console::Construct(int char_w, int char_h)
{
if (m_handle == INVALID_HANDLE_VALUE)
return BAD_HANDLE;
if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
return WINDOW_INFO_ERROR;
COORD screen_coord = { (short)m_width, (short)m_height };
if (!SetConsoleScreenBufferSize(m_handle, screen_coord))
return SCREEN_BUFFER_SIZE_ERROR;
CONSOLE_FONT_INFOEX cinfo;
cinfo.cbSize = sizeof(cinfo);
cinfo.dwFontSize.X = (short)char_w;
cinfo.dwFontSize.Y = (short)char_h;
cinfo.FontFamily = FF_DONTCARE;
cinfo.FontWeight = FW_NORMAL;
cinfo.nFont = 0;
wcscpy_s(cinfo.FaceName, L"Consolas");
if (!SetCurrentConsoleFontEx(m_handle, false, &cinfo))
return CONSOLE_FONT_ERROR;
CONSOLE_SCREEN_BUFFER_INFO cbuffinfo;
if (!GetConsoleScreenBufferInfo(m_handle, &cbuffinfo))
return GET_BUFFER_INFO_ERROR;
if (cbuffinfo.dwMaximumWindowSize.X < m_width)
return HORIZONTAL_SIZE_TOO_LARGE_ERROR;
if (cbuffinfo.dwMaximumWindowSize.Y < m_height)
return VERTICAL_SIZE_TOO_LARGE_ERROR;
m_screen = { 0, 0, (short)m_width - 1, (short)m_height - 1 };
if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
return WINDOW_INFO_ERROR;
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)HandleClose, true))
return CLOSE_HANDLER_ERROR;
return OK;
}
void Console::Start()
{
active = true;
std::thread t(&Console::DoLoop, this);
t.join();
}
void Console::DoLoop()
{
if (!OnCreate())
active = false;
std::chrono::high_resolution_clock::time_point t1, t2;
t1 = std::chrono::high_resolution_clock::now();
t2 = t1;
while (active)
{
t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> diff = t2 - t1;
t1 = t2;
float dt = diff.count();
if (!OnUpdate(dt))
active = false;
wchar_t title_buff[256];
swprintf_s(title_buff, L"Console Application - %s - FPS: %3.2f", m_title.c_str(), 1.0f / dt);
SetConsoleTitle(title_buff);
WriteConsoleOutput(m_handle, m_buffer, { (short)m_width, (short)m_height }, { 0, 0 }, &m_screen);
}
finished.notify_one();
}
void Console::Fill(int x, int y, short glyph, short color)
{
if (x < 0 || y < 0 || x >= m_width || y >= m_height) return;
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
void Console::Fill(int x1, int y1, int x2, int y2, short glyph, short color)
{
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x1 >= m_width) return;
if (y1 >= m_height) return;
if (x2 >= m_width) x2 = m_width - 1;
if (y2 >= m_height) y2 = m_height - 1;
if (x2 < 0) return;
if (y2 < 0) return;
for (int x = x1; x <= x2; x++)
{
for (int y = y1; y <= y2; y++)
{
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
}
}
void Console::Clear(short glyph, short color)
{
for (int x = 0; x < m_width; x++)
{
for (int y = 0; y < m_height; y++)
{
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
}
}
void Console::Line(int x1, int y1, int x2, int y2, short glyph, short color)
{
int dx = x2 - x1;
int dy = y2 - y1;
int adx = dx > 0 ? dx : -dx;
int ady = dy > 0 ? dy : -dy;
int dy2 = dy + dy;
int dx2 = dx + dx;
int adx2 = dx2 > 0 ? dx2 : -dx2;
int ady2 = dy2 > 0 ? dy2 : -dy2;
if (adx > ady)
{
if (x1 > x2)
{
int x = x1;
x1 = x2;
x2 = x;
dx = -dx;
dx2 = -dx2;
int y = y1;
y1 = y2;
y2 = y;
dy = -dy;
dy2 = -dy2;
}
int sy = dy > 0 ? 1 : dy < 0 ? -1 : 0;
int err = ady;
for (int x = x1, y = y1; x <= x2; x++)
{
Fill(x, y, glyph, color);
err += ady2;
if (err > adx2)
{
err -= adx2;
y += sy;
}
}
}
else
{
if (y1 > y2)
{
int x = x1;
x1 = x2;
x2 = x;
dx = -dx;
dx2 = -dx2;
int y = y1;
y1 = y2;
y2 = y;
dy = -dy;
dy2 = -dy2;
}
int sx = dx > 0 ? 1 : dx < 0 ? -1 : 0;
int err = adx;
for (int x = x1, y = y1; y <= y2; y++)
{
Fill(x, y, glyph, color);
err += adx2;
if (err > ady2)
{
err -= ady2;
x += sx;
}
}
}
}
void Console::Triangle(int x1, int y1, int x2, int y2, int x3, int y3, short glyph, short color)
{
Line(x1, y1, x2, y2, glyph, color);
Line(x2, y2, x3, y3, glyph, color);
Line(x3, y3, x1, y1, glyph, color);
}
BOOL Console::HandleClose(DWORD evt)
{
if (evt == CTRL_CLOSE_EVENT)
{
active = false;
std::unique_lock<std::mutex> ul(lock);
finished.wait(ul);
}
return true;
}
Here is the very short main code:
class Game : public Console
{
public:
Game(int width, int height):
Console(width, height)
{
}
bool OnCreate() override
{
return true;
}
bool OnUpdate(float dt) override
{
Clear(GLYPH_SOLID, FG_BLACK);
//Triangle(20, 20, 300, 40, 150, 200);
return true;
}
};
int main()
{
Game game(400, 300);
int err = game.Construct(2, 2);
if (err == Console::OK)
game.Start();
}
The error constants are just defined integer codes in the header file.
If any additional code is needed please let me know. Thanks in advance!
EDIT:
Related
I'm making my own graphics library and I have a Sprite class which is just an array of colors with a width and a height. I can set a pixel on the sprite by changing its color value. How can I draw a line on a sprite given a start position and an end position?
class Sprite
{
public:
Sprite();
public:
LongUtils::Pixel GetPixel(int32_t x, int32_t y) const;
bool SetPixel(int32_t x, int32_t y, Pixel p);
LongUtils::Pixel* GetData(); // return the *data
LongUtils::Pixel* GetBlockData(uint32_t x, uint32_t y, uint32_t w, uint32_t h);
private:
LongUtils::Pixel* data = nullptr;
int32_t width = 0;
int32_t height = 0;
};
Use something like Bresenham's line algorithm. Here's an example:
void Line( float x1, float y1, float x2, float y2, const Color& color )
{
// Bresenham's line algorithm
const bool steep = (fabs(y2 - y1) > fabs(x2 - x1));
if(steep)
{
std::swap(x1, y1);
std::swap(x2, y2);
}
if(x1 > x2)
{
std::swap(x1, x2);
std::swap(y1, y2);
}
const float dx = x2 - x1;
const float dy = fabs(y2 - y1);
float error = dx / 2.0f;
const int ystep = (y1 < y2) ? 1 : -1;
int y = (int)y1;
const int maxX = (int)x2;
for(int x=(int)x1; x<=maxX; x++)
{
if(steep)
{
SetPixel(y,x, color);
}
else
{
SetPixel(x,y, color);
}
error -= dy;
if(error < 0)
{
y += ystep;
error += dx;
}
}
}
I'm trying to draw a cube using software rendering. So I have I'm drawing a cube out of triangles. So I render a solid triangle. The cube is rendered successfully, but there are flicker while rotating it.
here is my solid triangle filling, can somebody notice a problem in the algorithm?
void Rasterizer::DrawSolidTriangle(int x0, int y0, int x1, int y1, int x2, int y2, Color&color)
{
// Sort our points into order of y
// 0 top
// 2 middle
// 1 bottom
if (y1 < y0)
{
swap(y1, y0);
swap(x1, x0);
}
if (y2 < y0)
{
swap(y2, y0);
swap(x2, x0);
}
if (y1 < y2)
{
swap(y2, y1);
swap(x2, x1);
}
float xl_edge = (float)x0; // left edge
float xr_edge = (float)x0; // right edge
float dxldy;
float dxrdy;
float dxdy1 = (float)(x2 - x0) / (y2 - y0);
float dxdy2 = (float)(x1 - x0) / (y1 - y0);
if (dxdy1 < dxdy2)
{
dxldy = dxdy1;
dxrdy = dxdy2;
}
else
{
dxldy = dxdy2;
dxrdy = dxdy1;
}
// Top of the triangle
for (int y = y0; y<y2; y++)
{
for (int x = xl_edge; x<xr_edge; x++)
{
SetPixel((unsigned int)x, (unsigned int)y, color);
}//end for loop x
xl_edge = xl_edge + dxldy;
xr_edge = xr_edge + dxrdy;
}// end for loop y
// Bottom half of the triangle
if (dxdy1 < dxdy2)
{
dxldy = (float)(x2 - x1) / (y2 - y1);
}
else
{
dxrdy = (float)(x2 - x1) / (y2 - y1);
}
for (int y = y2; y<y1; y++)
{
for (int x = xl_edge; x<xr_edge; x++)
{
SetPixel((unsigned int)x, (unsigned int)y, color);
}//end for loop x
xl_edge = xl_edge + dxldy;
xr_edge = xr_edge + dxrdy;
}// end for loop y
}
here is a screenshot of the problem, when rotating the cube so slow it does appear here:
I have this code which has a Bresenham() function that draws a simple line and then when the user clicks the right button, the bresenham line(s) is saved as a bmp image. But the problem that I have is that I don´t know how to calculate the width and the height of the bmpInfoHeader structure. And I also don´t know how to load the image so that it can save it.
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<gl/glut.h>
typedef struct
{
uint32_t Size;
uint16_t field;
uint32_t offset;
}bmpFileHeader;
typedef struct
{
uint32_t headerSize;
uint32_t width;
uint32_t hight;
uint16_t planes;
uint16_t dcm;
uint32_t compression;
uint32_t imageSize;
uint32_t bmpx;
uint32_t bmpy;
uint32_t colors;
uint32_t impColors;
}bmpInfoHeader;
float a[90000];
int x0=0,y0=0,xf=0,yf=0;
int print=0;
FILE *bmp = NULL;
bmpInfoHeader *infoHeader;
bmpFileHeader *fileHeader;
void init(void);
void putpixel(int x,int y);
void Bresenham(int x0,int y0,int x1,int y1);
void display(void);
void onMotion(int x,int y);
void onMouse(int button, int e, int x, int y);
void Save();
void onPassive(int x,int y);
void createFileHeader(bmpFileHeader *fileHeader);
void createInfoHeader(bmpInfoHeader *infoHeader);
void init(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0);
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0, 300.0, 0.0,300.0);
}
void putpixel(int x,int y)
{
glColor3f(0.0, 0.0,0.0);
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
if(print==1)
glDrawPixels(300,300,GL_RGB,GL_UNSIGNED_BYTE,a);
Bresenham(x0,y0,xf,yf);
glFlush();
}
void Bresenham(int x0,int y0,int x1,int y1)
{
int dx,dy,p,x,y,px = 1,py = 1,twoDy_Dx,twoDy,i;
glColor3f(0.0,0.0,1.0);
dx = x1-x0;
dy = y1-y0;
if(dx < 0)
dx = dx*-1;
if(dy < 0)
dy = dy*-1;
if(x1 < x0)
px = -1;
if(y1 < y0)
py = -1;
x = x0;
y = y0;
if( dx > dy )
{
putpixel(x,y);
p = 2 * dy - dx;
twoDy_Dx = 2 * ( dy - dx );
twoDy = 2 * dy;
for( i = 0; i < dx; i++ )
{
if( p >= 0 )
{
y += py;
p += twoDy_Dx;
}
else
p += twoDy;
x += px;
putpixel(x,y);
}
}
else
{
putpixel(x,y);
p = 2*dx - dy;
twoDy_Dx = 2 * ( dx - dy );
twoDy = 2*dx;
for( i = 0; i < dy; i++ )
{
if( p >= 0 )
{
x += px;
p += twoDy_Dx;
}
else
p += twoDy;
y += py;
putpixel(x,y);
}
}
glFlush();
}
void onMotion(int x,int y)
{
xf = x;
yf = 300-y;
glutPostRedisplay();
}
void onMouse(int button, int e, int x, int y)
{
if((button == GLUT_LEFT_BUTTON) && (e == GLUT_DOWN))
{
print = 1;
x0 = xf = x;
y0 = yf = abs(300-y);
}
else if((button == GLUT_LEFT_BUTTON) && (e == GLUT_UP))
print = 0;
else if((button == GLUT_RIGHT_BUTTON) && (e == GLUT_UP))
{
createFileHeader(fileHeader);
createInfoHeader(infoHeader);
Save();
}
}
void onPassive(int x,int y)
{
glReadPixels(0.0,0.0,300.0,300.0,GL_RGB,GL_UNSIGNED_BYTE,a);
Bresenham(x0,y0,xf,yf);
}
void createInfoHeader(bmpInfoHeader *infoHeader)
{
infoHeader = (bmpInfoHeader*)malloc(sizeof(bmpInfoHeader));
infoHeader->headerSize = sizeof(bmpInfoHeader);
infoHeader->width = ???;
infoHeader->hight = ???;
infoHeader->planes = 1;
infoHeader->dcm = 24;
infoHeader->compression = BI_RGB;
infoHeader->imageSize =;
infoHeader->bmpx = 0;
infoHeader->bmpy = 0;
infoHeader->colors = 0;
infoHeader->impColors = 0;
}
void createFileHeader(bmpFileHeader *fileHeader)
{
fileHeader = (bmpFileHeader*)malloc(sizeof(bmpFileHeader));
fileHeader->Size = 0;
fileHeader->field = 0;
fileHeader->offset = sizeof(bmpFileHeader)+sizeof(bmpInfoHeader);
}
void Save()
{
uint16_t type;
if((bmp = fopen("practica no. 7.bmp","wt"))!= NULL)
{
type = 0x4D42;
fwrite(&type,sizeof(type),1,bmp);
fwrite(&fileHeader,sizeof(bmpFileHeader),1,bmp);
fwrite(&infoHeader,sizeof(bmpInfoHeader),1,bmp);
}
else
printf("No se pudo crear fichero");
}
int main()
{
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(300, 300);
glutInitWindowPosition(100, 100);
glutCreateWindow();
init();
glutDisplayFunc(display);
glutMotionFunc(onMotion);
glutMouseFunc(onMouse);
glutPassiveMotionFunc(onPassive);
glutMainLoop();
}
As I can expose from your code, the image is 300 x 300. It means, your bmpInfoHeader.width and bmpInfoHeader.height should be 300, if you keep the scale. Otherwise, multiply window width with scale.
The other part of your question has answer in How to use GLUT to render to a file
First question, I have tried to calculate the expression, di+1=di+2*Δy−2*Δx(yi+1−yi) for the four quadrants. Irrespective of the quadrant, the expression was found to be the same, including signs.
Am I right, or, there has been some mistakes in my calculations (hence, I am wrong)?
Second question, if this expression is only applicable for the first octet, how can I apply this to other octets? To me, there is no way to determine which octet I am working on. Coz, the value of m always represent two opposite octets. For example, if 0<m<1, it represents 1st and 5th octet. Right?
Thirdly, how can we determine the initial/starting value of di?
#include <iostream>
#include "utils.h"
void BresenhamLine(double x1, double y1, double x2, double y2, int color)
{
if(x1>x2 || y1>y2)
{
Swap(x1, x2);
Swap(y1, y2);
}
double x = x1;
double y = y1;
double dx = x2 - x1;
double dy = y2 - y1;
double dt = 2 * (dy - dx);
double ds = 2 * dy;
double d = 2*dy - dx;
PlotPixel(x, y, color);
if(dx>=dy)
{
while(x<=x2)
{
x++;
if(d<0)
{
d = d + ds;
}
else
{
y++;
d = d + dt;
}
PlotPixel(x, y, color);
}
}
else
{
while(y<=y2)
{
y++;
if(d<0)
{
x++;
d = d + dt;
}
else
{
d = d + ds;
}
PlotPixel(x, y, color);
}
}
}
int main()
{
int gm = DETECT;
int gd = DETECT;
initgraph(&gm, &gd, "");
double x1 = 0;
double y1 = 0;
double r = 50;
double x2 = 0;
double y2 = 0;
double signx = 0;
double signy = 0;
for(int theta=0 ; theta<=360 ; theta++)
{
x2 = r * cos(DegreeToRad((double) theta));
y2 = r * sin(DegreeToRad((double) theta));
x1 = 5 * cos(DegreeToRad((double) theta));
y1 = 5 * sin(DegreeToRad((double) theta));
BresenhamLine(x1, y1, x2, y2, YELLOW);
}
getch();
closegraph();
return 0;
}
The lines that go through 2nd and 4th quadrant are not showing up.
How to fix that with some minor changes in my code?
With this input: x1: 100 y1: -100 x2: -100 y2: 100
this logic:
if(x1>x2 || y1>y2)
{
Swap(x1, x2);
Swap(y1, y2);
}
fails.
This page is a good place to start. It shows code as well for 1 of the octants:
http://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresenh.html
I think you need to swap the x if x1 > x2 and swap the y if y1 > y2 but not swap both if only 1 of those is true.
The external links section of the Wikipedia page contains several links to ready-made implementations that you can study.
Try this:
void BresenhamLine( double x1, double y1, double x2, double y2, int color )
{
const bool steep = (std::abs(y2 - y1) > std::abs(x2 - x1));
if(steep)
{
std::swap(x1, y1);
std::swap(x2, y2);
}
if(x1 > x2)
{
std::swap(x1, x2);
std::swap(y1, y2);
}
double dx = x2 - x1;
double dy = std::abs(y2 - y1);
double error = dx / 2;
int ystep = (y1 < y2) ? 1 : -1;
int y = (int)y1;
int maxX = (int)x2;
for(int x=(int)x1; x<maxX; x++)
{
if(steep)
{
PlotPixel(y, x, color);
}
else
{
PlotPixel(x, y, color);
}
error -= dy;
if(error < 0)
{
y += ystep;
error += dx;
}
}
}
I got this by slightly modifying the code here:http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#C.2B.2B
What i want is that, I have info of 2 points, the starting x,y and mid point x,y and i need to find end line like until some kind of border, like window
here is what I do:
//function for calculating the end point from one location, to specific end location
//like a bullet moving forward in a line
//x,y start location(mouse), x2,y2(rect point location one of the 4) mid point, qx,qy end point(shadow or triangle draw location)
void screenEnd(int x, int y, int x2, int y2, int*qx,int*qy)
{
x = x2-x;
y = y2-y;
float tx = x2,ty = y2;
float result = atan2((float)y,(float)x) * 180 / PI;
float tempx = cos ( result * PI / 180.0 );
float tempy = sin ( result * PI / 180.0 );
bool check = true;
//this part needs optimization
while(check)
{
if(tx < 0|| ty < 0|| tx > 1280 || ty > 720)
{
check = false;
}
else
{
tx += tempx;
ty += tempy;
}
}
*qx = tx;
*qy = ty;
}
what I do is just increase point until it reaches the end.
Is there any way faster?
A classic window clipping task.
Consider a parametric equation where p is the point (x,y).
p(0) = x, y
p(0.5) = x2, y2
p(1) = x+2*(x2-x), y + 2*(y2-y)
p(t) = p(0) + t*(p(1) - p(0))
clip window = 0,0 to 720, 1280 (suspect you really want 719,1279)
The segment to draw initially ranges from t=0.0 to t=1.0. The segment is tested against each of the 4 sides of the bounding box, potentially reducing the t range. Maybe even eliminating all together.
Follows is some old code, enough to get you going.
#include <math.h>
int cliptest(int dz, int z, double *t0, double *t1) {
if (dz < 0) {
double t = ((double) z) / dz;
if (t > *t1)
return 0;
if (t > *t0)
*t0 = t;
} else if (dz > 0) {
double t = ((double) z) / dz;
if (t < *t0)
return 0;
if (t < *t1)
*t1 = t;
} else {
if (z < 0)
return 0;
}
return 1;
}
int clipper(int *px0, int *py0, int *px1, int *py1, int minx, int miny,
int maxx, int maxy) {
double t0, t1;
int dx, dy;
t0 = 0.0;
t1 = 1.0;
dy = *py1 - *py0;
dx = *px1 - *px0;
if (cliptest(-dx, *px0 - minx, &t0, &t1)
&& cliptest(dx, maxx - *px0, &t0, &t1)
&& cliptest(-dy, *py0 - miny, &t0, &t1)
&& cliptest(dy, maxy - *py0, &t0, &t1)) {
if (t1 < 1.0) {
*px1 = round(*px0 + t1*dx);
*py1 = round(*py0 + t1*dy);
}
if (t0 > 0.0) {
*px0 = round(*px0 + t0*dx);
*py0 = round(*py0 + t0*dy);
}
return 1;
}
return 0;
}
int x0 = x;
int y0 = y;
int x1 = x + 2*(x2-x); // Form end point
int y1 = x + 2*(y2-y);
if (clipper(&x0, &y0, &x1, &y1, 0, 0, 720, 1280))
Draw(x0, y0, x1, y2);
else
Handle_LineTotallyClippedOut();