Vector subscript out of range - Update Method - c++

I'm having an error.
"Vector subscript is out of range, line 932."
I have two enemy types, an array of asteroids and then a single enemy on it's own. They are made exactly the same except the asteroids are multiple in number, and the Borg is on it's own. I've tracked the error down to the Borg's update method, which is exactly the same as the Asteroids method with some name changes for the Borg. I've tried changing the "for" to "if" and I have tried to change the vector array to a D3DXVECTOR3. I think it's an array error but my programming knowledge is limited. Here are the update methods:
asteroidgamestate.h
#ifndef ASTEROIDSGAMESTATE
#define ASTEROIDSGAMESTATE
#include "Game Engine.h"
#include "Game Constants.h"
#include <vector>
class AsteroidsGameState:public GameState
{
private:
// STL vector to hold a collection of asteroid game sprites.
std::vector<GameSprite*> m_pAsteroids;
// STL vector to hold motion vectors for each asteroid.
std::vector<D3DXVECTOR3*> m_vAsteroidMotionVectors;
// STL vector to hold scaling factors for each asteroid.
std::vector<D3DXVECTOR2*> m_vAsteroidRotation;
public:
AsteroidsGameState() { }
~AsteroidsGameState()
{
this->Release();
}
//Initialises Asteroids & Borg cube
virtual bool Init()
{
D3DXVECTOR3 cSpritePosition;
GameSprite* asteroid;
D3DXVECTOR3* motionVector;
D3DXVECTOR2* rotation;
// Set up the asteroids.
for (int i = 0; i < MaximumNumberOfAsteroids / 2; i++)
{
asteroid = new GameSprite();
if (!asteroid->Init(420,425,true,L"asteroid.png"))
return false;
// Set the sprites current position.
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
asteroid->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector
asteroid->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
asteroid->SetAlive();
asteroid->SetVisible();
float scale = ((float)MathsUtilities::Get().GetRandomNumber(100, 1000)/3000.0f);
asteroid->SetScaleMatrix(scale, scale);
asteroid->SetRotationMatrix(0.0f);
this->m_pAsteroids.push_back(asteroid);
motionVector = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
motionVector->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->z = 0.0f;
this->m_vAsteroidMotionVectors.push_back(motionVector);
rotation = new D3DXVECTOR2(0.0f, 0.0f);
rotation->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
this->m_vAsteroidRotation.push_back(rotation);
}
for (int i = MaximumNumberOfAsteroids / 2; i < MaximumNumberOfAsteroids; i++)
{
asteroid = new GameSprite();
if (!asteroid->Init(420,425,true,L"asteroid2.png"))
return false;
// Set the sprites current position.
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
asteroid->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector
asteroid->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
asteroid->SetAlive();
asteroid->SetVisible();
float scale = ((float)MathsUtilities::Get().GetRandomNumber(100, 1000)/3000.0f);
asteroid->SetScaleMatrix(scale, scale);
asteroid->SetRotationMatrix(0.0f);
this->m_pAsteroids.push_back(asteroid);
motionVector = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
motionVector->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->z = 0.0f;
this->m_vAsteroidMotionVectors.push_back(motionVector);
rotation = new D3DXVECTOR2(0.0f, 0.0f);
rotation->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
this->m_vAsteroidRotation.push_back(rotation);
}
//Spawns one Borg
for (int i = 2 / 2; i < 2; i++)
{
asteroid = new GameSprite();
int BorgHealth = 4;
if (!asteroid->Init(420,425,true,L"borgcube.png"))
return false;
// Set the sprites current position.
/*if (BorgHealth < 4)
{
D3DXCOLOR(1.0f,1.0f,0.0f, 1.0f);
}*/
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
asteroid->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector
asteroid->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
asteroid->SetAlive();
asteroid->SetVisible();
float scale = ((float)MathsUtilities::Get().GetRandomNumber(999, 1000)/3000.0f);
asteroid->SetScaleMatrix(scale, scale);
asteroid->SetRotationMatrix(0.0f);
this->m_pAsteroids.push_back(asteroid);
motionVector = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
motionVector->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->z = 0.0f;
this->m_vAsteroidMotionVectors.push_back(motionVector);
rotation = new D3DXVECTOR2(0.0f, 0.0f);
rotation->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
this->m_vAsteroidRotation.push_back(rotation);
}
return true;
}
// Update pposition, rotation of asteroids.
virtual void Update()
{
GameSprite* asteroid;
int i = 0;
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pAsteroids.begin(); it != m_pAsteroids.end(); it++)
{
asteroid = *it;
if (m_vAsteroidRotation[i]->y <= 0)
m_vAsteroidRotation[i]->x -= m_vAsteroidRotation[i]->y;
else
m_vAsteroidRotation[i]->x += m_vAsteroidRotation[i]->y;
asteroid->SetRotationMatrix(m_vAsteroidRotation[i]->x);
i++;
if (i >= MaximumNumberOfAsteroids)
i = 0;
if (asteroid->GetAlive())
{
asteroid->SetTranslationMatrix(*m_vAsteroidMotionVectors[i]);
asteroid->Update();
}
asteroid->CheckBoundary();
}
}
// At this time no action is required on entering the state.
virtual void Enter() { }
// At this time no action is required when leaving the state.
virtual void Exit() { }
// Render asteroids.
virtual void Render()
{
GameSprite* asteroid;
// Render all the asteroids.
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pAsteroids.begin(); it != m_pAsteroids.end(); it++)
{
asteroid = *it;
asteroid->Render();
}
}
// Free allocated resources.
virtual void Release()
{
// Remove Vector classes containing game objects.
this->FreeSTL(m_pAsteroids);
this->FreeSTL(m_vAsteroidMotionVectors);
this->FreeSTL(m_vAsteroidRotation);
}
// Getter functions.
// Get the STL vector to hold a collection of asteroid game sprites.
std::vector<GameSprite*> GetAsteroids() { return this-> m_pAsteroids; }
// Get the STL vector to hold motion vectors for each asteroid.
std::vector<D3DXVECTOR3*> GetAsteroidMotionVectors() { return this->m_vAsteroidMotionVectors; }
// Get the STL vector to hold scaling factors for each asteroid.
std::vector<D3DXVECTOR2*> GetAsteroidRotation() { return this->m_vAsteroidRotation; }
// Private template function to free allocatted resources.
private:
// Template methods to help destroy game objects.
template<typename T>
void FreeSTL(std::vector<T*> &list)
{
std::vector<T*>::iterator it;
it = list.begin();
while(it != list.end())
{
if ((*it) != NULL)
{
delete (*it);
it = list.erase(it);
}
else
it++;
}
list.clear();
}
// New game level requires bringing the asteroids back to life.
void NextLevelOfAsteroids()
{
GameSprite* asteroid;
D3DXVECTOR3 cSpritePosition;
float scale;
int i = 0;
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pAsteroids.begin(); it != m_pAsteroids.end(); it++)
{
asteroid = *it;
// Set the sprites current position.
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
asteroid->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector.
asteroid->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
asteroid->SetAlive();
asteroid->SetVisible();
// Scale the asteroids.
scale = ((float)MathsUtilities::Get().GetRandomNumber(100, 1000)/3000.0f);
asteroid->SetScaleMatrix(scale, scale);
asteroid->SetRotationMatrix(0.0f);
// Set motion vectors for the asteroids.
m_vAsteroidMotionVectors[i]->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
m_vAsteroidMotionVectors[i]->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
// Set up rotation vector for the asteroids.
m_vAsteroidRotation[i]->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
i++;
}
}
};
#endif
borggamestate.h
#pragma once
#ifndef BORGGAMESTATE
#define BORGGAMESTATE
#include "Game Engine.h"
#include "Game Constants.h"
#include <vector>
class BorgGameState:public GameState
{
private:
// STL vector to hold a collection of borg game sprites.
std::vector<GameSprite*> m_pBorg;
// STL vector to hold motion vectors for the borg.
std::vector<D3DXVECTOR3*> m_vBorgMotionVectors;
// STL vector to hold scaling factors for the borg.
std::vector<D3DXVECTOR2*> m_vBorgRotation;
public:
BorgGameState() { }
~BorgGameState()
{
this->Release();
}
//Initialises Borg cube
virtual bool Init()
{
D3DXVECTOR3 cSpritePosition;
GameSprite* borg;
D3DXVECTOR3* motionVector;
D3DXVECTOR2* rotation;
// Set up the borg.
//Spawns one Borg
for (int i = 2 / 2; i < 2; i++)
{
borg = new GameSprite();
int BorgHealth = 4;
if (!borg->Init(420,425,true,L"borgcube.png"))
return false;
// Set the sprites current position.
/*if (BorgHealth < 4)
{
D3DXCOLOR(1.0f,1.0f,0.0f, 1.0f);
}*/
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
borg->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector
borg->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
borg->SetAlive();
borg->SetVisible();
float scale = ((float)MathsUtilities::Get().GetRandomNumber(999, 1000)/3000.0f);
borg->SetScaleMatrix(scale, scale);
borg->SetRotationMatrix(0.0f);
this->m_pBorg.push_back(borg);
motionVector = new D3DXVECTOR3(0.0f, 0.0f, 0.0f);
motionVector->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
motionVector->z = 0.0f;
this->m_vBorgMotionVectors.push_back(motionVector);
rotation = new D3DXVECTOR2(0.0f, 0.0f);
rotation->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
this->m_vBorgRotation.push_back(rotation);
}
return true;
}
// Update position, rotation of borg.
virtual void Update()
{
GameSprite* borg;
int i = 0;
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pBorg.begin(); it != m_pBorg.end(); it++)
{
borg = *it;
if (m_vBorgRotation[i]->y <= 0)
m_vBorgRotation[i]->x -= m_vBorgRotation[i]->y;
else
m_vBorgRotation[i]->x += m_vBorgRotation[i]->y;
borg->SetRotationMatrix(m_vBorgRotation[i]->x);
i++;
if (i >= MaximumNumberOfAsteroids)
i = 0;
if (borg->GetAlive())
{
borg->SetTranslationMatrix(*m_vBorgMotionVectors[i]);
borg->Update();
}
borg->CheckBoundary();
}
}
// At this time no action is required on entering the state.
virtual void Enter() { }
// At this time no action is required when leaving the state.
virtual void Exit() { }
// Render borg.
virtual void Render()
{
GameSprite* borg;
// Render borg.
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pBorg.begin(); it != m_pBorg.end(); it++)
{
borg = *it;
borg->Render();
}
}
// Free allocated resources.
virtual void Release()
{
// Remove Vector classes containing game objects.
this->FreeSTL(m_pBorg);
this->FreeSTL(m_vBorgMotionVectors);
this->FreeSTL(m_vBorgRotation);
}
// Getter functions.
// Get the STL vector to hold a collection of borg game sprites.
std::vector<GameSprite*> GetBorg() { return this-> m_pBorg; }
// Get the STL vector to hold motion vectors for each borg.
std::vector<D3DXVECTOR3*> GetBorgMotionVectors() { return this->m_vBorgMotionVectors; }
// Get the STL vector to hold scaling factors for the borg.
std::vector<D3DXVECTOR2*> GetBorgRotation() { return this->m_vBorgRotation; }
// Private template function to free allocatted resources.
private:
// Template methods to help destroy game objects.
template<typename T>
void FreeSTL(std::vector<T*> &list)
{
std::vector<T*>::iterator it;
it = list.begin();
while(it != list.end())
{
if ((*it) != NULL)
{
delete (*it);
it = list.erase(it);
}
else
it++;
}
list.clear();
}
// New game level requires bringing the borg back to life.
void NextLevelOfBorg()
{
GameSprite* borg;
D3DXVECTOR3 cSpritePosition;
float scale;
int i = 0;
std::vector<GameSprite*>::iterator it;
for (std::vector<GameSprite*>::iterator it = m_pBorg.begin(); it != m_pBorg.end(); it++)
{
borg = *it;
// Set the sprites current position.
cSpritePosition.x = (float)(100 + MathsUtilities::Get().GetRandomNumber(0, Graphics2D::Get().GetWindowWidth() - 100));
cSpritePosition.y = 1.0f;
cSpritePosition.z = 0.9f;
borg->SetSpritePosition(cSpritePosition);
// Set the sprites motion vector.
borg->SetTranslationMatrix(D3DXVECTOR3(1.0f, 1.0f, 0.0f));
borg->SetAlive();
borg->SetVisible();
// Scale the borg.
scale = ((float)MathsUtilities::Get().GetRandomNumber(100, 1000)/3000.0f);
borg->SetScaleMatrix(scale, scale);
borg->SetRotationMatrix(0.0f);
// Set motion vectors for the borg.
m_vBorgMotionVectors[i]->x = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
m_vBorgMotionVectors[i]->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/150.0f) + 0.25f;
// Set up rotation vector for the borg.
m_vBorgRotation[i]->y = (float)(MathsUtilities::Get().GetRandomNumber(-1000, 1000)/10000.0f) + 0.001f;
i++;
}
}
};
endif
Thanks for your help, and sorry if I've missed anything.

