Основная проблема обнаружения 3D-столкновений

Я пытаюсь реализовать базовое обнаружение столкновений для трехмерной Java-игры на основе вокселей, над которой я работаю. Я пытаюсь реализовать алгоритм с этого сайта: https://sites.google.com/site/letsmakeavoxelengine/home/collision-detection, но я не могу понять это правильно. Мой вопрос в том, что он подразумевает под «преобразованием положения игрока в воксельное пространство». Должен ли я округлять координаты игрока до ближайшего блока, чтобы воксель, в котором находится игрок, был тем, в котором находится центр игрока? В моей игре игрок в настоящее время представляет собой один куб размером с воксель.

Парень на этом сайте пишет, что ему нужно проверить только один воксель на коллизии. Но если я проверю только воксель, в котором находится центр игрока, то центр игрока должен пересечь вещи, прежде чем он столкнется с ними. Так ведь не должно быть? Если центр игрока находится в неактивном вокселе, но часть куба игрока пересекает активный воксель, какой воксель я должен проверить?

Я понимаю, что этот текст довольно запутанный, но я надеюсь, что вы понимаете мой вопрос. Вот мой класс CollisionHandler, если вам захочется взглянуть на какой-нибудь код: (Он еще не следует алгоритму с сайта выше из-за проблем, с которыми я столкнулся. Он как бы заботится только о столкновениях вдоль оси x на данный момент)

public class CollisionHandler {
private static final float COLLISION_TOLERANCE = 0.4f;
private boolean xCol, yCol, zCol = false;

public void handleCollisions(ChunkManager chunkManager,
        FPCameraController player, float delta) {

    Vector3D playerPos = player.getPosition();
    Vector3D collision = findCollisionVector(player, chunkManager);

    if (collidesWithWorld()) {
        if (!(player.isFalling() && isGrounded(playerPos, chunkManager))) {
            player.setCollisionVector(collision);
            player.translateX(-playerPos.subtract(playerPos.round()).getX());
        }else{
            //123456
        }
    } else {
        if (player.isFalling()) {
            if (isGrounded(playerPos, chunkManager)) {
                float overlap = getYOverlap(player, chunkManager);
                player.translateY(overlap);
                player.setYSpeed(0);
                player.setIsFalling(false);
            } else {
                player.applyGravity(delta);
            }
        } else {
            if (!isGrounded(playerPos, chunkManager)) {
                player.setIsFalling(true);
                player.applyGravity(delta);
            }
        }

    }
}

private boolean collidesWithWorld() {
    return xCol || yCol || zCol;
}

/*
 * Returns a collision vector. Dot with velocity and then subtract it from
 * the player velocity.
 */
private Vector3D findCollisionVector(FPCameraController player,
        ChunkManager chunkManager) {

    Vector3D playerPos = player.getPosition();
    Vector3D distance = playerPos.subtract(playerPos.floor()).abs();

    Vector3D collisions = new Vector3D(1, 1, 1);
    float xDirection = (getCollisionDirection(distance.getX()));
    // float yDirection = (getCollisionDirection(distance.getY()));
    // float zDirection = (getCollisionDirection(distance.getZ()));

    try {
        Vector3D collision = getCollisionNormal(chunkManager, playerPos,
                xDirection, 'x');

        if (collision != null) {
            collisions = collision;
            xCol = true;
        } else {
            xCol = false;
        }

        // collision = getCollisionNormal(chunkManager, playerPos,
        // yDirection,
        // 'y');
        // if (collision != null) {
        // collisions.cross(collision);
        // yCol = true;
        // } else {
        // yCol = false;
        // }
        //
        // collision = getCollisionNormal(chunkManager, playerPos,
        // zDirection,
        // 'z');
        // if (collision != null) {
        // collisions.cross(collision);
        // zCol = true;
        // } else {
        // zCol = false;
        // }
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }

    return collisions;
}

/*
 * Returns the normal of the colliding block, given the axis and
 * direction.
 */
private static Vector3D getCollisionNormal(ChunkManager chunkManager,
        Vector3D playerPos, float direction, char axis)
        throws OutsideOfWorldException {
    Block b;
    Vector3D blockPos;
    if (direction != 0) {
        Vector3D dirVector;
        if (axis == 'x') {
            dirVector = new Vector3D(direction, 0, 0);
        } else if (axis == 'y') {
            dirVector = new Vector3D(0, direction, 0);
        } else if (axis == 'z') {
            dirVector = new Vector3D(0, 0, direction);
        } else {
            return null;
        }
        blockPos = playerPos.add(dirVector);

        b = chunkManager.getBlock(blockPos);

        if (b.isActive()) {

            return Plane3D.getBlockNormal(blockPos, direction, axis);
        }
    }
    return null;
}

private static float getCollisionDirection(float distance) {
    if (distance > COLLISION_TOLERANCE) {
        return 1;
    } else if (distance < COLLISION_TOLERANCE) {
        return -1;
    }
    return 0;
}

private static boolean isGrounded(Vector3D playerPosition,
        ChunkManager chunkManager) {
    try {
        return chunkManager.getBlock(
                playerPosition.add(new Vector3D(0, -1, 0))).isActive();
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }
    return true;
}

private static float getYOverlap(FPCameraController player,
        ChunkManager chunkManager) {
    Vector3D playerPosition = player.getPosition();
    Vector3D blockPosition = player.getLowestBlockPos();
    Block collisionBlock = null;

    try {
        collisionBlock = chunkManager.getBlock(blockPosition);

        // +" "+blockPosition);

        if (collisionBlock.isActive()) {
            float distance = playerPosition.subtract(blockPosition).getY();

            distance += player.getHeight();

            return -distance;

        }
    } catch (OutsideOfWorldException e) {
        e.printStackTrace();
    }

    return 0;
}
}

