Search code examples
c++collision-detectionvoxel

Collision detection in voxel world


I am kinda stuck with my basic voxel physics right now. It's very, very choppy and I am pretty sure my maths is broken somewhere, but let's see what you have to say:

// SOMEWHERE AT CLASS LEVEL (so not being reinstantiated every frame, but persisted instead!)
glm::vec3 oldPos;

// ACTUAL IMPL
glm::vec3 distanceToGravityCenter =
        this->entity->getPosition() -
        ((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time

if (!entity->grounded) {
    glm::vec3 entityPosition = entity->getPosition();

    if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) {
        glm::vec3 dir = entityPosition - oldPos; // Actually no need to normalize as we check for lesser, bigger or equal to 0

        std::cout << "falling dir: " << glm::to_string(dir) << std::endl;

        // Calculate offset (where to put after hit)
        int x = dir.x;
        int y = dir.y;
        int z = dir.z;

        if (dir.x >= 0) {
            x = -1;
        } else if (dir.x < 0) {
            x = 1;
        }

        if (dir.y >= 0) {
            y = -1;
        } else if (dir.y < 0) {
            y = 1;
        }

        if (dir.z >= 0) {
            z = -1;
        } else if (dir.z < 0) {
            z = 1;
        }

        glm::vec3 newPos = oldPos + glm::vec3(x, y, z);
        this->entity->setPosition(newPos);
        entity->grounded = true; // If some update happens, grounded needs to be changed
    } else {
        oldPos = entity->getPosition();
        this->entity->setPosition(distanceToGravityCenter);
    }
}

Basic idea was to determine from which direction entityt would hit the surface and then just position it one "unit" back into that direction. But obviously I am doing something wrong as that will always move entity back to the point where it came from, effectively holding it at the spawn point.

Also this could probably be much easier and I am overthinking it.


Solution

  • As @CompuChip already pointed out, your ifs could be further simplified.

    But what is more important is one logical issue that would explain the "choppiness" you describe (Sadly you did not provide any footage, so this is my best guess)

    From the code you posted:

    First you check if entity is grounded. If so you continue with checking if there is a collision and lastly, if there is not, you set the position.

    You have to invert that a bit.

    1. Save old position
    2. Check if grounded
    3. Set the position already to the new one!
    4. Do collision detection
    5. Reset to old position IF you registered a collision!

    So basically:

    glm::vec3 distanceToGravityCenter =
            this->entity->getPosition() -
            ((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
    
    oldPos = entity->getPosition(); // 1.
    
    if (!entity->grounded) { // 2.
        this->fallingStar->setPosition(distanceToGravityPoint); // 3
    
        glm::vec3 entityPosition = entity->getPosition();
    
        if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) { // 4, 5
            this->entity->setPosition(oldPos);
            entity->grounded = true; // If some update happens, grounded needs to be changed
        }
    }
    

    This should get you started :)

    I want to elaborate a bit more:

    If you check for collision first and then set position you create an "infinite loop" upon first collision/hit as you collide, then if there is a collision (which there is) you set back to the old position. Basically just mathematic inaccuracy will make you move, as on every check you are set back to the old position.