It is hard to say with the information provided, but
your problem might be the here:
In BorgGameState.h Update()
borg = *it;
if (m_vBorgRotation[i]->y <= 0)
m_vBorgRotation[i]->x -= m_vBorgRotation[i]->y;
else
m_vBorgRotation[i]->x += m_vBorgRotation[i]->y;
borg->SetRotationMatrix(m_vBorgRotation[i]->x);
i++;
if (i >= MaximumNumberOfAsteroids) // <--- should this be MaximumNumberOfBorg instead?
i = 0;
If m_vBorgRotation has fewer elements than MaximumNumberOfAsteroids you will get such an error.

Related

KDtree : bad performances when the point is far away

I have a basic KDtree implementation so far and the performance is very bad when the points are very far, i.e. if all the points are in [-10;10]^3 and the query is for example at (100, 100, 100), then the bruteforce is 20% faster than the normal version. Why?
This implementation is very basic, I know there is possible improvement everywhere, but just to make work with the minimum vital but still, it shouldn't be that slow... right?
KDTree.h
#pragma once
#include <memory>
#include <vector>
#include <cassert>
#include <vector>
#include <stdexcept>
#include <cmath>
#include <stack>
/**
* #brief K-d tree of 3D float points.
*
* #see Variable names, and algorithm inspired by https://youtu.be/TrqK-atFfWY?t=2567
*/
class KDTree
{
public:
/**** Utility structures ****/
struct Point
{
float x, y, z;
float& operator[](int i)
{
assert(i < 3 && i >= 0);
switch(i) {
case 0: return x;
case 1: return y;
case 2: return z;
}
throw std::runtime_error("Invalid index");
}
const float& operator[](int i) const
{
return const_cast<Point&>(*this)[i];
}
float distance(const Point& other) const
{
return sqrtf(distanceSquared(other));
}
float distanceSquared(const Point& other) const
{
const float dx = (x - other.x);
const float dy = (y - other.y);
const float dz = (z - other.z);
return dx*dx + dy*dy + dz*dz;
}
template<typename T> friend T& operator<<(T& lhs, const Point& rhs)
{
lhs << "(" << rhs.x << ", " << rhs.y << ", " << rhs.z << ")";
return lhs;
}
};
struct AABB
{
Point min, max;
bool inside(const Point& p) const
{
return p.x >= min.x && p.y >= min.y && p.z >= min.z
&& p.x < max.x && p.y < max.y && p.z < max.z;
}
};
public:
/**** Utility (static) functions ****/
/**
* #brief Compute the bounding box of a set of points.
* #param points
* The points to make the boudning box from.
* If the set of points is empty, the returned value is undefined.
* #return
* The bounding box of theses points.
* The bounding box will be always of minimum size, i.e. there will be a vertex on each of the 6 faces of the
* returned AABB.
*/
static AABB computeBoundingBox(const std::vector<Point> &points);
static float median(std::vector<float> vec);
static void store_min(float& current, float newValue)
{
if(current > newValue) {
current = newValue;
}
}
static void store_max(float& current, float newValue)
{
if(current < newValue) {
current = newValue;
}
}
public:
KDTree() = default;
explicit KDTree(const std::vector<Point>& points);
Point computeNearestNeighbor(const Point& pos) const;
private:
/**
* #brief Represent each node of the k-d tree.
*/
struct Node
{
/**
* #brief
* Children. Both of them or neither of them are null.
*/
std::unique_ptr<Node> left, right;
/**
* The distance between the origin and the wall to split (for left node),
* or the distance from the wall and the end to split (for right node).
*/
float splitDistance;
int splitDim;
std::vector<Point> points;
/**
* #return true if this node is a leaf node (it has no children).
*/
bool leaf() const
{
// left or right it doesn't matter
return left == nullptr;
}
};
struct SplitStack
{
int dim;
std::vector<Point> points;
Node* node;
AABB aabb;
};
private:
/**
* #brief Split the tree during the build.
* #param dim The dimension to split.
* #param points The list of remaining candidate points for this area, inside the AABB.
* #param node The node, allocated, to fill.
* #param aabb The bounding box of the node.
*/
void split(std::stack<SplitStack>& stack);
void searchRecursive(const Point& pos, Node* node, float& currentDist, Point& currentNeighbor) const;
private:
AABB m_rootAABB;
std::unique_ptr<Node> m_root;
};
KDTree.cpp
#include "KDTree.h"
#include <climits>
#include <cfloat>
#include <algorithm>
#include <iostream>
#include <cmath>
float KDTree::median(std::vector<float> vec)
{
size_t size = vec.size();
if (size == 0)
{
return 0; // Undefined, really.
}
else
{
std::sort(vec.begin(), vec.end());
if (size % 2 == 0)
{
return (vec[size / 2 - 1] + vec[size / 2]) / 2;
}
else
{
return vec[size / 2];
}
}
}
KDTree::AABB KDTree::computeBoundingBox(const std::vector<Point>& points)
{
AABB res;
if (!points.empty())
{
const auto& firstPoint = points.front();
// Initialize the bounding box to a point
for (int dim = 0; dim < 3; dim++)
{
res.min[dim] = firstPoint[dim];
res.max[dim] = firstPoint[dim];
}
// Grow the bounding box for each point if needed
for (const Point& point: points)
{
for (int dim = 0; dim < 3; dim++)
{
store_min(res.min[dim], point[dim]);
store_max(res.max[dim], point[dim]);
}
}
}
return res;
}
KDTree::KDTree(const std::vector<Point>& points)
{
m_rootAABB = computeBoundingBox(points);
m_root = std::make_unique<Node>();
std::stack<SplitStack> stack;
stack.push(SplitStack{0, points, m_root.get(), m_rootAABB});
split(stack);
}
void KDTree::split(std::stack<SplitStack>& stack)
{
// Split recursively in x, y, z, x, y, z...
// Split at the center
// dim axis -->
// 0 --------- aabb[dim].min --------------------------- aabb[dim].max --------- +inf
// ------------------|--------------------|-------------------|-------------------
// ---------------------- left node ----------- right node -----------------------
// <-----------------> <--------------->
// splitDistance: if left if right
while (!stack.empty())
{
std::vector<Point> points = std::move(stack.top().points);
Node& node = *stack.top().node;
int dim = stack.top().dim;
AABB aabb = stack.top().aabb;
stack.pop();
// Stop condition
if (points.size() > 100)
{
node.splitDim = dim;
// Absolute position in the dimension of the split
node.splitDistance = (aabb.max[dim] + aabb.min[dim]) / 2.0f;
AABB leftAABB = aabb;
leftAABB.max[dim] = node.splitDistance;
AABB rightAABB = aabb;
rightAABB.min[dim] = leftAABB.max[dim];
std::vector<Point> leftPoints, rightPoints;
for (const Point& p: points)
{
if (leftAABB.inside(p))
{
leftPoints.push_back(p);
}
else
{
rightPoints.push_back(p);
}
}
const int nextDim = (dim + 1) % 3;
node.right = std::make_unique<Node>();
stack.push(SplitStack{nextDim, std::move(rightPoints), node.right.get(), rightAABB});
node.left = std::make_unique<Node>();
stack.push(SplitStack{nextDim, std::move(leftPoints), node.left.get(), leftAABB});
}
else
{
// Leaf
node.points = std::move(points);
}
}
}
KDTree::Point KDTree::computeNearestNeighbor(const KDTree::Point& pos) const
{
// Are we left or right?
const Node *node = m_root.get();
AABB aabb = m_rootAABB;
float dist = FLT_MAX;
Point res;
searchRecursive(pos, m_root.get(), dist, res);
return res;
}
void KDTree::searchRecursive(const Point& pos, Node *node, float& currentDist, Point& currentNeighbor) const
{
// Are we on a leaf?
if (node->leaf())
{
// We are on a leaf
// Search brute force into the leaf node
for (const auto& other: node->points)
{
const float d = other.distanceSquared(pos);
if (d < currentDist)
{
currentDist = d;
currentNeighbor = other;
}
}
}
else
{
Node *front, *back;
// Are we on the left side?
if (pos[node->splitDim] < node->splitDistance)
{
// Pos is on the left side
front = node->left.get();
back = node->right.get();
}
else
{
// Pos is on the right side
front = node->right.get();
back = node->left.get();
}
searchRecursive(pos, front, currentDist, currentNeighbor);
// If the current closest point is closer than the closest point of the back face, no need to search in the back
// face because it will be always further.
// If not, we save half of the time for the current node
const float backDist = fabsf(node->splitDistance - pos[node->splitDim]);
// Do not forget all distances all squared
if (backDist * backDist <= currentDist)
{
// If it can be closer, search also in this node
searchRecursive(pos, back, currentDist, currentNeighbor);
}
}
}
main.cpp
#include <iostream>
#include <vector>
#include <random>
#include "KDTree.h"
#include "viewer.h"
#include <chrono>
class Timer {
public:
Timer(const std::string& title) : title_(title), beg_(clock_::now()) {}
~Timer() {
std::cout << title_ << " elapsed: " << elapsed() << "s" << std::endl;
}
void reset() { beg_ = clock_::now(); }
double elapsed() const {
return std::chrono::duration_cast<second_> (clock_::now() - beg_).count();
}
private:
std::string title_;
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> beg_;
};
std::vector<KDTree::Point> randomPoints(int size, float bounds = 10.0f)
{
std::vector<KDTree::Point> points;
std::uniform_real_distribution<float> dist(-bounds, bounds);
std::mt19937 engine;
for(int i = 0; i < size; i++)
{
KDTree::Point point;
for(int dim = 0; dim < 3; dim++) {
point[dim] = dist(engine);
}
points.push_back(point);
}
return points;
}
int main()
{
auto points = randomPoints(1'000'000);
for(int i = 0; i < points.size(); i++) {
auto& point = points[i];
}
auto aabb = KDTree::computeBoundingBox(points);
KDTree kdtree;
{
Timer timer("KDTree build");
kdtree = KDTree(points);
}
{
const int N = 1'000;
auto testPts = randomPoints(N, 100.0f);
std::vector<KDTree::Point> resKD(N), resBrute(N);
{
Timer timer("KDTree");
for(int i = 0; i < testPts.size(); i++) {
resKD[i] = kdtree.computeNearestNeighbor(testPts[i]);
}
}
{
Timer timer("Bruteforce");
for(int i = 0; i < testPts.size(); i++) {
const auto& test = testPts[i];
// Brute force
KDTree::Point cur;
float curDist = FLT_MAX;
for(const auto& brute : points) {
if(brute.distanceSquared(test) < curDist) {
curDist = brute.distanceSquared(test);
cur = brute;
}
}
resBrute[i] = cur;
}
}
{
float delta = 0.0f;
for(int i = 0; i < N; i++) {
delta += resKD[i].x - resBrute[i].x;
delta += resKD[i].y - resBrute[i].y;
delta += resKD[i].z - resBrute[i].z;
}
std::cout << "delta = " << delta << std::endl;
}
}
return 0;
}
Output:
KDTree build elapsed: 0.190593s
KDTree elapsed: 2.69598s
Bruteforce elapsed: 2.34136s
delta = 0
When I lower to randomPoints(N, 10.0f):
KDTree build elapsed: 0.195519s
KDTree elapsed: 0.000914431s
Bruteforce elapsed: 2.35679s
delta = 0
As written on wikipedia:
Additionally, even in low-dimensional space, if the average pairwise distance between the k nearest neighbors of the query point is significantly less than the average distance between the query point and each of the k nearest neighbors, the performance of nearest neighbor search degrades towards linear, since the distances from the query point to each nearest neighbor are of similar magnitude. (In the worst case, consider a cloud of points distributed on the surface of a sphere centered at the origin. Every point is equidistant from the origin, so a search for the nearest neighbor from the origin would have to iterate through all points on the surface of the sphere to identify the nearest neighbor – which in this case is not even unique.)
So this is normal to see performance decrease in a KD Tree when the points are far.

