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, 2021 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\$ Jan 26, 2021 at 15:28

1 Answer 1

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\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .