# Why is my sliding AABB collision acting strange?

I'm working on a Minecraft like game engine and recently I've been trying to add good collision response which I have kind of. The collision response is perfect when I try and go through the right side or front side of this cube.

I'm doing a dot product between my player's velocity vector and the normals for each side of the cube (ex: new Vector3f(0.0f,1.0f,0.0f) //Up) and seeing which normal gives the dot product which is the greatest and then I multiply my velocity vector by that normal vector in order to create this cool sliding effect.

This sliding effect only works for the right and front sides of the cube and the rest of the sides have a weird effect which is not sliding at all. Anyway if anyone can help with this it would be very much appreciated :). This is my method for detecting collision which works efficiently. Then there's the method for detecting which way the player should slide which is called velocityDirection. Update: Whenever I try and touch ANY side of the cube (I am printing out the variable BestMatch from the velocityDirection class which is one of the normals from the array of normals) it returns a value of Vector3f(0,1,0) and when I try and move and go through the right side like a ghost it returns Vector3f(-1,0,0) and for the front side it returns Vector3f(0,0,-1), and remember the right and front sides both have perfect collision.

Collision Detection Method:

public static boolean IsCollision(AABB player, AABB block)
{

boolean x = player.getMaxX() > block.getMinX() && player.getMinX() < block.getMaxX();

boolean y = player.getMaxY() > block.getMinY() && player.getMinY() < block.getMaxY();

boolean z = player.getMaxZ() > block.getMinZ() && player.getMinZ() < block.getMaxZ();

return (x && y && z);
}


Sliding Response Method :

public static Vector3f velocityDirection(Vector3f velocityDirection){
Vector3f normalsArray[] = new Vector3f[]{
new Vector3f(0.0f,1.0f,0.0f),  // Up
new Vector3f(1.0f,0.0f,0.0f),//Right
new Vector3f(0.0f,-1.0f,0.0f), // Down
new Vector3f(-1.0f,0.0f,0.0f), //Left
new Vector3f(0.0f,0.0f,1.0f), //Front
new Vector3f(0.0f,0.0f,-1.0f) //Back

};
Vector3f BestMatch = new Vector3f();
Vector3f TrueVelocity = new Vector3f();
float max = 0;
for(int i = 0 ; i < normalsArray.length ; i++){
float dot = Vector3f.dot(velocityDirection, normalsArray[i]);
if(dot > max){
max = dot;
BestMatch = normalsArray[i];

}
}
TrueVelocity = new Vector3f(velocityDirection.x  * BestMatch.x, velocityDirection.y  * BestMatch.y, velocityDirection.z  * BestMatch.z );

return TrueVelocity;
}


Camera Class:

public class Camera {

private static Vector3f position = new Vector3f(0,0,0);
public static float pitch = 20 ;
public static float rx, ry, rz;

public static float velocityX;
public static float velocityY;
public static float velocityZ;

public static float yaw;
public static float roll;
private  final static float RUN_SPEED = 20;

private final static float TURN_SPEED = 150;
private static float JUMP_POWER = 10;
private static float currentSpeed = 0;
public static float currentTurnSpeed = 0;
public static boolean isMovingX = false;
public static boolean isMovingY = false;

public float angleAroundPlayer = 0;
private static final Vector4f Forward = new Vector4f(0,0,-1,0);
private static final Vector4f Backward = new Vector4f(0,0,1,0);

public static boolean isColliding = false;

public static void moveCamera(AABB player,AABB box){
calculatePitch();
if(Keyboard.isKeyDown(Keyboard.KEY_W)){
isMovingX = true;

currentSpeed = -RUN_SPEED;

}
else if(Keyboard.isKeyDown(Keyboard.KEY_S)){
currentSpeed = RUN_SPEED;
isMovingX = true;
}
else{
isMovingX = false;
currentSpeed = 0;
}

if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
currentTurnSpeed = -TURN_SPEED;

} else if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
currentTurnSpeed = TURN_SPEED;
} else {
currentTurnSpeed = 0;
}

if(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)){
isMovingY = true;
velocityY = -JUMP_POWER * DisplayManager.getFrameTimeSeconds();

}
else if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
isMovingY = true;
velocityY = JUMP_POWER * DisplayManager.getFrameTimeSeconds();

}
else{
isMovingY = false;
velocityY = 0;
}

yaw -= currentTurnSpeed * DisplayManager.getFrameTimeSeconds();
Matrix4f rotation = calculateCameraRotationMatrix();
Vector3f TrueForward = new Vector3f(Matrix4f.transform(rotation,Forward , null));
Vector3f TrueBackward = new Vector3f(Matrix4f.transform(rotation,Backward , null));
Vector3f CameraPos = Camera.getPosition();

float distance = currentSpeed * DisplayManager.getFrameTimeSeconds();
velocityX = (float) (distance * Math.sin(Math.toRadians(getRy())));
velocityZ = (float) (distance * Math.cos(Math.toRadians(getRy())));
increasePosition(velocityX,velocityY,velocityZ);
System.out.println(new Vector3f(velocityX, velocityY, velocityZ));
if(isColliding){
Vector3f velocityDirection = VectorMath.velocityDirection(new Vector3f(velocityX, velocityY, velocityZ));

increasePosition(velocityDirection.x , velocityDirection.y, velocityDirection.z );
}

