diff options
| -rw-r--r-- | src/Entities/Player.cpp | 41 | ||||
| -rw-r--r-- | src/Entities/Player.hpp | 2 | ||||
| -rw-r--r-- | src/World/Chunk.cpp | 5 | ||||
| -rw-r--r-- | src/World/Chunk.hpp | 1 | ||||
| -rw-r--r-- | src/World/ChunkIndex.hpp | 2 | ||||
| -rw-r--r-- | src/World/Position.hpp | 25 | ||||
| -rw-r--r-- | src/World/VoxelTraversal.hpp | 37 | ||||
| -rw-r--r-- | src/World/World.cpp | 11 | ||||
| -rw-r--r-- | src/World/World.hpp | 9 |
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 ); } |