Skeletal animation is skewed

I followed the popular tutorials on skeletal animation by Thin Matrix and another code sample on
GitHub
The mesh renders find without any animations. But as soon as animations are applied it gets skewed.
If I pass identity matrices as bonetransforms, it works. Works as in it still renders properly just without any animation.
Also I noticed that the collada file I use uses Z as up and I use Y as up. But I export all data without changing a thing to make sure all transforms and vertex data work as intended. I later on plan on adjusting this as I export so that the data uses Y as up as well.
Here's my code for Skeletal Animation:
Header:
class SkeletalAnimation {
typedef struct Bone{
int id;
std::string name;
glm::mat4 offset;
std::vector<Bone> children;
} Bone;
typedef struct {
std::vector<float> translationTimestamps;
std::vector<float> rotationTimetamps;
std::vector<float> scalingTimetamps;
std::vector<glm::vec3> translations;
std::vector<glm::quat> rotations;
std::vector<glm::vec3> scalings;
} BoneTransforms;
typedef struct Animation {
float duration;
float ticksPerSecond;
std::unordered_map<std::string, BoneTransforms> boneTransforms;
Animation(float pDuration, float ticksPerSecond) :
duration(pDuration),
ticksPerSecond(ticksPerSecond),
boneTransforms({})
{}
Animation() {}
} Animation;
typedef std::unordered_map<std::string, std::pair<int, glm::mat4>> BoneData;
typedef std::unordered_map<std::string, Animation> AnimationMap;
typedef std::vector<glm::mat4> Pose;
typedef struct{
unsigned int segment;
float fracture;
} Segment;
typedef struct {
Pose pose;
BoneData boneData;
unsigned int boneCount;
std::string name;
Bone skeleton;
} MeshEntry;
typedef std::unordered_map<std::string, MeshEntry> MeshBoneMap;
private:
const std::string mPath;
SDL_Renderer* mRenderer;
std::vector<MeshEntry> mMeshEntries;
std::vector<SkeletalMesh*> mMeshes;
std::vector<ImageTexture*> mTextures;
std::vector<unsigned int> mMeshToTexture;
std::string* mCurrentAnimation;
std::vector<std::string> mAnimations;
AnimationMap mAnimationMap;
Segment mCurrentSegment;
glm::mat4 mGlobalInverseTransform;
MeshBoneMap mMeshBoneMap;
static glm::mat4 sIdentityMatrix;
void LoadNode(aiNode* pNode, const aiScene* pScene);
void LoadSkeletalMesh(aiMesh* pMesh, const aiScene* pScene);
bool LoadBones(Bone& pBone, aiNode* pNode, BoneData& pBoneData);
void LoadAnimations(const aiScene* pScene);
void LoadMaterials(const aiScene* pScene);
void Animate(float pDeltaTime, Bone& pSkeleton, Pose& pPose, glm::mat4& pParentTransform);
static inline glm::mat4 aiToGlmMat4(const aiMatrix4x4& pAiMat);
static inline glm::vec3 aiToGlmVec3(const aiVector3D& pAiVec);
static inline glm::quat aiToGlmQuat(const aiQuaternion& pAiVec);
static inline void GetSegment(Segment* pSegment,const std::vector<float>& pTimestamps,const float pDeltaTime);
public:
SkeletalAnimation(const std::string pPath, SDL_Renderer* pRenderer);
~SkeletalAnimation();
void LoadAnimation();
void GetAllAnimations(std::vector<std::string>* pAnimations);
float GetAnimationDuration();
void SetAnimationTime(float pTime);
void SetAnimation(std::string pAnimation);
void RenderAnimation(float pDeltaTime, SkeletalAnimationShader* pSkeletalAnimationShader);
void RenderStill(SkeletalAnimationShader* pSkeletalAnimationShader);
void ClearModel();
};
Source:
glm::mat4 SkeletalAnimation::sIdentityMatrix = glm::mat4();
SkeletalAnimation::SkeletalAnimation(const std::string pPath, SDL_Renderer* pRenderer) :
mPath(pPath),
mRenderer(pRenderer),
mCurrentAnimation(new std::string()),
mAnimations({}),
mAnimationMap({}),
mMeshBoneMap({})
{}
SkeletalAnimation::~SkeletalAnimation() {
}
void SkeletalAnimation::LoadAnimation() {
Assimp::Importer _importer;
const aiScene* _scene = _importer.ReadFile(mPath, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenSmoothNormals | aiProcess_JoinIdenticalVertices);
if (!_scene) {
SDL_Log("Assimp Error Loading Animation at path: %s \n Error: %s .", mPath.c_str(), _importer.GetErrorString());
return;
}
mGlobalInverseTransform = glm::inverse(aiToGlmMat4(_scene->mRootNode->mTransformation));
LoadNode(_scene->mRootNode, _scene);
LoadAnimations(_scene);
LoadMaterials(_scene);
}
void SkeletalAnimation::LoadNode(aiNode* pNode, const aiScene* pScene) {
for (size_t i = 0; i < pNode->mNumMeshes; i++) {
LoadSkeletalMesh(pScene->mMeshes[pNode->mMeshes[i]], pScene);
}
for (size_t i = 0; i < pNode->mNumChildren; i++) {
LoadNode(pNode->mChildren[i], pScene);
}
}
void SkeletalAnimation::LoadSkeletalMesh(aiMesh* pMesh, const aiScene* pScene) {
MeshEntry _meshEntry;
_meshEntry.boneCount = pMesh->mNumBones;
_meshEntry.name = std::string(pMesh->mName.C_Str());
_meshEntry.pose = {};
_meshEntry.pose.resize(pMesh->mNumBones, sIdentityMatrix);
_meshEntry.boneData = {};
SkeletalMeshData _meshData;
for (size_t i = 0; i < pMesh->mNumVertices; i++) {
_meshData.vertices.insert(_meshData.vertices.end(),
{
pMesh->mVertices[i].x,
pMesh->mVertices[i].y , //Swaped Y and Z since Blender uses Z as up and I use Y as up.
pMesh->mVertices[i].z });
if (pMesh->mTextureCoords[0]) {
_meshData.uvs.insert(_meshData.uvs.end(),
{
pMesh->mTextureCoords[0][i].x,
pMesh->mTextureCoords[0][i].y
});
}
else {
_meshData.uvs.insert(_meshData.uvs.end(),
{
0.0f,
0.0f
});
}
_meshData.normals.insert(_meshData.normals.end(),
{
pMesh->mNormals[i].x,
pMesh->mNormals[i].y ,
pMesh->mNormals[i].z });
_meshData.boneIDs.insert(_meshData.boneIDs.end(), {
0,
0,
0,
0});
_meshData.weights.insert(_meshData.weights.end(), {
0.0f,
0.0f,
0.0f,
0.0f});
}
for (size_t i = 0; i < pMesh->mNumFaces; i++) {
aiFace _face = pMesh->mFaces[i];
for (size_t j = 0; j < _face.mNumIndices; j++) {
_meshData.indices.push_back(_face.mIndices[j]);
}
}
for (size_t i = 0; i < pMesh->mNumBones; i++) {
aiBone* _bone = pMesh->mBones[i];
glm::mat4 _offset = aiToGlmMat4(_bone->mOffsetMatrix);
_meshEntry.boneData[_bone->mName.C_Str()] = std::make_pair(i , _offset);
for (size_t j = 0; j < _bone->mNumWeights; j++) {
aiVertexWeight _weight = _bone->mWeights[j];
unsigned int _vertexID = _weight.mVertexId * 4;
for (size_t k = 0; k < 4; k++) {
if (_meshData.weights[_vertexID + k] == 0.0f) {
_meshData.weights[_vertexID + k] = _weight.mWeight;
_meshData.boneIDs[_vertexID + k] = i;
break;
}
}
}
}
for (size_t i = 0; i < _meshData.weights.size(); i+=4) {
float _totalWeight =
_meshData.weights[i] +
_meshData.weights[i+1] +
_meshData.weights[i+2] +
_meshData.weights[i+3];
if (_totalWeight > 0.0f) {
_meshData.weights[i] /= _totalWeight;
_meshData.weights[i+1] /= _totalWeight;
_meshData.weights[i+2] /= _totalWeight;
_meshData.weights[i+3] /= _totalWeight;
}
}
SkeletalMesh* _newMesh = new SkeletalMesh();
_newMesh->BuildMesh(_meshData);
mMeshes.push_back(_newMesh);
mMeshToTexture.push_back(pMesh->mMaterialIndex);
LoadBones(_meshEntry.skeleton, pScene->mRootNode, _meshEntry.boneData);
mMeshEntries.push_back(_meshEntry);
}
bool SkeletalAnimation::LoadBones(Bone& pBone ,aiNode* pNode, BoneData& pBoneData) {
if (pBoneData.find(pNode->mName.C_Str()) != pBoneData.end()) {
pBone.name = pNode->mName.C_Str();
pBone.id = pBoneData[pBone.name].first;
pBone.offset = pBoneData[pBone.name].second;
for (size_t i = 0; i < pNode->mNumChildren; i++) {
Bone _child;
LoadBones(_child, pNode->mChildren[i], pBoneData);
pBone.children.push_back(_child);
}
return true;
}
else {
for (size_t i = 0; i < pNode->mNumChildren; i++) {
if (LoadBones(pBone, pNode->mChildren[i], pBoneData)) {
return true;
}
}
}
return false;
}
void SkeletalAnimation::LoadAnimations(const aiScene* pScene) {
for (size_t i = 0; i < pScene->mNumAnimations; i++) {
Animation _currentInternalAnimation(0.0f, 1.0f);
aiAnimation* _currentAiAnimation = pScene->mAnimations[i];
mAnimations.push_back(std::string(_currentAiAnimation->mName.C_Str()));
if (i == 0) {
*mCurrentAnimation = mAnimations[0];
}
if (_currentAiAnimation->mTicksPerSecond != 0.0f) {
_currentInternalAnimation.ticksPerSecond = _currentAiAnimation->mTicksPerSecond;
}
else {
_currentInternalAnimation.ticksPerSecond = 1;
}
_currentInternalAnimation.duration = _currentAiAnimation->mDuration * _currentAiAnimation->mTicksPerSecond;
_currentInternalAnimation.boneTransforms = {};
BoneTransforms _transforms;
for (size_t j = 0; j < _currentAiAnimation->mNumChannels; j++) {
aiNodeAnim* _channel = _currentAiAnimation->mChannels[j];
for (size_t k = 0; k < _channel->mNumPositionKeys; k++) {
_transforms.translations.push_back(aiToGlmVec3(_channel->mPositionKeys[k].mValue));
_transforms.translationTimestamps.push_back(_channel->mPositionKeys[k].mTime);
}
for (size_t k = 0; k < _channel->mNumRotationKeys; k++) {
_transforms.rotations.push_back(aiToGlmQuat(_channel->mRotationKeys[k].mValue));
_transforms.rotationTimetamps.push_back(_channel->mRotationKeys[k].mTime);
}
for (size_t k = 0; k < _channel->mNumScalingKeys; k++) {
_transforms.scalings.push_back(aiToGlmVec3(_channel->mScalingKeys[k].mValue));
_transforms.scalingTimetamps.push_back(_channel->mScalingKeys[k].mTime);
}
_currentInternalAnimation.boneTransforms[_channel->mNodeName.C_Str()] = _transforms;
}
mAnimationMap[_currentAiAnimation->mName.C_Str()] = _currentInternalAnimation;
}
}
void SkeletalAnimation::LoadMaterials(const aiScene* pScene) {
mTextures.resize(pScene->mNumMaterials);
for (size_t i = 0; i < pScene->mNumMaterials; i++) {
aiMaterial* _material = pScene->mMaterials[i];
mTextures[i] = nullptr;
if (_material->GetTextureCount(aiTextureType_DIFFUSE)) {
aiString _path;
if (_material->GetTexture(aiTextureType_DIFFUSE, 0, &_path) == AI_SUCCESS) {
int _idx = std::string(_path.data).rfind("\\");
std::string _fileName = std::string(_path.data).substr(_idx + 1);
std::string _texturePath = std::string("assets/") + _fileName;
SDL_Log("Model Loading Texture at path: %s .", _texturePath.c_str());
mTextures[i] = new ImageTexture(_texturePath, mRenderer);
mTextures[i]->Load();
if (!mTextures[i]->IsLoaded()) {
delete mTextures[i];
mTextures[i] = nullptr;
SDL_Log("Model Error Loading Texture at path: %s .", _texturePath.c_str());
}
}
}
}
}
float SkeletalAnimation::GetAnimationDuration() {
return mAnimationMap[*mCurrentAnimation].duration;
}
void SkeletalAnimation::SetAnimationTime(float pTime) {
for (size_t i = 0; i < mMeshes.size(); i++) {
MeshEntry& _entry = mMeshEntries[i];
Animate(pTime, _entry.skeleton, _entry.pose, sIdentityMatrix);
}
}
void SkeletalAnimation::Animate(float pDeltaTime, Bone& pSkeleton, Pose& pPose, glm::mat4& pParentTransform) {
Animation& _currentAnimation = mAnimationMap[*mCurrentAnimation];
BoneTransforms& _boneTransforms = _currentAnimation.boneTransforms[pSkeleton.name];
pDeltaTime = fmod(pDeltaTime, _currentAnimation.duration);
//Calculate translations
GetSegment(&mCurrentSegment, _boneTransforms.translationTimestamps, pDeltaTime);
glm::vec3 _translation = glm::mix(
_boneTransforms.translations[mCurrentSegment.segment - 1],
_boneTransforms.translations[mCurrentSegment.segment],
mCurrentSegment.fracture);
//Calculate rotations
GetSegment(&mCurrentSegment, _boneTransforms.rotationTimetamps, pDeltaTime);
glm::quat _rotation = glm::slerp(
_boneTransforms.rotations[mCurrentSegment.segment - 1],
_boneTransforms.rotations[mCurrentSegment.segment],
mCurrentSegment.fracture);
//Calculate scalings
GetSegment(&mCurrentSegment, _boneTransforms.scalingTimetamps, pDeltaTime);
glm::vec3 _scaling = glm::mix(
_boneTransforms.scalings[mCurrentSegment.segment - 1],
_boneTransforms.scalings[mCurrentSegment.segment],
mCurrentSegment.fracture);
glm::mat4 _translationMatrix = glm::translate(glm::mat4(1.0f), _translation);
glm::mat4 _rotationMatrix = glm::toMat4(_rotation);
glm::mat4 _scalingMatrix = glm::scale(glm::mat4(1.0f), _scaling); glm::mat4(1.0f);
glm::mat4 _localTransform = _translationMatrix * _rotationMatrix * _scalingMatrix;
glm::mat4 _globalTransform = pParentTransform * _localTransform;
pPose[pSkeleton.id] = mGlobalInverseTransform * _globalTransform * pSkeleton.offset;
for (Bone& _child : pSkeleton.children) {
Animate(pDeltaTime, _child, pPose, _globalTransform);
}
}
void SkeletalAnimation::GetAllAnimations(std::vector<std::string>* pAnimations) {
pAnimations->clear();
*pAnimations = mAnimations;
}
void SkeletalAnimation::SetAnimation(std::string pAnimation) {
assert(std::find(mAnimations.begin(), mAnimations.end(), pAnimation) != mAnimations.end(), "Animation does not exist.");
*mCurrentAnimation = pAnimation;
}
void SkeletalAnimation::RenderAnimation(float pDeltaTime, SkeletalAnimationShader* pSkeletalAnimationShader) {
for (size_t i = 0; i < mMeshes.size(); i++) {
unsigned int _materialIndex = mMeshToTexture[i];
if (_materialIndex < mTextures.size() && mTextures[_materialIndex]) {
mTextures[_materialIndex]->Enable();
}
MeshEntry& _entry = mMeshEntries[i];
Animate(pDeltaTime, _entry.skeleton, _entry.pose, sIdentityMatrix);
pSkeletalAnimationShader->SetBoneTransforms(_entry.boneCount, _entry.pose);
mMeshes[i]->Render();
}
}
void SkeletalAnimation::RenderStill(SkeletalAnimationShader* pSkeletalAnimationShader) {
for (size_t i = 0; i < mMeshes.size(); i++) {
unsigned int _materialIndex = mMeshToTexture[i];
if (_materialIndex < mTextures.size() && mTextures[_materialIndex]) {
mTextures[_materialIndex]->Enable();
}
MeshEntry& _entry = mMeshEntries[i];
pSkeletalAnimationShader->SetBoneTransforms(_entry.boneCount, _entry.pose);
mMeshes[i]->Render();
}
}
void SkeletalAnimation::ClearModel() {
for (size_t i = 0; i < mMeshes.size(); i++) {
if (mMeshes[i]) {
delete mMeshes[i];
mMeshes[i] = nullptr;
}
}
for (size_t i = 0; i < mTextures.size(); i++) {
if (mTextures[i]) {
delete mTextures[i];
mTextures[i] = nullptr;
}
}
}
void SkeletalAnimation::GetSegment(Segment* pSegment,const std::vector<float>& pTimestamps, const float pDeltaTime) {
unsigned int _segment = 1;
while (pDeltaTime > pTimestamps[_segment]) {
_segment++;
}
float _start = pTimestamps[_segment - 1];
float _end = pTimestamps[_segment];
float _fracture = (pDeltaTime - _start) / (_end - _start);
pSegment->segment = _segment;
pSegment->fracture = _fracture;
}
glm::mat4 SkeletalAnimation::aiToGlmMat4(const aiMatrix4x4& pAiMat) {
glm::mat4 _glmMat;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
_glmMat[x][y] = pAiMat[y][x];
}
}
return _glmMat;
}
glm::vec3 SkeletalAnimation::aiToGlmVec3(const aiVector3D& pAiVec) {
return glm::vec3(pAiVec.x, pAiVec.y, pAiVec.z); //Swapped Y and Z to correct Blender ups.
}
glm::quat SkeletalAnimation::aiToGlmQuat(const aiQuaternion& pAiQuat) {
return glm::quat(pAiQuat.w, pAiQuat.x, pAiQuat.y, pAiQuat.z);
}
I read my code line by line multiple times to see what I'm doing wrong but I can't think of anything. I don't think my shader is the issue but here's the vertex shader:
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 uv;
layout (location = 2) in vec3 normal;
layout (location = 3) in ivec4 boneIds;
layout (location = 4) in vec4 boneWeights;
out vec2 textureUV;
out vec3 lightNormal;
out vec4 worldPosition;
uniform mat4 model;
uniform mat4 projectionView;
uniform mat4 boneTransforms[50];
void main()
{
mat4 boneTransform = mat4(0.0f);
for(int i = 0; i < 4; i++){
boneTransform += boneTransforms[boneIds[i]] * boneWeights[i];
}
worldPosition = boneTransform * vec4(position, 1.0f);
worldPosition = model * worldPosition;
gl_Position = projectionView * worldPosition;
textureUV = uv;
lightNormal = mat3(transpose(inverse(model * boneTransform))) * normal;
}
The result:
I figured it out. BoneTransforms needed to be moved within the for loop. The same instance was getting over-written each loop cycle.

