summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2024-02-02 16:16:25 +0100
committerMel <einebeere@gmail.com>2024-02-02 16:16:25 +0100
commita4a61ac2e6389af0968ac4fb305b82c6eb90bf0e (patch)
tree800749ed6a5d503837a6bf0c389c133fcb59143d /src
parent250e37c742f3ad46f093f4534098cdf8f68a29a9 (diff)
downloadmeowcraft-a4a61ac2e6389af0968ac4fb305b82c6eb90bf0e.tar.zst
meowcraft-a4a61ac2e6389af0968ac4fb305b82c6eb90bf0e.zip
Placing blocks
Diffstat (limited to 'src')
-rw-r--r--src/Entities/Player.cpp41
-rw-r--r--src/Entities/Player.hpp2
-rw-r--r--src/World/Chunk.cpp5
-rw-r--r--src/World/Chunk.hpp1
-rw-r--r--src/World/ChunkIndex.hpp2
-rw-r--r--src/World/Position.hpp25
-rw-r--r--src/World/VoxelTraversal.hpp37
-rw-r--r--src/World/World.cpp11
-rw-r--r--src/World/World.hpp9
9 files changed, 110 insertions, 23 deletions
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index a8abb1d..da96d56 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -24,17 +24,7 @@ void Player::update(const Time& time, GFX::Window& window, GFX::Camera& camera,
 
     update_camera_position(camera);
 
-    if (window.mouse(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS)) {
-        auto look_transform = camera_transform();
-        auto look_direction = -look_transform.forward(); // Why does this need to be inverted?
-        auto block = world.traverse(
-            Ray{look_transform.position(), look_direction},
-            4.0,
-            [](auto b) { return b.type.is_solid(); }
-        );
-
-        if (block != Position::BlockWorld{}) world.break_block(block);
-    }
+    actions(window, world);
 }
 
 void Player::move(Position::WorldOffset by) {
@@ -105,6 +95,35 @@ Vec3 Player::flying_velocity(GFX::Window& window, const Time& time, Vec3 input_d
     return direction * (base_move_speed + boost) * time.delta();
 }
 
+void Player::actions(GFX::Window& window, World::World& world) {
+    auto constexpr max_block_reach = 4.0;
+
+    auto left_click = window.mouse(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS);
+    auto right_click = window.mouse(GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS);
+    if (left_click || right_click) {
+        auto const look_transform = camera_transform();
+        Ray const ray{look_transform.position(), -look_transform.forward()};
+        auto const traversal = world.traverse(
+            ray,
+            [](auto b) { return b.type.is_solid(); },
+            max_block_reach
+        );
+
+        if (traversal.hit) {
+            if (left_click) {
+                world.break_block(traversal.block);
+            } else {
+                auto const new_block = Position::BlockWorld(traversal.block + traversal.normal);
+                auto const current_box = bounding_box_for_position(m_transform.position());
+                auto const block_box = World::Chunk::block_bounds(new_block);
+
+                if (!current_box.collides(block_box)) // Don't place blocks inside the player.
+                    world.set_block(new_block, World::BlockType::Stone);
+            }
+        }
+    }
+}
+
 #define STUCK_THRESHOLD 100
 
 Player::ProcessCollisionsResult Player::process_collisions(World::World& world, Position::World from, Position::World to) {
diff --git a/src/Entities/Player.hpp b/src/Entities/Player.hpp
index b1ec8e7..f6c1f9a 100644
--- a/src/Entities/Player.hpp
+++ b/src/Entities/Player.hpp
@@ -40,6 +40,8 @@ private:
     Vec3 walking_velocity(GFX::Window& window, const Time& time, Vec3 input_direction);
     Vec3 flying_velocity(GFX::Window& window, const Time& time, Vec3 input_direction);
 
+    void actions(GFX::Window& window, World::World& world);
+
     Transform camera_transform() const;
     void update_camera_position(GFX::Camera& camera) const;
 
diff --git a/src/World/Chunk.cpp b/src/World/Chunk.cpp
index 6cb67bb..b4a9ece 100644
--- a/src/World/Chunk.cpp
+++ b/src/World/Chunk.cpp
@@ -50,4 +50,9 @@ AABB Chunk::block_bounds(Position::BlockLocal pos) {
     };
 }
 
+AABB Chunk::block_bounds(Position::BlockWorld pos) {
+    auto chunk_position = ChunkIndex::from_position(pos).world_position();
+    return block_bounds(pos.to_local()).offset(Vec3(chunk_position));
+}
+
 }
