Rendering an array of the same sprite at different locations - c++

I am trying to recreate Space Invaders and I am having trouble with getting my invader sprite to display in a row instead of on top of each other. I have a STRUCT called Sprite that contains the x position, y position, width, height, and color and I am passing that information into a custom function that draws the sprite. Everything works if I am just creating one sprite. But I want to create multiple ones (say 5 for testing purposes). So I created an array of the Sprite structure and in a For-Loop I tried to change the x position of each element within the array and than use the custom function to draw an element of the array at each iteration of the loop but for some reason I am not getting the proper result. I can see in the debugger that the x position is in fact being changed but everything is still getting drawn on top of each other I think.
struct INVADER
{
int xPos = 100;
int yPos;
int width;
int height;
D3DCOLOR color;
INVADER()
{
xPos;
yPos;
width = 64;
height = 64;
color = D3DCOLOR_XRGB(255, 255, 255);
}
};
INVADER invaderArmy[5];
for (int index = 0; index < 5; index++)
{
//invaderArmy->xPos = 100;
invaderArmy[index].xPos *= index;
Draw_And_Rotate_Sprite(invaderImage, invaderArmy[index].xPos);
}

I'm only wiriting here because i dont have 50 rep to comment.
Shouldn't the parameter from the called Draw_And_Rotate_Sprite() function be invaderArmy[index] instead of invaderArmy[index].xPos ?
Also there isn't a problem i can see in the code presented. Please show us the Draw_And_Rotate_Sprite() function definition.

Related

C++: how do you update player height based on height of terrain?

I used to have a project that used a grey scale image to set the height of vertices in a simple flat mesh, which resulted in nice looking height mapped terrain. However, I have since converted my project to C++ and can no longer use the help of BufferedImage and such to use the old grey scale approach for creating height-mapped terrain from a flat mesh.
Because of this my C++ project now uses a .obj file for the terrain, but I am finding it very difficult to update the player/camera height as the user walks around the terrain, instead I just float through everything as the player height never changes or only does so very frequently (so I know the height is at least being updated, just not correctly).
Bellow is a small sample of code that does the actual updating of the player height based on the terrain.obj file, it does this by storing all the vertices of the .obj file and comparing each of their x and z components with the x and z components of the player and if there is a match then set the y value of the player to the current vertexes y position:
Vector3f *playerPos = freeMoveObjects[0]->GetParent()->GetTransform()->GetPos();
float playerXPos = playerPos->GetX();
float playerZPos = playerPos->GetZ();
int playerXPosInt = (int)playerXPos;
int playerZPosInt = (int)playerZPos;
for (Vector3f currentVector : meshObjects[0]->getMeshVertices()) {
int meshHeightXInt = (int)currentVector.GetX();
int meshHeightZInt = (int)currentVector.GetZ();
if (meshHeightXInt == playerXPosInt & meshHeightZInt == playerZPosInt){//currentVector.GetX() <= playerXPos & currentVector.GetZ() <= playerZPos) {
freeMoveObjects[0]->GetParent()->GetTransform()->GetPos()->SetY(currentVector.GetY());
}
}
What you're doing is very inefficient right now; you shouldn't have to loop through all vertices if you already have a heightmap which you can use for updating the player's position.
Why don't you store the heightmap in a pseudo-2d-array, like this:
class HeightMap {
private:
int width;
int height;
std::unique_ptr<int[]> data = std::make_unique<int[]>(width * height);
public:
HeightMap(int width, int height) : width{width}, height{height} {}
// you can default copy, move constructors and assignments
int heightAt(int x, int y) {
return heightmap[x + y * width];
}
}
Using heightAt you now have efficient random access to the data in the map. Thanks to unique_ptr you don't need to manage any memory manually and other constructors can be defaulted.
Note that you need to handle errors like x or y being out of range manually.

Trying to draw rectangles stored in an array, but only one rectangle appears?