Using uninitialized memory c++ box2D

I'm trying to create a simple Arkanoid game with Box2d, and right now I'm trying to detect the collision between the ball and the blocks, but when I debug the code, it keeps giving me the error: "The variable 'block' is being used without being initialized". What should I do the solve this problem? The blocks (red) are like this in the game:
#ifndef ARKANOID_H
#define ARKANOID_H
#include "../Framework/Test.h"
#include <vector>
struct Contact
{
Contact(const b2Vec2& normal, const b2Vec2& contactPt, float32 penetration = 0.0f)
: m_normal(normal)
, m_contactPt(contactPt)
, m_penetrationDepth(penetration) {}
b2Vec2 m_normal;
b2Vec2 m_contactPt;
float32 m_penetrationDepth;
};
class Ball
{
public:
Ball(): m_position(0.0f,0.0f), m_velocity(0.0f,0.0f), m_radius(0.5f){}
Ball(const b2Vec2& position,const b2Vec2& velocity, float32 radius = 0.5f)
: m_position(position)
, m_velocity(velocity)
, m_radius(radius)
{
m_invMass = m_mass > 0.0f ? 1.0f / m_mass : 0.0f;
}
const b2Vec2& GetPosition(){return m_position;}
void SetPosition(const b2Vec2& newPosition){m_position = newPosition;}
float GetRadius() { return m_radius; }
void Update(float deltaTime);
void Render(DebugDraw& debugDraw);
void AddContact(const Contact& cp);
void HandleContacts();
void ApplyForce(const b2Vec2& force);
protected:
b2Vec2 m_position;
b2Vec2 m_velocity;
float32 m_radius;
float m_invMass;
float m_mass;
std::vector<Contact> m_contacts;
};
class Block
{
public:
Block( const b2Vec2 center, const b2Vec2& halfExtent );
void Render(DebugDraw& debugDraw);
b2Vec2 GetClosestPosition(const b2Vec2& pos);
protected:
b2AABB m_aabb;
};
class Paddle
{
public:
Paddle(b2Vec2& center, float width);
void Update(float deltaTime);
void Render(DebugDraw& debugDraw);
void SetSpeed(float speed) { m_speed = speed; }
b2Vec2 GetLeftPos() { return m_center - b2Vec2( m_width *0.5f, 0.0f ); }
b2Vec2 GetRightPos() { return m_center + b2Vec2( m_width *0.5f, 0.0f ); }
float GetHeight() { return m_center.y; }
void SetMoveRight();
void SetMoveLeft();
void Stop();
void SetWorldLimits(float min, float max);
protected:
b2Vec2 m_center;
float m_width;
float m_min_X;
float m_max_X;
b2Vec2 m_direction;
float m_speed;
};
class ArkanoidGame : public Test
{
public:
static Test* Create()
{
return new ArkanoidGame;
}
ArkanoidGame();
void CreateBlocks();
virtual void Step(Settings* settings);
void CheckCollisions();
void CheckOutofWorld();
bool IsOutofWorld(Ball* ball);
void UpdateBalls(float deltaTime);
void Render();
void Keyboard(unsigned char key);
void KeyboardUp(unsigned char key);
void AddBall();
void RemoveBall(Ball* ball);
void ApplyGravity();
b2Vec2 m_worldBoxMin;
b2Vec2 m_worldBoxMax;
Paddle m_paddle;
std::vector<Ball*> m_balls;
std::vector<Block*> m_blocks;
};
#endif
#include "../Framework/Render.h"
#include "Arkanoid.h"
#include <vector>
void Ball::Render(DebugDraw& debugDraw)
{
debugDraw.DrawSolidCircle(m_position, m_radius, b2Vec2(0.0f, 1.0f), b2Color(1.0f, 0.0f, 0.0f));
}
void Ball::Update(float deltaTime)
{
HandleContacts();
//Update position
//***To Do***
b2Vec2 m_force(b2Vec2_zero);
b2Vec2 acceleration = m_invMass * m_force;
m_velocity += deltaTime * acceleration;
m_position += deltaTime * m_velocity;
m_force = b2Vec2_zero;
}
void Ball::AddContact(const Contact& cp)
{
m_contacts.push_back(cp);
}
void Ball::HandleContacts()
{
//Resolve Collision
if (m_contacts.size() > 0)
{
//Prevent interpenetration => directly update position
b2Vec2 deltaPos(0.0f, 0.0f);
for (size_t i = 0; i<m_contacts.size(); ++i)
{
deltaPos += m_contacts[i].m_penetrationDepth * m_contacts[i].m_normal;
}
m_position += deltaPos;
//Average contact normal
b2Vec2 collisionNormal(0.0f, 0.0f);
for (size_t i = 0; i<m_contacts.size(); ++i)
{
collisionNormal += m_contacts[i].m_normal;
}
collisionNormal.Normalize();
//Update velocity
//***To Do*** ///fait
float restitution = 0.6f;
b2Vec2 vp = b2Dot(m_velocity, collisionNormal) * (collisionNormal);
b2Vec2 vt = m_velocity - vp;
m_velocity = vt + (-restitution * vp);
}
m_contacts.clear();
}
void Ball::ApplyForce(const b2Vec2& force)
{
b2Vec2 m_force(b2Vec2_zero);
m_force += force;
}
Block::Block(const b2Vec2 center, const b2Vec2& halfExtent)
{
m_aabb.lowerBound = center - halfExtent;
m_aabb.upperBound = center + halfExtent;
}
void Block::Render(DebugDraw& debugDraw)
{
debugDraw.DrawAABB(&m_aabb, b2Color(1.0f, 0.0f, 0.0f));
}
b2Vec2 Block::GetClosestPosition(const b2Vec2& pos)
{
b2Vec2 closestPosition;
if (pos.x < m_aabb.lowerBound.x)
{
if (pos.y < m_aabb.lowerBound.y)
{
closestPosition = m_aabb.lowerBound;
}
else if (pos.y > m_aabb.upperBound.y)
{
closestPosition = b2Vec2(m_aabb.lowerBound.x, m_aabb.upperBound.y);
}
else
{
closestPosition = b2Vec2(m_aabb.lowerBound.x, pos.y);
}
}
else if (pos.x > m_aabb.upperBound.x)
{
if (pos.y < m_aabb.lowerBound.y)
{
closestPosition = b2Vec2(m_aabb.upperBound.x, m_aabb.lowerBound.y);
}
else if (pos.y > m_aabb.upperBound.y)
{
closestPosition = m_aabb.upperBound;
}
else
{
closestPosition = b2Vec2(m_aabb.upperBound.x, pos.y);
}
}
else
{
if (pos.y < m_aabb.lowerBound.y)
{
closestPosition = b2Vec2(pos.x, m_aabb.lowerBound.y);
}
else if (pos.y > m_aabb.upperBound.y)
{
closestPosition = b2Vec2(pos.x, m_aabb.upperBound.y);
}
else
{
closestPosition = pos;
}
}
return closestPosition;
}
Paddle::Paddle(b2Vec2& center, float width)
: m_center(center)
, m_width(width)
, m_direction(0.0f, 0.0f)
, m_speed(12.0f)
, m_min_X(-b2_maxFloat)
, m_max_X(b2_maxFloat)
{
}
void Paddle::SetWorldLimits(float min, float max)
{
//***To Do***
}
void Paddle::Update(float deltaTime)
{
//***To Do***
}
void Paddle::Render(DebugDraw& debugDraw)
{
b2Vec2 halfExtent(m_width*0.5f, 0.0f);
debugDraw.DrawSegment(m_center + halfExtent, m_center - halfExtent, b2Color(0.0f, 1.0f, 0.0f));
}
void Paddle::SetMoveRight()
{
m_direction = b2Vec2(1.0f, 0.0f);
}
void Paddle::SetMoveLeft()
{
m_direction = b2Vec2(-1.0f, 0.0f);
}
void Paddle::Stop()
{
m_direction = b2Vec2_zero;
}
ArkanoidGame::ArkanoidGame()
: m_worldBoxMin(-25.0f,0.0f)
, m_worldBoxMax(25.0f,50.0f)
, m_paddle(b2Vec2(0.0f, 2.0f), 4.0f)
{
m_paddle.SetWorldLimits(m_worldBoxMin.x, m_worldBoxMax.x);
CreateBlocks();
}
void ArkanoidGame::CreateBlocks()
{
b2Vec2 blockHalfExtent(3.0f, 1.0f);
int nbColumn = 6;
int nbNbRow = 3;
b2Vec2 startPos = b2Vec2(-15.0f, 35.0f);
for (int i = 0; i < nbNbRow; i++)
{
for (int j = 0; j < nbColumn; j++)
{
b2Vec2 pos = startPos + b2Vec2(blockHalfExtent.x * 2.0f * j, blockHalfExtent.y * 2.0f * i);
Block* newBlock = new Block(pos, blockHalfExtent);
m_blocks.push_back(newBlock);
}
}
}
void ArkanoidGame::Step(Settings* settings)
{
float timeStep = settings->hz > 0.0f ? 1.0f / settings->hz : float32(0.0f);
ApplyGravity();
m_paddle.Update(timeStep);
UpdateBalls(timeStep);
CheckCollisions();
CheckOutofWorld();
Render();
}
void ArkanoidGame::UpdateBalls(float deltaTime)
{
for (size_t i = 0; i < m_balls.size(); ++i)
{
m_balls[i]->Update(deltaTime);
}
}
void ArkanoidGame::CheckCollisions()
{
//Box interior normals
b2Vec2 rightN(-1.0f,0.0f);
b2Vec2 leftN(1.0f,0.0f);
b2Vec2 upN(0.0f,-1.0f);
b2Vec2 downN(0.0f, 1.0f);
b2Vec2 blockHalfExtent(3.0f, 1.0f); /////
Block* block;
//Check collisions for all particules
std::vector<Ball*>::iterator it;
for (it=m_balls.begin(); it!= m_balls.end(); ++it)
{
Ball* ball = *it;
b2Vec2 pos = ball->GetPosition();
float32 radius = ball->GetRadius();
//Check collisions for each wall
//Left
float left = pos.x - radius;
if( left <= m_worldBoxMin.x )
{
ball->AddContact( Contact(leftN, b2Vec2( left, pos.y ), m_worldBoxMin.x - left ) );
}
//Right
float right = pos.x + radius;
if( right >= m_worldBoxMax.x )
{
ball->AddContact(Contact(rightN, b2Vec2( right, pos.y ), right - m_worldBoxMax.x ) );
}
//Up
float up = pos.y + radius;
if( up >= m_worldBoxMax.y )
{
ball->AddContact(Contact(upN, b2Vec2( pos.x, up ), up - m_worldBoxMax.y ) );
}
//Check Collision with paddle
//***To Do***
//Check collisions with blocks
//***To Do***
Block*block;
b2Vec2 toClosest = ball->GetPosition()- block->GetClosestPosition(pos);
float distance = toClosest.Normalize();
float penetrationDepth = distance - ball->GetRadius();
if (penetrationDepth < 0.0f)
{
ball->HandleContacts();
}
//Add contact and destroy the block
}
}
void ArkanoidGame::CheckOutofWorld()
{
for (size_t i = 0; i < m_balls.size(); )
{
if (IsOutofWorld(m_balls[i]))
{
//Remove ball
RemoveBall(m_balls[i]);
}
else
{
i++;
}
}
}
bool ArkanoidGame::IsOutofWorld(Ball* ball)
{
//***To Do***
return false;
}
void ArkanoidGame::Render()
{
m_paddle.Render(m_debugDraw);
//Render Ball
for (size_t i = 0; i < m_balls.size();++i)
{
m_balls[i]->Render(m_debugDraw);
}
//Render Blocks
for (size_t i = 0; i < m_blocks.size(); ++i)
{
m_blocks[i]->Render(m_debugDraw);
}
//Render Box
b2Vec2 box[4];
box[0].Set(m_worldBoxMin.x, m_worldBoxMin.y);
box[1].Set(m_worldBoxMin.x, m_worldBoxMax.y);
box[2].Set(m_worldBoxMax.x, m_worldBoxMax.y);
box[3].Set(m_worldBoxMax.x, m_worldBoxMin.y);
m_debugDraw.DrawSegment(box[0], box[1], b2Color(0.0f, 0.0f, 1.0f));
m_debugDraw.DrawSegment(box[1], box[2], b2Color(0.0f, 0.0f, 1.0f));
m_debugDraw.DrawSegment(box[2], box[3], b2Color(0.0f, 0.0f, 1.0f));
}
void ArkanoidGame::Keyboard(unsigned char key)
{
switch (key)
{
case 'a':
{
m_paddle.SetMoveLeft();
}
break;
case 'd':
{
m_paddle.SetMoveRight();
}
break;
case 'n':
{
AddBall();
}
break;
}
}
void ArkanoidGame::KeyboardUp(unsigned char key)
{
switch (key)
{
case 'a':
case 'd':
m_paddle.Stop();
break;
}
}
void ArkanoidGame::AddBall()
{
float angle = RandomFloat(-b2_pi*0.25f, b2_pi*0.25f);
b2Rot rot(angle);
float speed = RandomFloat(10.0f, 20.0f);
b2Vec2 dir(0.0f, speed);
Ball* ball = new Ball(b2Vec2(0.0f, 5.0f), b2Mul(rot, dir));
m_balls.push_back(ball);
}
void ArkanoidGame::RemoveBall(Ball* ball)
{
std::vector<Ball*>::iterator it = m_balls.begin();
while (it != m_balls.end())
{
if ((*it) == ball)
{
std::swap(*it, m_balls.back());
m_balls.pop_back();
break;
}
++it;
}
}
void ArkanoidGame::ApplyGravity()
{
for (Ball* ball : m_balls)
{
ball->ApplyForce(b2Vec2(0.0f,-9.81f));
}
}
You have the following code:
Block*block;
b2Vec2 toClosest = ball->GetPosition()- block->GetClosestPosition(pos);
You never assign anything to block, so which block should it be getting the closest position of?
My guess is you want to loop through all the blocks, so you need another iterator, something like this:
std::vector<Block*>::iterator itb;
for (itb = m_blocks.begin); itb != m_blocks.end(); ++itb) {
b2Vec2 toClosest = ball->getPosition - itb->getClosestPosition(pos);
float distance = toClosest.Normalize();
float penetrationDepth = distance - ball->GetRadius();
if (penetrationDepth < 0.0f)
{
ball->HandleContacts();
}
}

