Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I have programmed a simple top-down car driving game that resembles the first GTA, on the GameBoyAdvance. I have used only vector graphics for doing so, and the GBA doesn't handle it very well; basically with 5 pedestrian instances it lags.
I don't have much experience in optimizing code, so I would like to know if there are some tweaks I could make to my code in order to make it run faster, not depending on the fact that it runs on a GBA.
The collision testing I use is SAT (separating axis theorem) as I've found it to be the easisest one for collision check with vector graphics; the game is very simple itself.
Here is the code:
/*
GTA Vector City
Author: Alberto Taiuti
Version: 2.0
*/
#include "Global.h"
#include <string.h>
#include <cstdio>
#include "font.h"
#include "CVector2D.h"
#include "CCar.h"
#include "CPed.h"
#include <vector>
#include <memory>
/* GLOBAL VARIABLES */
void CheckCollisionsRect(CRect *test_a, CRect *test_b);
std::vector<CVector2D> PrepVectors(CRect *shape);
CVector2D GetMinMaxShape(std::vector<CVector2D> vect_shape, CVector2D axis);
void CheckCollisionRectVSPoint(CRect *test_a, CVector2D *point);
/* MAIN */
// The entry point for the game
int main()
{
// Frame counter
uint32_t frames = 0;
// Previous & current buttons states
static uint16_t prev_buttons = 0, cur_buttons = 0;
// Put the display into bitmap mode 3, and enable background 2.
REG_DISPCNT = MODE4 | BG2_ENABLE;
// Set up the palette.
SetPaletteBG(BLACK, RGB(0, 0, 0)); // black
SetPaletteBG(WHITE, RGB(31, 31, 31)); // white
SetPaletteBG(GREY, RGB(15, 15, 15)); // grey
SetPaletteBG(RED, RGB(31, 0, 0)); // red
SetPaletteBG(GREEN, RGB(0, 31, 0)); // green
SetPaletteBG(BLUE, RGB(0, 0, 31)); // blue
// Create car instance
CCar *car = new CCar(50,50);
// Create a building
/*CRect *test_b = new CRect(100.0f, 100.0f, 30, 30);
CRect *test_c = new CRect(120.0f, 120.0f, 30, 30);
CRect *test_d = new CRect(30.0f, 30.0f, 30, 30);*/
// Pedestrian instances
int ped_number = 10; // Number of pedestrians
std::vector<CPed*> peds; // Ped. entities container (made of smart pointers)
typedef std::vector<CPed*>::iterator p_itor; // Iterator
for(int i = 1; i <= ped_number; i++)
{
peds.push_back(new CPed(i, RED, 2.0f));
}
// Check whether the game is over
bool end = false;
// Main loop
while (!end)
{
// Flip the screen
FlipBuffers();
//Clear the screen
ClearScreen8(BLACK);
// Update frame counter
frames ++;
// Get the current state of the buttons.
cur_buttons = REG_KEYINPUT;
// Handle Input
car->HandleInput(prev_buttons, cur_buttons);
// Logic
car->Update();
for(int i = 0; i < ped_number; i++)
{
peds[i]->Update();
}
for(int i = 0; i < ped_number; i++)
{
CheckCollisionRectVSPoint(car->shape, peds[i]->pos);
}
/*CheckCollisionsRect(car->shape, test_b);
CheckCollisionsRect(car->shape, test_c);
CheckCollisionsRect(car->shape, test_d);
CheckCollisionRectVSPoint(car->shape, test_ped->pos);*/
// Render
car->Draw();
for(int i = 0; i < ped_number; i++)
{
peds[i]->Draw();
}
/*test_b->DrawFrame8(GREEN);
test_c->DrawFrame8(WHITE);
test_d->DrawFrame8(RED);
test_ped->Draw();*/
prev_buttons = cur_buttons;
// VSync
WaitVSync();
}
// Free memory
delete car;
//delete test_b; delete test_c; delete test_d;
//delete test_ped;
for(p_itor itor = peds.begin(); itor != peds.end(); itor ++)// Delete pedestrians
{
peds.erase(itor);
}
return 0;
}
void CheckCollisionsRect(CRect *test_a, CRect *test_b)
{
// If the two shapes are close enough, check for collision, otherways skip and save calculations to the CPU
//if((pow((test_a->points[0]->x - test_b->points[0]->x), 2) + pow((test_a->points[0]->y - test_b->points[0]->y), 2)) < 25.0f)
{
// Prepare the normals for both shapes
std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();
std::vector<CVector2D> normals_b = test_b->GetNormalsAsArray();
// Create two containers for holding the various vectors used for collision check
std::vector<CVector2D> vect_test_a = PrepVectors(test_a);
std::vector<CVector2D> vect_test_b = PrepVectors(test_b);
// Get the min and max vectors for each shape for each projection (needed for SAT)
CVector2D result_P1 = GetMinMaxShape(vect_test_a, normals_a[1]); //
CVector2D result_P2 = GetMinMaxShape(vect_test_b, normals_a[1]); //
// If the two objects are not colliding
if(result_P1.y < result_P2.x || result_P2.y < result_P1.x)
{
return;
}
CVector2D result_Q1 = GetMinMaxShape(vect_test_a, normals_a[0]); // First axis couple
CVector2D result_Q2 = GetMinMaxShape(vect_test_b, normals_a[0]); //
if(result_Q1.y < result_Q2.x || result_Q2.y < result_Q1.x)
{
return;
}
CVector2D result_R1 = GetMinMaxShape(vect_test_a, normals_b[1]); //
CVector2D result_R2 = GetMinMaxShape(vect_test_b, normals_b[1]); //
if(result_R1.y < result_R2.x || result_R2.y < result_R1.x)
{
return;
}
CVector2D result_S1 = GetMinMaxShape(vect_test_a, normals_b[0]); // Second axis couple
CVector2D result_S2 = GetMinMaxShape(vect_test_b, normals_b[0]); //
if(result_S1.y < result_S2.x || result_S2.y < result_S1.x)
{
return;
}
// Do something
PlotPixel8(200, 10, WHITE);
PlotPixel8(200, 11, WHITE);
PlotPixel8(200, 12, WHITE);
}
}
// Check for collision between an OOBB and a point
void CheckCollisionRectVSPoint(CRect *test_a, CVector2D *point)
{
// Prepare the normals for the shape
std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();
// Create a container for holding the various vectors used for collision check
std::vector<CVector2D> vect_test_a = PrepVectors(test_a);
// Get projections for the OOBB (needed for SAT)
CVector2D result_P1 = GetMinMaxShape(vect_test_a, normals_a[1]);
float result_point = point->DotProduct(normals_a[1]);
// If the two objects are not colliding on this axis
if(result_P1.y < result_point || result_point < result_P1.x)
{
return;
}
CVector2D result_Q1 = GetMinMaxShape(vect_test_a, normals_a[0]);
result_point = point->DotProduct(normals_a[0]);
// If the two objects are not colliding on this axis
if(result_Q1.y < result_point || result_point < result_Q1.x)
{
return;
}
// Do something
PlotPixel8(200, 10, WHITE);
PlotPixel8(200, 11, WHITE);
PlotPixel8(200, 12, WHITE);
}
// Returns a container with projection vectors for a given shape
std::vector<CVector2D> PrepVectors(CRect *shape)
{
std::vector<CVector2D> vect;
// Create vectors for projection and load them into the arrays
for( uint16_t i=0; i < 5; i++)
{
// Get global position of vectors and then add them to the array
vect.push_back(shape->GetVectorGlobal(i));
}
return vect;
}
CVector2D GetMinMaxShape(std::vector<CVector2D> vect_shape, CVector2D axis)
{
// Set initial minimum and maximum for shape's projection vectors
float min_proj = vect_shape[1].DotProduct(axis);
float max_proj = vect_shape[1].DotProduct(axis);
// Calculate max and min projection vectors by iterating along all of the corners
for(uint16_t i = 2; i < vect_shape.size(); i ++)
{
float current_proj = vect_shape[i].DotProduct(axis);
// Select minimum projection on axis
if(current_proj < min_proj) // If current projection is smaller than the minimum one
min_proj = current_proj;
// Select maximum projection on axis
if(current_proj > max_proj) // If current projection is greater than the minimum one
max_proj = current_proj;
}
return (CVector2D(min_proj, max_proj)); // Return a vector2D as it is a handy way for returning a couple of values
}
Many thanks in advance to everyone and sorry for the messy code!
I gave it a really quick reading so I may have overlooked something. Well, there are obvious tips for improving performance such as passing vectors to functions by reference. Using prefix incrementation instead of postfix is also a good habit. These two rules are definitely nothing like 'premature optimization the, root of ...'. Do not delete pedestrians one by one but use std::vector::clear(). And If you claim you use smart pointers, you shoud, because it seems you have memory leak because you did not delete the pedestrian pointers. And use const keyword whereever possible. Once you make the obvious correction, and the speed is still not satisfactory, then you need to use profiler.
And read something about optimization, here for example: http://www.agner.org/optimize/optimizing_cpp.pdf
One thing leaps out at me (apart from the continuous passing of vectors by value rather than reference, which will be incredibly costly!)
In you collision detection, you're seeing if the car hits each pedestrian
for(int i = 0; i < ped_number; i++)
{
CheckCollisionRectVSPoint(car->shape, peds[i]->pos);
}
Then, in the collision detector, you're repeating a lot of the same processing on the car shape every time:-
// Prepare the normals for both shapes
std::vector<CVector2D> normals_a = test_a->GetNormalsAsArray();
// Create two containers for holding the various vectors used for collision check
std::vector<CVector2D> vect_test_a = PrepVectors(test_a);
.. etc...
You should rework that loop to create the normals etc for the car just once, and then reuse the results for each check against a pedestrian.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I'm programming a Breakout game in C++. I'm having a HUGE problem that's preventing me from giving the game multi-ball functionality. I think it has something to do with the destructor. Have a look:
for loop for the balls (Driver.cpp):
for (Ball& b : balls) { // Loops over all balls
(...)
// Collision for when you miss
if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
balls.erase(balls.begin() + b.getID()); // Wipe the ball out of memory to make room (Troublesome line)
Ball::addToID(-1); // Shift the ball ID to assign to the next ball back one
(...)
}
And I get this error:
Debug Error!
Program: Breakout.exe
abort() has been called
(Press Retry to debug the application)
Do you know why this mysterious crash is happening? Or more importantly, a fix for it?
Here's a replicable piece of code to help:
Driver.cpp:
#include <vector>
#include <allegro5\allegro.h>
#include "Ball.h"
using namespace std;
vector<Ball> balls(0); // Pay attention to this line
const POS WIDTH = 1600, HEIGHT = 900;
int main {
while (running) {
if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls[Ball::getIDtoAssign()].setYSpeed(5);
}
for (Ball& b : balls) { // Pay attention to this loop
b.draw(); // This line is what's crashing.
b.move();
(...)
// Collision for when you miss
balls.erase(
remove_if(balls.begin(), balls.end(),
[=](Ball& b) {
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
}
}
}
}
return 0;
}
Ball.h:
#pragma once
#include <allegro5\allegro_primitives.h>
using namespace std;
class Ball {
public:
Ball();
Ball(float x, float y, float w, float h);
~Ball();
void draw();
void move();
float getYPos();
void setYSpeed(float set);
private:
float xPos; // Horizontal position
float yPos; // Vertical position (upside down)
float width; // Sprite width
float height; // Sprite height
float xSpeed; // Horizontal speed
float ySpeed; // Vertical speed (inverted)
}
Ball.cpp:
#include "Ball.h"
short Ball::ballIDtoAssign = 0;
Ball::Ball() {
this->xPos = 0;
this->yPos = 0;
this->width = 0;
this->height = 0;
this->xSpeed = 0;
this->ySpeed = 0;
}
Ball::Ball(float x, float y, float w, float h) {
this->xPos = x;
this->yPos = y;
this->width = w;
this->height = h;
this->xSpeed = 0;
this->ySpeed = 0;
}
Ball::~Ball() {
// Destructor
}
void Ball::draw() {
al_draw_filled_rectangle(xPos, yPos, xPos + width, yPos + height, al_map_rgb(0xFF, 0xFF, 0xFF));
}
void Ball::move() {
xPos += xSpeed;
yPos += ySpeed;
}
float Ball::getYPos() {
return yPos;
}
void Ball::setYSpeed(float set) {
ySpeed = set;
}
You cannot modify a container while you are iterating through it with a range-for loop. You don't have access to the iterator that the loop uses internally, and erase() will invalidate that iterator.
You can use the container's iterators manually, paying attention to the new iterator that erase() returns, eg:
for(auto iter = balls.begin(); iter != balls.end(); ) { // Loops over all balls
Ball& b = *iter:
...
// Collision for when you miss
if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
...
iter = balls.erase(iter); // Wipe the ball out of memory to make room
}
else {
++iter;
}
}
Alternatively, use the erase-remove idiom via std::remove_if() instead:
balls.erase(
std::remove_if(balls.begin(), balls.end(),
[=](Ball &b){
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
UPDATE: now that you have posted more of your code, it is clear to see that you are trying to use ID numbers as indexes into the vector, but you are not implementing those IDs correctly, and they are completely unnecessary and should be eliminated.
The Ball::ballID member is never being assigned any value, so in this statement:
balls.erase(balls.begin() + b.getID()); // The troublesome line
Trying to erase() the result of balls.begin() + b.getID() causes undefined behavior since the iterator has an indeterminate value, thus you can end up trying to erase the wrong Ball object, or even an invalid Ball object (which is likely the root cause of your runtime crash).
Also, in this section of code:
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls[Ball::getIDtoAssign()].setYSpeed(5);
Ball::addToID(1);
Since you want to access the Ball object you just pushed, that code can be simplified to this:
balls.back().setYSpeed(5);
And I already gave you code further above to show you how to remove balls from the vector without using IDs.
So, there is need for an ID system at all.
With that said, try something more like this:
Driver.cpp:
#include <vector>
...
#include "Ball.h"
using namespace std;
vector<Ball> balls;
const POS WIDTH = 1600, HEIGHT = 900;
int main {
...
while (running) {
...
if (input.type == ALLEGRO_EVENT_TIMER) { // Runs at 60FPS
...
if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls.back().setYSpeed(5);
}
for (auto iter = balls.begin(); iter != balls.end(); ) {
Ball &b = *iter;
...
if (b.getYPos() > HEIGHT) { // Collision for when you miss
iter = balls.erase(iter);
}
else {
++iter;
}
}
/* alternatively:
for (Ball& b : balls) {
b.draw();
b.move();
}
balls.erase(
std::remove_if(balls.begin(), balls.end(),
[=](Ball &b){
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
*/
}
}
return 0;
}
Ball.h:
#pragma once
...
class Ball {
public:
...
// NO ID METHODS!
private:
...
// NO ID MEMBERS!
}
Ball.cpp:
#include "Ball.h"
...
// NO ID MEMBER/METHODS!
OK, so I managed to figure out why the program crashes. It was because I had the erase-remove inside the for loop which can cause all sorts of problems.
So I am working on this game and I want to implement a score system.
This is the first time I am working with Cocos2d.
I tried a couple of things but didn't really had succes.
I would like it if for each enemy that gets removed from the scene a int with the name score would be increased by one.
Do you guys have some suggestions ?
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
USING_NS_CC;
#define BACKGROUND_MUSIC_SFX "bee.mp3"
#define PEW_PEW_SFX "splatter.mp3"
// These bit masks define the physics categories; monster + projectile with two values to specify no type or all types, I use this to see what objects are allowed to collide
enum class PhysicsCategory {
None = 0,
Monster = (1 << 0), // 1
Projectile = (1 << 1), // 2
All = PhysicsCategory::Monster | PhysicsCategory::Projectile // 3
};
Scene* HelloWorld::createScene()
{
// 'scene' is an autorelease object, turn psysics on
auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setGravity(Vec2(0,0));
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_NONE);
// 'layer' is an autorelease object
auto layer = HelloWorld::create();
// add layer child to scene
scene->addChild(layer);
// return scene
return scene;
}
// initialize instance
bool HelloWorld::init()
{
// call the super class’s init method, if succeeds proceed HelloWorldScene‘s setup
if ( !Layer::init() ) {
return false;
}
// get window bounds using game Director singleton.
auto origin = Director::getInstance()->getVisibleOrigin();
auto winSize = Director::getInstance()->getVisibleSize();
// create a DrawNode to draw a green rectangle that fills the screen
auto background = DrawNode::create();
background->drawSolidRect(origin, winSize, Color4F(0.0,0.6,0.0,0.7));
this->addChild(background);
// create the player sprite, position 10% from the left edge of the screen, centered vertically
_player = Sprite::create("player.png");
_player->setPosition(Vec2(winSize.width * 0.1, winSize.height * 0.5));
this->addChild(_player);
// seed the random number generator
srand((unsigned int)time(nullptr));
this->schedule(schedule_selector(HelloWorld::addMonster), 1.5);
auto eventListener = EventListenerTouchOneByOne::create();
eventListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener, _player);
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegan, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this);
SimpleAudioEngine::getInstance()->playBackgroundMusic(BACKGROUND_MUSIC_SFX, true);
return true;
}
void HelloWorld::menuCloseCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
return;
#endif
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
void HelloWorld::addMonster(float dt) {
auto monster = Sprite::create("monster.png");
//Create a PhysicsBody for the sprite, Physics bodies represent the object in Cocos2d physics simulation.
auto monsterSize = monster->getContentSize();
auto physicsBody = PhysicsBody::createBox(Size(monsterSize.width , monsterSize.height),
PhysicsMaterial(0.1f, 1.0f, 0.0f));
//Set the sprite to be dynamic. physics engine will not apply forces to the bear.
physicsBody->setDynamic(true);
// set the category, collision and contact test bit masks:
physicsBody->setCategoryBitmask((int)PhysicsCategory::Monster);
physicsBody->setCollisionBitmask((int)PhysicsCategory::None);
physicsBody->setContactTestBitmask((int)PhysicsCategory::Projectile);
monster->setPhysicsBody(physicsBody);
// create a bear sprite and place it offscreen to the right, random y position
auto monsterContentSize = monster->getContentSize();
auto selfContentSize = this->getContentSize();
int minY = monsterContentSize.height/2;
int maxY = selfContentSize.height - monsterContentSize.height/2;
int rangeY = maxY - minY;
int randomY = (rand() % rangeY) + minY;
monster->setPosition(Vec2(selfContentSize.width + monsterContentSize.width/2, randomY));
this->addChild(monster);
// random duration for bear, each bear will move the same distance across the screen, varying the duration results in bears with random speeds.
int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int randomDuration = (rand() % rangeDuration) + minDuration;
// move the bear across the screen.
auto actionMove = MoveTo::create(randomDuration, Vec2(-monsterContentSize.width/2, randomY));
auto actionRemove = RemoveSelf::create();
monster->runAction(Sequence::create(actionMove,actionRemove, nullptr));
}
bool HelloWorld::onTouchBegan(Touch *touch, Event *unused_event) {
// 1 - get the _player object
//auto node = unused_event->getCurrentTarget();
// coordinate of the click within the scene’s coordinate system, calculate the offset of this point from the current position. vector math.
Vec2 touchLocation = touch->getLocation();
Vec2 offset = touchLocation - _player->getPosition();
// If offset‘s x value is negative, the player tries to shoot backwards,return without firing.
if (offset.x < 0) {
return true;
}
// Create projectile, add to screen
auto projectile = Sprite::create("projectile.png");
projectile->setPosition(_player->getPosition());
auto projectileSize = projectile->getContentSize();
auto physicsBody = PhysicsBody::createCircle(projectileSize.width/2 );
physicsBody->setDynamic(true);
physicsBody->setCategoryBitmask((int)PhysicsCategory::Projectile);
physicsBody->setCollisionBitmask((int)PhysicsCategory::None);
physicsBody->setContactTestBitmask((int)PhysicsCategory::Monster);
projectile->setPhysicsBody(physicsBody);
this->addChild(projectile);
// call normalize() to convert the offset into a unit vector, which is a vector of length 1. Multiplying that by 1000 that points in the direction of the user’s tap.
offset.normalize();
auto shootAmount = offset * 1000;
// Adding the vector to the projectile’s position gives the target position.
auto realDest = shootAmount + projectile->getPosition();
// move the projectile to the target position, remove after 5 sec.
auto actionMove = MoveTo::create(5.0f, realDest);
auto actionRemove = RemoveSelf::create();
projectile->runAction(Sequence::create(actionMove,actionRemove, nullptr));
SimpleAudioEngine::getInstance()->playEffect(PEW_PEW_SFX);
return true;
}
// PhysicsContact passed to this method is collision, remove at the end
bool HelloWorld::onContactBegan(PhysicsContact &contact) {
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
nodeA->removeFromParent();
nodeB->removeFromParent();
return true;
}
I'm not sure that I have fully understood your question, but it seems that you want to create a label to show the current score.
Of course you will need a variable to keep track of your score. Let's assume the variable is an integer and it's called m_score.
You will then need to create and add a Label to your scene. In your init() function add:
m_scoreLabel = Label::createWithTTF(std::to_string(m_score), "fonts/Arial.ttf", 36);
this->addChild(m_scoreLabel);
You will need m_scoreLabel to keep a reference to your Label so that you will be able to change its display text later on. Now, whenever you change the score, first update the value of m_score and then to update the text shown by the label call:
m_scoreLabel->setString(std::to_string(m_score));
I am currently working on a game and I want to know if there is any way of handling with the elements i am drawing . For example : if i draw in a loop 100 cubes , how can i show / hide the cube number 15 or 63 or n ... I thought that initializing elements in a list would work , but i didn't find any property of it that could help.
GLuint cube;
cube = glGenLists(1);
glNewList(cube,GL_COMPILE);
for(int i = -30; i < 3; i++) {
for(int j = -30; j < 3; j++) {
glPushMatrix();
glTranslatef(i*2.0,0,j * 2.0);
Dcube();
glPopMatrix();
}
}
glEndList();
//something like : glDeleteList(cube); but that only works with entire list not with individual objects..
You have a display list, very good. So now you're back to using your regular language primitives to simply call that function.
std::array<bool, 100> cubes;
std::fill(cubes.begin(), cubes.end(), true);
cubes[15] = false;
cubes[63] = false;
for (bool drawCube : cubes) {
if (drawCube) {
// move a bit, perhaps using glTranslate
glCallList(cube);
}
}
OpenGL isn't your statekeeper. It just draws what you tell it to, you're responsible for keeping your objects.
I've been trying to realize a mesh that has all face normals pointing outward.
In order to realize this, I load a mesh from a *.ctm file, then walk over all
triangles to determine the normal using a cross product and if the normal
is pointing to the negative z direction, I flip v1 and v2 (thus the normal orientation).
After this is done I save the result to a *.ctm file and view it with Meshlab.
The result in Meshlab still shows that normals are pointing in both positive and
negative z direction ( can be seen from the black triangles). Also when viewing
the normals in Meshlab they are really pointing backwards.
Can anyone give me some advice on how to solve this?
The source code for the normalization part is:
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud1 (new pcl::PointCloud<pcl::PointXYZRGBA> ());
pcl::fromROSMsg (meshFixed.cloud,*cloud1);for(std::vector<pcl::Vertices>::iterator it = meshFixed.polygons.begin(); it != meshFixed.polygons.end(); ++it)
{
alglib::real_2d_array v0;
double _v0[] = {cloud1->points[it->vertices[0]].x,cloud1->points[it->vertices[0]].y,cloud1->points[it->vertices[0]].z};
v0.setcontent(3,1,_v0); //3 rows, 1col
alglib::real_2d_array v1;
double _v1[] = {cloud1->points[it->vertices[1]].x,cloud1->points[it->vertices[1]].y,cloud1->points[it->vertices[1]].z};
v1.setcontent(3,1,_v1); //3 rows, 1col
alglib::real_2d_array v2;
double _v2[] = {cloud1->points[it->vertices[2]].x,cloud1->points[it->vertices[2]].y,cloud1->points[it->vertices[2]].z};
v2.setcontent(1,3,_v2); //3 rows, 1col
alglib::real_2d_array normal;
normal = cross(v1-v0,v2-v0);
//if z<0 change indices order v1->v2 and v2->v1
alglib::real_2d_array normalizedNormal;
if(normal[2][0]<0)
{
int index1,index2;
index1 = it->vertices[1];
index2 = it->vertices[2];
it->vertices[1] = index2;
it->vertices[2] = index1;
//make normal of length 1
double normalScaling = 1.0/sqrt(dot(normal,normal));
normal[0][0] = -1*normal[0][0];
normal[1][0] = -1*normal[1][0];
normal[2][0] = -1*normal[2][0];
normalizedNormal = normalScaling * normal;
}
else
{
//make normal of length 1
double normalScaling = 1.0/sqrt(dot(normal,normal));
normalizedNormal = normalScaling * normal;
}
//add to normal cloud
pcl::Normal pclNormalizedNormal;
pclNormalizedNormal.normal_x = normalizedNormal[0][0];
pclNormalizedNormal.normal_y = normalizedNormal[1][0];
pclNormalizedNormal.normal_z = normalizedNormal[2][0];
normalsFixed.push_back(pclNormalizedNormal);
}
The result from this code is:
I've found some code in the VCG library to orient the face and vertex normals.
After using this a large part of the mesh has correct face normals, but not all.
The new code:
// VCG library implementation
MyMesh m;
// Convert pcl::PolygonMesh to VCG MyMesh
m.Clear();
// Create temporary cloud in to have handy struct object
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud1 (new pcl::PointCloud<pcl::PointXYZRGBA> ());
pcl::fromROSMsg (meshFixed.cloud,*cloud1);
// Now convert the vertices to VCG MyMesh
int vertCount = cloud1->width*cloud1->height;
vcg::tri::Allocator<MyMesh>::AddVertices(m, vertCount);
for(unsigned int i=0;i<vertCount;++i)
m.vert[i].P()=vcg::Point3f(cloud1->points[i].x,cloud1->points[i].y,cloud1->points[i].z);
// Now convert the polygon indices to VCG MyMesh => make VCG faces..
int triCount = meshFixed.polygons.size();
if(triCount==1)
{
if(meshFixed.polygons[0].vertices[0]==0 && meshFixed.polygons[0].vertices[1]==0 && meshFixed.polygons[0].vertices[2]==0)
triCount=0;
}
Allocator<MyMesh>::AddFaces(m, triCount);
for(unsigned int i=0;i<triCount;++i)
{
m.face[i].V(0)=&m.vert[meshFixed.polygons[i].vertices[0]];
m.face[i].V(1)=&m.vert[meshFixed.polygons[i].vertices[1]];
m.face[i].V(2)=&m.vert[meshFixed.polygons[i].vertices[2]];
}
vcg::tri::UpdateBounding<MyMesh>::Box(m);
vcg::tri::UpdateNormal<MyMesh>::PerFace(m);
vcg::tri::UpdateNormal<MyMesh>::PerVertexNormalizedPerFace(m);
printf("Input mesh vn:%i fn:%i\n",m.VN(),m.FN());
// Start to flip all normals to outside
vcg::face::FFAdj<MyMesh>::FFAdj();
vcg::tri::UpdateTopology<MyMesh>::FaceFace(m);
bool oriented, orientable;
if ( vcg::tri::Clean<MyMesh>::CountNonManifoldEdgeFF(m)>0 ) {
std::cout << "Mesh has some not 2-manifold faces, Orientability requires manifoldness" << std::endl; // text
return; // can't continue, mesh can't be processed
}
vcg::tri::Clean<MyMesh>::OrientCoherentlyMesh(m, oriented,orientable);
vcg::tri::Clean<MyMesh>::FlipNormalOutside(m);
vcg::tri::Clean<MyMesh>::FlipMesh(m);
//vcg::tri::UpdateTopology<MyMesh>::FaceFace(m);
//vcg::tri::UpdateTopology<MyMesh>::TestFaceFace(m);
vcg::tri::UpdateNormal<MyMesh>::PerVertexNormalizedPerFace(m);
vcg::tri::UpdateNormal<MyMesh>::PerVertexFromCurrentFaceNormal(m);
// now convert VCG back to pcl::PolygonMesh
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGBA>);
cloud->is_dense = false;
cloud->width = vertCount;
cloud->height = 1;
cloud->points.resize (vertCount);
// Now fill the pointcloud of the mesh
for(int i=0; i<vertCount; i++)
{
cloud->points[i].x = m.vert[i].P()[0];
cloud->points[i].y = m.vert[i].P()[1];
cloud->points[i].z = m.vert[i].P()[2];
}
pcl::toROSMsg(*cloud,meshFixed.cloud);
std::vector<pcl::Vertices> polygons;
// Now fill the indices of the triangles/faces of the mesh
for(int i=0; i<triCount; i++)
{
pcl::Vertices vertices;
vertices.vertices.push_back(m.face[i].V(0)-&*m.vert.begin());
vertices.vertices.push_back(m.face[i].V(1)-&*m.vert.begin());
vertices.vertices.push_back(m.face[i].V(2)-&*m.vert.begin());
polygons.push_back(vertices);
}
meshFixed.polygons = polygons;
Which results in: (Meshlab still shows normals are facing both sides)
I finally solved the problem. So I'm still using VCG library. From the above new code I slightly updated the following section:
vcg::tri::Clean<MyMesh>::OrientCoherentlyMesh(m, oriented,orientable);
//vcg::tri::Clean<MyMesh>::FlipNormalOutside(m);
//vcg::tri::Clean<MyMesh>::FlipMesh(m);
//vcg::tri::UpdateTopology<MyMesh>::FaceFace(m);
//vcg::tri::UpdateTopology<MyMesh>::TestFaceFace(m);
vcg::tri::UpdateNormal<MyMesh>::PerVertexNormalizedPerFace(m);
vcg::tri::UpdateNormal<MyMesh>::PerVertexFromCurrentFaceNormal(m);
Now I've updated the vcg::tri::Clean<MyMesh>::OrientCoherentlyMesh() function in clean.h. Here the update is to orient the first polygon of a group correctly. Also after swapping the edge the normal of the face is calculated and updated.
static void OrientCoherentlyMesh(MeshType &m, bool &Oriented, bool &Orientable)
{
RequireFFAdjacency(m);
assert(&Oriented != &Orientable);
assert(m.face.back().FFp(0)); // This algorithms require FF topology initialized
Orientable = true;
Oriented = true;
tri::UpdateSelection<MeshType>::FaceClear(m);
std::stack<FacePointer> faces;
for (FaceIterator fi = m.face.begin(); fi != m.face.end(); ++fi)
{
if (!fi->IsD() && !fi->IsS())
{
// each face put in the stack is selected (and oriented)
fi->SetS();
// New section of code to orient the initial face correctly
if(fi->N()[2]>0.0)
{
face::SwapEdge<FaceType,true>(*fi, 0);
face::ComputeNormal(*fi);
}
// End of new code section.
faces.push(&(*fi));
// empty the stack
while (!faces.empty())
{
FacePointer fp = faces.top();
faces.pop();
// make consistently oriented the adjacent faces
for (int j = 0; j < 3; j++)
{
//get one of the adjacent face
FacePointer fpaux = fp->FFp(j);
int iaux = fp->FFi(j);
if (!fpaux->IsD() && fpaux != fp && face::IsManifold<FaceType>(*fp, j))
{
if (!CheckOrientation(*fpaux, iaux))
{
Oriented = false;
if (!fpaux->IsS())
{
face::SwapEdge<FaceType,true>(*fpaux, iaux);
// New line to update face normal
face::ComputeNormal(*fpaux);
// end of new section.
assert(CheckOrientation(*fpaux, iaux));
}
else
{
Orientable = false;
break;
}
}
// put the oriented face into the stack
if (!fpaux->IsS())
{
fpaux->SetS();
faces.push(fpaux);
}
}
}
}
}
if (!Orientable) break;
}
}
Besides I also updated the function bool CheckOrientation(FaceType &f, int z) to perform a calculation based on normal z-direction.
template <class FaceType>
bool CheckOrientation(FaceType &f, int z)
{
// Added next section to calculate the difference between normal z-directions
FaceType *original = f.FFp(z);
double nf2,ng2;
nf2=f.N()[2];
ng2=original->N()[2];
// End of additional section
if (IsBorder(f, z))
return true;
else
{
FaceType *g = f.FFp(z);
int gi = f.FFi(z);
// changed if statement from: if (f.V0(z) == g->V1(gi))
if (nf2/abs(nf2)==ng2/abs(ng2))
return true;
else
return false;
}
}
The result is as I expect and desire from the algorithm:
For a school project, my group is using OpenCV to capture video. From these (top-down) images, positions of objects are extracted and turned into a list of Points. Those Points then get triangulated using http://code.google.com/p/poly2tri/ (to overcome the problem of possible non-convex objects). Then, using the coordinates of the triangulated ground pane, we draw the objects in 3D using freeglut. (Side and Top panes are calculated using the ground pane coordinates). The problem we have is that when we delete our old list of Points, the application randomly crashes. Sometimes after 1 second, sometimes after 30 seconds, sometimes after a few minutes. The error we get is "Access violation writing location 0xCCCCCCCC"
Our code:
void WorldLayoutBuilder::update()
{
pointList.clear();
// Capture image
<code to capture image and get countours>
for(size_t i = 0; i < contours.size(); i++)
{
if(contours[i].size() > 50)
{
approxPolyDP(contours[i], approxShape, cv::arcLength(cv::Mat(contours[i]), true)*0.04, true);
drawContours(drawing, contours, i, cv::Scalar(255, 0, 0), 0);
std::vector<Point> newObject;
for(size_t j = 0; j < contours[i].size(); j++)
{
cv::Point newPoint = contours[i][j];
newObject.push_back(Point((float) newPoint.x / 100, 0.0f,(float) newPoint.y / 100));
}
pointList.push_back(newObject);
}
}
ObjectCreator3D::createObjects(&pointList);
contours.clear();
<code to release images, etc>
}
This captures an image, retrieves coordinates of objects, and then calls ObjectCreator3D::createObjects():
void ObjectCreator3D::createObjects(std::list<std::vector<Point>>* inputList)
{
std::list<WorldObject>* tempObjects = new std::list<WorldObject>;
for(std::vector<Point>&pointObject : *inputList)
{
WorldObject worldObject(&pointObject);
tempObjects->push_back(worldObject);
}
DataStorage::getInstance()->setObjects(tempObjects);
}
All objects are turned into WorldObjects:
#include <list>
#include <iostream>
#include <GL/glut.h>
#include <GL/freeglut.h>
#include <time.h>
#include "WorldObject.h"
#include "Point.h"
//Constant height - adjustable/randomized solution is partially implemented in the constructor.
const float WorldObject::HEIGHT = 5.0f;
template <class C> void FreeClear(C & cntr)
{
for(typename C::iterator it = cntr.begin(); it != cntr.end(); ++it)
{
delete * it;
}
cntr.clear();
}
WorldObject::WorldObject(std::vector<Point>* pointList)
{
//TODO, when we have time. Seems difficult because height will change each update...
/*srand (time(NULL));
float fGeneratedY = (rand() % 20 + 2) / 2.0f;*/
cdt = nullptr;
for (Point &point : *pointList)
//point.setY(fGeneratedY);
point.setY(HEIGHT);
this->pointList = pointList;
}
WorldObject::~WorldObject()
{
//Cleanup
delete cdt;
FreeClear(polyPoints);
}
/*
Author Tim Cocu & Bas Rops
Function for drawing the WorldObject
*/
void WorldObject::draw()
{
glPushMatrix();
glColor3f(0.8f, 0.8f, 0.8f);
//Calculate our bottom pane
calculateTriangles();
//BOTTOM PANE
for (unsigned int i = 0; i < calculatedTriangles.size(); i++)
{
p2t::Triangle& t = *calculatedTriangles[i];
p2t::Point& a = *t.GetPoint(0);
p2t::Point& b = *t.GetPoint(1);
p2t::Point& c = *t.GetPoint(2);
glBegin(GL_TRIANGLES);
glNormal3f(0, -1, 0);
glVertex3f((GLfloat)a.x, (GLfloat)0.0f, (GLfloat)a.y);
glVertex3f((GLfloat)b.x, (GLfloat)0.0f, (GLfloat)b.y);
glVertex3f((GLfloat)c.x, (GLfloat)0.0f, (GLfloat)c.y);
glEnd();
}
//TOP PANE
for (unsigned int i = 0; i < calculatedTriangles.size(); i++)
{
p2t::Triangle& t = *calculatedTriangles[i];
p2t::Point& a = *t.GetPoint(0);
p2t::Point& b = *t.GetPoint(1);
p2t::Point& c = *t.GetPoint(2);
glBegin(GL_TRIANGLES);
glNormal3f(0, 1, 0);
glVertex3f((GLfloat)a.x, (GLfloat)HEIGHT, (GLfloat)a.y);
glVertex3f((GLfloat)b.x, (GLfloat)HEIGHT, (GLfloat)b.y);
glVertex3f((GLfloat)c.x, (GLfloat)HEIGHT, (GLfloat)c.y);
glEnd();
}
glColor3f(1.0f, 1.0f, 1.0f);
//SIDE PANES
for(std::size_t iPaneCounter = 0; iPaneCounter < pointList->size(); iPaneCounter++)
{
Point firstPoint = (*pointList)[iPaneCounter];
Point secondPoint (0.0f, 0.0f, 0.0f);
if(iPaneCounter + 1 < pointList->size())
secondPoint.set((*pointList)[iPaneCounter + 1].getX(), (*pointList)[iPaneCounter + 1].getY(), (*pointList)[iPaneCounter + 1].getZ() );
else
secondPoint.set((*pointList)[0].getX(), (*pointList)[0].getY(), (*pointList)[0].getZ());
glBegin(GL_POLYGON);
float fNormalX = (firstPoint.getY() * secondPoint.getZ()) - (firstPoint.getZ() * secondPoint.getY());
float fNormalY = -((secondPoint.getZ() * firstPoint.getX()) - (secondPoint.getX() * firstPoint.getZ()));
float fNormalZ = (firstPoint.getX() * secondPoint.getY()) - (firstPoint.getY() * secondPoint.getX());
glNormal3f(fNormalX, fNormalY, fNormalZ);
glVertex3f(firstPoint.getX(), 0.0f, firstPoint.getZ());
glVertex3f(secondPoint.getX(), 0.0f, secondPoint.getZ());
glVertex3f(secondPoint.getX(), secondPoint.getY(), secondPoint.getZ());
glVertex3f(firstPoint.getX(), firstPoint.getY(), firstPoint.getZ());
glEnd();
}
}
/*
Calculates triangles that make a ground or top pane. Used for calculating possible non-convex objects
*/
void WorldObject::calculateTriangles()
{
//Empty the polyPoints list
if(polyPoints.size() > 0)
FreeClear(polyPoints);
//Convert our Points to p2t::Points
for(std::size_t iBottomIndex = 0; iBottomIndex < pointList->size(); iBottomIndex++)
polyPoints.push_back(new p2t::Point((*pointList)[iBottomIndex].getX(), (*pointList)[iBottomIndex].getZ()));
if(cdt == nullptr)
//Create CDT (Constrained Delaunay Triangulation) and add primary polyPoints
//NOTE: polyPoints must be a simple polygon. The polyPoints' points constitute constrained edges. No repeating points are allowed!
cdt = new p2t::CDT(polyPoints);
//Turn our polyPoints into p2t::Triangles
cdt->Triangulate();
//Set the triangles to use for drawing
calculatedTriangles = cdt->GetTriangles();
}
/*
Retrieve a pointer to a list of Points
*/
std::vector<Point>* WorldObject::getPoints()
{
return pointList;
}
/*
Retrieve a pointer to a list of p2t::Triangles
*/
std::vector<p2t::Triangle*> WorldObject::getCalculatedTriangles()
{
return calculatedTriangles;
}
When all WorldObjects are created, they are stored in DataStorage, DataStorage::getInstance()->setObjects() is called:
void DataStorage::setObjects(std::list<WorldObject>* objectList)
{
delete this->objectList;
this->objectList = objectList;
}
The application seems to crash on delete this->objectList; in setObjects(), so we think the application is trying to delete things he can't delete.
Any help would be greatly appreciated, we've been on this for a few days already
Here, you pass a pointer to an object owned by the list to the constructor of WorldObject:
for(std::vector<Point>&pointObject : *inputList)
{
WorldObject worldObject(&pointObject);
tempObjects->push_back(worldObject);
}
In WorldObject you store the pointer:
//Default Constructor
WorldObject::WorldObject(std::vector<Point>* pointList)
{
float fGeneratedY = (rand() % 20 + 2) / 2.0f;*/
cdt = nullptr;
for (Point &point : *pointList)
point.setY(HEIGHT);
this->pointList = pointList;
}
Which means WorldObject::pointList is only valid so long as the std::list which you constructed your WorldObjects from is still around. (After that, the result is undefined -- it could work, it could crash, it could format your hard drive and leak your identity to Texas).
If you insist on working with raw pointers, you as programmer are responsible for checking and keeping track of the lifetime of every single pointer. This is error prone and will cause random crashes that you will find difficult to track down.
Stop using raw pointers. Instead, if an object owns a resource, store it in a std::unique_ptr<>. If you want the same resource to be shared by multiple objects, use std::shared_ptr and std::weak_ptr, unless the lifetime of all but one of these objects is much, much shorter than the others in a guaranteed way.