My code is here:
As stated above, I'm trying to draw a series of bars across the screen with different x positions and I've stored them in arrays. It seems the code only draws 1 rectangle, even though I've checked and each bar has a different x position so I'm sure its an issue with how I'm drawing the objects but it feels right.
I've also made a similar program with vectors using the same loop for drawing but with .at(i) instead which does work but this oddly does not.
I've been trying to figure this out for a while and I'm tired now so please help, point out my errors... etc...
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(640, 640), "Square", sf::Style::Close | sf::Style::Resize);
sf::RectangleShape bar[64] = {sf::RectangleShape(sf::Vector2f( (window.getSize().x)/64.0f ,100.0f))};
// creates 64 bars of equal width
for (int i = 0; i < 64; i++)
{
bar[i].setFillColor(sf::Color(0, 0, 255, 255));
bar[i].setPosition( 10*i , (window.getSize().y)/2);
// sets bars x position to shift over for every bar
}
bar[3].setPosition(600, 300);
// just a test doesn't render even though it should
while (window.isOpen())
{
//////////////////////////////////////////////////
window.clear(sf::Color(130, 130, 150, 255));
for (int i = 0; i < 64; i++)
{
window.draw(bar[i]);
}
window.display();
/////////////////////////////////////////////////
}```
I cut out the rest of the code as the rest works and really has nothing to do with the code for simplicity sake
I want it to render out rectangles across the screen but it only displays one and I can't figure out why?
sf::RectangleShape has default ctor:
sf::RectangleShape::RectangleShape ( const Vector2f & size = Vector2f(0, 0) )
You have defined rectangle's size only for the first one, other 63 have default size (0,0).
You can copy/paste your rect definition in raw array, or use std::vector and call ctor which takes value and number of elements:
std::vector<sf::RectangleShape> bars( 64, // num of elems
sf::RectangleShape( sf::Vector2f(window.getSize().x/64.0f ,100.0f) ) );
Another solution is to call setSize in every iteration of loop (just like with setFillColor, setPosition etc).

Dynamic Body gets stuck in static body after flipping

I have a dynamic body with many polygon shapes for my game character. In order to turn back the game character I flip vertices using this code:
void Box2dManager::flipFixtures(bool horizzontally, b2Body* physBody)
{
b2Fixture* fix = physBody->GetFixtureList();
while(fix)
{
b2Shape* shape = fix->GetShape();
if(shape->GetType()== b2Shape::e_polygon)
{
// flipping x or y coordinates
b2PolygonShape* ps = (b2PolygonShape*)shape;
for(int i=0; i < ps->GetVertexCount(); i++)
horizzontally ? ps->m_vertices[i].x *= -1 : ps->m_vertices[i].y *= -1;
// revert the vertices (no need after Box2D 2.3.0 as polygon creation computes the convex hull)
b2Vec2* reVert = new b2Vec2[ps->GetVertexCount()];
int j = ps->GetVertexCount() -1;
for(int i=0; i<ps->GetVertexCount();i++)
reVert[i] = ps->m_vertices[j--];
ps->Set(&reVert[0], ps->GetVertexCount());
}
fix = fix->GetNext();
}
}
I also have static edge shapes as walls. And it happens, that when I flip the character, sometimes vertices of the same polygon appear to be in different sides of the same static edge shape. As a result my character sticks to the wall (it is being trapped in the static edge shape). How I should handle this situation?

How to solve performance issues with QPixmap (large drawingjobs)?

I am coding a small map editor (with rectangle tiles) and I need a way to draw a large amount of images OR one big image. The application is simple: You draw images on an empty screen with your mouse and when you are finished you can save it. A tile consists of a small image.
I tried out several solutions to display the tiles:
Each tile has its own QGraphicsItem (This works until you have a
1000x1000 map)
Each tile gets drawn on one big QPixmap (This means a very large image. Example: Map with 1000x100 and each tile has a size of 32x32 means that the QPixmap has a size of 32000x32000. This is a problem for QPainter.)
The current solution: Iterate through width & height of the TileLayer and draw each single tile with painter->drawPixmap(). The paint() method of my TileLayer looks like this:
void TileLayerGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,QWidget* /*widget*/)
{
painter->setClipRect(option->exposedRect);
int m_width=m_layer->getSize().width();
int m_height=m_layer->getSize().height();
for(int i=0;i<m_width;i++)
{
for(int j=0;j<(m_height);j++)
{
Tile* thetile=m_layer->getTile(i,j);
if(thetile==NULL)continue;
const QRectF target(thetile->getLayerPos().x()*thetile->getSize().width(),thetile->getLayerPos().y()*thetile->getSize().height(),thetile->getSize().width(),thetile->getSize().height());
const QRectF source(0, 0, thetile->getSize().width(), thetile->getSize().height());
painter->drawImage(target,*thetile->getImage(),source);
}
}}
This works for small maps with 100x100 or even 1000x100 tiles. But not for 1000x1000. The whole application begins to lag, this is of course because I have a for loop that is extremely expensive. To make my tool useful I need to be able to make at least 1000x1000 tilemaps without lags. Does anyone have an idea what I can do? How should I represent the tiles?
Update:
I changed the following: Only maps that exceed the window size of the minimap will be drawn with drawing single pixels for each tile. This is my render function now:
void RectangleRenderer::renderMinimapImage(QPainter* painter, TileMap* map,QSize windowSize)
{
for(int i=0;i<map->getLayers().size();i++)
{
TileLayer* currLayer=map->getLayers().at(i);
//if the layer is small draw it completly
if(windowSize.width()>currLayer->getSize().width()&&windowSize.height()>currLayer->getSize().height())
{
...
}
else // This is the part where the map is so big that only some pixels are drawn!
{
painter->fillRect(0,0,windowSize.width(),windowSize.height(),QBrush(QColor(map->MapColor)));
for(float i=0;i<windowSize.width();i++)
for(float j=0;j<windowSize.height();j++)
{
float tX=i/windowSize.width();
float tY=j/windowSize.height();
float pX=lerp(i,currLayer->getSize().width(),tX);
float pY=lerp(j,currLayer->getSize().height(),tY);
Tile* thetile=currLayer->getTile((int)pX,(int)pY);
if(thetile==NULL)continue;
QRgb pixelcolor=thetile->getImage()->toImage().pixel(thetile->getSize().width()/2,thetile->getSize().height()/2);
QPen pen;
pen.setColor(QColor::fromRgb(pixelcolor));
painter->setPen(pen);
painter->drawPoint(i,j);
}
}
}
}
This works not correct, however it is pretty fast. The problem is my lerp(linear interpolation) function to get the correct tiles to draw a pixel from.
Does anyone have a better solution to get the correct tiles while I iterate through the minimap pixels? At the moment I use linear interpolation between 0 and the maximum size of the tilemap and it does not work correctly.
UPDATE 2
//currLayer->getSize() returns how many tiles are in the map
// currLayer->getTileSize() returns how big each tile is (32 pixels width for example)
int raw_width = currLayer->getSize().width()*currLayer->getTileSize().width();
int raw_height = currLayer->getSize().height()*currLayer->getTileSize().height();
int desired_width = windowSize.width();
int desired_height = windowSize.height();
int calculated_width = 0;
int calculated_height = 0;
// if dealing with a one dimensional image buffer, this ensures
// the rows come out clean, and you don't lose a pixel occasionally
desired_width -= desired_width%2;
// http://qt-project.org/doc/qt-5/qt.html#AspectRatioMode-enum
// Qt::KeepAspectRatio, and the offset can be used for centering
qreal ratio_x = (qreal)desired_width / raw_width;
qreal ratio_y = (qreal)desired_height / raw_height;
qreal floating_factor = 1;
QPointF offset;
if(ratio_x < ratio_y)
{
floating_factor = ratio_x;
calculated_height = raw_height*ratio_x;
calculated_width = desired_width;
offset = QPointF(0, (qreal)(desired_height - calculated_height)/2);
}
else
{
floating_factor = ratio_y;
calculated_width = raw_width*ratio_y;
calculated_height = desired_height;
offset = QPointF((qreal)(desired_width - calculated_width)/2,0);
}
for (int r = 0; r < calculated_height; r++)
{
for (int c = 0; c < calculated_width; c++)
{
//trying to do the following: use your code to get the desired pixel. Then divide that number by the size of the tile to get the correct pixel
Tile* thetile=currLayer->getTile((int)((r * floating_factor)*raw_width)/currLayer->getTileSize().width(),(int)(((c * floating_factor)*raw_height)/currLayer->getTileSize().height()));
if(thetile==NULL)continue;
QRgb pixelcolor=thetile->getImage()->toImage().pixel(thetile->getSize().width()/2,thetile->getSize().height()/2);
QPen pen;
pen.setColor(QColor::fromRgb(pixelcolor));
painter->setPen(pen);
painter->drawPoint(r,c);
}
}
Trying to reverse engineer the example code, but it still does not work correctly.
Update 3
I tried (update 1) with linear interpolation again. And while I looked at the code I saw the error:
float pX=lerp(i,currLayer->getSize().width(),tX);
float pY=lerp(j,currLayer->getSize().height(),tY);
should be:
float pX=lerp(0,currLayer->getSize().width(),tX);
float pY=lerp(0,currLayer->getSize().height(),tY);
That's it. Now it works.
This shows how to do it properly. You use a level of detail (lod) variable to determine how to draw the elements that are currently visible on the screen, based on their zoom.
http://qt-project.org/doc/qt-5/qtwidgets-graphicsview-chip-example.html
Also don't iterate through all the elements that could be visible, but only go through the ones that have changed, and of those, only the ones that are currently visible.
Your next option to use is some other manual caching, so you don't have to repeatedly iterate through O(n^2) constantly.
If you can't optimize it for QGraphicsView/QGraphicsScene... then OpenGL is probably what you may want to look into. It can do a lot of the drawing and caching directly on the graphics card so you don't have to worry about it as much.
UPDATE:
Pushing changes to QImage on a worker thread can let you cache, and update a cache, while leaving the rest of your program responsive, and then you use a Queued connection to get back on the GUI thread to draw the QImage as a Pixmap.
QGraphicsView will let you know which tiles are visible if you ask nicely:
http://qt-project.org/doc/qt-5/qgraphicsview.html#items-5
UPDATE 2:
http://qt-project.org/doc/qt-5/qtwidgets-graphicsview-chip-chip-cpp.html
You may need to adjust the range of zooming out that is allowed on the project to test this feature...
Under where it has
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
Add in something like:
if(lod < 0.05)
{
// using some sort of row/col value to know which ones to not draw...
// This below would only draw 1/3 of the rows and 1/3 of the column
// speeding up the redraw by about 9x.
if(row%3 != 0 || col%3 != 0)
return;// don't do any painting, return
}
UPDATE 3:
Decimation Example:
// How to decimate an image to any size, properly
// aka fast scaling
int raw_width = 1000;
int raw_height = 1000;
int desired_width = 300;
int desired_height = 200;
int calculated_width = 0;
int calculated_height = 0;
// if dealing with a one dimensional image buffer, this ensures
// the rows come out clean, and you don't lose a pixel occasionally
desired_width -= desired_width%2;
// http://qt-project.org/doc/qt-5/qt.html#AspectRatioMode-enum
// Qt::KeepAspectRatio, and the offset can be used for centering
qreal ratio_x = (qreal)desired_width / raw_width();
qreal ratio_y = (qreal)desired_height / raw_height();
qreal floating_factor = 1;
QPointF offset;
if(ratio_x < ratio_y)
{
floating_factor = ratio_x;
calculated_height = raw_height*ratio_x;
calculated_width = desired_width;
offset = QPointF(0, (qreal)(desired_height - calculated_height)/2);
}
else
{
floating_factor = ratio_y;
calculated_width = raw_width*ratio_y;
calculated_height = desired_height;
offset = QPointF((qreal)(desired_width - calculated_width)/2);
}
for (int r = 0; r < calculated_height; r++)
{
for (int c = 0; c < calculated_width; c++)
{
pixel[r][c] = raw_pixel[(int)(r * floating_factor)*raw_width][(int)(c * floating_factor)];
}
}
Hope that helps.

CPU Ray Casting

I'm attempting ray casting an octree on the CPU (I know the GPU is better, but I'm unable to get that working at this time, I believe my octree texture is created incorrectly).
I understand what needs to be done, and so far I cast a ray for each pixel, and check if that ray intersects any nodes within the octree. If it does and the node is not a leaf node, I check if the ray intersects it's child nodes. I keep doing this until a leaf node is hit. Once a leaf node is hit, I get the colour for that node.
My question is, what is the best way to draw this to the screen? Currently im storing the colours in an array and drawing them with glDrawPixels, but this does not produce correct results, with gaps in the renderings, as well as the projection been wrong (I am using glRasterPos3fv).
Edit: Here is some code so far, it needs cleaning up, sorry. I have omitted the octree ray casting code as I'm not sure it's needed, but I will post if it'll help :)
void Draw(Vector cameraPosition, Vector cameraLookAt)
{
// Calculate the right Vector
Vector rightVector = Cross(cameraLookAt, Vector(0, 1, 0));
// Set up the screen plane starting X & Y positions
float screenPlaneX, screenPlaneY;
screenPlaneX = cameraPosition.x() - ( ( WINDOWWIDTH / 2) * rightVector.x());
screenPlaneY = cameraPosition.y() + ( (float)WINDOWHEIGHT / 2);
float deltaX, deltaY;
deltaX = 1;
deltaY = 1;
int currentX, currentY, index = 0;
Vector origin, direction;
origin = cameraPosition;
vector<Vector4<int>> colours(WINDOWWIDTH * WINDOWHEIGHT);
currentY = screenPlaneY;
Vector4<int> colour;
for (int y = 0; y < WINDOWHEIGHT; y++)
{
// Set the current pixel along x to be the left most pixel
// on the image plane
currentX = screenPlaneX;
for (int x = 0; x < WINDOWWIDTH; x++)
{
// default colour is black
colour = Vector4<int>(0, 0, 0, 0);
// Cast the ray into the current pixel. Set the length of the ray to be 200
direction = Vector(currentX, currentY, cameraPosition.z() + ( cameraLookAt.z() * 200 ) ) - origin;
direction.normalize();
// Cast the ray against the octree and store the resultant colour in the array
colours[index] = RayCast(origin, direction, rootNode, colour);
// Move to next pixel in the plane
currentX += deltaX;
// increase colour arry index postion
index++;
}
// Move to next row in the image plane
currentY -= deltaY;
}
// Set the colours for the array
SetFinalImage(colours);
// Load array to 0 0 0 to set the raster position to (0, 0, 0)
GLfloat *v = new GLfloat[3];
v[0] = 0.0f;
v[1] = 0.0f;
v[2] = 0.0f;
// Set the raster position and pass the array of colours to drawPixels
glRasterPos3fv(v);
glDrawPixels(WINDOWWIDTH, WINDOWHEIGHT, GL_RGBA, GL_FLOAT, finalImage);
}
void SetFinalImage(vector<Vector4<int>> colours)
{
// The array is a 2D array, with the first dimension
// set to the size of the window (WINDOW_WIDTH * WINDOW_HEIGHT)
// Second dimension stores the rgba values for each pizel
for (int i = 0; i < colours.size(); i++)
{
finalImage[i][0] = (float)colours[i].r;
finalImage[i][1] = (float)colours[i].g;
finalImage[i][2] = (float)colours[i].b;
finalImage[i][3] = (float)colours[i].a;
}
}
Your pixel drawing code looks okay. But I'm not sure that your RayCasting routines are correct. When I wrote my raytracer, I had a bug that caused horizontal artifacts in on the screen, but it was related to rounding errors in the render code.
I would try this...create a result set of vector<Vector4<int>> where the colors are all red. Now render that to the screen. If it looks correct, then the opengl routines are correct. Divide and conquer is always a good debugging method.
Here's a question though....why are you using Vector4 when later on you write the image as GL_FLOAT? I'm not seeing any int->float conversion here....
You problem may be in your 3DDDA (octree raycaster), and specifically with adaptive termination. It results from the quantisation of rays into gridcell form, that causes certain octree nodes which lie slightly behind foreground nodes (i.e. of a higher z depth) and which thus should be partly visible & partly occluded, to not be rendered at all. The smaller your voxels are, the less noticeable this will be.
There is a very easy way to test whether this is the problem -- comment out the adaptive termination line(s) in your 3DDDA and see if you still get the same gap artifacts.