Auto iteration iterates past the vectors end (I think)

I am currently trying to code a PacMan game with c++ and OpenGL. At the moment I am having some trouble when trying to delete a "Coin" pointer from a vector of "GameObject" pointers:
bool MovableObject::collisionHandling(std::vector<GameObject*>& v) {
bool deleteCoin = false;
int vectorIt;
for (auto it = begin(v); it != end(v); ++it) {
if (*it != this) {
// If there is a collision imminent:
if (collisionInt(this, *it)) {
// For PacMan:
if (this->classType == "PacMan") {
// Collision with coin:
if ((*it)->classType == "Coin") {
PacMan* ptr = static_cast<PacMan*>(this);
ptr->score += 10;
vectorIt = it - v.begin();
deleteCoin = true;
}
}
}
}
}
if (deleteCoin) {
Coin* ptr = static_cast<Coin*>(v[vectorIt]);
v.erase(v.begin() + vectorIt);
delete ptr;
}
return false;
}
This throws an exception in a vector file, which reads:
Exception thrown: read access violation.
_Mycont was nullptr. The exception is thrown on line 72, which has the following code:
_STL_VERIFY(_Ptr < _Mycont->_Mylast, "can't increment vector iterator past end")
It seems like the exception gets thrown between another for loop, where the collisionHandling function gets called from:
void GameLevel::update(GLFWwindow *window) {
std::string classType;
// Delta time:
float timeSinceStart = float(glfwGetTime());
deltaTime = timeSinceStart - oldTimeSinceStart;
oldTimeSinceStart = timeSinceStart;
if (!paused) {
std::cout << "Testing...";
// We don't need to update walls:
for (auto it = begin(objectsPtr); it != end(objectsPtr); ++it) {
classType = (*it)->classType;
if (classType == "Ghost") {
Ghost* ptr = static_cast<Ghost*>(*it);
ptr->deltaTime = deltaTime;
ptr->pickRandomOffDirection();
ptr->updateSpeed();
ptr->collisionHandling(objectsPtr);
ptr->move();
ptr->animate();
}
else if (classType == "PacMan") {
PacMan* ptr = static_cast<PacMan*>(*it);
ptr->deltaTime = deltaTime;
ptr->input(window);
ptr->updateSpeed();
paused = ptr->collisionHandling(objectsPtr);
ptr->move();
ptr->animate();
}
}
std::cout << "complete\n";
}
As I understand it, this shouldn't throw an exception. (Testing similar code with a vector of ints, where the last element was erased in the loop didn't fail.) This code also worked properly until I added the collision with coin part, so I know it has something to do with the vector loop. Additionally, this only fails when some specific coins should get deleted. Most of them get deleted without any problem.
I appreciate any feedback or attempt to help me.
Just in case you need more information, the collisionHandling function contains more code, but I don't think it has anything to do with the error. Here is the full code:
bool MovableObject::collisionHandling(std::vector<GameObject*>& v) {
bool deleteCoin = false;
int vectorIt;
for (auto it = begin(v); it != end(v); ++it) {
if (*it != this) {
// If there is a collision imminent:
if (collisionInt(this, *it)) {
// For PacMan:
if (this->classType == "PacMan") {
// Collision with wall:
if ((*it)->classType == "GameObject") {
bool xCollision = false, yCollision = false;
glm::vec2 oldSpeed = speed;
// Check x-axis:
speed.y = 0.f;
xCollision = collisionInt(this, *it);
// Check y-axis:
speed.y = oldSpeed.y;
speed.x = 0.f;
yCollision = collisionInt(this, *it);
speed = glm::vec2(!xCollision * oldSpeed.x, !yCollision * oldSpeed.y);
}
// Collision with ghost
if ((*it)->classType == "Ghost") {
std::cout << "GAME OVER\n";
return true;
}
// Collision with coin:
if ((*it)->classType == "Coin") {
PacMan* ptr = static_cast<PacMan*>(this);
ptr->score += 10;
vectorIt = it - v.begin();
deleteCoin = true;
}
}
// For Ghost:
else if (this->classType == "Ghost") {
// Collision with wall:
if ((*it)->classType == "GameObject") {
// Same as with PacMan:
bool xCollision = false, yCollision = false;
glm::vec2 oldSpeed = speed;
// Check x-axis:
speed.y = 0.f;
xCollision = collisionInt(this, *it);
// Check y-axis:
speed.y = oldSpeed.y;
speed.x = 0.f;
yCollision = collisionInt(this, *it);
speed = glm::vec2(!xCollision * oldSpeed.x, !yCollision * oldSpeed.y);
}
}
}
}
}
if (deleteCoin) {
Coin* ptr = static_cast<Coin*>(v[vectorIt]);
v.erase(v.begin() + vectorIt);
delete ptr;
}
return false;
}
Thanks to rafix07 for pointing out my mistake in a comment:
Undefined behaviour, it is invalidated in for loop inside update by collisionHandling. collisionHandling iterates over the same container, calling erase
I fixed this by deleting the desired object after the for loop inside the update method, instead of inside the collisionHandling method.