Вот еще один актуальный метод:

    public static Vector3D getBlockNormal(Vector3D blockPos, float direction,
        char axis) {
    float offset = Block.BLOCK_RENDER_SIZE / 2f;

    Vector3D pointA = null;
    Vector3D pointB = null;
    Vector3D pointC = null;

    Vector3D a = blockPos.round();

    a = a.addScalar(Block.BLOCK_RENDER_SIZE / 2f);
    float factor = -direction;
    if (axis == 'x') {
        pointA = a.add(new Vector3D(factor * offset, -offset, -offset));
        pointB = a.add(new Vector3D(factor * offset, offset, -offset));
        pointC = a.add(new Vector3D(factor * offset, -offset, offset));
    } else if (axis == 'y') {
        pointA = a.add(new Vector3D(-offset, factor * offset, offset));
        pointB = a.add(new Vector3D(offset, factor * offset, offset));
        pointC = a.add(new Vector3D(offset, factor * offset, -offset));
    } else if (axis == 'z') {
        pointA = a.add(new Vector3D(-offset, -offset, factor * offset));
        pointB = a.add(new Vector3D(offset, -offset, factor * offset));
        pointC = a.add(new Vector3D(offset, offset, factor * offset));
    } else {
        return null;
    }

    Vector3D v = new Vector3D(pointB.getX() - pointA.getX(), pointB.getY()
            - pointA.getY(), pointB.getZ() - pointA.getZ()).normalize();
    Vector3D w = new Vector3D(pointC.getX() - pointA.getX(), pointC.getY()
            - pointA.getY(), pointC.getZ() - pointA.getZ()).normalize();
    Vector3D normal = v.cross(w).scale(-1);

    return normal.scale(factor);

}

person user2517217    schedule 24.06.2013    source источник


Ответы (1)


Это ваше дизайнерское решение, как обрабатывать коллизии, идите с тем, что вы считаете наиболее естественным (далее следует уточнение):

Единственный воксель, ближайший к положению игроков, является базой, из которой вы можете легко получить более сложные методы, просто заставив обнаружение столкновений использовать воксель для проверки. Затем вы можете легко расширить его, чтобы проверить несколько соседних вокселей, чтобы дать игроку размер, который вы хотите, чтобы он имел.

Например, вы можете рассматривать игрока как цилиндр и проверять все воксели под кругом, который охватывает цилиндр. Если вы обнаружите (например) один воксель лавы под кругом, вы можете применить урон от лавы (независимо от свойств земли, с которыми работает ваша игра).

Еще одна проблема, с которой вам нужно поэкспериментировать, — это высота. Вы берете самый высокий, самый низкий или какое-то среднее из покрытых вокселей, чтобы определить одиннадцать, где игрок в настоящее время находится (или при полете, на какой высоте он сталкивается с землей)?.

Не существует единого рецепта, чтобы заставить его «чувствовать себя хорошо». Вам нужно будет немного поэкспериментировать, чтобы найти то, что вы считаете «естественным» для предполагаемой физической модели вашей игры.

Если ваша физика допускает быстрое движение, вам может потребоваться расширить проверки столкновений, чтобы проверить всю форму, покрытую объектом, между двумя игровыми шагами, чтобы избежать странных явлений, таких как пули, проходящие через препятствия. Потому что технически они могут двигаться так быстро, что никогда не окажутся внутри препятствия, хотя вектор их движения явно пересекает препятствие.

Таким образом, «преобразование координат игрока в воксельное пространство» может означать что угодно, не определяя подробно метод. Для начального тестирования вашего «округления до ближайшего блока», вероятно, будет достаточно, для окончательной игры вам, вероятно, потребуется применить некоторые из концепций, изложенных выше, чтобы ее физика «чувствовала себя правильно».

person Durandal    schedule 24.06.2013