Collision problem

Started by ic5y, July 03, 2013, 21:09:06

Previous topic - Next topic

ic5y

So, I have a problem with collision response. I save the last good, not colliding position, and then, when the player collides with a wall, I set the position to the previous position. But when I run it, lastGoodPos is ALWAYS set to position. Even when there is collision.. any idea?

public void update()
	{
        float forward = 0f;
        float strafe = 0f;
        float vertical = 0f;

		if(Keyboard.isKeyDown(Keyboard.KEY_W))
		{
            forward += 1f;
		}
		else if(Keyboard.isKeyDown(Keyboard.KEY_S))
		{
            forward -= 1f;
		}
		else if(Keyboard.isKeyDown(Keyboard.KEY_A))
		{
            strafe -= 1f;
		}
		else if(Keyboard.isKeyDown(Keyboard.KEY_D))
		{
            strafe += 1f;
		}
        else if(Keyboard.isKeyDown(Keyboard.KEY_Q) && freeCam)
        {
            vertical -= 1f;
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_E) && freeCam)
        {
            vertical += 1f;
        }

        int dx = Mouse.getDX();
        int dy = Mouse.getDY();

        yrot += dx * 0.5f;
        xrot -= dy * 0.5f;

        if(xrot > 90)
            xrot = 90;
        else if(xrot < -90)
            xrot = -90;

        GL11.glRotatef(xrot, 1, 0, 0);
        GL11.glRotatef(yrot, 0, 1, 0);

        forward *= Game.graphics.getDelta();
        strafe *= Game.graphics.getDelta();

        Vector3f movement = new Vector3f();

        if(strafe == 0f || forward != 0)
        {
            movement.x += SPEED * forward * Math.cos(Math.toRadians(yrot + 90));

            if(freeCam)
                movement.y += SPEED * vertical;
            else
                movement.y = -0.5f;

            movement.z += SPEED * forward * Math.sin(Math.toRadians(yrot + 90));
        }
        else
        {
            movement.x += SPEED * strafe * Math.cos(Math.toRadians(yrot + 180));

            if(freeCam)
                movement.y += SPEED * vertical;
            else
                movement.y = -0.5f;

            movement.z += SPEED * strafe * Math.sin(Math.toRadians(yrot + 180));

        }

        position.x -= movement.x;
        position.z -= movement.z;

        bb.setVecMin(new Vector3f(position.x - BBSIZE, 0f, position.z - BBSIZE));
        bb.setVecMax(new Vector3f(position.x + BBSIZE, 1f, position.z + BBSIZE));

        boolean c = false;

        for(BoundingBox b : Game.level.bbs)
        {
            if(BoundingBox.isCollided(bb, b))
            {
                c = true;
                break;
            }
        }

        if(c)
        {
            System.out.println("Collided!");
            position = lastGoodPos;
        }
        else
            lastGoodPos = position;

        GL11.glTranslatef(-position.x, -0.5f, -position.z);
	}

quew8

Instances of objects (like position and lastGoodPos) in java are stored by reference. This means that when you say lastGoodPos = position, you are not setting the values stored in lastGoodPos to the values stored in position. Rather you are saying lastGoodPos IS position ie they are the same object. So when later on you start changing the values of position you are implicitly also changing the values of lastGoodPos. Which is causing you these problems.

I will let you find your own solution to this as it is a vital concept for java programmers to grasp.

ic5y

Thanks, I solved it. A newbie Java programmer's mistake :P

On the other hand, currently I'm resolving the collision by reset the position to the last good one. But I stuck on the walls, so if I hit a wall with an angle of 45°, I can't move forward and "sliding" on the wall. I was thinking about to get the angle between the bounding box of the wall we collided with and the player's position. But as I moved further the X axis, the angle just got smaller. :/

Vector3f center = new Vector3f();

center.x = c.vecMax.x - (Math.abs(c.vecMax.x - c.vecMin.x) / 2);
center.y = c.vecMax.y - (Math.abs(c.vecMax.y - c.vecMin.y) / 2);
center.z = c.vecMax.z - (Math.abs(c.vecMax.z - c.vecMin.z) / 2);

float angle = Vector3f.angle(center, position);

quew8

I'm not familiar with LWJGL vector classes (For some reason I wrote my own and never used LWJGL's). However I expect the method they use is to arc-cosine the dot product of the two vectors (don't worry if you don't know what that means) for which it is necessary that both vectors be normalized. If not then stuff like what you say would happen.

This then leads me to ask if you sure you know what you're doing with the angle between these two vectors. As far as I can see, it makes no sense. This method will give you the angle between the two position vectors of these positions (a position vector is the vector from the origin to the position).

