i've got a problem on my collision Check Algorithm.
The problem is when i try to resolve collisions between 3 objects, 1 of them is still not colliding and not resolving collisions, here's the code:
void check_collisions(engine_t* engine)
{
for (int i = 0; i < engine->actor_count; i++)
{
actor_t* first = (actor_t*)engine->collision_pairs->data[i];
collider_t* a = (collider_t*)get_component_by_name(first, "collider");
for(int j = 0; j < engine->actor_count; j++)
{
actor_t* second = (actor_t*)engine->collision_pairs->data[j];
if(second == first)
continue;
collider_t* b = (collider_t*)get_component_by_name(second, "collider");
hit_state_t hit = aabb(a, b);
resolve_collisions(a, b, hit.normal);
}
}
}
The problem is that when for example: i have A, B, C
A could collide with B and C at the same frame time, it seems that when more object are colliding the first one (first) will not be calculated anymore.. any idea?
void resolve_collisions(collider_t* a, collider_t* b, vec2_t normal)
{
//Stop rigidbody
vec2_t position = a->owner->transform.position;
vec2_t position2 = b->owner->transform.position;
rigid_body_t* rb = (rigid_body_t*)get_component_by_name(a->owner, "rigid_body");
// if(!rb) { SDL_Log("rigid_body not while resolving collisions"); return; }
//hit from dx
if (normal.x > 0.0f && position.x < b->owner->transform.position.x + b->size.x)
{
rb->velocity.x = 0.0f;
// SDL_Log("collided dx");
position.x = (b->owner->transform.position.x + b->size.x) + 0.7f;
}
//hit from sx
if (normal.x < 0.0f && position.x + a->size.x > b->owner->transform.position.x)
{
rb->velocity.x = 0.0f;
float offset = b->size.x - a->size.x;
float offset2 = a->size.x - b->size.x;
// SDL_Log("collided sx");
position.x = (b->owner->transform.position.x - b->size.x) + offset;
position2.x = (a->owner->transform.position.x - a->size.x) + offset2;
}
//hit from top
if (normal.y < 0.0f && position.y + a->size.y > b->owner->transform.position.y)
{
rb->velocity.y = 0.0f;
float offset = b->size.y - a->size.y;
position.y = (b->owner->transform.position.y - b->size.y) + offset;
}
//hit from bottom
if (normal.y > 0.0f && position.y < b->owner->transform.position.y + b->size.y)
{
rb->velocity.y = 0.0f;
// SDL_Log("collided bottom");
position.y = (b->owner->transform.position.y + b->size.y) + 0.7f;
}
//change pos
a->owner->transform.position = position;
}
any help would be much appreciated!
-Thanks
Related
This is my first attempt with SFML and game development, and I'm having issues with the collision and gravity.
I'm making a 2D platformer game using a tilemap system.
Collision seems to be working(slightly) but is very choppy and I just can't seem to get gravity to work properly. I've tried a few different tutorials but I cant get anything working and I'm at a bit of a loss right now.
If someone could point out what I'm missing here it'd be greatly appreciated!!
Heres the code I'm using for these elements:
In PlayerSprite class(attempt at gravity)
PlayerSprite::PlayerSprite(const sf::Vector2f& size) : AnimatedSprite(size)
{
playerPos = (sf::Vector2f(300.0f, 400.0f));
dead = false;
jumpHeight = 5.f;
scale = 50.f;
accelGravity = 0.5f;
maxGravity = 5.f;
velocity.x = 2.0f;
velocity.y = 2.0f;
playerTexture.loadFromFile("gfx/spritemansheet.png");
setSize(sf::Vector2f(48, 48));
setPosition(playerPos);
setTexture(&playerTexture);
}
PlayerSprite::~PlayerSprite()
{
}
void PlayerSprite::update(float dt)
{
onGround = false;
if (input->isKeyDown(sf::Keyboard::A)) {
input->setKeyUp(sf::Keyboard::A);
playerPos.x -= (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walkBack;
}
if (input->isKeyDown(sf::Keyboard::D)) {
input->setKeyUp(sf::Keyboard::D);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walk;
}
if (input->isKeyDown(sf::Keyboard::W) ) {
input->setKeyUp(sf::Keyboard::Space);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &jump;
velocity.y = -5.f * -1;
}
if (onGround == false) {
velocity.y += accelGravity;
if (velocity.y > maxGravity) {
velocity.y = maxGravity;
}
}
if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
input->setMouseLeftUp(sf::Mouse::Left);
currentAnimation = &attack;
}
}
void PlayerSprite::setInput(Input* newInp)
{
input = newInp;
}
void PlayerSprite::collisionResponse(Sprite* sp)
{
if (velocity.x > 0) {
velocity.x = 0.f;
}
if (velocity.x < 0) {
velocity.x = 0.f;
}
if (velocity.y > 0) {
velocity.y = 0.f;
onGround = true;
}
if (velocity.y < 0) {
velocity.y = 0.f;
}
setPosition(getPosition().x, sp->getPosition().y - getSize().y);
}
Collision Detection in Game.cpp
void Game::update(float dt)
{
player.update(dt);
manager.update(dt);
std::vector<Tile>* world = worldMap.getScene();
for (int i = 0; i < (int)world->size(); i++) {
if ((*world)[i].isAlive()) {
if (checkGroundBounding(&player, &(*world)[i])) {
player.collisionResponse(&(*world)[i]);
}
}
}
void Game::render() {
beginDraw();
window->draw(bg);
window->draw(player);
manager.render(window);
worldMap.render(window);
endDraw();
}
bool Game::checkGroundBounding(PlayerSprite* b1, Tile* b2)
{
//get radius of sprites
float r1 = b1->getSize().x / 2;
float r2 = b2->getSize().x / 2;
float xposb1 = b1->getPosition().x + r1;
float xposb2 = b2->getPosition().x + r2;
float yposb1 = b1->getPosition().y + r1;
float yposb2 = b2->getPosition().y + r2;
if (pow(xposb2 - xposb1, 2) + pow(yposb2 - yposb1, 2) < pow(r1 + r2, 2)) {
return true;
}
return false;
}
you're collision is super wonky, simply set a oldPos variable to your player position, then do your movement code but only for the X axis, if collision is detected, set the position to oldPos, then set oldPos to the current position again, and repeat for the Y axis:
Pseudocode example:
//X axis
oldPos = position
if(leftKey):
position.x -= speed
if(rightKey):
position.x += speed
if(collision):
position = oldPos
//Y axis
oldPos = position
if(upKey):
position.y -= speed
if(downKey):
position.y += speed
if(collision):
position = oldPos
also you can replace your tile iteration with:
for(Tile t : *world){
t->doStuff();
}
I also don't recommend declaring your tile vector in your update function unless you hate performance
I need to create a function that returns a bool that is false if there is no collision between two sprites and true if there is, I was thinking for a long time and I can not find an exact solution, the objective is to detect if there is a collision per pixel, that is if two pixels with the alpha value (from rgba) different than 0 (it is visible) coincide in the same place in the space, the function has the following signature :
bool checkPixelCollision(
const Vector2& pixelPos1,
const Vector2& pixelSize1,
const vector<uint8_t> pixel1,
const Vector2& pixelPos2,
const Vector2& pixelSize2,
const vector<uint8_t> pixel2);
Vector2 is a struct with the next form:
struct Vector2
{
float x;
float y;
};
pixelPos1 is the position of the upper left corner of the rectangle that contains sprite 1, pixelSize1 is the size (x = width; y = height) of the rectangle that contains sprite 1, pixel1 is a vector that has the rgba values of each pixel of the sprite, they are stored from 4 to 4 so that i contains the amount of r of the pixel i; i + 1 the amount of g of the pixel i; i + 2 the amount of b of the pixel i; i + 3 the amount of alpha of the pixel i, so that if i + 3 is different from 0 is a visible pixel, the size of pixel1 is given by pixelSize1.x * pixelSize1.y * 4.
The other three parameters of the header are those corresponding to sprite 2. The objective would therefore be to check when there is a collision (either on the side or corner that is) and from there establish a collision rectangle between both rectangles (the coincident area), and set two indexes that travel pixel1 and pixel2 (since each one will have to start from a different position in its corresponding vector).
The problem is that I can not find an optimal and / or easy way to do it and that it works. If anyone knows any way to do it, I would appreciate it very much.
EDIT
Here is my code (it doesn't work)
#include <algorithm>
#include <stdint.h>
#include <vector>
struct Vector2
{
float x;
float y;
};
float clamp(float val, float min, float max) {
return std::max(min, std::min(max, val));
}
bool checkPixelCollision(const Vector2& pixelPos1, const Vector2& pixelSize1, const vector<uint8_t> pixel1, const Vector2& pixelPos2, const Vector2& pixelSize2, const vector<uint8_t> pixel2) {
return check(pixelPos1,pixelSize1,pixel1,pixelPos2,pixelSize2,pixel2)||check(pixelPos2,pixelSize2,pixel2,pixelPos1,pixelSize1,pixel1);
}
bool check(const Vector2& pixelsPos1, const Vector2& pixelsSize1, const vector<uint8_t> pixels1, const Vector2& pixelsPos2, const Vector2& pixelsSize2, const vector<uint8_t> pixels2){
bool res = false;
if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y <= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y >= pixelsPos2.y) {
float i = pixelsSize2.x - (pixelsSize1.y*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
float j = pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y));
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i2 = 0;
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)), 0.0f, pixelsSize2.x);
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y+pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float j = 0;
float i2 = 0;
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y),0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)),0.0f, pixelsSize2.x);
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y<= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y >= pixelsPos2.y) {
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j = clamp(pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y)),0.0f, pixelsSize2.y);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float j = 0;
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
return res;
}
Start by checking if the bounding rectangles of the two sprites overlap. If they don't, great; no collision is possible. If they do overlap, calculate the overlapping rectangle for each sprite and compare pixel by pixel - if pixel a or pixel b is transparent then there is no collision caused by that pixel, if both pixels are non-transparent there is a collision and you are done. If you finish checking all pixels in the overlapping area and there are no collisions you are also done.
I've successfully implemented BVH as described in PBRT. This one although has a slightly huge issue - the traversal looks through ALL nodes that intersect the ray, which is wrong (in terms of performance).
So I ended up optimizing the ray traversal, currently I use the version from Aila & Laine implementation of their "Understanding the efficiency of ray traveral on GPU". First, here is the code:
INLINE bool BVH::Traverse(TriangleWoop* prims, Ray* ray, IntersectResult* result)
{
unsigned int todo[32];
unsigned int todoOffset = 0;
unsigned int nodeNum = 0;
bool hit = false;
IntersectResult tmp = IntersectResult();
*(int*)&tmp.data.w = -1;
float tmin = 2e30f;
float4 origin = ray->origin;
float4 direction = ray->direction;
float4 invdir = rcp(direction);
float tmpx = 0.0f, tmpy = 0.0f;
while(true)
{
while(this->nodes[nodeNum].prim_count == 0)
{
tmpx += 0.01f;
tmpy += 0.001f;
float4 c0v1 = (this->nodes[nodeNum + 1].bounds.minPt - origin) * invdir;
float4 c0v2 = (this->nodes[nodeNum + 1].bounds.maxPt - origin) * invdir;
float4 c1v1 = (this->nodes[this->nodes[nodeNum].above_child].bounds.minPt - origin) * invdir;
float4 c1v2 = (this->nodes[this->nodes[nodeNum].above_child].bounds.maxPt - origin) * invdir;
float4 c0n = f4min(c0v1, c0v2);
float4 c0f = f4max(c0v1, c0v2);
float4 c1n = f4min(c1v1, c1v2);
float4 c1f = f4max(c1v1, c1v2);
float n0 = max(c0n.x, max(c0n.y, c0n.z));
float f0 = min(c0f.x, min(c0f.y, c0f.z));
float n1 = max(c1n.x, max(c1n.y, c1n.z));
float f1 = min(c1f.x, min(c1f.y, c1f.z));
bool child0 = (f0 > 0.0f) && (n0 < f0);
bool child1 = (f1 > 0.0f) && (n1 < f1);
child0 &= (n0 < tmin);
child1 &= (n1 < tmin);
unsigned int nodeAddr = this->nodes[nodeNum].above_child;
nodeNum = nodeNum + 1;
if(child0 != child1)
{
if(child1)
{
nodeNum = nodeAddr;
}
}
else
{
if(!child0)
{
if(todoOffset == 0)
{
goto result;
}
nodeNum = todo[--todoOffset];
}
else
{
if(n1 < n0)
{
swap(nodeNum, nodeAddr);
}
todo[todoOffset++] = nodeAddr;
}
}
}
if(this->nodes[nodeNum].prim_count > 0)
{
for(unsigned int i = this->nodes[nodeNum].prim_offset; i < this->nodes[nodeNum].prim_offset + this->nodes[nodeNum].prim_count; i++)
{
const TriangleWoop* tri = &prims[this->indexes[i]];
if(IntersectRayTriangleWoop(ray, tri, &tmp))
{
if(tmp.data.z > 0.0f && tmp.data.z < result->data.z)
{
tmin = tmp.data.z;
result->data.z = tmp.data.z;
result->data.x = tmp.data.x;
result->data.y = tmp.data.y;
*(int*)&result->data.w = this->indexes[i];
hit = true;
}
}
}
}
if(todoOffset == 0)
{
goto result;
}
nodeNum = todo[--todoOffset];
}
result:
result->data.x = tmpx;
result->data.y = tmpy;
return hit;
}
Technically it's just a standard while-while stack ray-bvh traversal. Now to the main problem, look at next image (viewing sponza from outside), in color you can see how much nodes in BVH has been visited (full red = 100, full yellow = 1100):
Next image shows similar situation inside:
As you can see this is kind of a problem - it just has to traverse much more nodes than it's supposed to. Can someone see something wrong with my code? Any advice is welcomed as I'm stucked with this for few days already and can't think off some solution.
I'm trying to fix this triangle rasterizer, but cannot make it work correctly. For some reason it only draws half of the triangles.
void DrawTriangle(Point2D p0, Point2D p1, Point2D p2)
{
Point2D Top, Middle, Bottom;
bool MiddleIsLeft;
if (p0.y < p1.y) // case: 1, 2, 5
{
if (p0.y < p2.y) // case: 1, 2
{
if (p1.y < p2.y) // case: 1
{
Top = p0;
Middle = p1;
Bottom = p2;
MiddleIsLeft = true;
}
else // case: 2
{
Top = p0;
Middle = p2;
Bottom = p1;
MiddleIsLeft = false;
}
}
else // case: 5
{
Top = p2;
Middle = p0;
Bottom = p1;
MiddleIsLeft = true;
}
}
else // case: 3, 4, 6
{
if (p0.y < p2.y) // case: 4
{
Top = p1;
Middle = p0;
Bottom = p2;
MiddleIsLeft = false;
}
else // case: 3, 6
{
if (p1.y < p2.y) // case: 3
{
Top = p1;
Middle = p2;
Bottom = p0;
MiddleIsLeft = true;
}
else // case 6
{
Top = p2;
Middle = p1;
Bottom = p0;
MiddleIsLeft = false;
}
}
}
float xLeft, xRight;
xLeft = xRight = Top.x;
float mLeft, mRight;
// Region 1
if(MiddleIsLeft)
{
mLeft = (Top.x - Middle.x) / (Top.y - Middle.y);
mRight = (Top.x - Bottom.x) / (Top.y - Bottom.y);
}
else
{
mLeft = (Top.x - Bottom.x) / (Top.y - Bottom.y);
mRight = (Middle.x - Top.x) / (Middle.y - Top.y);
}
int finalY;
float Tleft, Tright;
for (int y = ceil(Top.y); y < (int)Middle.y; y++)
{
Tleft=float(Top.y-y)/(Top.y-Middle.y);
Tright=float(Top.y-y)/(Top.y-Bottom.y);
for (int x = ceil(xLeft); x <= ceil(xRight) - 1 ; x++)
{
FrameBuffer::SetPixel(x, y, p0.r,p0.g,p0.b);
}
xLeft += mLeft;
xRight += mRight;
finalY = y;
}
// Region 2
if (MiddleIsLeft)
{
mLeft = (Bottom.x - Middle.x) / (Bottom.y - Middle.y);
}
else
{
mRight = (Middle.x - Bottom.x) / (Middle.y - Bottom.y);
}
for (int y = Middle.y; y <= ceil(Bottom.y) - 1; y++)
{
Tleft=float(Bottom.y-y)/(Bottom.y-Middle.y);
Tright=float(Top.y-y)/(Top.y-Bottom.y);
for (int x = ceil(xLeft); x <= ceil(xRight) - 1; x++)
{
FrameBuffer::SetPixel(x, y, p0.r,p0.g,p0.b);
}
xLeft += mLeft;
xRight += mRight;
}
}
Here is what happens when I use it to draw shapes.
When I disable the second region, all those weird triangles disappear.
The wireframe mode works perfect, so this eliminates all the other possibilities other than the triangle rasterizer.
I kind of got lost in your implementation, but here's what I do (I have a slightly more complex version for arbitrary convex polygons, not just triangles) and I think apart from the Bresenham's algorithm it's very simple (actually the algorithm is simple too):
#include <stddef.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define SCREEN_HEIGHT 22
#define SCREEN_WIDTH 78
// Simulated frame buffer
char Screen[SCREEN_HEIGHT][SCREEN_WIDTH];
void SetPixel(long x, long y, char color)
{
if ((x < 0) || (x >= SCREEN_WIDTH) ||
(y < 0) || (y >= SCREEN_HEIGHT))
{
return;
}
Screen[y][x] = color;
}
void Visualize(void)
{
long x, y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
for (x = 0; x < SCREEN_WIDTH; x++)
{
printf("%c", Screen[y][x]);
}
printf("\n");
}
}
typedef struct
{
long x, y;
unsigned char color;
} Point2D;
// min X and max X for every horizontal line within the triangle
long ContourX[SCREEN_HEIGHT][2];
#define ABS(x) ((x >= 0) ? x : -x)
// Scans a side of a triangle setting min X and max X in ContourX[][]
// (using the Bresenham's line drawing algorithm).
void ScanLine(long x1, long y1, long x2, long y2)
{
long sx, sy, dx1, dy1, dx2, dy2, x, y, m, n, k, cnt;
sx = x2 - x1;
sy = y2 - y1;
if (sx > 0) dx1 = 1;
else if (sx < 0) dx1 = -1;
else dx1 = 0;
if (sy > 0) dy1 = 1;
else if (sy < 0) dy1 = -1;
else dy1 = 0;
m = ABS(sx);
n = ABS(sy);
dx2 = dx1;
dy2 = 0;
if (m < n)
{
m = ABS(sy);
n = ABS(sx);
dx2 = 0;
dy2 = dy1;
}
x = x1; y = y1;
cnt = m + 1;
k = n / 2;
while (cnt--)
{
if ((y >= 0) && (y < SCREEN_HEIGHT))
{
if (x < ContourX[y][0]) ContourX[y][0] = x;
if (x > ContourX[y][1]) ContourX[y][1] = x;
}
k += n;
if (k < m)
{
x += dx2;
y += dy2;
}
else
{
k -= m;
x += dx1;
y += dy1;
}
}
}
void DrawTriangle(Point2D p0, Point2D p1, Point2D p2)
{
int y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
ContourX[y][0] = LONG_MAX; // min X
ContourX[y][1] = LONG_MIN; // max X
}
ScanLine(p0.x, p0.y, p1.x, p1.y);
ScanLine(p1.x, p1.y, p2.x, p2.y);
ScanLine(p2.x, p2.y, p0.x, p0.y);
for (y = 0; y < SCREEN_HEIGHT; y++)
{
if (ContourX[y][1] >= ContourX[y][0])
{
long x = ContourX[y][0];
long len = 1 + ContourX[y][1] - ContourX[y][0];
// Can draw a horizontal line instead of individual pixels here
while (len--)
{
SetPixel(x++, y, p0.color);
}
}
}
}
int main(void)
{
Point2D p0, p1, p2;
// clear the screen
memset(Screen, ' ', sizeof(Screen));
// generate random triangle coordinates
srand((unsigned)time(NULL));
p0.x = rand() % SCREEN_WIDTH;
p0.y = rand() % SCREEN_HEIGHT;
p1.x = rand() % SCREEN_WIDTH;
p1.y = rand() % SCREEN_HEIGHT;
p2.x = rand() % SCREEN_WIDTH;
p2.y = rand() % SCREEN_HEIGHT;
// draw the triangle
p0.color = '1';
DrawTriangle(p0, p1, p2);
// also draw the triangle's vertices
SetPixel(p0.x, p0.y, '*');
SetPixel(p1.x, p1.y, '*');
SetPixel(p2.x, p2.y, '*');
Visualize();
return 0;
}
Output:
*111111
1111111111111
111111111111111111
1111111111111111111111
111111111111111111111111111
11111111111111111111111111111111
111111111111111111111111111111111111
11111111111111111111111111111111111111111
111111111111111111111111111111111111111*
11111111111111111111111111111111111
1111111111111111111111111111111
111111111111111111111111111
11111111111111111111111
1111111111111111111
11111111111111
11111111111
1111111
1*
The original code will only work properly with triangles that have counter-clockwise winding because of the if-else statements on top that determines whether middle is left or right. It could be that the triangles which aren't drawing have the wrong winding.
This stack overflow shows how to Determine winding of a 2D triangles after triangulation
The original code is fast because it doesn't save the points of the line in a temporary memory buffer. Seems a bit over-complicated even given that, but that's another problem.
The following code is in your implementation:
if (p0.y < p1.y) // case: 1, 2, 5
{
if (p0.y < p2.y) // case: 1, 2
{
if (p1.y < p2.y) // case: 1
{
Top = p0;
Middle = p1;
Bottom = p2;
MiddleIsLeft = true;
}
else // case: 2
{
Top = p0;
Middle = p2;
Bottom = p1;
MiddleIsLeft = false;
}
}
This else statement means that p2.y (or Middle) can equal p1.y (or Bottom). If this is true, then when region 2 runs
if (MiddleIsLeft)
{
mLeft = (Bottom.x - Middle.x) / (Bottom.y - Middle.y);
}
else
{
mRight = (Middle.x - Bottom.x) / (Middle.y - Bottom.y);
}
That else line will commit division by zero, which is not possible.
So, after hours of Googling and reading, I've found that the basic process of detecting a collision using SAT is:
for each edge of poly A
project A and B onto the normal for this edge
if intervals do not overlap, return false
end for
for each edge of poly B
project A and B onto the normal for this edge
if intervals do not overlap, return false
end for
However, as many ways as I try to implement this in code, I just cannot get it to detect the collision. My current code is as follows:
for (unsigned int i = 0; i < asteroids.size(); i++) {
if (asteroids.valid(i)) {
asteroids[i]->Update();
// Player-Asteroid collision detection
bool collision = true;
SDL_Rect asteroidBox = asteroids[i]->boundingBox;
// Bullet-Asteroid collision detection
for (unsigned int j = 0; j < player.bullets.size(); j++) {
if (player.bullets.valid(j)) {
Bullet b = player.bullets[j];
collision = true;
if (b.x + (b.w / 2.0f) < asteroidBox.x - (asteroidBox.w / 2.0f)) collision = false;
if (b.x - (b.w / 2.0f) > asteroidBox.x + (asteroidBox.w / 2.0f)) collision = false;
if (b.y - (b.h / 2.0f) > asteroidBox.y + (asteroidBox.h / 2.0f)) collision = false;
if (b.y + (b.h / 2.0f) < asteroidBox.y - (asteroidBox.h / 2.0f)) collision = false;
if (collision) {
bool realCollision = false;
float min1, max1, min2, max2;
// Create a list of vertices for the bullet
CrissCross::Data::LList<Vector2D *> bullVerts;
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));
// Create a list of vectors of the edges of the bullet and the asteroid
CrissCross::Data::LList<Vector2D *> bullEdges;
CrissCross::Data::LList<Vector2D *> asteroidEdges;
for (int k = 0; k < 4; k++) {
int n = (k == 3) ? 0 : k + 1;
bullEdges.insert(new Vector2D(bullVerts[k]->x - bullVerts[n]->x,
bullVerts[k]->y - bullVerts[n]->y));
asteroidEdges.insert(new Vector2D(asteroids[i]->vertices[k]->x - asteroids[i]->vertices[n]->x,
asteroids[i]->vertices[k]->y - asteroids[i]->vertices[n]->y));
}
Vector2D *vectOffset = new Vector2D(asteroids[i]->center.x - b.x, asteroids[i]->center.y - b.y);
for (unsigned int k = 0; k < asteroidEdges.size(); k++) {
Vector2D *axis = asteroidEdges[k]->getPerpendicular();
axis->normalize();
min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
float test = axis->dotProduct(asteroids[i]->vertices[l]);
min1 = (test < min1) ? test : min1;
max1 = (test > max1) ? test : max1;
}
min2 = max2 = axis->dotProduct(bullVerts[0]);
for (unsigned int l = 1; l < bullVerts.size(); l++) {
float test = axis->dotProduct(bullVerts[l]);
min2 = (test < min2) ? test : min2;
max2 = (test > max2) ? test : max2;
}
float offset = axis->dotProduct(vectOffset);
min1 += offset;
max1 += offset;
delete axis; axis = NULL;
float d0 = min1 - max2;
float d1 = min2 - max1;
if ( d0 > 0 || d1 > 0 ) {
realCollision = false;
break;
} else {
realCollision = true;
}
}
if (realCollision) {
for (unsigned int k = 0; k < bullEdges.size(); k++) {
Vector2D *axis = bullEdges[k]->getPerpendicular();
axis->normalize();
min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
float test = axis->dotProduct(asteroids[i]->vertices[l]);
min1 = (test < min1) ? test : min1;
max1 = (test > max1) ? test : max1;
}
min2 = max2 = axis->dotProduct(bullVerts[0]);
for (unsigned int l = 1; l < bullVerts.size(); l++) {
float test = axis->dotProduct(bullVerts[l]);
min2 = (test < min2) ? test : min2;
max2 = (test > max2) ? test : max2;
}
float offset = axis->dotProduct(vectOffset);
min1 += offset;
max1 += offset;
delete axis; axis = NULL;
float d0 = min1 - max2;
float d1 = min2 - max1;
if ( d0 > 0 || d1 > 0 ) {
realCollision = false;
break;
} else {
realCollision = true;
}
}
}
if (realCollision) {
player.bullets.remove(j);
int numAsteroids;
float newDegree;
srand ( j + asteroidBox.x );
if ( asteroids[i]->degree == 90.0f ) {
if ( rand() % 2 == 1 ) {
numAsteroids = 3;
newDegree = 30.0f;
} else {
numAsteroids = 2;
newDegree = 45.0f;
}
for ( int k = 0; k < numAsteroids; k++)
asteroids.insert(new Asteroid(asteroidBox.x + (10 * k), asteroidBox.y + (10 * k), newDegree));
}
delete asteroids[i];
asteroids.remove(i);
}
while (bullVerts.size()) {
delete bullVerts[0];
bullVerts.remove(0);
}
while (bullEdges.size()) {
delete bullEdges[0];
bullEdges.remove(0);
}
while (asteroidEdges.size()) {
delete asteroidEdges[0];
asteroidEdges.remove(0);
}
delete vectOffset; vectOffset = NULL;
}
}
}
}
}
bullEdges is a list of vectors of the edges of a bullet, asteroidEdges is similar, and bullVerts and asteroids[i].vertices are, obviously, lists of vectors of each vertex for the respective bullet or asteroid.
Honestly, I'm not looking for code corrections, just a fresh set of eyes.
Turns out my mathematical understanding of the theorem was perfectly fine. Instead, the problem lay in the fact that I was not including the center points of the polygons in the vertice vectors.
Thank you everyone for their time.
You've added this vectOffset part which is wrong - both your asteroids' and bullets' coordinate systems are the same, right? (It must be, if the bounding box test is working.)
Are your asteroids squares? If so, then the bounding box test will always be exact, and realCollision and collision should always be identical. If not, then you're not building asteroidEdges properly - you need to iterate over the number of vertices, not 4.
But seriously, make this code a separate method and write a unit test for it, it's the only way I can run your code to see what is going on.
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));
It looks like you're creating an asteroids clone, in which case you'd expect the bullet to be rotated, but this code always treats the bullet as though it is fully upright. Could that be your problem?
Something that may help find the problem is to make the bullet a point. It might illuminate problems with other parts of your code. Plus, then if your point makes a collision but the bullet does not you will get something concrete to look at.
In other words, simplify your problem until a solution emerges. ;)
Besides the whole offset thing, which is buggy, the rest of the algorithm seems OK. Have you tried tracing through it to spot the problem?
BTW, there are several stylistic quirks that make the code hard to read at a glance:
Why the pointers everywhere, instead of allocating all of those temporary Vector2Ds on the stack?
Why CrissCross::Data::LList instead of "good old" std::vector?
Surely Vector2D has an overloaded operator-?
Here's a quick-and-dirty self-contained implementation of the algorithm. I've somewhat tested it, but make no guarantees:
#include <vector>
#include <limits>
using namespace std;
class Vector2D
{
public:
Vector2D() : x(0), y(0) {}
Vector2D(double x, double y) : x(x), y(y) {}
Vector2D operator-(const Vector2D &other) const
{
return Vector2D(x - other.x, y - other.y);
}
double dot(const Vector2D &other) const
{
return x * other.x + y*other.y;
}
Vector2D perp() const
{
return Vector2D(-y, x);
}
double x,y;
};
bool checkCollisionOneSided(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
int nume = object1.size();
for(int i=0; i<nume; i++)
{
Vector2D edge = object1[(i+1)%nume] - object1[i];
Vector2D normal = edge.perp();
double min1 = numeric_limits<double>::infinity();
double min2 = min1;
double max1 = -numeric_limits<double>::infinity();
double max2 = max1;
for(int j=0; j<object1.size(); j++)
{
double dot = normal.dot(object1[j]);
min1 = std::min(min1, dot);
max1 = std::max(max1, dot);
}
for(int j=0; j<object2.size(); j++)
{
double dot = normal.dot(object2[j]);
min2 = std::min(min2, dot);
max2 = std::max(max2, dot);
}
if(min2 > max1 || min1 > max2)
return false;
}
return true;
}
bool isColliding(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
return checkCollisionOneSided(object1, object2) && checkCollisionOneSided(object2, object1);
}