Wavefront OBJ Loading Error: Missing and incorrect faces

I'm trying to load Wavefront OBJ files for a game I'm trying to make. I have it down... sort of. Some of the faces are either missing when rendered, or they have incorrect vertex positions. The way I'm loading them probably isn't very optimized, too. I'm using LWJGL and it's OpenGL binding (I think it's a binding) to render my model.
Here's what the model is supposed to look like:
http://i1291.photobucket.com/albums/b560/DandDMC/blender2014-07-0415-28-40-23_zps0fbfcebb.png
Here's what it looks like ingame:
http://i1291.photobucket.com/albums/b560/DandDMC/javaw2014-07-0415-52-54-99_zpsdf14fb6c.png
Here's the loading method:
public static Model loadModel(String fileLocation)
{
File file = new File(fileLocation);
if(!file.exists())
{
try
{
throw new FileNotFoundException("The file named: " + fileLocation + " doesn't exist!");
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
return null;
}
ArrayList<Vertex> vertices = new ArrayList<Vertex> ();
ArrayList<Vertex> texVertices = new ArrayList<Vertex> ();
ArrayList<Face> faces = new ArrayList<Face> ();
try
{
BufferedReader r = new BufferedReader(new FileReader(file));
String s = "";
int refIndex = 1;
int vertIndex = 1;
int texVertIndex = 1;
while((s = r.readLine()) != null)
{
String[] split = s.split(" ");
if(split[0].equals("v"))
{
vertices.add(new Vertex(Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]), vertIndex));
vertIndex ++;
}
else if(split[0].equals("vt"))
{
texVertices.add(new Vertex(Double.parseDouble(split[1]), Double.parseDouble(split[2]), 0.0, texVertIndex));
texVertIndex ++;
}
else if(split[0].equals("f"))
{
ArrayList<Integer> vert = new ArrayList<Integer> ();
ArrayList<Integer> texVert = new ArrayList<Integer> ();
for(int i = 1; i < split.length; i ++)
{
String[] fSplit = split[i].split("/");
vert.add(Integer.parseInt(fSplit[0]));
texVert.add(Integer.parseInt(fSplit[1]));
}
faces.add(new Face(vert, texVert));
}
else if(split[0].equals("#") || split[0].equals("o") || split[0].equals("mtllib") || split[0].equals("usemtl") || split[0].equals("s"))
{
// Don't have a use for as of now
}
else
{
throw new Exception("The syntax at line: " + refIndex + " is incorrect.");
}
refIndex ++;
}
r.close();
}
catch (Exception e)
{
e.printStackTrace();
}
return new Model(vertices, texVertices, faces);
}
Here's the Face class (it's pretty simple):
package com.glh.model;
import java.util.*;
public class Face
{
public ArrayList<Integer> verticeIndexes;
public ArrayList<Integer> texVerticeIndexes;
public Face(ArrayList<Integer> verticeIndexes, ArrayList<Integer> texVerticeIndexes)
{
this.verticeIndexes = verticeIndexes;
this.texVerticeIndexes = texVerticeIndexes;
}
public ArrayList<Vertex> getVertexPositions(Model parentModel)
{
ArrayList<Vertex> l = new ArrayList<Vertex> ();
for(int i : verticeIndexes)
{
l.add(parentModel.vertices.get(i - 1));
}
return l;
}
public ArrayList<Vertex> getTextureVertexPositions(Model parentModel)
{
ArrayList<Vertex> l = new ArrayList<Vertex> ();
for(int i : texVerticeIndexes)
{
l.add(parentModel.texVertices.get(i - 1));
}
return l;
}
}
The Vertex class even simpler:
package com.glh.model;
public class Vertex
{
public double x;
public double y;
public double z;
public int index;
public Vertex(double x, double y, double z, int index)
{
this.x = x;
this.y = y;
this.z = z;
this.index = index;
}
public Vertex()
{
this(0.0, 0.0, 0.0, 0);
}
}
And the method to render it:
public void renderModel_tri(Model m)
{
// The model is much smaller in Blender
glScalef(0.3F, 0.3F, 0.3F);
glBegin(GL_QUADS);
for(Face f : m.faces)
{
ArrayList<Vertex> vertices = f.getVertexPositions(m);
ArrayList<Vertex> texVertices = f.getTextureVertexPositions(m);
for(int i = 0; i < vertices.size(); i ++)
{
Vertex vert = vertices.get(i);
Vertex texVert = texVertices.get(i);
glTexCoord2d(texVert.x, texVert.y);
glVertex3d(vert.x, vert.y, vert.z);
}
}
glEnd();
}
The Model class just holds a list of all the vertices and faces.
package com.glh.model;
import java.util.*;
public class Model
{
public ArrayList<Vertex> vertices;
public ArrayList<Vertex> texVertices;
public ArrayList<Face> faces;
public Model(ArrayList<Vertex> vertices, ArrayList<Vertex> texVertices, ArrayList<Face> faces)
{
this.vertices = vertices;
this.texVertices = texVertices;
this.faces = faces;
}
}
I found it... My model is composed of triangles, and I was drawing QUADS in the game. I changed the: glBegin(GL_QUADS); to glBegin(GL_TRIANGLES);