From 68694a9c9d7d27d3b79c7b96bb67f56db2f75c45 Mon Sep 17 00:00:00 2001 From: untodesu Date: Thu, 11 Sep 2025 18:18:08 +0500 Subject: Metadata voxels! --- game/shared/game_voxels.cc | 141 +++++++++------------ game/shared/game_voxels.hh | 29 +++-- game/shared/world/chunk.cc | 25 ++-- game/shared/world/chunk.hh | 9 +- game/shared/world/dimension.cc | 30 +++-- game/shared/world/dimension.hh | 11 +- game/shared/world/feature.cc | 7 +- game/shared/world/feature.hh | 7 +- game/shared/world/ray_dda.cc | 2 +- game/shared/world/ray_dda.hh | 3 +- game/shared/world/voxel.cc | 120 ++++++++++++++++++ game/shared/world/voxel.hh | 236 ++++++++++++++++++++++++++++++++++++ game/shared/world/voxel_registry.cc | 194 +++++------------------------ game/shared/world/voxel_registry.hh | 137 ++------------------- game/shared/world/voxels/generic.hh | 27 +++++ 15 files changed, 555 insertions(+), 423 deletions(-) create mode 100644 game/shared/world/voxel.cc create mode 100644 game/shared/world/voxel.hh create mode 100644 game/shared/world/voxels/generic.hh (limited to 'game/shared') diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index 51de1a1..59d035d 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -2,41 +2,71 @@ #include "shared/game_voxels.hh" +#include "shared/world/voxels/generic.hh" + #include "shared/world/voxel_registry.hh" -voxel_id game_voxels::cobblestone = NULL_VOXEL_ID; -voxel_id game_voxels::dirt = NULL_VOXEL_ID; -voxel_id game_voxels::grass = NULL_VOXEL_ID; -voxel_id game_voxels::stone = NULL_VOXEL_ID; -voxel_id game_voxels::vtest = NULL_VOXEL_ID; -voxel_id game_voxels::vtest_ck = NULL_VOXEL_ID; -voxel_id game_voxels::oak_leaves = NULL_VOXEL_ID; -voxel_id game_voxels::oak_planks = NULL_VOXEL_ID; -voxel_id game_voxels::oak_log = NULL_VOXEL_ID; -voxel_id game_voxels::glass = NULL_VOXEL_ID; -voxel_id game_voxels::slime = NULL_VOXEL_ID; -voxel_id game_voxels::mud = NULL_VOXEL_ID; +const world::Voxel* game_voxels::cobblestone = nullptr; +const world::Voxel* game_voxels::dirt = nullptr; +const world::Voxel* game_voxels::grass = nullptr; +const world::Voxel* game_voxels::stone = nullptr; +const world::Voxel* game_voxels::vtest = nullptr; +const world::Voxel* game_voxels::vtest_ck = nullptr; +const world::Voxel* game_voxels::oak_leaves = nullptr; +const world::Voxel* game_voxels::oak_planks = nullptr; +const world::Voxel* game_voxels::oak_log = nullptr; +const world::Voxel* game_voxels::glass = nullptr; +const world::Voxel* game_voxels::slime = nullptr; +const world::Voxel* game_voxels::mud = nullptr; void game_voxels::populate(void) { - // Stone; the backbone of the generated world - game_voxels::stone = - world::voxel_registry::construct("stone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/stone_01.png") - .add_texture_default("textures/voxel/stone_02.png") - .add_texture_default("textures/voxel/stone_03.png") - .add_texture_default("textures/voxel/stone_04.png") - .set_surface(world::voxel_surface::STONE) - .build(); + using namespace world; + using namespace world::voxels; + using world::voxel_registry::register_voxel; + + stone = register_voxel(GenericCube("stone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/stone_01.png", "textures/voxel/stone_02.png", "textures/voxel/stone_03.png", "textures/voxel/stone_04.png")); + + cobblestone = register_voxel(GenericCube("cobblestone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/cobblestone_01.png", "textures/voxel/cobblestone_02.png")); + + vtest = register_voxel(GenericCube("vtest", VRENDER_OPAQUE, true, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/vtest_F1.png", "textures/voxel/vtest_F2.png", "textures/voxel/vtest_F3.png", "textures/voxel/vtest_F4.png")); + + vtest_ck = register_voxel(GenericCube("vtest_ck", VRENDER_OPAQUE, false, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/chromakey.png")); + + oak_leaves = register_voxel(GenericCube("oak_leaves", VRENDER_BLEND, false, VMAT_GRASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/oak_leaves.png")); - // Cobblestone; should drop when a stone is broken, might also be present in surface features - game_voxels::cobblestone = - world::voxel_registry::construct("cobblestone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/cobblestone_01.png") - .add_texture_default("textures/voxel/cobblestone_02.png") - .set_surface(world::voxel_surface::STONE) + oak_planks = register_voxel(GenericCube("oak_planks", VRENDER_OPAQUE, false, VMAT_WOOD, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/oak_planks_01.png", "textures/voxel/oak_planks_02.png")); + + glass = register_voxel(GenericCube("glass", VRENDER_BLEND, false, VMAT_GLASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/glass_01.png")); + + slime = register_voxel(GenericCube("slime", VRENDER_BLEND, false, VMAT_SLOSH, VTOUCH_BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f), + "textures/voxel/slime_01.png")); + +#if 0 + + // Oak logs; greenery. TODO: add trees as surface features + game_voxels::oak_log = + world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) + .add_texture_default("textures/voxel/oak_wood_01.png") + .add_texture_default("textures/voxel/oak_wood_02.png") + .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") + .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") + .set_surface(world::voxel_surface::WOOD) .build(); + // Glass; blend rendering test + game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) + .add_texture_default("textures/voxel/glass_01.png") + .set_surface(world::voxel_surface::GLASS) + .build(); + // Dirt with a grass layer on top; the top layer of plains biome game_voxels::grass = world::voxel_registry::construct("grass", world::voxel_type::CUBE, false, false) @@ -60,60 +90,5 @@ void game_voxels::populate(void) .add_texture_default("textures/voxel/dirt_04.png") .set_surface(world::voxel_surface::DIRT) .build(); - - // VTest; a test voxel to ensure animations work - game_voxels::vtest = world::voxel_registry::construct("vtest", world::voxel_type::CUBE, true, false) - .add_texture_default("textures/voxel/vtest_F1.png") - .add_texture_default("textures/voxel/vtest_F2.png") - .add_texture_default("textures/voxel/vtest_F3.png") - .add_texture_default("textures/voxel/vtest_F4.png") - .build(); - - // VTest-CK; a pure blue chromakey I used to make the game's logo - game_voxels::vtest_ck = world::voxel_registry::construct("vtest_ck", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/chromakey.png") - .build(); - - // Oak leaves; greenery. TODO: add trees as surface features - game_voxels::oak_leaves = world::voxel_registry::construct("oak_leaves", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_leaves.png") - .set_surface(world::voxel_surface::GRASS) - .build(); - - // Oak planks; the thing that comes out of oak logs - game_voxels::oak_planks = world::voxel_registry::construct("oak_planks", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_planks_01.png") - .add_texture_default("textures/voxel/oak_planks_02.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Oak logs; greenery. TODO: add trees as surface features - game_voxels::oak_log = - world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_wood_01.png") - .add_texture_default("textures/voxel/oak_wood_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Glass; blend rendering test - game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) - .add_texture_default("textures/voxel/glass_01.png") - .set_surface(world::voxel_surface::GLASS) - .build(); - - // Slime; it's bouncy! - game_voxels::slime = world::voxel_registry::construct("slime", world::voxel_type::CUBE, false, true) - .set_touch(world::voxel_touch::BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f)) - .add_texture_default("textures/voxel/slime_01.png") - .build(); - - // Mud; you sink in it - game_voxels::mud = world::voxel_registry::construct("mud", world::voxel_type::CUBE, false, false) - .set_touch(world::voxel_touch::SINK, glm::fvec3(0.50f, 0.75f, 0.50f)) - .add_texture_default("textures/voxel/mud_01.png") - .add_texture_default("textures/voxel/mud_02.png") - .set_surface(world::voxel_surface::DIRT) - .build(); +#endif } diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh index 21102d0..7b24ec4 100644 --- a/game/shared/game_voxels.hh +++ b/game/shared/game_voxels.hh @@ -2,22 +2,25 @@ #define SHARED_GAME_VOXELS 1 #pragma once -#include "shared/types.hh" +namespace world +{ +class Voxel; +} // namespace world namespace game_voxels { -extern voxel_id cobblestone; -extern voxel_id dirt; -extern voxel_id grass; -extern voxel_id stone; -extern voxel_id vtest; -extern voxel_id vtest_ck; -extern voxel_id oak_leaves; -extern voxel_id oak_planks; -extern voxel_id oak_log; -extern voxel_id glass; -extern voxel_id slime; -extern voxel_id mud; +extern const world::Voxel* cobblestone; +extern const world::Voxel* dirt; +extern const world::Voxel* grass; +extern const world::Voxel* stone; +extern const world::Voxel* vtest; +extern const world::Voxel* vtest_ck; +extern const world::Voxel* oak_leaves; +extern const world::Voxel* oak_planks; +extern const world::Voxel* oak_log; +extern const world::Voxel* glass; +extern const world::Voxel* slime; +extern const world::Voxel* mud; } // namespace game_voxels namespace game_voxels diff --git a/game/shared/world/chunk.cc b/game/shared/world/chunk.cc index e59b68d..b1b19a6 100644 --- a/game/shared/world/chunk.cc +++ b/game/shared/world/chunk.cc @@ -2,6 +2,8 @@ #include "shared/world/chunk.hh" +#include "shared/world/voxel_registry.hh" + #include "shared/coord.hh" world::Chunk::Chunk(entt::entity entity, Dimension* dimension) @@ -12,30 +14,35 @@ world::Chunk::Chunk(entt::entity entity, Dimension* dimension) m_biome = BIOME_VOID; } -voxel_id world::Chunk::get_voxel(const local_pos& lpos) const +const world::Voxel* world::Chunk::get_voxel(const local_pos& lpos) const { return get_voxel(coord::to_index(lpos)); } -voxel_id world::Chunk::get_voxel(const std::size_t index) const +const world::Voxel* world::Chunk::get_voxel(const std::size_t index) const { if(index >= CHUNK_VOLUME) { - return NULL_VOXEL_ID; - } - else { - return m_voxels[index]; + return nullptr; } + + return voxel_registry::find(m_voxels[index]); } -void world::Chunk::set_voxel(voxel_id voxel, const local_pos& lpos) +void world::Chunk::set_voxel(const Voxel* voxel, const local_pos& lpos) { set_voxel(voxel, coord::to_index(lpos)); } -void world::Chunk::set_voxel(voxel_id voxel, const std::size_t index) +void world::Chunk::set_voxel(const Voxel* voxel, const std::size_t index) { if(index < CHUNK_VOLUME) { - m_voxels[index] = voxel; + if(voxel == nullptr) { + m_voxels[index] = NULL_VOXEL_ID; + return; + } + + m_voxels[index] = voxel->get_id(); + return; } } diff --git a/game/shared/world/chunk.hh b/game/shared/world/chunk.hh index c5bba12..4a1e557 100644 --- a/game/shared/world/chunk.hh +++ b/game/shared/world/chunk.hh @@ -9,6 +9,7 @@ constexpr static unsigned int BIOME_VOID = 0U; namespace world { class Dimension; +class Voxel; } // namespace world namespace world @@ -18,11 +19,11 @@ public: explicit Chunk(entt::entity entity, Dimension* dimension); virtual ~Chunk(void) = default; - voxel_id get_voxel(const local_pos& lpos) const; - voxel_id get_voxel(const std::size_t index) const; + const Voxel* get_voxel(const local_pos& lpos) const; + const Voxel* get_voxel(const std::size_t index) const; - void set_voxel(voxel_id voxel, const local_pos& lpos); - void set_voxel(voxel_id voxel, const std::size_t index); + void set_voxel(const Voxel* voxel, const local_pos& lpos); + void set_voxel(const Voxel* voxel, const std::size_t index); const VoxelStorage& get_voxels(void) const; void set_voxels(const VoxelStorage& voxels); diff --git a/game/shared/world/dimension.cc b/game/shared/world/dimension.cc index dd28449..31a19af 100644 --- a/game/shared/world/dimension.cc +++ b/game/shared/world/dimension.cc @@ -3,6 +3,7 @@ #include "shared/world/dimension.hh" #include "shared/world/chunk.hh" +#include "shared/world/voxel_registry.hh" #include "shared/coord.hh" #include "shared/globals.hh" @@ -107,7 +108,7 @@ void world::Dimension::remove_chunk(Chunk* chunk) } } -voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const +const world::Voxel* world::Dimension::get_voxel(const voxel_pos& vpos) const { auto cpos = coord::to_chunk(vpos); auto lpos = coord::to_local(vpos); @@ -115,12 +116,11 @@ voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const if(auto chunk = find_chunk(cpos)) { return chunk->get_voxel(lpos); } - else { - return NULL_VOXEL_ID; - } + + return nullptr; } -voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const +const world::Voxel* world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const { // This allows accessing get_voxel with negative // local coordinates that usually would result in an @@ -128,19 +128,33 @@ voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpo return get_voxel(coord::to_voxel(cpos, lpos)); } -bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos) +bool world::Dimension::set_voxel(const Voxel* voxel, const voxel_pos& vpos) { auto cpos = coord::to_chunk(vpos); auto lpos = coord::to_local(vpos); if(auto chunk = find_chunk(cpos)) { + if(auto old_voxel = chunk->get_voxel(lpos)) { + if(old_voxel != voxel) { + // Notify the old voxel that it is + // being replaced with a different voxel + old_voxel->on_remove(this, vpos); + } + } + chunk->set_voxel(voxel, lpos); + if(voxel) { + // If we're not placing air, notify the + // new voxel that it has been placed + voxel->on_place(this, vpos); + } + VoxelSetEvent event; event.dimension = this; + event.voxel = voxel; event.cpos = cpos; event.lpos = lpos; - event.voxel = voxel; event.chunk = chunk; globals::dispatcher.trigger(event); @@ -151,7 +165,7 @@ bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos) return false; } -bool world::Dimension::set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos) +bool world::Dimension::set_voxel(const Voxel* voxel, const chunk_pos& cpos, const local_pos& lpos) { // This allows accessing set_voxel with negative // local coordinates that usually would result in an diff --git a/game/shared/world/dimension.hh b/game/shared/world/dimension.hh index bf9bfe1..58e0765 100644 --- a/game/shared/world/dimension.hh +++ b/game/shared/world/dimension.hh @@ -11,6 +11,7 @@ class ConfigMap; namespace world { class Chunk; +class Voxel; class VoxelStorage; } // namespace world @@ -40,11 +41,11 @@ public: void remove_chunk(Chunk* chunk); public: - voxel_id get_voxel(const voxel_pos& vpos) const; - voxel_id get_voxel(const chunk_pos& cpos, const local_pos& lpos) const; + const Voxel* get_voxel(const voxel_pos& vpos) const; + const Voxel* get_voxel(const chunk_pos& cpos, const local_pos& lpos) const; - bool set_voxel(voxel_id voxel, const voxel_pos& vpos); - bool set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos); + bool set_voxel(const Voxel* voxel, const voxel_pos& vpos); + bool set_voxel(const Voxel* voxel, const chunk_pos& cpos, const local_pos& lpos); public: virtual void init(io::ConfigMap& config); @@ -92,9 +93,9 @@ struct ChunkUpdateEvent final { struct VoxelSetEvent final { Dimension* dimension; + const Voxel* voxel; chunk_pos cpos; local_pos lpos; - voxel_id voxel; Chunk* chunk; }; } // namespace world diff --git a/game/shared/world/feature.cc b/game/shared/world/feature.cc index 4212043..cbf660e 100644 --- a/game/shared/world/feature.cc +++ b/game/shared/world/feature.cc @@ -4,7 +4,6 @@ #include "shared/world/chunk.hh" #include "shared/world/dimension.hh" -#include "shared/world/voxel_storage.hh" #include "shared/coord.hh" @@ -30,7 +29,7 @@ void world::Feature::place(const voxel_pos& vpos, Dimension* dimension) const } } -void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const +void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& chunk) const { for(const auto [rpos, voxel, overwrite] : (*this)) { auto it_vpos = vpos + rpos; @@ -40,14 +39,14 @@ void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelSt auto it_lpos = coord::to_local(it_vpos); auto it_index = coord::to_index(it_lpos); - if(voxels[it_index] && !overwrite) { + if(chunk.get_voxel(it_index) && !overwrite) { // There is something in the way // and the called intentionally requested // we do not force feature to overwrite voxels continue; } - voxels[it_index] = voxel; + chunk.set_voxel(voxel, it_index); } } } diff --git a/game/shared/world/feature.hh b/game/shared/world/feature.hh index 20a809b..09b913e 100644 --- a/game/shared/world/feature.hh +++ b/game/shared/world/feature.hh @@ -4,19 +4,20 @@ namespace world { +class Chunk; class Dimension; -class VoxelStorage; +class Voxel; } // namespace world namespace world { -class Feature final : public std::vector> { +class Feature final : public std::vector> { public: Feature(void) = default; virtual ~Feature(void) = default; public: void place(const voxel_pos& vpos, Dimension* dimension) const; - void place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const; + void place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& chunk) const; }; } // namespace world diff --git a/game/shared/world/ray_dda.cc b/game/shared/world/ray_dda.cc index b337e9d..7bb1068 100644 --- a/game/shared/world/ray_dda.cc +++ b/game/shared/world/ray_dda.cc @@ -71,7 +71,7 @@ void world::RayDDA::reset(const world::Dimension& dimension, const chunk_pos& st reset(&dimension, start_chunk, start_fpos, direction); } -voxel_id world::RayDDA::step(void) +const world::Voxel* world::RayDDA::step(void) { if(side_dist.x < side_dist.z) { if(side_dist.x < side_dist.y) { diff --git a/game/shared/world/ray_dda.hh b/game/shared/world/ray_dda.hh index 54bd48a..3071be2 100644 --- a/game/shared/world/ray_dda.hh +++ b/game/shared/world/ray_dda.hh @@ -7,6 +7,7 @@ namespace world { class Dimension; +class Voxel; } // namespace world namespace world @@ -20,7 +21,7 @@ public: void reset(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); void reset(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - voxel_id step(void); + const Voxel* step(void); public: const Dimension* dimension; diff --git a/game/shared/world/voxel.cc b/game/shared/world/voxel.cc new file mode 100644 index 0000000..d51b5a1 --- /dev/null +++ b/game/shared/world/voxel.cc @@ -0,0 +1,120 @@ +#include "shared/pch.hh" + +#include "shared/world/voxel.hh" + +#include "core/math/crc64.hh" + +#include "shared/world/dimension.hh" + +void world::Voxel::on_place(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::on_remove(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::on_tick(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::set_id(voxel_id id) noexcept +{ + m_id = id; +} + +std::size_t world::Voxel::get_random_texture_index(VoxelFace face, const voxel_pos& vpos) const +{ + const auto& textures = get_face_textures(face); + + assert(textures.size()); + + std::uint64_t hash = 0U; + hash = math::crc64(&vpos.x, sizeof(vpos.x), hash); + hash = math::crc64(&vpos.y, sizeof(vpos.y), hash); + hash = math::crc64(&vpos.z, sizeof(vpos.z), hash); + + return static_cast(hash % textures.size()); +} + +void world::Voxel::set_face_cache(VoxelFace face, std::size_t offset, std::size_t plane) +{ + assert(face < m_cached_face_offsets.size()); + assert(face < m_cached_face_planes.size()); + + m_cached_face_offsets[face] = offset; + m_cached_face_planes[face] = plane; +} + +std::uint64_t world::Voxel::calculate_checksum(std::uint64_t combine) const +{ + combine = math::crc64(m_name.data(), m_name.size(), combine); + combine += static_cast(m_shape); + combine += static_cast(m_render_mode); + return combine; +} + +std::shared_ptr world::Voxel::clone(void) const +{ + return std::make_shared(*this); +} + +void world::Voxel::set_name(std::string_view name) noexcept +{ + assert(name.size()); + + m_name = name; +} + +void world::Voxel::set_render_mode(VoxelRender mode) noexcept +{ + m_render_mode = mode; +} + +void world::Voxel::set_shape(VoxelShape shape) noexcept +{ + m_shape = shape; +} + +void world::Voxel::set_animated(bool animated) noexcept +{ + m_animated = animated; +} + +void world::Voxel::set_touch_type(VoxelTouch type) noexcept +{ + m_touch_type = type; +} + +void world::Voxel::set_touch_values(const glm::fvec3& values) noexcept +{ + m_touch_values = values; +} + +void world::Voxel::set_surface_material(VoxelMaterial material) noexcept +{ + m_surface_material = material; +} + +void world::Voxel::set_collision(const math::AABBf& box) noexcept +{ + m_collision = box; +} + +void world::Voxel::add_default_texture(std::string_view path) +{ + assert(path.size()); + + m_default_textures.emplace_back(path); +} + +void world::Voxel::add_face_texture(VoxelFace face, std::string_view path) +{ + assert(face < m_face_textures.size()); + assert(path.size()); + + m_face_textures[face].emplace_back(path); +} diff --git a/game/shared/world/voxel.hh b/game/shared/world/voxel.hh new file mode 100644 index 0000000..0bd83b7 --- /dev/null +++ b/game/shared/world/voxel.hh @@ -0,0 +1,236 @@ +#pragma once + +#include "core/math/aabb.hh" + +#include "shared/types.hh" + +namespace world +{ +class Dimension; +} // namespace world + +namespace world +{ +enum VoxelRender : unsigned short { + VRENDER_NONE = 0U, ///< The voxel is not rendered at all + VRENDER_OPAQUE, ///< The voxel is fully opaque + VRENDER_BLEND, ///< The voxel is blended (e.g. water, glass) +}; + +enum VoxelShape : unsigned short { + VSHAPE_CUBE = 0U, ///< Full cube shape + VSHAPE_CROSS, ///< TODO: Cross shape + VSHAPE_MODEL, ///< TODO: Custom model shape +}; + +enum VoxelFace : unsigned short { + VFACE_NORTH = 0U, ///< Positive Z face + VFACE_SOUTH, ///< Negative Z face + VFACE_EAST, ///< Positive X face + VFACE_WEST, ///< Negative X face + VFACE_TOP, ///< Positive Y face + VFACE_BOTTOM, ///< Negative Y face + VFACE_CROSS_NWSE, ///< Diagonal cross face northwest-southeast + VFACE_CROSS_NESW, ///< Diagonal cross face northeast-southwest + VFACE_COUNT +}; + +enum VoxelTouch : unsigned short { + VTOUCH_NONE = 0xFFFFU, + VTOUCH_SOLID = 0U, ///< The entity is stopped in its tracks + VTOUCH_BOUNCE, ///< The entity bounces back with some energy loss + VTOUCH_SINK, ///< The entity phases/sinks through the voxel +}; + +enum VoxelMaterial : unsigned short { + VMAT_DEFAULT = 0U, + VMAT_STONE, + VMAT_DIRT, + VMAT_GLASS, + VMAT_GRASS, + VMAT_GRAVEL, + VMAT_METAL, + VMAT_SAND, + VMAT_WOOD, + VMAT_SLOSH, + VMAT_COUNT +}; + +enum VoxelVisBits : unsigned short { + VVIS_NORTH = 1U << VFACE_NORTH, ///< Positive Z + VVIS_SOUTH = 1U << VFACE_SOUTH, ///< Negative Z + VVIS_EAST = 1U << VFACE_EAST, ///< Positive X + VVIS_WEST = 1U << VFACE_WEST, ///< Negative X + VVIS_UP = 1U << VFACE_TOP, ///< Positive Y + VVIS_DOWN = 1U << VFACE_BOTTOM, ///< Negative Y +}; +} // namespace world + +namespace world +{ +class Voxel { +public: + /// Called when the voxel is placed in the world + /// @param dimension The dimension the voxel is placed in + /// @param vpos The absolute voxel position where the voxel is placed + virtual void on_place(Dimension* dimension, const voxel_pos& vpos) const; + + /// Called when the voxel is removed from the world + /// @param dimension The dimension the voxel is removed from + /// @param vpos The absolute voxel position where the voxel is removed + virtual void on_remove(Dimension* dimension, const voxel_pos& vpos) const; + + /// Called when the voxel is ticked by something + /// @param dimension The dimension the voxel is ticked in + /// @param vpos The absolute voxel position where the voxel is ticked + virtual void on_tick(Dimension* dimension, const voxel_pos& vpos) const; + + constexpr std::string_view get_name(void) const noexcept; + constexpr voxel_id get_id(void) const noexcept; + void set_id(voxel_id id) noexcept; + + constexpr VoxelRender get_render_mode(void) const noexcept; + constexpr VoxelShape get_shape(void) const noexcept; + constexpr bool is_animated(void) const noexcept; + + constexpr VoxelTouch get_touch_type(void) const noexcept; + constexpr const glm::fvec3& get_touch_values(void) const noexcept; + constexpr VoxelMaterial get_surface_material(void) const noexcept; + + constexpr const math::AABBf& get_collision(void) const noexcept; + + constexpr const std::vector& get_default_textures(void) const noexcept; + constexpr const std::vector& get_face_textures(VoxelFace face) const noexcept; + constexpr std::size_t get_cached_face_offset(VoxelFace face) const noexcept; + constexpr std::size_t get_cached_face_plane(VoxelFace face) const noexcept; + + /// Non-model voxel shapes support texture variation based on the + /// voxel position on the world; this method handles the math behind this + /// @param face The face of the voxel to get the texture index for + /// @param vpos The absolute voxel position to get the texture index for + /// @return The index of the texture to use for the given face at the given position + /// @remarks On client-side: plane[get_cached_face_plane][get_cached_face_offset + thisFunctionResult] + std::size_t get_random_texture_index(VoxelFace face, const voxel_pos& vpos) const; + + /// Assign cached plane index and plane offset for a given face + /// @param face The face to assign the cache for + /// @param offset The offset to assign to the face + /// @param plane The plane index to assign to the face + void set_face_cache(VoxelFace face, std::size_t offset, std::size_t plane); + + /// Calculate a checksum for the voxel's properties + /// @param combine An optional initial checksum to combine with + /// @return The calculated checksum + std::uint64_t calculate_checksum(std::uint64_t combine = 0U) const; + + /// Produce a copy of itself as a shared pointer; the voxel registry + /// does not change the owner of a voxel instance it registers + virtual std::shared_ptr clone(void) const; + +protected: + void set_name(std::string_view name) noexcept; + + void set_render_mode(VoxelRender mode) noexcept; + void set_shape(VoxelShape shape) noexcept; + void set_animated(bool animated) noexcept; + + void set_touch_type(VoxelTouch type) noexcept; + void set_touch_values(const glm::fvec3& values) noexcept; + void set_surface_material(VoxelMaterial material) noexcept; + + void set_collision(const math::AABBf& box) noexcept; + + void add_default_texture(std::string_view path); + void add_face_texture(VoxelFace face, std::string_view path); + +private: + std::string m_name; + voxel_id m_id { NULL_VOXEL_ID }; + + VoxelRender m_render_mode { VRENDER_OPAQUE }; + VoxelShape m_shape { VSHAPE_CUBE }; + bool m_animated { false }; + + VoxelTouch m_touch_type { VTOUCH_SOLID }; + glm::fvec3 m_touch_values { 0.0f }; + VoxelMaterial m_surface_material { VMAT_DEFAULT }; + + math::AABBf m_collision { { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } }; + + std::vector m_default_textures; + std::array, VFACE_COUNT> m_face_textures; + std::array m_cached_face_offsets; + std::array m_cached_face_planes; +}; +} // namespace world + +constexpr std::string_view world::Voxel::get_name(void) const noexcept +{ + return m_name; +} + +constexpr voxel_id world::Voxel::get_id(void) const noexcept +{ + return m_id; +} + +constexpr world::VoxelRender world::Voxel::get_render_mode(void) const noexcept +{ + return m_render_mode; +} + +constexpr world::VoxelShape world::Voxel::get_shape(void) const noexcept +{ + return m_shape; +} + +constexpr bool world::Voxel::is_animated(void) const noexcept +{ + return m_animated; +} + +constexpr world::VoxelTouch world::Voxel::get_touch_type(void) const noexcept +{ + return m_touch_type; +} + +constexpr const glm::fvec3& world::Voxel::get_touch_values(void) const noexcept +{ + return m_touch_values; +} + +constexpr world::VoxelMaterial world::Voxel::get_surface_material(void) const noexcept +{ + return m_surface_material; +} + +constexpr const math::AABBf& world::Voxel::get_collision(void) const noexcept +{ + return m_collision; +} + +constexpr const std::vector& world::Voxel::get_default_textures(void) const noexcept +{ + return m_default_textures; +} + +constexpr const std::vector& world::Voxel::get_face_textures(VoxelFace face) const noexcept +{ + assert(face <= m_face_textures.size()); + + return m_face_textures[face]; +} + +constexpr std::size_t world::Voxel::get_cached_face_offset(VoxelFace face) const noexcept +{ + assert(face <= m_cached_face_offsets.size()); + + return m_cached_face_offsets[face]; +} + +constexpr std::size_t world::Voxel::get_cached_face_plane(VoxelFace face) const noexcept +{ + assert(face <= m_cached_face_planes.size()); + + return m_cached_face_planes[face]; +} diff --git a/game/shared/world/voxel_registry.cc b/game/shared/world/voxel_registry.cc index b44845e..4c2f360 100644 --- a/game/shared/world/voxel_registry.cc +++ b/game/shared/world/voxel_registry.cc @@ -2,195 +2,65 @@ #include "shared/world/voxel_registry.hh" -#include "core/math/crc64.hh" +static std::uint64_t registry_checksum = 0U; +emhash8::HashMap world::voxel_registry::names; +std::vector> world::voxel_registry::voxels; -std::unordered_map world::voxel_registry::builders = {}; -std::unordered_map world::voxel_registry::names = {}; -std::vector> world::voxel_registry::voxels = {}; - -world::VoxelInfoBuilder::VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending) -{ - prototype.name = name; - prototype.type = type; - prototype.animated = animated; - prototype.blending = blending; - - switch(type) { - case voxel_type::CUBE: - prototype.textures.resize(static_cast(voxel_face::CUBE__NR)); - break; - case voxel_type::CROSS: - prototype.textures.resize(static_cast(voxel_face::CROSS__NR)); - break; - case voxel_type::MODEL: - // Custom models should use a different texture - // resource management that is not a voxel atlas - // TODO: actually implement custom models lol - prototype.textures.resize(0); - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", name, static_cast(type)); - std::terminate(); - } - - // Physics properties - prototype.touch_type = voxel_touch::SOLID; - prototype.touch_values = glm::fvec3(0.0f, 0.0f, 0.0f); - prototype.surface = voxel_surface::DEFAULT; - - // Things set in future by item_def - prototype.item_pick = NULL_ITEM_ID; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture_default(std::string_view texture) +static void recalculate_checksum(void) { - default_texture.paths.push_back(std::string(texture)); - return *this; -} + registry_checksum = 0U; -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture(voxel_face face, std::string_view texture) -{ - const auto index = static_cast(face); - prototype.textures[index].paths.push_back(std::string(texture)); - return *this; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_touch(voxel_touch type, const glm::fvec3& values) -{ - prototype.touch_type = type; - prototype.touch_values = values; - return *this; + for(const auto& voxel : world::voxel_registry::voxels) { + registry_checksum = voxel->calculate_checksum(registry_checksum); + } } -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_surface(voxel_surface surface) +world::Voxel* world::voxel_registry::register_voxel(const Voxel& voxel_template) { - prototype.surface = surface; - return *this; -} + assert(voxel_template.get_name().size()); + assert(nullptr == find(voxel_template.get_name())); -voxel_id world::VoxelInfoBuilder::build(void) const -{ - const auto it = world::voxel_registry::names.find(prototype.name); + const auto id = static_cast(voxels.size()); - if(it != world::voxel_registry::names.cend()) { - spdlog::warn("voxel_registry: cannot build {}: name already present", prototype.name); - return it->second; - } + auto voxel = voxel_template.clone(); + voxel->set_id(id); - std::size_t state_count; - - switch(prototype.type) { - case voxel_type::CUBE: - case voxel_type::CROSS: - case voxel_type::MODEL: - state_count = 1; - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", prototype.name, static_cast(prototype.type)); - std::terminate(); - } + names.emplace(std::string(voxel_template.get_name()), id); + voxels.push_back(std::move(voxel)); - if((world::voxel_registry::voxels.size() + state_count) >= MAX_VOXEL_ID) { - spdlog::critical("voxel_registry: voxel registry overflow"); - std::terminate(); - } + recalculate_checksum(); - auto new_info = std::make_shared(); - new_info->name = prototype.name; - new_info->type = prototype.type; - new_info->animated = prototype.animated; - new_info->blending = prototype.blending; - - new_info->textures.resize(prototype.textures.size()); - - for(std::size_t i = 0; i < prototype.textures.size(); ++i) { - if(prototype.textures[i].paths.empty()) { - new_info->textures[i].paths = default_texture.paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - else { - new_info->textures[i].paths = prototype.textures[i].paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - } - - // Physics properties - new_info->touch_type = prototype.touch_type; - new_info->touch_values = prototype.touch_values; - new_info->surface = prototype.surface; - - // Things set in future by item_def - new_info->item_pick = prototype.item_pick; - - // Base voxel identifier offset - new_info->base_voxel = world::voxel_registry::voxels.size() + 1; - - for(std::size_t i = 0; i < state_count; ++i) - world::voxel_registry::voxels.push_back(new_info); - world::voxel_registry::names.insert_or_assign(new_info->name, new_info->base_voxel); - - return new_info->base_voxel; -} - -world::VoxelInfoBuilder& world::voxel_registry::construct(std::string_view name, voxel_type type, bool animated, bool blending) -{ - const auto it = world::voxel_registry::builders.find(std::string(name)); - - if(it != world::voxel_registry::builders.cend()) { - return it->second; - } - else { - return world::voxel_registry::builders.emplace(std::string(name), VoxelInfoBuilder(name, type, animated, blending)).first->second; - } + return voxels.back().get(); } -world::VoxelInfo* world::voxel_registry::find(std::string_view name) +world::Voxel* world::voxel_registry::find(std::string_view name) { - const auto it = world::voxel_registry::names.find(std::string(name)); + const auto it = names.find(name); - if(it != world::voxel_registry::names.cend()) { - return world::voxel_registry::find(it->second); - } - else { + if(it == names.end()) { return nullptr; } + + return voxels[it->second].get(); } -world::VoxelInfo* world::voxel_registry::find(const voxel_id voxel) +world::Voxel* world::voxel_registry::find(voxel_id id) { - if((voxel != NULL_VOXEL_ID) && (voxel <= world::voxel_registry::voxels.size())) { - return world::voxel_registry::voxels[voxel - 1].get(); - } - else { + if(id >= voxels.size()) { return nullptr; } + + return voxels[id].get(); } void world::voxel_registry::purge(void) { - world::voxel_registry::builders.clear(); - world::voxel_registry::names.clear(); - world::voxel_registry::voxels.clear(); + registry_checksum = 0U; + voxels.clear(); + names.clear(); } -std::uint64_t world::voxel_registry::calculate_checksum(void) +std::uint64_t world::voxel_registry::get_checksum(void) { - std::uint64_t result = 0; - - for(const std::shared_ptr& info : world::voxel_registry::voxels) { - result = math::crc64(info->name, result); - result += static_cast(info->type); - result += static_cast(info->base_voxel); - result += info->blending ? 256 : 1; - } - - return result; + return registry_checksum; } diff --git a/game/shared/world/voxel_registry.hh b/game/shared/world/voxel_registry.hh index a75f59f..b9d9a34 100644 --- a/game/shared/world/voxel_registry.hh +++ b/game/shared/world/voxel_registry.hh @@ -1,141 +1,18 @@ #pragma once -#include "shared/types.hh" - -namespace world -{ -enum class voxel_face : unsigned short { - CUBE_NORTH = 0x0000, - CUBE_SOUTH = 0x0001, - CUBE_EAST = 0x0002, - CUBE_WEST = 0x0003, - CUBE_TOP = 0x0004, - CUBE_BOTTOM = 0x0005, - CUBE__NR = 0x0006, - - CROSS_NESW = 0x0000, - CROSS_NWSE = 0x0001, - CROSS__NR = 0x0002, -}; - -enum class voxel_type : unsigned short { - CUBE = 0x0000, - CROSS = 0x0001, // TODO - MODEL = 0x0002, // TODO -}; - -enum class voxel_facing : unsigned short { - NORTH = 0x0000, - SOUTH = 0x0001, - EAST = 0x0002, - WEST = 0x0003, - UP = 0x0004, - DOWN = 0x0005, - NESW = 0x0006, - NWSE = 0x0007, -}; - -enum class voxel_touch : unsigned short { - SOLID = 0x0000, // The entity is stopped in its tracks - BOUNCE = 0x0001, // The entity bounces back with some energy loss - SINK = 0x0002, // The entity phases/sinks through the voxel - NOTHING = 0xFFFF, -}; - -enum class voxel_surface : unsigned short { - DEFAULT = 0x0000, - STONE = 0x0001, - DIRT = 0x0002, - GLASS = 0x0003, - GRASS = 0x0004, - GRAVEL = 0x0005, - METAL = 0x0006, - SAND = 0x0007, - WOOD = 0x0008, - SLOSH = 0x0009, - COUNT = 0x000A, - UNKNOWN = 0xFFFF, -}; - -using voxel_vis = unsigned short; -constexpr static voxel_vis VIS_NORTH = 1 << static_cast(voxel_facing::NORTH); -constexpr static voxel_vis VIS_SOUTH = 1 << static_cast(voxel_facing::SOUTH); -constexpr static voxel_vis VIS_EAST = 1 << static_cast(voxel_facing::EAST); -constexpr static voxel_vis VIS_WEST = 1 << static_cast(voxel_facing::WEST); -constexpr static voxel_vis VIS_UP = 1 << static_cast(voxel_facing::UP); -constexpr static voxel_vis VIS_DOWN = 1 << static_cast(voxel_facing::DOWN); -} // namespace world - -namespace world -{ -struct VoxelTexture final { - std::vector paths; - std::size_t cached_offset; // client-side only - std::size_t cached_plane; // client-side only -}; - -struct VoxelInfo final { - std::string name; - voxel_type type; - bool animated; - bool blending; - - std::vector textures; - - // Physics properties go here - // TODO: player_move friction modifiers - // that would make the voxel very sticky or - // very slippery to walk on - voxel_touch touch_type; - glm::fvec3 touch_values; - voxel_surface surface; - - // Some voxel types might occupy multiple voxel_id - // values that reference to the exact same VoxelInfo - // structure; the actual numeric state is figured out by - // subtracting base_voxel from the checking voxel_id - voxel_id base_voxel; - - // These will be set by item_registry - // and by default set to NULL_ITEM_ID - item_id item_pick; -}; -} // namespace world - -namespace world -{ -class VoxelInfoBuilder final { -public: - explicit VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending); - virtual ~VoxelInfoBuilder(void) = default; - -public: - VoxelInfoBuilder& add_texture_default(std::string_view texture); - VoxelInfoBuilder& add_texture(voxel_face face, std::string_view texture); - VoxelInfoBuilder& set_touch(voxel_touch type, const glm::fvec3& values); - VoxelInfoBuilder& set_surface(voxel_surface surface); - -public: - voxel_id build(void) const; - -private: - VoxelTexture default_texture; - VoxelInfo prototype; -}; -} // namespace world +#include "shared/world/voxel.hh" namespace world::voxel_registry { -extern std::unordered_map builders; -extern std::unordered_map names; -extern std::vector> voxels; +extern emhash8::HashMap names; +extern std::vector> voxels; } // namespace world::voxel_registry namespace world::voxel_registry { -VoxelInfoBuilder& construct(std::string_view name, voxel_type type, bool animated, bool blending); -VoxelInfo* find(std::string_view name); -VoxelInfo* find(const voxel_id voxel); +Voxel* register_voxel(const Voxel& voxel_template); +Voxel* find(std::string_view name); +Voxel* find(voxel_id id); } // namespace world::voxel_registry namespace world::voxel_registry @@ -145,5 +22,5 @@ void purge(void); namespace world::voxel_registry { -std::uint64_t calculate_checksum(void); +std::uint64_t get_checksum(void); } // namespace world::voxel_registry diff --git a/game/shared/world/voxels/generic.hh b/game/shared/world/voxels/generic.hh new file mode 100644 index 0000000..a661792 --- /dev/null +++ b/game/shared/world/voxels/generic.hh @@ -0,0 +1,27 @@ +#pragma once + +#include "shared/world/voxel.hh" + +namespace world::voxels +{ +class GenericCube final : public Voxel { +public: + template + requires(std::is_convertible_v && ...) + explicit GenericCube(std::string_view name, VoxelRender render_mode, bool animated, VoxelMaterial surface_material, VoxelTouch touch, + const glm::fvec3& touch_values, TexturesT&&... textures) noexcept + { + set_name(name); + + set_shape(VoxelShape::CUBE); + set_render_mode(render_mode); + set_animated(animated); + + set_surface_material(surface_material); + set_touch_values(touch_values); + set_touch_type(touch); + + add_texture_default(std::forward(textures)...); + } +}; +} // namespace world::voxels -- cgit