Browse Source

client side player physics and collision detection

Kajetan Johannes Hammerle 3 years ago
parent
commit
7c6689f3c8

+ 22 - 39
client/Game.cpp

@@ -22,6 +22,7 @@ static StartGUI startGUI;
 
 static void tickConnectedState() {
     GameClient::consumeEvents();
+    Game::world.tick();
 }
 
 static void renderConnectedState() {
@@ -54,78 +55,60 @@ bool Game::init() {
     tickState = tickConnectState;
     renderState = renderConnectState;
 
-    player.position = Vector3(0.0f, 30.0f, 0.0f);
-    player.lastLengthAngle = 0.0f;
-    player.lengthAngle = 0.0f;
-    player.lastWidthAngle = 0.0f;
-    player.widthAngle = 0.0f;
+    player.setPosition(Vector3(0.0f, 30.0f, 0.0f));
     world.addPlayer(&player);
     return false;
 }
 
 void Game::tick() {
     tickState();
-    world.preTick();
 
-    Quaternion q(Vector3(0.0f, 1.0f, 0.0f), player.lengthAngle);
-    q *= Quaternion(Vector3(1.0f, 0.0f, 0.0f), player.widthAngle);
+    Quaternion q = player.getRotation();
 
     Vector3 up(0.0f, 1.0f, 0.0f);
     Vector3 back = q * Vector3(0.0f, 0.0f, -1.0f);
-    // back.setAngles(player.widthAngle, player.lengthAngle);
-    // back[1] = 0.0f;
-    // back.normalize();
+    back[1] = 0.0f;
+    back.normalize();
     Vector3 right = back.cross(up);
 
-    const float speed = 0.25f;
+    constexpr float speed = 0.1f;
     if(controller.down.isDown()) {
-        player.acceleration += back * speed;
+        player.addForce(back * speed);
     }
     if(controller.up.isDown()) {
-        player.acceleration -= back * speed;
+        player.addForce(back * -speed);
     }
     if(controller.left.isDown()) {
-        player.acceleration -= right * speed;
+        player.addForce(right * -speed);
     }
     if(controller.right.isDown()) {
-        player.acceleration += right * speed;
+        player.addForce(right * speed);
     }
-    if(controller.jump.isDown()) {
-        player.acceleration += up * speed;
+    if(controller.jump.isDown() && player.isOnGround()) {
+        player.addForce(up * 0.5f);
     }
     if(controller.sneak.isDown()) {
-        player.acceleration -= up * speed;
+        player.addForce(up * -speed);
     }
 
-    const float rotationSpeed = 5.0f;
+    constexpr float rotationSpeed = 4.0f;
     if(controller.camLeft.isDown()) {
-        player.lengthAngle -= rotationSpeed;
+        player.addLengthAngle(-rotationSpeed);
     }
     if(controller.camRight.isDown()) {
-        player.lengthAngle += rotationSpeed;
+        player.addLengthAngle(rotationSpeed);
     }
-    if(controller.camUp.isDown() &&
-       player.widthAngle - rotationSpeed > -90.0f) {
-        player.widthAngle -= rotationSpeed;
+    if(controller.camUp.isDown()) {
+        player.addWidthAngle(-rotationSpeed * 0.5f);
     }
-    if(controller.camDown.isDown() &&
-       player.widthAngle + rotationSpeed < 90.0f) {
-        player.widthAngle += rotationSpeed;
+    if(controller.camDown.isDown()) {
+        player.addWidthAngle(rotationSpeed * 0.5f);
     }
-
-    world.tick();
 }
 
 void Game::renderWorld() {
-    Quaternion q(Vector3(0.0f, 1.0f, 0.0f),
-                 Utils::interpolate(player.lastLengthAngle, player.lengthAngle,
-                                    Engine::lag));
-    q *= Quaternion(Vector3(1.0f, 0.0f, 0.0f),
-                    Utils::interpolate(player.lastWidthAngle, player.widthAngle,
-                                       Engine::lag));
-    Engine::matrix.update(
-        Utils::interpolate(player.lastPosition, player.position, Engine::lag),
-        q);
+    Engine::matrix.update(player.getRenderPosition(Engine::lag),
+                          player.getRenderRotation(Engine::lag));
     worldRenderer.render();
 }
 

+ 4 - 5
client/rendering/renderer/WorldRenderer.cpp

@@ -56,11 +56,10 @@ void WorldRenderer::addCube(TypedBuffer<Triangle>& buffer, float x, float y,
     Vector3 v110(x + 1, y + 1, z);
     Vector3 v111(x + 1, y + 1, z + 1);
 
-    const float ERROR = 0.0001f;
-    Vector2 t1(0.1875f + ERROR, 0.0f + ERROR);
-    Vector2 t2(0.25f - ERROR, 0.0f + ERROR);
-    Vector2 t3(0.25f - ERROR, 0.0625f - ERROR);
-    Vector2 t4(0.1875f + ERROR, 0.0625f - ERROR);
+    Vector2 t1(0.1875f, 0.0f);
+    Vector2 t2(0.25f, 0.0f);
+    Vector2 t3(0.25f, 0.0625f);
+    Vector2 t4(0.1875f, 0.0625f);
 
     if(isAir(x, y - 1, z)) {
         Vector2 tb(0.125f, 0.0625f);

+ 11 - 1
common/block/Block.cpp

@@ -1,6 +1,10 @@
 #include "common/block/Block.h"
 
-Block::Block(BlockId id, const BlockName& registry) : id(id), registry(registry) {
+Block::Block(BlockId id, const BlockName& registry)
+    : id(id), registry(registry) {
+    if(id > 0) {
+        boxes.add(Vector3(1.0f, 1.0f, 1.0f));
+    }
 }
 
 BlockId Block::getId() const {
@@ -9,4 +13,10 @@ BlockId Block::getId() const {
 
 const BlockName& Block::getRegistry() const {
     return registry;
+}
+
+void Block::addBoxes(List<CollisionBox>& list, const Vector3& pos) const {
+    for(const CollisionBox& box : boxes) {
+        list.add(box.offset(pos));
+    }
 }

+ 8 - 2
common/block/Block.h

@@ -2,16 +2,22 @@
 #define BLOCK_H
 
 #include "common/block/BlockTypes.h"
+#include "common/utils/CollisionBox.h"
+#include "utils/ArrayList.h"
+#include "utils/List.h"
 
 class Block final {
     BlockId id;
     BlockName registry;
-    
+    ArrayList<CollisionBox, 1> boxes;
+
 public:
     Block(BlockId id, const BlockName& registry);
-    
+
     BlockId getId() const;
     const BlockName& getRegistry() const;
+
+    void addBoxes(List<CollisionBox>& list, const Vector3& pos) const;
 };
 
 #endif

+ 93 - 0
common/entities/Player.cpp

@@ -0,0 +1,93 @@
+#include "common/entities/Player.h"
+#include "utils/Utils.h"
+
+Player::Player()
+    : lastLengthAngle(0.0f), lengthAngle(0.0f), lastWidthAngle(0.0f),
+      widthAngle(0.0f), size(Vector3(0.5f, 1.8f, 0.5f)) {
+}
+
+void Player::tick() {
+    lastPosition = position;
+    lastLengthAngle = lengthAngle;
+    lastWidthAngle = widthAngle;
+
+    constexpr float drag = 0.48f;
+    acceleration += Vector3(-velocity[0] * drag, 0.0f, -velocity[2] * drag);
+    velocity += acceleration;
+
+    acceleration = Vector3(0.0f, -0.08f, 0.0f);
+}
+
+void Player::addForce(const Vector3& force) {
+    acceleration += force;
+}
+
+const Vector3& Player::getVelocity() const {
+    return velocity;
+}
+
+void Player::move(const Vector3& v) {
+    position += v;
+    onGround = v[1] == 0.0f && velocity[1] < 0.0f;
+    velocity = v;
+}
+
+CollisionBox Player::getCollisionBox() const {
+    return CollisionBox(size).offset(position);
+}
+
+bool Player::isOnGround() const {
+    return onGround;
+}
+
+void Player::setPosition(const Vector3& pos) {
+    setPosition(pos, lengthAngle, widthAngle);
+}
+
+void Player::setPosition(const Vector3& pos, float lengthAngle,
+                         float widthAngle) {
+    Player::lastPosition = pos;
+    Player::position = pos;
+    Player::lastLengthAngle = lengthAngle;
+    Player::lengthAngle = lengthAngle;
+    Player::lastWidthAngle = widthAngle;
+    Player::widthAngle = widthAngle;
+    Player::velocity = Vector3();
+    Player::acceleration = Vector3();
+}
+
+Vector3 Player::getRenderPosition(float lag) const {
+    return Utils::interpolate(lastPosition, position, lag) +
+           Vector3(size[0] * 0.5f, size[1], size[2] * 0.5f);
+}
+
+Quaternion Player::getRotation() const {
+    return getRenderRotation(1.0f);
+}
+
+Quaternion Player::getRenderRotation(float lag) const {
+    float lAngle = Utils::interpolate(lastLengthAngle, lengthAngle, lag);
+    float wAngle = Utils::interpolate(lastWidthAngle, widthAngle, lag);
+    Quaternion q(Vector3(0.0f, 1.0f, 0.0f), lAngle);
+    q *= Quaternion(Vector3(1.0f, 0.0f, 0.0f), wAngle);
+    return q;
+}
+
+void Player::addLengthAngle(float angle) {
+    lengthAngle += angle;
+    constexpr float full = 360.0f;
+    while(lengthAngle < 0.0f) {
+        lengthAngle += full;
+        lastLengthAngle += full;
+    }
+    while(lengthAngle >= full) {
+        lengthAngle -= full;
+        lastLengthAngle -= full;
+    }
+}
+
+void Player::addWidthAngle(float angle) {
+    constexpr float border = 89.9f;
+    widthAngle += angle;
+    widthAngle = std::min(std::max(widthAngle + angle, -border), border);
+}

+ 23 - 1
common/entities/Player.h

@@ -1,9 +1,10 @@
 #ifndef PLAYER_H
 #define PLAYER_H
 
+#include "common/utils/CollisionBox.h"
 #include "math/Quaternion.h"
 
-struct Player {
+class Player {
     Vector3 lastPosition;
     Vector3 position;
     float lastLengthAngle;
@@ -12,6 +13,27 @@ struct Player {
     float widthAngle;
     Vector3 velocity;
     Vector3 acceleration;
+    Vector3 size;
+    bool onGround;
+
+public:
+    Player();
+
+    void tick();
+
+    void addForce(const Vector3& force);
+    const Vector3& getVelocity() const;
+    void move(const Vector3& v);
+    CollisionBox getCollisionBox() const;
+    bool isOnGround() const;
+
+    void setPosition(const Vector3& pos);
+    void setPosition(const Vector3& pos, float lengthAngle, float widthAngle);
+    Vector3 getRenderPosition(float lag) const;
+    Quaternion getRotation() const;
+    Quaternion getRenderRotation(float lag) const;
+    void addLengthAngle(float angle);
+    void addWidthAngle(float angle);
 };
 
 #endif

+ 34 - 0
common/utils/CollisionBox.cpp

@@ -0,0 +1,34 @@
+#include "common/utils/CollisionBox.h"
+
+CollisionBox::CollisionBox(const Vector3& min, const Vector3& max)
+    : min(min), max(max) {
+}
+
+CollisionBox::CollisionBox(const Vector3& size) : max(size) {
+}
+
+CollisionBox CollisionBox::offset(const Vector3& offset) const {
+    return CollisionBox(offset + min, offset + max);
+}
+
+bool CollisionBox::collidesWith(const CollisionBox& other) const {
+    return max[0] > other.min[0] && min[0] < other.max[0] &&
+           max[1] > other.min[1] && min[1] < other.max[1] &&
+           max[2] > other.min[2] && min[2] < other.max[2];
+}
+
+CollisionBox CollisionBox::expand(const Vector3& offset) const {
+    Vector3 add(offset[0] * (offset[0] >= 0.0), offset[1] * (offset[1] >= 0.0),
+                offset[2] * (offset[2] >= 0.0));
+    Vector3 sub(offset[0] * (offset[0] <= 0.0), offset[1] * (offset[1] <= 0.0),
+                offset[2] * (offset[2] <= 0.0));
+    return CollisionBox(min + sub, max + add);
+}
+
+const Vector3& CollisionBox::getMin() const {
+    return min;
+}
+
+const Vector3& CollisionBox::getMax() const {
+    return max;
+}

+ 33 - 0
common/utils/CollisionBox.h

@@ -0,0 +1,33 @@
+#ifndef COLLISION_BOX_H
+#define COLLISION_BOX_H
+
+#include "math/Vector.h"
+#include "utils/StringBuffer.h"
+
+class CollisionBox {
+    Vector3 min;
+    Vector3 max;
+
+    CollisionBox(const Vector3& min, const Vector3& max);
+
+public:
+    CollisionBox(const Vector3& size);
+
+    CollisionBox offset(const Vector3& offset) const;
+    bool collidesWith(const CollisionBox& other) const;
+    CollisionBox expand(const Vector3& offset) const;
+
+    const Vector3& getMin() const;
+    const Vector3& getMax() const;
+
+    template<int L>
+    void toString(StringBuffer<L>& s) const {
+        s.append("CollisionBox(")
+            .append(min)
+            .append(", ")
+            .append(max)
+            .append(")");
+    }
+};
+
+#endif

+ 94 - 22
common/world/World.cpp

@@ -1,5 +1,7 @@
-#include "common/world/World.h"
+#include <cmath>
+
 #include "common/world/HighMap.h"
+#include "common/world/World.h"
 #include "utils/Random.h"
 
 World::World(const BlockRegistry& blockRegistry)
@@ -26,32 +28,102 @@ void World::addPlayer(Player* p) {
     players.add(p);
 }
 
-void World::preTick() {
-    for(Player* p : players) {
-        p->lastPosition = p->position;
-        p->lastLengthAngle = p->lengthAngle;
-        p->lastWidthAngle = p->widthAngle;
-        p->acceleration = Vector3(0.0f, -0.5f, 0.0f);
+List<CollisionBox> World::getBoxes(const CollisionBox& box) const {
+    int minX = floorf(box.getMin()[0]);
+    int minY = floorf(box.getMin()[1]);
+    int minZ = floorf(box.getMin()[2]);
+    int maxX = floorf(box.getMax()[0]);
+    int maxY = floorf(box.getMax()[1]);
+    int maxZ = floorf(box.getMax()[2]);
+
+    List<CollisionBox> boxes;
+    for(int x = minX; x <= maxX; x++) {
+        for(int y = minY; y <= maxY; y++) {
+            for(int z = minZ; z <= maxZ; z++) {
+                getBlock(x, y, z).addBoxes(
+                    boxes, Vector3(static_cast<float>(x), static_cast<float>(y),
+                                   static_cast<float>(z)));
+            }
+        }
     }
+    return boxes;
 }
 
 void World::tick() {
     for(Player* p : players) {
-        p->acceleration +=
-            Vector3(-p->velocity[0] * 0.7f, 0.0f, -p->velocity[2] * 0.7f);
-        p->velocity += p->acceleration;
-        p->position += p->velocity;
-
-        int x = p->position[0];
-        int y = p->position[1];
-        int z = p->position[2];
-        while(getBlock(x, y, z).getId() != 0 ||
-              getBlock(x - 1, y, z).getId() != 0 ||
-              getBlock(x + 1, y, z).getId() != 0 ||
-              getBlock(x, y, z - 1).getId() != 0 ||
-              getBlock(x, y, z + 1).getId() != 0) {
-            y++;
+        p->tick();
+
+        Vector3 move = p->getVelocity();
+        CollisionBox box = p->getCollisionBox();
+        List<CollisionBox> boxes = getBoxes(box.expand(move));
+        if(boxes.getLength() == 0) {
+            p->move(move);
+            continue;
+        }
+        Vector3 realMove;
+
+        constexpr float step = 0.05f;
+        while(move[0] != 0.0f || move[1] != 0.0f || move[2] != 0.0f) {
+            Vector3 old = realMove;
+            if(move[0] > step) {
+                realMove[0] += step;
+                move[0] -= step;
+            } else if(move[0] < -step) {
+                realMove[0] -= step;
+                move[0] += step;
+            } else if(move[0] != 0.0f) {
+                realMove[0] += move[0];
+                move[0] = 0.0f;
+            }
+            CollisionBox moved = box.offset(realMove);
+            for(const CollisionBox& box : boxes) {
+                if(box.collidesWith(moved)) {
+                    move[0] = 0.0f;
+                    realMove = old;
+                    break;
+                }
+            }
+
+            old = realMove;
+            if(move[1] > step) {
+                realMove[1] += step;
+                move[1] -= step;
+            } else if(move[1] < -step) {
+                realMove[1] -= step;
+                move[1] += step;
+            } else if(move[1] != 0.0f) {
+                realMove[1] += move[1];
+                move[1] = 0.0f;
+            }
+            moved = box.offset(realMove);
+            for(const CollisionBox& box : boxes) {
+                if(box.collidesWith(moved)) {
+                    move[1] = 0.0f;
+                    realMove = old;
+                    break;
+                }
+            }
+
+            old = realMove;
+            if(move[2] > step) {
+                realMove[2] += step;
+                move[2] -= step;
+            } else if(move[2] < -step) {
+                realMove[2] -= step;
+                move[2] += step;
+            } else if(move[2] != 0.0f) {
+                realMove[2] += move[2];
+                move[2] = 0.0f;
+            }
+            moved = box.offset(realMove);
+            for(const CollisionBox& box : boxes) {
+                if(box.collidesWith(moved)) {
+                    move[2] = 0.0f;
+                    realMove = old;
+                    break;
+                }
+            }
         }
-        p->position[1] = y + 1.0f;
+        p->move(realMove);
     }
 }

+ 3 - 1
common/world/World.h

@@ -24,8 +24,10 @@ public:
 
     void addPlayer(Player* p);
 
-    void preTick();
     void tick();
+
+private:
+    List<CollisionBox> getBoxes(const CollisionBox& box) const;
 };
 
 #endif

+ 3 - 1
meson.build

@@ -7,6 +7,8 @@ src_common = [
     'common/world/BlockStorage.cpp',
     'common/world/World.cpp', 
     'common/world/HighMap.cpp',
+    'common/utils/CollisionBox.cpp',
+    'common/entities/Player.cpp',
 ]
 
 src_server = ['server/Main.cpp',
@@ -53,7 +55,7 @@ libgamingcore_dep = libgamingcore_proj.get_variable('libgamingcore_dep')
 common = static_library('common', 
     sources: src_common,
     dependencies: [libgamingcore_dep],
-    c_args: args)
+    cpp_args: args)
 common_dep = declare_dependency(
     link_with: common, 
     include_directories: ['subprojects/gaming-core'])