So in case you now have to rethink your method, I shall suggest another using a bit of pseudo-physics. (Please note I have to assume you know nothing about this so I may be a bit patronizing) The normal of a face is a vector perpendicular to the face. The normal reaction is the force a face applies to an object (parallel to the normal) when the object pushes on it. (Newtons third law - You push down on the ground, the ground pushes up on you). You can work out this normal reaction as a velocity (rather than a force) and if you add it to the objects velocity before actually moving it, then the object will slide along the face rather than being stopped dead.

To work out the normal reaction. (Very simple really, hard to get head around)
If an object as velocity V, and hits a face with normal N

Then Normal Reaction R = N * -( N . V )
( "." means dot product )

If you add this to the objects speed then you get a final velocity. So all together:

Final Velocity Vf = V + ( N * -( N . V ) )

Just be careful to normalize the NORMAL (keys in the name) but not to normalize the velocity.

ic5y

Let's see if I understand it correctly..

First, I check if there was a collision. If there was any, I get the wall's normal, and calculate the new velocity from that formula, and then I add it to the position?

quew8

You got it. Probably best to store the normal of the face as it can be quite expensive to calculate. You perhaps have it anyway for lighting / collision detection purposes. But otherwise that's it exactly.

ic5y

EDIT: my normals were wrong, got it perfectly! Thank you!

EDIT2: Hmm.. the player is "flickering" when colliding with the wall, but the sliding thing is working. How could I smoothen that movement? Especially on lower FPS... I tried multiplying with delta, and even dividing by it, no success :/

if(c != null)
        {
            Vector3f reaction = new Vector3f();
            reaction.x = c.normal.x * - (Vector3f.dot(c.normal, movement));
            reaction.y = c.normal.y * - (Vector3f.dot(c.normal, movement));
            reaction.z = c.normal.z * - (Vector3f.dot(c.normal, movement));

            Vector3f finalV = new Vector3f();

            Vector3f.add(movement, reaction, finalV);

            Vector3f.add(finalV, lastGoodPos, position);

	}

quew8

OK, I have to admit I forgot about multiplying by delta.

What I meant was that final velocity should actually replace the initial velocity. What you have now is that each frame the object ties to move into the wall and each frame gets rebuffed off. Repetitive actions like this tend to cause flickering/juddering effects. Not entirely sure why but I recon its due to the varying values of delta each frame.

In short, change
Vector3f finalV = new Vector3f();

Vector3f.add(movement, reaction, finalV);

Vector3f.add(finalV, lastGoodPos, position);


to

Vector3f.add(movement, reaction, movement);

Vector3f.add(movement, lastGoodPos, position);

ic5y

I uploaded my code to pastebin, because it's too long to post here: link

Still flickering... I have no idea... I guess the step that the player takes in one frame is too big


quew8

Two things I notice from that code:

1) You currently have:
Quotereaction.x = c.normal.x * + (Vector3f.dot(c.normal, movement));
Unless you are actually storing the negative of the normal (or something like that) shouldn't you have
...c.normal.x * -(Vector3f.dot...

2) I get the impression that your bounding boxes are in fact axis-aligned cuboid type things. If this is correct, they should have 6 faces each with its own normal. I wonder how you're getting the right normal from just:
Quote...c.normal...

ic5y

I currently not calculating the normal, I just hardcoded them, it looked easier for me, as each AABB is "just a plane", so e.g. a wall with a facing of north, has 0 depth, so the AABB's minimum vector's Z and the maximum's Z is equal. I add the dot value because, maybe from the bad normals, the player move inverted on the X and the Z axis, that's why I added the value.

ic5y

Interesting, I solved it.. Yes, the reaction should be ...* - (Vector3f.dot(c.normal, m..., but then I had to add the inverse of the movement vector. I don't know why, but now works like a charm. Thank you

quew8

In reply to pm:

If your bounding boxes are axis-aligned then, by definition, the normal must be facing along one of the axes and in this case, the best option is to simply tell it what axis it points along (bearing in mind it can face forward or backwards) when you create it.

If not axis-aligned, then we need some maths. Given 3 vertices of the face in order V1, V2, V3.

    The Normal, N = ( V1 - V2 ) X ( V3 - V2 )

where X is the cross product (aka vector product). However, it is highly unlikely that this normal will be normalized, so you shall have to do it.

The final problem is which way it faces, since this could give you the correct forward facing vector, or the negation of it facing backwards. Which it gives you depends on the order you give the vertices (there is some kind of left/right hand rule for this but I can't remember if it is the left or the right hand). Since your vertices should always be wound counter-clockwise for OpenGL (unless you changed the setting) you don't have to test this on a per-face basis. Set it one way, try it, and if they are facing the  wrong way, swap around V1 and V3 in the sum. Hope this helps.