diff --git a/src/World/Chunk.hpp b/src/World/Chunk.hpp
index 9a29284..bab20f2 100644
--- a/src/World/Chunk.hpp
+++ b/src/World/Chunk.hpp
@@ -68,6 +68,7 @@ public:
 
     static Bool is_valid_position(Position::BlockLocal pos);
     static AABB block_bounds(Position::BlockLocal pos);
+    static AABB block_bounds(Position::BlockWorld pos);
 private:
     static U64 pos(U32 x, U32 y, U32 z);
 
diff --git a/src/World/ChunkIndex.hpp b/src/World/ChunkIndex.hpp
index da23e3d..1e2b9ac 100644
--- a/src/World/ChunkIndex.hpp
+++ b/src/World/ChunkIndex.hpp
@@ -18,7 +18,7 @@ struct ChunkIndex {
 
     Position::BlockWorld local_to_world_position(Position::BlockLocal local) const {
         using namespace ChunkDimensions;
-        return {x * Width + local.x(), local.y(), y * Width + local.z()};
+        return {TO(I64, x) * Width + local.x(), local.y(), TO(I64, y) * Width + local.z()};
     }
 
     Position::BlockWorld world_position() const {
diff --git a/src/World/Position.hpp b/src/World/Position.hpp
index f2915aa..f9b8ad1 100644
--- a/src/World/Position.hpp
+++ b/src/World/Position.hpp
@@ -2,6 +2,7 @@
 
 #include <array>
 #include <algorithm>
+#include "../Common/Casts.hpp"
 #include "ChunkDimensions.hpp"
 #include "../Math/Common.hpp"
 #include "../Math/Vector.hpp"
@@ -19,6 +20,18 @@ class BlockLocalOffset : public Vector<3, I16> {
 public:
     MC_POSITION_MAKE_DEFAULT_CONSTRUCTORS(BlockLocalOffset, I16)
 
+    // Create a block local offset from a unit normal,
+    // which is a vector with exactly one component being 1 or -1,
+    // with the rest being 0.
+    template<typename T>
+    static BlockLocalOffset from_unit_normal(Vector<3, T> normal) {
+        return {
+            TO(I16, normal.x()),
+            TO(I16, normal.y()),
+            TO(I16, normal.z())
+        };
+    }
+
     Bool fits_within_chunk() const {
         using namespace MC::World::ChunkDimensions;
         return x() >= 0 && x() < Width && y() >= 0 && y() < Height && z() >= 0 && z() < Width;
@@ -44,6 +57,18 @@ public:
 class BlockWorldOffset : public Vector<3, I64> {
 public:
     MC_POSITION_MAKE_DEFAULT_CONSTRUCTORS(BlockWorldOffset, I64)
+
+    // Create a block world offset from a unit normal,
+    // which is a vector with exactly one component being 1 or -1,
+    // with the rest being 0.
+    template<typename T>
+    static BlockWorldOffset from_unit_normal(Vector<3, T> normal) {
+        return {
+            TO(I64, normal.x()),
+            TO(I64, normal.y()),
+            TO(I64, normal.z())
+        };
+    }
 };
 
 // Position of a block within entire world.
diff --git a/src/World/VoxelTraversal.hpp b/src/World/VoxelTraversal.hpp
index 1638901..08a53eb 100644
--- a/src/World/VoxelTraversal.hpp
+++ b/src/World/VoxelTraversal.hpp
@@ -6,11 +6,22 @@
 
 namespace MC::World::VoxelTraversal {
 
+struct TraversalResult {
+    // Whether the ray hit a block.
+    Bool hit{};
+    // The block that was hit.
+    Position::BlockWorld block;
+    // The vector from the ray origin to where the block that was hit.
+    Position::WorldOffset approach;
+    // The normal of the collision.
+    Position::BlockWorldOffset normal;
+};
+
 // Amanatides, John, and Andrew Woo. 1987.
 // "A Fast Voxel Traversal Algorithm for Ray Tracing."
 // https://doi.org/10.2312/EGTP.19871000.
 template <typename P>
-Position::BlockWorld traverse(Ray ray, Real max_distance, P&& predicate) {
+TraversalResult traverse(Ray ray, P&& predicate, Real max_distance = 0) {
     // Find the voxel grid cell containing the origin of the ray.
     Position::BlockWorld block_pos = Position::World(ray.origin).round_to_block();
     Position::BlockWorldOffset const step = {
@@ -31,30 +42,48 @@ Position::BlockWorld traverse(Ray ray, Real max_distance, P&& predicate) {
         TO(Real, step.z()) / ray.direction.z()
     };
 
+    // The original algorithm does not mention calculating the normal of the
+    // block that was hit. When we increment t_max to a collision on one of the axes
+    // of the voxel grid, the axis gives us the normal of the current block.
+    Position::BlockWorldOffset normal = {};
+
+    // Also not mentioned in the original algorithm, but we need to keep track of
+    // how far along we have traversed the voxel grid.
+    // This just means we add the direction of the ray at the axis we have progressed along.
+    Position::WorldOffset approach = {};
+
     while (!predicate(block_pos)) {
-        // TODO: Calculate distance exactly.
-        if (ray.origin.distance(Vec3(block_pos)) > max_distance) return {};
+        if (max_distance > 0 && approach.magnitude() > max_distance)
+            return {};
 
         if (t_max.x() < t_max.y()) {
             if (t_max.x() < t_max.z()) {
                 block_pos.x() += step.x();
                 t_max.x() += t_delta.x();
+                normal = {-step.x(), 0, 0};
+                approach.x() += ray.direction.x();
             } else {
                 block_pos.z() += step.z();
                 t_max.z() += t_delta.z();
+                normal = {0, 0, -step.z()};
+                approach.z() += ray.direction.z();
             }
         } else {
             if (t_max.y() < t_max.z()) {
                 block_pos.y() += step.y();
                 t_max.y() += t_delta.y();
+                normal = {0, -step.y(), 0};
+                approach.y() += ray.direction.y();
             } else {
                 block_pos.z() += step.z();
                 t_max.z() += t_delta.z();
+                normal = {0, 0, -step.z()};
+                approach.z() += ray.direction.z();
             }
         }
     }
 
-    return block_pos;
+    return {true, block_pos, approach, normal};
 }
 
 }
diff --git a/src/World/World.cpp b/src/World/World.cpp
index cc5d91f..2b5848b 100644
--- a/src/World/World.cpp
+++ b/src/World/World.cpp
@@ -47,17 +47,20 @@ Chunk::BlockData World::block_at(Position::BlockWorld pos) {
     return {};
 }
 
-void World::break_block(Position::BlockWorld pos) {
+void World::set_block(Position::BlockWorld pos, BlockType type) {
     auto& chunk_data = m_registry.find(pos);
     if (!chunk_data.chunk.has_value()) return;
 
     auto& block_data = chunk_data.chunk->at(pos.to_local());
-    if (!block_data.type.is_solid()) return;
-
-    block_data.type = {};
+    block_data.type = type;
     chunk_data.damage();
 }
 
+void World::break_block(Position::BlockWorld pos) {
+    if (!block_at(pos).type.is_solid()) return;
+    set_block(pos, BlockType::Air);
+}
+
 std::vector<ChunkIndex> World::get_visible_chunk_indices(const Position::World position) const {
     ChunkIndex center = ChunkIndex::from_position(position.round_to_block());
 
diff --git a/src/World/World.hpp b/src/World/World.hpp
index af49378..76005da 100644
--- a/src/World/World.hpp
+++ b/src/World/World.hpp
@@ -16,14 +16,17 @@ public:
     std::vector<ChunkRegistry::Data*> get_visible_chunks(Position::World position);
 
     Chunk::BlockData block_at(Position::BlockWorld pos);
+    void set_block(Position::BlockWorld pos, BlockType type);
     void break_block(Position::BlockWorld pos);
 
+    using TraversalResult = VoxelTraversal::TraversalResult;
+
     template <typename F>
-    Position::BlockWorld traverse(Ray ray, Real max_distance, F&& predicate) {
+    TraversalResult traverse(Ray ray, F&& predicate, Real max_distance = 0) {
         return VoxelTraversal::traverse(
             ray,
-            max_distance,
-            [this, &predicate](Position::BlockWorld pos) { return predicate(block_at(pos)); }
+            [this, &predicate](Position::BlockWorld pos) { return predicate(block_at(pos)); },
+            max_distance
         );
     }