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.
Related
Trying to implement A* pathfinding for a small game.
Running into some strange bugs.
Code:
class PathNode
{
private:
const PathNode* parent;
GameObject position;
int g;
int h;
int f;
public:
PathNode(GameObject _position, int _g, int _h, const PathNode* _parent = nullptr) {
parent = _parent;
position = _position;
g = _g; // distance between the start node and the current node
h = _h; // distance between the current node and the end node
f = g + h; // Total cost of the node (g + h)
}
PathNode(const PathNode& other) {
position = other.position;
g = other.g; // distance between the start node and the current node
h = other.h; // distance between the current node and the end node
f = other.f; // Total cost of the node (g + h)
parent = other.parent;
}
bool operator==(const PathNode& other) const {
return (this->position == other.position);
}
bool operator!=(const PathNode& other) const {
return !operator==(other);
}
// GETTERS:
int getg() const;
int geth() const;
int getf() const;
const PathNode* getParent() const;
const GameObject getPosition() const;
// SETTERS:
void setg(int newG);
void seth(int newH);
void setf(int newF);
void setParent(const PathNode* newParent);
void setPosition(GameObject newPos);
};
And The PathFinding code:
char Ghost::aStar(char board[BoardYSize][BoardXSize], GameObject end) {
int startEndDistance = pow((getX() - end.getX()), 2) + pow((getY() - end.getY()), 2);
PathNode startNode(getPosition(), 0, startEndDistance); // in the ctor of PathNode, the default values of Parent == nullptr.
PathNode endNode(end, startEndDistance, 0); // in the ctor of PathNode, the default values of Parent == nullptr.
// Initialize both Open and Closed Lists:
vector<PathNode> openList;
vector<PathNode> closedList;
openList.reserve(500);
closedList.reserve(500);
// Add the StartNode:
openList.push_back(startNode);
// Loop until you find the end:
while (openList.size() > 0) {
// Get the current Node.
PathNode currentNode(openList[0]); // Default copy ctor.
int index = 0;
for (int i = 0; i < openList.size(); i++) {
if (openList[i].getf() < currentNode.getf()) {
index = i;
currentNode = openList[i];
}
}
// Pop Current off openList, and add to closedList:
openList.erase(openList.begin() + index);
closedList.push_back(currentNode);
// If found the end, return:
if (currentNode == endNode) {
// return direction of the first move of the path:
const PathNode* nextNode = ¤tNode;
const PathNode* firstMoveNode = nullptr;
while (*nextNode != startNode) {
firstMoveNode = nextNode;
nextNode = nextNode->getParent();
}
GameObject newPos = firstMoveNode->getPosition();
GameObject currentPos = startNode.getPosition();
if (newPos.getX() - currentPos.getX() == 0 && newPos.getY() - currentPos.getY() == 1)
return 'w';
else if (newPos.getX() - currentPos.getX() == 0 && newPos.getY() - currentPos.getY() == -1)
return 'x';
else if (newPos.getX() - currentPos.getX() == 1 && newPos.getY() - currentPos.getY() == 0)
return 'd';
else if (newPos.getX() - currentPos.getX() == -1 && newPos.getY() - currentPos.getY() == 0)
return 'a';
else
return 's';
}
// Generate Children:
vector <PathNode> children;
// temp is used to generate the children of the currentNode.
GameObject temp[4];
temp[0] = GameObject(0, 1); // Position above currentNode
temp[1] = GameObject(1, 0); // Position right of currentNode
temp[2] = GameObject(0, -1); // Position left of currentNode
temp[3] = GameObject(-1, 0); // Position below currentNode
for (int i = 0 ; i < 4; i++) {
// Get Node Position:
GameObject nodePosition(currentNode.getPosition() + temp[i]);
// Make Sure within Range of board & Not a Wall (walkable terrain):
if (!checkLegalMove(nodePosition, board))
continue;
PathNode newNode(nodePosition, 0, 0, ¤tNode);
children.push_back(newNode);
}
// Loop through Children:
for (auto child : children) {
bool addChild = true;
// Child is on the closedList:
for (auto closedChild : closedList) {
if (child == closedChild) {
addChild = false;
continue;
}
}
if (!addChild) continue;
// Create the G H and F values:
child.setg(currentNode.getg() + 1);
child.seth(pow((child.getPosition().getX() - endNode.getPosition().getX()), 2) + pow((child.getPosition().getY() - endNode.getPosition().getY()), 2));
child.setf(child.getg() + child.geth());
// Child is already in the openList:
for (auto openNode : openList) {
if (child == openNode && child.getg() > openNode.getg()) {
addChild = false;
continue;
}
}
if (!addChild) continue;
openList.push_back(child);
}
}
return 's';
}
My goal is to use A* to find a route and simply return the direction of the first position to travel to.
The Problem I run into:
The problem is that when the condition of currentNode == endNode is met (reached the goal).
Every PathNode's parent is simply a pointing to itself (according to the debugger).
I don't understand why it's happening, I assume The current pointer gets destroyed at some point in time but i can't figure out why it's happening.
You're storing a pointer to a local variable, so every node will have that same local variable as the parent (and any access to it would be Undefined Behavior once it has been destroyed). This happens in the PathNode newNode(nodePosition, 0, 0, ¤tNode); declaration.
You might want to change currentNode to be a pointer, rather than an object, and have it point to the parent object in the closedList vector. This would allow you to update what node it points at without having to make copies. You've reserved enough space for 500 entries so you won't have a problem with dangling pointers until you add the 501st element to the vector.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am running into a few errors, a memory error with the back function and failing to pass many of the tests that the program checks for.
I need to get this code working, which is in Vector.cpp:
#include <stdexcept>
#include "Vector.h"
using namespace std;
void Vector::grow()
{
const int GROWER = 1.6;
capacity = capacity * GROWER;
}
Vector::Vector()
{
capacity = CHUNK;
n_elems = 0;
data_ptr = new int[capacity];
for (size_t i = 0; i < capacity; i++)
{
data_ptr[i] = 0;
}
}
Vector::Vector(const Vector& v)
{
n_elems = 0;
capacity = v.capacity;
data_ptr = new int[capacity];
for (size_t i = 0; i < capacity; i++)
{
data_ptr[i] = v.data_ptr[i];
n_elems++;
}
}
Vector& Vector::operator=(const Vector& v)
{
capacity = v.capacity;
data_ptr = new int[capacity];
for (size_t i = 0; i < capacity; i++)
{
data_ptr[i] = v.data_ptr[i];
}
return *this;
}
Vector::~Vector()
{
delete[] data_ptr;
}
int Vector::front() const
{
if (n_elems != 0)
{
return data_ptr[0];
}
else
{
return -1;
throw range_error("Range Error");
}
}
int Vector::back() const
{
if (n_elems != 0)
{
return data_ptr[n_elems - 1];
}
else
{
throw range_error("Range Error");
}
}
int Vector::at(size_t pos) const
{
if (pos >= 0 && pos < capacity)
{
return data_ptr[pos];
}
else
{
throw range_error("Range Error");
}
}
size_t Vector::size() const
{
return n_elems;
}
bool Vector::empty() const
{
if (n_elems == 0)
{
return true;
}
else
{
return false;
}
}
int& Vector::operator[](size_t pos)
{
return data_ptr[pos];
}
void Vector::push_back(int item)
{
grow();
data_ptr[n_elems - 1] = item;
}
void Vector::pop_back()
{
if (n_elems >= 0)
{
--n_elems;
}
else
{
throw range_error("Range Error");
}
}
void Vector::erase(size_t pos)
{
if (pos >= 0 && pos < capacity)
{
for (size_t i = pos; i < capacity; i++)
{
data_ptr[i] = data_ptr[i + 1];
}
n_elems--;
}
else
{
throw range_error("Range Error");
}
}
void Vector::insert(size_t pos, int item)
{
int moveCount = n_elems - pos;
grow();
if (pos >= 0 && pos < capacity)
{
for (size_t i = n_elems; i >= 0; i--)
{
data_ptr[i] = data_ptr[i - 1];
}
data_ptr[pos] = item;
n_elems++;
}
else
{
throw range_error("Range Error");
}
}
void Vector::clear()
{
n_elems = 0;
}
int* Vector::begin()
{
if (n_elems == 0)
{
return nullptr;
}
else
{
return data_ptr;
}
}
int* Vector::end()
{
if (n_elems == 0)
{
return nullptr;
}
else
{
return (data_ptr + (n_elems - 1));
}
}
bool Vector::operator==(const Vector& v) const
{
bool flag = true;
for (size_t i = 0; i < capacity; i++)
{
if (data_ptr[i] == v.data_ptr[i])
{
flag = true;
}
else
{
flag = false;
break;
}
}
return flag;
}
bool Vector::operator!=(const Vector& v) const
{
bool flag = true;
for (size_t i = 0; i < capacity; i++)
{
if (data_ptr[i] != v.data_ptr[i])
{
flag = true;
}
else
{
flag = false;
break;
}
}
return flag;
}
To pass these tests, which are located in a file called testVector.cpp:
#include "Vector.h"
#include "test.h"
#include <stdexcept>
using namespace std;
int main() {
// Test exceptions
Vector v;
throw_(v.at(0), range_error);
throw_(v.pop_back(), range_error);
throw_(v.erase(0), range_error);
throw_(v.front(), range_error);
throw_(v.back(), range_error);
// Test adding an element
v.push_back(1);
test_(v.size() == 1);
test_(v.at(0) == 1);
test_(v[0] == 1);
test_(v.front() == 1);
test_(v.back() == 1);
test_(!v.empty());
// Add another
v.push_back(2);
test_(v.size() == 2);
test_(v.at(0) == 1);
test_(v.at(1) == 2);
test_(v[0] == 1);
test_(v[1] == 2);
test_(v.front() == 1);
test_(v.back() == 2);
test_(!v.empty());
// Test iterators
auto iter = v.begin();
test_(*iter == 1);
++iter;
test_(*iter == 2);
++iter;
test_(iter == v.end());
// Test copy and ==
Vector v2 = v;
test_(v2.size() == 2);
test_(v2.at(0) == 1);
test_(v2.at(1) == 2);
test_(v2[0] == 1);
test_(v2[1] == 2);
test_(v2.front() == 1);
test_(v2.back() == 2);
test_(!v2.empty());
test_(v == v2);
iter = v2.begin();
test_(*iter == 1);
++iter;
test_(*iter == 2);
++iter;
test_(iter == v2.end());
// Test assignment
Vector v3;
v3 = v;
test_(v3.size() == 2);
test_(v3.at(0) == 1);
test_(v3.at(1) == 2);
test_(v3[0] == 1);
test_(v3[1] == 2);
test_(v3.front() == 1);
test_(v3.back() == 2);
test_(!v3.empty());
//iter = v3.begin();
//test_(*iter == 1);
//++iter;
//test_(*iter == 2);
//++iter;
//test_(iter == v3.end());
// Test assignment
v[1] = -2;
test_(v.back() == -2);
test_(v.at(1) == -2);
test_(v[1] == -2);
// Test pop_back
v.pop_back();
test_(v.size() == 1);
test_(v.front() == 1);
test_(v.back() == 1);
test_(v.at(0) == 1);
test_(v[0] == 1);
// Test clear and !=
v.clear();
test_(v.size() == 0);
test_(v.empty());
throw_(v.at(0), range_error);
throw_(v.pop_back(), range_error);
throw_(v.erase(0), range_error);
throw_(v.front(), range_error);
throw_(v.back(), range_error);
test_(v != v2);
// Test erase
v3.erase(0);
test_(v3.size() == 1);
test_(v3.at(0) == 2);
test_(v3[0] == 2);
test_(v3.front() == 2);
test_(v3.back() == 2);
// Test insert
//v3.insert(0,1);
test_(v3.size() == 2);
test_(v3.at(0) == 1);
test_(v3[0] == 1);
test_(v3[1] == 2);
test_(v3.front() == 1);
test_(v3.back() == 2);
// Test grow
Vector v4;
for (int i = 1; i <= 10; ++i)
v4.push_back(i);
test_(v4.size() == 10);
test_(v4.front() == 1);
test_(v4.back() == 10);
v4.insert(10,11);
test_(v4.size() == 11);
test_(v4.front() == 1);
test_(v4.back() == 11);
report_();
}
push_back() and insert() both call grow(), which fails to increase the capacity because GROWER is an int, so 1.6 truncates to 1, and multiplying capacity * 1 doesn't change its value. But even if capacity were increased properly, the data_ptr array is not being re-allocated at all to fit the new capacity.
But even if grow() were working properly, there is no need to call grow() on every insertion of a new element, that defeats the purpose of separating n_elems from capacity to begin with. You should grow the array only when n_elems has reached capacity.
There are many other problems with your class, as well:
operator= is not testing for self-assignment, and is leaking the allocated memory of the old array. Consider using the copy-swap idiom instead.
front() does not reach the throw statement when the array is empty.
at() and erase() are performing bounds checking using capacity instead of n_elems.
push_back() is inserting the new value at the wrong index, and not incrementing n_elems.
pop_back() does not throw an error when the array is empty, causing n_elems to decrement below 0, which wraps around to the max positive value of size_t because size_t is unsigned.
erase() and operator== go out of bounds while iterating the array.
begin() and end() should not be returning nullptr for an empty array. And end() is returning a pointer to the last element in a non-empty array rather than returning a pointer to 1 past the last element.
operator== and operator!= do not perform any bounds checking to make sure the 2 vectors have the same same n_elems before iterating their arrays. And they are comparing element values beyond n_elems. Also, operator!= is returning the wrong result.
With that said, try something more like this:
#include <stdexcept>
#include <algorithm>
#include "Vector.h"
void Vector::grow()
{
static const float GROWER = 1.6f;
size_t new_capacity = capacity * GROWER;
if (new_capacity <= capacity)
throw std::overflow_error("cant grow capacity");
int *new_data_ptr = new int[new_capacity];
std::copy(data_ptr, data_ptr + n_elems, new_data_ptr);
delete[] data_ptr;
data_ptr = new_data_ptr;
capacity = new_capacity;
}
Vector::Vector()
{
capacity = CHUNK;
n_elems = 0;
data_ptr = new int[capacity];
}
Vector::Vector(const Vector& v)
{
capacity = v.capacity;
n_elems = v.n_elems;
data_ptr = new int[capacity];
std::copy(v.begin(), v.end(), data_ptr);
}
Vector& Vector::operator=(const Vector& v)
{
if (this != &v)
{
Vector temp(v);
std::swap(capacity, temp.capacity);
std::swap(n_elems, temp.n_elems);
std::swap(data_ptr, temp.data_ptr);
}
return *this;
}
Vector::~Vector()
{
delete[] data_ptr;
}
int Vector::front() const
{
if (n_elems == 0)
throw std::range_error("Range Error");
return data_ptr[0];
}
int Vector::back() const
{
if (n_elems == 0)
throw std::range_error("Range Error");
return data_ptr[n_elems - 1];
}
int Vector::at(size_t pos) const
{
if (pos >= n_elems)
throw std::range_error("Range Error");
return data_ptr[pos];
}
size_t Vector::size() const
{
return n_elems;
}
bool Vector::empty() const
{
return (n_elems == 0);
}
int& Vector::operator[](size_t pos)
{
return data_ptr[pos];
}
void Vector::push_back(int item)
{
if (n_elems == capacity)
grow();
data_ptr[n_elems] = item;
++n_elems;
}
void Vector::pop_back()
{
if (n_elems == 0)
throw std::range_error("Range Error");
--n_elems;
}
void Vector::erase(size_t pos)
{
if (pos >= n_elems)
throw std::range_error("Range Error");
std::copy(data_ptr + pos + 1, data_ptr + n_elems, data_ptr + pos);
--n_elems;
}
void Vector::insert(size_t pos, int item)
{
if (pos > n_elems) // insert at end() is OK...
throw range_error("Range Error");
if (n_elems == capacity)
grow();
std::copy_backward(data_ptr + pos, data_ptr + n_elems, data_ptr + n_elems + 1);
data_ptr[pos] = item;
++n_elems;
}
void Vector::clear()
{
n_elems = 0;
}
int* Vector::begin()
{
return data_ptr;
}
int* Vector::end()
{
return data_ptr + n_elems;
}
bool Vector::operator==(const Vector& v) const
{
return (n_elems == v.n_elems) && std::equal(v.begin(), v.end(), data_ptr);
}
bool Vector::operator!=(const Vector& v) const
{
return !(*this == v);
}
Your'e missing declaration of vector here, but I can surmize its structure. First mistake you doing here:
const int GROWER = 1.6;
capacity = capacity * GROWER; // it will never change
Reallocation doesn't happen, where it is? You just call upon dietis of Undefined Behaviour.
You either have to make GROWER float or increment size by fixed size. Also it's very unusual for a vector to grow geometrically. And very ineffective to grow on each push_back. Instead you have to have a CAPACITY and ALLOCATED SIZE. Latter may be greater and would grow if push_back would increase capacity outside of its limits. You apparently have a number of elements n_elems there, but you ignore it?
Many of those are doing too much extra work and don't do what they should.
Like this could actually be
data_ptr = new int[capacity] {}; // value initialization
//for (size_t i = 0; i < capacity; i++)
//{
// data_ptr[i] = 0;
//}
Use initialization lists, not side effects. Or you may lose them. Copy constructor also doesn't take in account n_elems of v and may copy garbage, converting it into new elements.
Comparisons simply wrong, because vectors can have different capacity. E.g.
bool Vector::operator==(const Vector& v) const
{
// check here if either of vectors is empty,
// if both are empty, they are equal?
// if their capacities are unequal, vectors are not equal. Those are shortcuts
// capacities are same
for (size_t i = 0; i < capacity; i++ )
if (data_ptr[i] != v.data_ptr[i])
return false;
return true;
}
I've trying to make the following code implement by iteration instead of recursive(the "hit" function of the BVH_node class),but I've no idea how to do this.I did found some topic about this,but they are not very helpful.Could anybody help me?thanks so much.(by the way this is the third time I ask this question ,people says that I'm not making my question clear,please give me some advice on asking question)
class Ray {
public:
vec3 origin;
vec3 direction;
float t = 10000.0f;
};
class AABB{
public:
bool hit(Ray ray){
//test if ray hit itself
//but don't write the t into the ray
}
vec3 min;
vec3 max;
};
class Geometry {
public:
bool hit(Ray& ray) {
float t;
//test if the ray hit this geometry
//ray.origin + t * ray.direction = ......
//then solve the t
if (t < ray.t && t>0.0f) {
ray.t = t;
return true;
}
return false;
}
private:
//...some private data
};
class BVH_node {
public:
void construction(/*......some data for construction*/) {
//is_node_or_geo = ......
//if(is_node_or_geo == 0)
//L_node = ......; R_node = ......;
//if(is_node_or_geo == 1)
//L_geo = ......; R_geo = ......;
}
bool hit(Ray& ray) {
if(aabb.hit(ray)){
if (is_node_or_geo == 0) {
bool hit_left = L_node->hit(ray);
bool hit_right = R_node->hit(ray);
return hit_left || hit_right;
}
if (is_node_or_geo == 1) {
bool hit_left = L_geo->hit(ray);
bool hit_right = R_geo->hit(ray);
return hit_left || hit_right;
}
}
return false;
}
private:
int is_node_or_geo;//node = 0,geo = 1
AABB aabb;
BVH_node* L_node;
BVH_node* R_node;
Geometry* L_geo;
Geometry* R_geo;
};
int main(){
BVH_node* root = new BVH_node;
root->construction(/*.......*/);
Ray ray;//ray = .......;
bool hit = root->hit(ray);
delete root;
}
most tutorial I found on how to change recursion to iteration(like this http://blog.moertel.com/posts/2013-05-11-recursive-to-iterative.html) don't consider some situation where member variable is involved.
Follow up on the approach mentioned in a comment of Alan Birtles:
Let us focus on the hit function. In order to make our life easier, it's better to first rewrite it as follows:
bool hit(Ray& ray) {
if(!aabb.hit(ray)) return false;
if (is_node_or_geo == 0) {
if (L_node->hit(ray)) return true;
if (R_node->hit(ray)) return true;
return false;
} else if (is_node_or_geo == 1) {
if (L_geo->hit(ray)) return true;
if (R_geo->hit(ray)) return true;
return false;
}
}
Now we can add a loop and a stack (implemented with a std::vector, since I'm not familiar with std::stack). We also make the method static, as it does not relate to a single object:
static bool hit(Ray& ray, BVH_node * root) {
std::vector<BVH_node *> nodes {root};
while (!nodes.empty()){
BVH_node & top = *nodes.back();
nodes.pop_back();
if(!top.aabb.hit(ray)) continue;
if (top.is_node_or_geo == 0) {
nodes.push_back(top.L_node);
nodes.push_back(top.R_node);
continue;
} else if (top.is_node_or_geo == 1) {
if (top.L_geo->hit(ray)) return true;
if (top.R_geo->hit(ray)) return true;
continue;
}
}
return false;
}
This code wasn't tested so it might require minor tweacking.
Note that depending on your implementation, you might want to add a bunch of consts - after every mention of BVH_node
I am struggling with removing a unique_ptr from vector. I have a vector:
std::vector<std::unique_ptr<Agent>>;
which I am filling with certain number of Agents. I do it in the following way:
In class constructor list:
agentsVec(std::accumulate(agentsQuantity.begin(), agentsQuantity.end(), 0,
[](int value, const QuantityMap::value_type& p) { return value + p.second; }))
And in a function responsible for generating agents:
auto agentVecIter = agentsVec.begin();
std::for_each(agentsQuantity.begin(), agentsQuantity.end(), [this, &agentVecIter](const QuantityMap::value_type& p)
{
std::generate_n(agentVecIter, p.second, [this, &p]()
{
auto agent = factory.createAgent(p.first);
changeAgentOnLattice(generatePosition(), agent->getID());
return agent;
});
std::advance(agentVecIter, p.second);
});
agentsQuantity is a map which denotes number of agents of certain type. Agent factory returns std::unique_ptr;
During the program there can be a need to delete some Agent from vector, based on it's ID (Agent's ID is then returned to freeID's stack).
Kill agent:
std::experimental::erase_if(agentsVec, [this, &positionID](auto const& agent) {
auto agentID = agent->getID();
if (positionID == agentID) {
Utils::addIDToStack(agentID);
return true;
}
return false;
});
To test that, I added 5 agents which will be "killed". In about 40% of cases I got:
Debug Assertion Failed! Vector iterator not dereferencable
What am I doing wrong?
EDIT: More code for clarification. Lattice of agents (which contains agentsVec) is created in Environment class. Environment then step into infinite loop and iterates through all positions in lattice and check if something exists there. If yes -> it moves the agent and updates its health:
Environment::Health update:
for (int i = 0; i < latticeSize; i++) {
for (int j = 0; j < latticeSize; j++) {
if (lattice->getAgentID(Position(i, j))) {
Agent* currentAgent = lattice->getAgentInstance(Position(i, j));
currentAgent->updateHealth();
if (currentAgent->getAgentType() == Enums::AgentType::Predator && currentAgent->getHealth() <= -10) {
lattice->killAgent(Position(i, j));
}
}
}
}
Lattice::Move agent:
const auto originRow = Utils::BoundaryCondition(origin.first, latticeSize);
const auto originCol = Utils::BoundaryCondition(origin.second, latticeSize);
const auto destRow = Utils::BoundaryCondition(destination.first, latticeSize);
const auto destCol = Utils::BoundaryCondition(destination.second, latticeSize);
if (getAgentID(origin) == static_cast<int>(Enums::AgentType::Field)) {
Logger::getInstance().Log("lattice", "\tCannot move - there is no moving creature on origin position");
return false;
}
if (getAgentID(destination) != static_cast<int>(Enums::AgentType::Field)) {
Logger::getInstance().Log("lattice", "\tCannot move - destination position is occupied");
return false;
}
latticeMap[destRow][destCol] = static_cast<int>(getAgentID(origin));
latticeMap[originRow][originCol] = static_cast<int>(Enums::AgentType::Field);
Logger::getInstance().Log("lattice", "\tMoved succesfully");
return true;
Agent getter (used in Environment to get Agent pointer from Lattice::agentsVec)
Agent* Lattice::getAgentInstance(Position position) {
int ID = getAgentID(position);
auto it = std::find_if(std::execution::par, agentsVec.begin(), agentsVec.end(),
[=](std::unique_ptr<Agent>& agent) { return agent->getID() == ID; });
if (it != agentsVec.end()) {
return it->get();
}
return nullptr;
}
And AgentType getter:
Enums::AgentType Lattice::checkAgentType(int ID)
{
auto it = std::find_if(std::execution::par, agentsVec.begin(), agentsVec.end(),
[=](std::unique_ptr<Agent>& agent) { return agent->getID() == ID; });
if(it != agentsVec.end()) {
return it->get()->getAgentType();
}
return Enums::AgentType::Unknown;
}
I'm having a really bad time here looking for the error in my code.
My collision detection won't work here even the algorithm I searched in Google.
void PollEvents()
{
for (int i = 0;i < NUMBER_OF_BLOCKS; ++i)
{
Rectangle& a = blocks[i];
if (mouse.state == GLFW_PRESS)
{
//look for any block to grab
if (mouse.leftClick && !blocks[selectedBlock].Grab() &&
a.Hover(mouse.pos.x, mouse.pos.y))
{
//prevent grabbing another block
if (i != selectedBlock) {
selectedBlock = i;
}
a.Grab() = true;
if (a.IsTypeHorizontal()) {
diff = mouse.pos.x - a.Left();
} else {
diff = mouse.pos.y - a.Top();
}
}
if (a.Grab())
{
for (int j = 0;j < NUMBER_OF_BLOCKS; ++j)
{
//skip for any self-checking
if (i == j) continue;
Rectangle& b = blocks[j];
//check for rectangle collision
if (!a.Collide(b) || b.Collide(a)) {
//j++;
//how does this block will move.
if (a.IsTypeVertical()) {
a.SetY(mouse.pos.y - diff);
} else {
a.SetX(mouse.pos.x - diff);
}
} else {
switch (a.sideHit)
{
case UP:
//a.SetY(b.Bottom());
printf("UP\n");
break;
case DOWN:
//a.SetY(b.Top() + a.GetHeight());
printf("DOWN\n");
break;
case LEFT:
//a.SetX(b.Right());
printf("LEFT\n");
break;
case RIGHT:
//a.SetX(b.Left() - a.GetWidth());
printf("RIGHT\n");
break;
}
}
//check for bound collision
a.BoundCheck(1.f);
}
}
} else {
a.Grab() = false;
}
}
}
Collision detection:
bool Rectangle::Collide(const Rectangle& r) {
if (IsTypeHorizontal()) {
if (r.Hover(Left(), Top()) && r.Hover(Right(), Top())) {
sideHit = UP;
return true;
} else if (r.Hover(Right(), Bottom()) && r.Hover(Left(), Bottom())) {
sideHit = DOWN;
return true;
}
// } else if (r.Hover(Left(), Top())) {
// sideHit = UP;
// return true;
// } else if (r.Hover(Right(), Top())) {
// sideHit = UP;
// return true;
// } else if (r.Hover(Right(), Bottom())) {
// sideHit = DOWN;
// return true;
// } else if (r.Hover(Left(), Bottom())) {
// sideHit = DOWN;
// return true;
// }
} else {
if (r.Hover(Left(), Top()) && r.Hover(Left(), Bottom())) {
sideHit = LEFT;
return true;
} else if (r.Hover(Right(), Top()) && r.Hover(Right(), Bottom())) {
sideHit = RIGHT;
return true;
}
// } else if (r.Hover(Left(), Top())) {
// sideHit = LEFT;
// return true;
// } else if (r.Hover(Left(), Bottom())) {
// sideHit = LEFT;
// return true;
// } else if (r.Hover(Right(), Top())) {
// sideHit = RIGHT;
// return true;
// } else if (r.Hover(Right(), Bottom())) {
// sideHit = RIGHT;
// return true;
// }
}
return false;
}
Code for Hover:
inline float Hover(float X, float Y) const {
return X >= Left() &&
X <= Right() &&
Y >= Bottom() &&
Y <= Top();
}
I'm trying to make my own unblockme.
Please help me on my collision-detection. It's been 3 days now since I got stuck in this problem.
UPDATE
I have found out the problem why all rect-rect collision detection won't work in my program.
Bug:
if (!a.Collide(b)) {
//Move()
} else {
//Resolve collision
}
This one should be
if (!Rectangle::Collide(a, b)) {
//Move()
} else {
//Resolve collision
}
Making the Collide() a static member of Rectangle because, as you can see in my implementation of Collide(), it bases its decision on its own member so a.Hover(b.x, b.y) doesn't make any sense.
Hope this helps a little bit to all newbies like me.
To do rect/rect collision detection, if any of one (edges parallel to x and y axis) rect's four points is inside the other rect, we have a collision.
An easier way than to check each of the four points is to check if one X edge is between both the other rect's X edges, and if one Y edge is between both the other rect's Y edges - if both are true, we have a collision (because the two edges must meet at a point inside of the other rect). So we just check this in both directions:
bool isclamped(float mid, float A, float B)
{
if (A > B)
{
return mid >= B && mid <= A;
}
return mid >= A && mid <= B;
}
bool checkcollisiononeway(rect rectA, rect rectB)
{
if (isclamped(rectA.left, rectB.left, rectB.right)
|| isclamped(rectA.right, rectB.left, rectB.right))
&& (isclamped(rectA.bottom, rectB.bottom, rectB.top)
|| isclamped(rectA.top, rectB.bottom, rectB.top))
{
return true;
}
return false;
}
bool checkcollisionbothways(rect rectA, rect rectB)
{
return checkcollisiononeway(rectA, rectB) || checkcollisiononeway(rectB, rectA);
}
To determine the angle of collision after detecting a collision, find the angle between their two centers using atan2(rectA.y - rectB.y, rectA.x - rectB.x) (the angle is returned in radians, not in degrees)