0
\$\begingroup\$

I was following this tutorial which explains how to detect and resolve a 2d swept aabb collisions. I got the detection part working on 3d, but know I need a way to properly make a response, like sliding on the face because right now my player just get stuck on the wall. To do that, I need the surface normal, but I couldn't correctly translate the normal calculation in the tutorial from 2d to 3d. Here is the code, written in Java, but you can always check the tutorial:

private float sweptAABB(Vector3f velocity, float x, float y, float z, float minTime){
    // find the distance between the objects on the near and far sides for both x, y and z
    // and find time of collision and time of leaving for each axis
    if (velocity.x > 0.0f) {
        invEntry.x = x - maxI.x;
        entry.x = invEntry.x / velocity.x;
        invExit.x = x + 1 - minI.x;
        exit.x = invExit.x / velocity.x;
    } else if (velocity.x < 0.0f){
        invEntry.x = x + 1 - minI.x;
        entry.x = invEntry.x / velocity.x;
        invExit.x = x - maxI.x;
        exit.x = invExit.x / velocity.x;
    } else {
        invEntry.x = x + 1 - minI.x;
        invExit.x = x - maxI.x;
        entry.x = -Float.MAX_VALUE;
        exit.x = Float.MAX_VALUE;
    }

    if (velocity.y > 0.0f) {
        invEntry.y = y - maxI.y;
        entry.y = invEntry.y / velocity.y;
        invExit.y = y + 1 - minI.y;
        exit.y = invExit.y / velocity.y;
    } else if (velocity.y < 0.0f){
        invEntry.y = y + 1 - minI.y;
        entry.y = invEntry.y / velocity.y;
        invExit.y = y - maxI.y;
        exit.y = invExit.y / velocity.y;
    } else {
        invEntry.y = y + 1 - minI.y;
        invExit.y = y - maxI.y;
        entry.y = -Float.MAX_VALUE;
        exit.y = Float.MAX_VALUE;
    }

    if (velocity.z > 0.0f) {
        invEntry.z = z - maxI.z;
        entry.z = invEntry.z / velocity.z;
        invExit.z = z + 1 - minI.z;
        exit.z = invExit.z / velocity.z;
    } else if (velocity.z < 0.0f){
        invEntry.z = z + 1 - minI.z;
        entry.z = invEntry.z / velocity.z;
        invExit.z = z - maxI.z;
        exit.z = invExit.z / velocity.z;
    } else {
        invEntry.z = z + 1 - minI.z;
        invExit.z = z - maxI.z;
        entry.z = -Float.MAX_VALUE;
        exit.z = Float.MAX_VALUE;
    }
    // find the earliest/latest times of collision
    if (entry.y > 1.0f) entry.y = -Float.MAX_VALUE;
    if (entry.x > 1.0f) entry.x = -Float.MAX_VALUE;
    if (entry.z > 1.0f) entry.z = -Float.MAX_VALUE;

    float entryTime = Math.max(Math.max(entry.x,entry.z),entry.y);

    if(entryTime >= minTime) return 1.0f;

    float exitTime = Math.min(Math.min(exit.x,exit.z),exit.y);

    if(entry.x < 0.0f && entry.y < 0.0f && entry.z < 0.0f) // equals to entryTime < 0.0f
        return 1.0f;

    if (entryTime > exitTime) return 1.0f;

    if(entry.x > 1.0f) {
        if(max.x < x || min.x > x + 1)
            return 1.0f;
    }

    if(entry.y > 1.0f){
        if(max.y < y || min.y > y + 1)
            return 1.0f;
    }

    if(entry.z > 1.0f){
        if(max.z < z || min.z > z + 1)
            return 1.0f;
    }
    return entryTime;

Basically, in the tutorial, to get normals, they do a test on the entry values, here is how I tried to translate this to 3d:

if (entry.x > entry.y) {
        if(entry.x > entry.z){
            if (invEntry.x < 0.0f)
                normal.x = 1.0f;
            else
                normal.x = -1.0f;

            normal.y = 0.0f;
            normal.z = 0.0f;
        }else{
            if (invEntry.z < 0.0f)
                normal.z = 1.0f;
            else
                normal.z = -1.0f;

            normal.x = 0.0f;
            normal.y = 0.0f;
        }
    } else {
        if (entry.y > entry.z) {
            if (invEntry.y < 0.0f)
                normal.y = 1.0f;
            else
                normal.y = -1.0f;

            normal.x = 0.0f;
            normal.z = 0.0f;

        } else {
            if (invEntry.z < 0.0f)
                normal.z = 1.0f;
            else
                normal.z = -1.0f;

            normal.x = 0.0f;
            normal.y = 0.0f;
        }
    }

I'm getting correct directions, but the values are always negative and I have trouble picturing the maths behind the normal testing. My question is: how can I correctly calculate the normals based on the code from the tutorial?

Thanks!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The sign of the normal should always be opposite the sign of your velocity on that component, so you can use that as a shortcut. \$\endgroup\$ – DMGregory Jan 26 at 14:13
  • \$\begingroup\$ Wow what a simple and yet elegant solution, this solves the issue and simplifies the code thanks! You should post an answer so I can accept it as the correct one \$\endgroup\$ – Thibault Abry Jan 26 at 15:28
1
\$\begingroup\$

Based on what @DMGregory proposed in the comments, here is the simplified and working code to calculate the normals:

if (entry.x > entry.y) {
            if(entry.x > entry.z){
                normal.x = -Maths.sign(velocity.x);
                normal.y = 0.0f;
                normal.z = 0.0f;
            }else{
                normal.z = -Maths.sign(velocity.z);
                normal.x = 0.0f;
                normal.y = 0.0f;
            }
        } else {
            if (entry.y > entry.z) {
                normal.y = -Maths.sign(velocity.y);
                normal.x = 0.0f;
                normal.z = 0.0f;

            } else {
                normal.z = -Maths.sign(velocity.z);
                normal.x = 0.0f;
                normal.y = 0.0f;
            }
        }

With Maths.sign(int a){ return a >= 0 ? 1 : -1; }

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.