increaseRotation(0, currentTurnSpeed * DisplayManager.getFrameTimeSeconds(), 0);

yaw %= 360;

}
private static Matrix4f calculateCameraRotationMatrix() {
Matrix4f rotation = new Matrix4f();
rotation.rotate((float) Math.toRadians(getPitch()), new Vector3f(1, 0, 0));
rotation.rotate((float) Math.toRadians(getYaw()), new Vector3f(0, 1, 0));
return rotation;
}

public static void increasePosition(float dx, float dy, float dz){
position.x += dx;
position.y += dy;
position.z += dz;
}
public static void increaseRotation(float dx, float dy, float dz){
rx += dx;
ry += dy;
rz += dz;

}

private static void calculatePitch() {

float pitchChange = Mouse.getDY() * 0.63f;
pitch -= pitchChange;
if (pitch < -40) {
pitch = -40;
} else if (pitch > 90) {
pitch = 90;
}

}

public static Vector3f getPosition() {
return position;
}

public static void setPosition(Vector3f position) {
Camera.position = position;
}

public static float getPitch() {
return pitch;
}
public static float getRx() {
return rx;
}
public static float getRy() {
return ry;
}
public static float getRz() {
return rz;
}
public  static float getYaw() {
return yaw;
}
public static float getRoll() {
return roll;
}

• Only a brief look first, so I won't put it as an answer, but usually if your only getting right and front sides, likely means that its only considering positive coordinates or vectors, might be missing a multiplication somewhere. Aug 27 '17 at 23:28
• Nope that's not it, I checked and the velocity vector is returning negative and positive values Aug 28 '17 at 0:07
• did you check the debugger for the the array ? to see whats happening as it pulled them up ? Aug 28 '17 at 0:21
• The values in the array stay constant from what I saw Aug 28 '17 at 0:32
• strange idea but have you tried using doubles instead of floats ? Aug 28 '17 at 0:50

Before reading the rest of my waffling, try this

TrueVelocity = new Vector3f(velocityDirection.x  * Mathf.Abs(BestMatch.x), velocityDirection.y  * Mathf.Abs(BestMatch.y), velocityDirection.z  * Mathf.Abs(BestMatch.z) );


You might be multiplying your velocity with negative numbers, which isn't what you want, so take the absolute value with Mathf.Abs()

Anyhow...If I'm reading your code correctly, you're finding the axis that the velocity best aligns with, then "straightening" it. But what if the player is mostly facing the wall, but just rotated just slightly, like this... In the above diagram we are looking top down on the scene, and the best match for the players direction (or velocity) is the y-axis, but we probably want to slide horizontally.

My first time implementing wall sliding in a FPS was from Mark Overmar's FPS tutorial for GameMaker, it's 10 years old but it's full of great gems. Here's his wall sliding code (modified for clarity).

// undo last movement (because collision)
x = xprevious;
y = yprevious;

// if our velocity is more horizontal, wall slide horizontally
if (abs(hspeed) >= abs(vspeed) &&
not place_meeting(x+hspeed,y,obj_wall_basic)) {
x += hspeed;
} else if (abs(vspeed) >= abs(hspeed) &&
not place_meeting(x,y+vspeed,obj_wall_basic)) {
y += vspeed; exit;
}
// no sliding
speed = 0;


Which I now realise doesn't behave as I expect my diagram to...

Also: Are you doing AABB tests against every block in this voxel world? this could get costly. You could improve it by reformulating the ANDs into ORs (with De Morgans theorem) and get early termination (short circuiting), so that when a single comparison is true, the function can return false without having to evaluate all 6 conditions. Since it's more likely that a block is NOT colliding with the player.

return !(player.getMaxX() < block.getMinX() ||
player.getMaxY() < block.getMinY() ||
player.getMaxZ() < block.getMinZ() ||
player.getMinX() > block.getMaxX() ||
player.getMinY() > block.getMaxY() ||
player.getMinZ() > block.getMaxZ());

• I added code for camera class to see if it could possibly help. Aug 28 '17 at 21:32
• Thanks for the aabb code it works perfect, anyway making the the x, y, z values of the TrueVelocity vector always positive makes it more buggy. Aug 28 '17 at 22:12
• By aabb code I mean the de morgans theorem Reuben Aug 29 '17 at 0:21
• Could you send me your unity project, so I can hack around myself? Nothing is super obvious from here Aug 29 '17 at 12:57
• ummm this is lwjgl Aug 29 '17 at 20:05

I managed to find the solution, It took me about a week and a half to solve this but I have finally solved it. So what I did is I checked which side of the cube the player is touching to determine where and how the player moved, the collision response is not 100% perfect but it's nearly impossible to go through objects and the sliding is pretty cool so I'm happy about that :)