diff --git a/res/content/base/blocks/torch.json b/res/content/base/blocks/torch.json index 133f9739..47f02790 100644 --- a/res/content/base/blocks/torch.json +++ b/res/content/base/blocks/torch.json @@ -15,5 +15,21 @@ "obstacle": false, "grounded": true, "rotation": "pipe", - "material": "base:wood" + "material": "base:wood", + "particles": { + "lifetime": 2.0, + "spawn_interval": 0.3, + "acceleration": [0, 0, 0], + "velocity": [0, 0.3, 0], + "explosion": [0, 0, 0], + "size": [0.2, 0.2, 0.2], + "spawn_shape": "ball", + "spawn_spread": [0.05, 0.05, 0.05], + "lighting": false, + "frames": [ + "particles:fire_0", + "particles:smoke_0", + "particles:smoke_1" + ] + } } diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua new file mode 100644 index 00000000..5719c4fb --- /dev/null +++ b/res/content/base/scripts/world.lua @@ -0,0 +1,12 @@ +function on_block_broken(id, x, y, z, playerid) + particles.emit({x+0.5, y+0.5, z+0.5}, 64, { + lifetime=1.0, + spawn_interval=0.0001, + explosion={4, 4, 4}, + texture="blocks:"..block.get_textures(id)[1], + random_sub_uv=0.1, + size={0.1, 0.1, 0.1}, + spawn_shape="box", + spawn_spread={0.4, 0.4, 0.4} + }) +end diff --git a/res/content/base/textures/particles/fire_0.png b/res/content/base/textures/particles/fire_0.png new file mode 100644 index 00000000..b17942d3 Binary files /dev/null and b/res/content/base/textures/particles/fire_0.png differ diff --git a/res/content/base/textures/particles/smoke_0.png b/res/content/base/textures/particles/smoke_0.png new file mode 100644 index 00000000..9350bf62 Binary files /dev/null and b/res/content/base/textures/particles/smoke_0.png differ diff --git a/res/content/base/textures/particles/smoke_1.png b/res/content/base/textures/particles/smoke_1.png new file mode 100644 index 00000000..469971d1 Binary files /dev/null and b/res/content/base/textures/particles/smoke_1.png differ diff --git a/res/preload.json b/res/preload.json index 72f96c6b..8971103f 100644 --- a/res/preload.json +++ b/res/preload.json @@ -27,6 +27,7 @@ ], "atlases": [ "blocks", - "items" + "items", + "particles" ] } diff --git a/res/shaders/entity.glslf b/res/shaders/entity.glslf index b332c02f..5da4142c 100644 --- a/res/shaders/entity.glslf +++ b/res/shaders/entity.glslf @@ -16,7 +16,7 @@ void main() { float depth = (a_distance/256.0); float alpha = a_color.a * tex_color.a; // anyway it's any alpha-test alternative required - if (alpha < 0.9f) + if (alpha < 0.5f) discard; f_color = mix(a_color * tex_color, vec4(fogColor,1.0), min(1.0, pow(depth*u_fogFactor, u_fogCurve))); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 3406f6d9..f855a89d 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -22,6 +22,7 @@ #include "voxels/Block.hpp" #include "data/dv_util.hpp" #include "data/StructLayout.hpp" +#include "presets/ParticlesPreset.hpp" namespace fs = std::filesystem; using namespace data; @@ -335,6 +336,11 @@ void ContentLoader::loadBlock( perform_user_block_fields(def.name, *def.dataStruct); } + if (root.has("particles")) { + def.particles = std::make_unique(); + def.particles->deserialize(root["particles"]); + } + if (def.tickInterval == 0) { def.tickInterval = 1; } diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 6c54a83d..977a0350 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -25,7 +25,7 @@ LevelFrontend::LevelFrontend( "block-previews" ); controller->getBlocksController()->listenBlockInteraction( - [=](Player* player, glm::ivec3 pos, const Block& def, BlockInteraction type) { + [=](auto player, const auto& pos, const auto& def, BlockInteraction type) { auto material = level->content->findBlockMaterial(def.material); if (material == nullptr) { return; diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index c16dd5dd..fd3e4a3d 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -9,6 +9,7 @@ #include "graphics/ui/elements/TrackBar.hpp" #include "graphics/ui/elements/InputBindBox.hpp" #include "graphics/render/WorldRenderer.hpp" +#include "graphics/render/ParticlesRenderer.hpp" #include "logic/scripting/scripting.hpp" #include "objects/Player.hpp" #include "objects/Entities.hpp" @@ -83,6 +84,12 @@ std::shared_ptr create_debug_panel( bool culling = settings.graphics.frustumCulling.get(); return L"frustum-culling: "+std::wstring(culling ? L"on" : L"off"); })); + panel->add(create_label([=]() { + return L"particles: " + + std::to_wstring(ParticlesRenderer::visibleParticles) + + L" emitters: " + + std::to_wstring(ParticlesRenderer::aliveEmitters); + })); panel->add(create_label([=]() { return L"chunks: "+std::to_wstring(level->chunks->getChunksCount())+ L" visible: "+std::to_wstring(level->chunks->visible); diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index c0d4aa88..2dda016c 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -14,6 +14,7 @@ #include "graphics/core/PostProcessing.hpp" #include "graphics/core/Viewport.hpp" #include "graphics/render/WorldRenderer.hpp" +#include "graphics/render/Decorator.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/GUI.hpp" #include "logic/LevelController.hpp" @@ -29,20 +30,26 @@ static debug::Logger logger("level-screen"); -LevelScreen::LevelScreen(Engine* engine, std::unique_ptr level) +LevelScreen::LevelScreen(Engine* engine, std::unique_ptr levelPtr) : Screen(engine), postProcessing(std::make_unique()) { + Level* level = levelPtr.get(); + auto& settings = engine->getSettings(); auto assets = engine->getAssets(); auto menu = engine->getGUI()->getMenu(); menu->reset(); - controller = std::make_unique(engine, std::move(level)); + controller = std::make_unique(engine, std::move(levelPtr)); frontend = std::make_unique(controller->getPlayer(), controller.get(), assets); worldRenderer = std::make_unique(engine, frontend.get(), controller->getPlayer()); hud = std::make_unique(engine, frontend.get(), controller->getPlayer()); - + + decorator = std::make_unique( + *controller, *worldRenderer->particles, *assets + ); + keepAlive(settings.graphics.backlight.observe([=](bool) { controller->getLevel()->chunks->saveAndClear(); worldRenderer->clear(); @@ -66,7 +73,7 @@ void LevelScreen::initializeContent() { for (auto& entry : content->getPacks()) { initializePack(entry.second.get()); } - scripting::on_frontend_init(hud.get()); + scripting::on_frontend_init(hud.get(), worldRenderer.get()); } void LevelScreen::initializePack(ContentPackRuntime* pack) { @@ -156,6 +163,7 @@ void LevelScreen::update(float delta) { } controller->update(glm::min(delta, 0.2f), !inputLocked, hud->isPause()); hud->update(hudVisible); + decorator->update(delta, *camera); } void LevelScreen::draw(float delta) { diff --git a/src/frontend/screens/LevelScreen.hpp b/src/frontend/screens/LevelScreen.hpp index d2fe1a58..db0f04ff 100644 --- a/src/frontend/screens/LevelScreen.hpp +++ b/src/frontend/screens/LevelScreen.hpp @@ -12,6 +12,7 @@ class WorldRenderer; class TextureAnimator; class PostProcessing; class ContentPackRuntime; +class Decorator; class Level; class LevelScreen : public Screen { @@ -20,6 +21,7 @@ class LevelScreen : public Screen { std::unique_ptr worldRenderer; std::unique_ptr animator; std::unique_ptr postProcessing; + std::unique_ptr decorator; std::unique_ptr hud; void saveWorldPreview(); diff --git a/src/graphics/core/Batch3D.cpp b/src/graphics/core/Batch3D.cpp index dd6261e5..41db4294 100644 --- a/src/graphics/core/Batch3D.cpp +++ b/src/graphics/core/Batch3D.cpp @@ -118,12 +118,12 @@ void Batch3D::texture(const Texture* new_texture){ } void Batch3D::sprite( - glm::vec3 pos, - glm::vec3 up, - glm::vec3 right, + const glm::vec3& pos, + const glm::vec3& up, + const glm::vec3& right, float w, float h, const UVRegion& uv, - glm::vec4 color + const glm::vec4& color ){ const float r = color.r; const float g = color.g; @@ -175,7 +175,7 @@ inline glm::vec4 do_tint(float value) { } void Batch3D::xSprite( - float w, float h, const UVRegion& uv, const glm::vec4 tint, bool shading + float w, float h, const UVRegion& uv, const glm::vec4& tint, bool shading ) { face( glm::vec3(-w * 0.25f, 0.0f, -w * 0.25f), @@ -194,10 +194,10 @@ void Batch3D::xSprite( } void Batch3D::cube( - const glm::vec3 coord, - const glm::vec3 size, + const glm::vec3& coord, + const glm::vec3& size, const UVRegion(&texfaces)[6], - const glm::vec4 tint, + const glm::vec4& tint, bool shading ) { const glm::vec3 X(1.0f, 0.0f, 0.0f); @@ -237,22 +237,24 @@ void Batch3D::cube( } void Batch3D::blockCube( - const glm::vec3 size, + const glm::vec3& size, const UVRegion(&texfaces)[6], - const glm::vec4 tint, + const glm::vec4& tint, bool shading ) { cube((1.0f - size) * -0.5f, size, texfaces, tint, shading); } -void Batch3D::vertex(glm::vec3 coord, glm::vec2 uv, glm::vec4 tint) { +void Batch3D::vertex( + const glm::vec3& coord, const glm::vec2& uv, const glm::vec4& tint +) { if (index + B3D_VERTEX_SIZE >= capacity) { flush(); } vertex(coord, uv, tint.r, tint.g, tint.b, tint.a); } -void Batch3D::point(glm::vec3 coord, glm::vec4 tint) { +void Batch3D::point(const glm::vec3& coord, const glm::vec4& tint) { if (index + B3D_VERTEX_SIZE >= capacity) { flushPoints(); } diff --git a/src/graphics/core/Batch3D.hpp b/src/graphics/core/Batch3D.hpp index bd5f7b4e..672021fd 100644 --- a/src/graphics/core/Batch3D.hpp +++ b/src/graphics/core/Batch3D.hpp @@ -49,36 +49,36 @@ public: void begin(); void texture(const Texture* texture); void sprite( - glm::vec3 pos, - glm::vec3 up, - glm::vec3 right, + const glm::vec3& pos, + const glm::vec3& up, + const glm::vec3& right, float w, float h, const UVRegion& uv, - glm::vec4 tint + const glm::vec4& tint ); void xSprite( float w, float h, const UVRegion& uv, - const glm::vec4 tint, + const glm::vec4& tint, bool shading = true ); void cube( - const glm::vec3 coords, - const glm::vec3 size, + const glm::vec3& coords, + const glm::vec3& size, const UVRegion (&texfaces)[6], - const glm::vec4 tint, + const glm::vec4& tint, bool shading = true ); void blockCube( - const glm::vec3 size, + const glm::vec3& size, const UVRegion (&texfaces)[6], - const glm::vec4 tint, + const glm::vec4& tint, bool shading = true ); - void vertex(glm::vec3 pos, glm::vec2 uv, glm::vec4 tint); - void point(glm::vec3 pos, glm::vec4 tint); + void vertex(const glm::vec3& pos, const glm::vec2& uv, const glm::vec4& tint); + void point(const glm::vec3& pos, const glm::vec4& tint); void flush() override; void flushPoints(); }; diff --git a/src/graphics/core/Model.cpp b/src/graphics/core/Model.cpp index 3da5d897..3595a8e2 100644 --- a/src/graphics/core/Model.cpp +++ b/src/graphics/core/Model.cpp @@ -8,7 +8,12 @@ inline constexpr glm::vec3 X(1, 0, 0); inline constexpr glm::vec3 Y(0, 1, 0); inline constexpr glm::vec3 Z(0, 0, 1); -void Mesh::addPlane(glm::vec3 pos, glm::vec3 right, glm::vec3 up, glm::vec3 norm) { +void Mesh::addPlane( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm +) { vertices.push_back({pos-right-up, {0,0}, norm}); vertices.push_back({pos+right-up, {1,0}, norm}); vertices.push_back({pos+right+up, {1,1}, norm}); @@ -18,7 +23,23 @@ void Mesh::addPlane(glm::vec3 pos, glm::vec3 right, glm::vec3 up, glm::vec3 norm vertices.push_back({pos-right+up, {0,1}, norm}); } -void Mesh::addBox(glm::vec3 pos, glm::vec3 size) { +void Mesh::addPlane( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& uv +) { + vertices.push_back({pos-right-up, {uv.u1, uv.v1}, norm}); + vertices.push_back({pos+right-up, {uv.u2, uv.v1}, norm}); + vertices.push_back({pos+right+up, {uv.u2, uv.v2}, norm}); + + vertices.push_back({pos-right-up, {uv.u1, uv.v1}, norm}); + vertices.push_back({pos+right+up, {uv.u2, uv.v2}, norm}); + vertices.push_back({pos-right+up, {uv.u1, uv.u2}, norm}); +} + +void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) { addPlane(pos+Z*size, X*size, Y*size, Z); addPlane(pos-Z*size, -X*size, Y*size, -Z); @@ -29,6 +50,19 @@ void Mesh::addBox(glm::vec3 pos, glm::vec3 size) { addPlane(pos-X*size, Z*size, Y*size, -X); } +void Mesh::addBox( + const glm::vec3& pos, const glm::vec3& size, const UVRegion (&uvs)[6] +) { + addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0]); + addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1]); + + addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2]); + addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3]); + + addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4]); + addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5]); +} + void Mesh::scale(const glm::vec3& size) { for (auto& vertex : vertices) { vertex.coord *= size; diff --git a/src/graphics/core/Model.hpp b/src/graphics/core/Model.hpp index b92fb030..da295cbb 100644 --- a/src/graphics/core/Model.hpp +++ b/src/graphics/core/Model.hpp @@ -4,6 +4,8 @@ #include #include +#include "maths/UVRegion.hpp" + namespace model { struct Vertex { glm::vec3 coord; @@ -14,9 +16,27 @@ namespace model { struct Mesh { std::string texture; std::vector vertices; - - void addPlane(glm::vec3 pos, glm::vec3 right, glm::vec3 up, glm::vec3 norm); - void addBox(glm::vec3 pos, glm::vec3 size); + bool lighting = true; + + void addPlane( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm + ); + void addPlane( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& region + ); + void addBox(const glm::vec3& pos, const glm::vec3& size); + void addBox( + const glm::vec3& pos, + const glm::vec3& size, + const UVRegion (&texfaces)[6] + ); void scale(const glm::vec3& size); }; diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp new file mode 100644 index 00000000..4bac77af --- /dev/null +++ b/src/graphics/render/Decorator.cpp @@ -0,0 +1,111 @@ +#include "Decorator.hpp" + +#include "ParticlesRenderer.hpp" +#include "assets/assets_util.hpp" +#include "content/Content.hpp" +#include "voxels/Chunks.hpp" +#include "voxels/Block.hpp" +#include "world/Level.hpp" +#include "window/Camera.hpp" +#include "logic/LevelController.hpp" + +/// @brief Not greather than 64 for this BIG_PRIME value +inline constexpr int UPDATE_AREA_DIAMETER = 32; +/// @brief Number of blocks in the volume +inline constexpr int UPDATE_BLOCKS = + UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER; +/// @brief Number of update iterations +inline constexpr int ITERATIONS = 512; +/// @brief Big prime number used for pseudo-random 3d array iteration +inline constexpr int BIG_PRIME = 666667; + +Decorator::Decorator( + LevelController& controller, ParticlesRenderer& particles, const Assets& assets +) + : level(*controller.getLevel()), particles(particles), assets(assets) { + controller.getBlocksController()->listenBlockInteraction( + [this](auto player, const auto& pos, const auto& def, BlockInteraction type) { + if (type == BlockInteraction::placing && def.particles) { + addParticles(def, pos); + } + }); +} + +void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { + const auto& found = blockEmitters.find(pos); + if (found == blockEmitters.end()) { + auto treg = util::get_texture_region( + assets, def.particles->texture, "" + ); + blockEmitters[pos] = particles.add(std::make_unique( + level, + glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, + *def.particles, + treg.texture, + treg.region, + -1 + )); + } +} + +void Decorator::update( + float delta, const glm::ivec3& areaStart, const glm::ivec3& areaCenter +) { + int index = currentIndex; + currentIndex = (currentIndex + BIG_PRIME) % UPDATE_BLOCKS; + + const auto& chunks = *level.chunks; + const auto& indices = *level.content->getIndices(); + + int lx = index % UPDATE_AREA_DIAMETER; + int lz = (index / UPDATE_AREA_DIAMETER) % UPDATE_AREA_DIAMETER; + int ly = (index / UPDATE_AREA_DIAMETER / UPDATE_AREA_DIAMETER); + + auto pos = areaStart + glm::ivec3(lx, ly, lz); + + if (auto vox = chunks.get(pos)) { + const auto& def = indices.blocks.require(vox->id); + if (def.particles) { + addParticles(def, pos); + } + } +} + +void Decorator::update(float delta, const Camera& camera) { + glm::ivec3 pos = camera.position; + pos -= glm::ivec3(UPDATE_AREA_DIAMETER / 2); + for (int i = 0; i < ITERATIONS; i++) { + update(delta, pos, camera.position); + } + const auto& chunks = *level.chunks; + const auto& indices = *level.content->getIndices(); + auto iter = blockEmitters.begin(); + while (iter != blockEmitters.end()) { + auto emitter = particles.getEmitter(iter->second); + if (emitter == nullptr) { + iter = blockEmitters.erase(iter); + continue; + } + + bool remove = false; + if (auto vox = chunks.get(iter->first)) { + const auto& def = indices.blocks.require(vox->id); + if (def.particles == nullptr) { + remove = true; + } + } else { + iter = blockEmitters.erase(iter); + continue; + } + if (util::distance2(iter->first, glm::ivec3(camera.position)) > + UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER) { + remove = true; + } + if (remove) { + emitter->stop(); + iter = blockEmitters.erase(iter); + continue; + } + iter++; + } +} diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp new file mode 100644 index 00000000..32f9540a --- /dev/null +++ b/src/graphics/render/Decorator.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include + +class Level; +class Chunks; +class Camera; +class Assets; +struct Block; +class LevelController; +class ParticlesRenderer; + +class Decorator { + const Level& level; + const Assets& assets; + ParticlesRenderer& particles; + std::unordered_map blockEmitters; + int currentIndex = 0; + + void update( + float delta, const glm::ivec3& areaStart, const glm::ivec3& areaCenter + ); + void addParticles(const Block& def, const glm::ivec3& pos); +public: + Decorator( + LevelController& level, ParticlesRenderer& particles, const Assets& assets + ); + + void update(float delta, const Camera& camera); +}; diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp new file mode 100644 index 00000000..582410db --- /dev/null +++ b/src/graphics/render/Emitter.cpp @@ -0,0 +1,123 @@ +#include "Emitter.hpp" + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include "window/Camera.hpp" +#include "graphics/core/Texture.hpp" +#include "objects/Entities.hpp" +#include "world/Level.hpp" + +Emitter::Emitter( + const Level& level, + std::variant origin, + ParticlesPreset preset, + const Texture* texture, + const UVRegion& region, + int count +) + : level(level), + origin(std::move(origin)), + prototype({this, 0, glm::vec3(), preset.velocity, preset.lifetime, region}), + texture(texture), + count(count), + preset(std::move(preset)) { + this->prototype.emitter = this; + timer = preset.spawnInterval; +} + +const Texture* Emitter::getTexture() const { + return texture; +} + +static inline glm::vec3 generate_coord(ParticleSpawnShape shape) { + switch (shape) { + case ParticleSpawnShape::BALL: + return glm::ballRand(1.0f); + case ParticleSpawnShape::SPHERE: + return glm::sphericalRand(1.0f); + case ParticleSpawnShape::BOX: + return glm::linearRand(glm::vec3(-1.0f), glm::vec3(1.0f)); + default: + return {}; + } +} + +void Emitter::update( + float delta, + const glm::vec3& cameraPosition, + std::vector& particles +) { + const float spawnInterval = preset.spawnInterval; + if (count == 0 || (count == -1 && spawnInterval < FLT_EPSILON)) { + return; + } + glm::vec3 position {}; + if (auto staticPos = std::get_if(&origin)) { + position = *staticPos; + } else if (auto entityId = std::get_if(&origin)) { + if (auto entity = level.entities->get(*entityId)) { + position = entity->getTransform().pos; + } else { + stop(); + return; + } + } + const float maxDistance = preset.maxDistance; + if (glm::distance2(position, cameraPosition) > maxDistance * maxDistance) { + if (count > 0) { + if (spawnInterval < FLT_EPSILON) { + count = 0; + return; + } + int skipped = timer / spawnInterval; + count = std::max(0, count - skipped); + timer -= skipped * spawnInterval; + } + return; + } + timer += delta; + while (count && timer > spawnInterval) { + // spawn particle + Particle particle = prototype; + particle.emitter = this; + particle.random = random.rand32(); + + glm::vec3 spawnOffset = generate_coord(preset.spawnShape); + spawnOffset *= preset.spawnSpread; + + particle.position = position + spawnOffset; + particle.lifetime *= 1.0f - preset.lifetimeSpread * random.randFloat(); + particle.velocity += glm::ballRand(1.0f) * preset.explosion; + if (preset.randomSubUV < 1.0f) { + particle.region.autoSub( + preset.randomSubUV, + preset.randomSubUV, + random.randFloat(), + random.randFloat() + ); + } + particles.push_back(std::move(particle)); + timer -= spawnInterval; + if (count > 0) { + count--; + } + } +} + +void Emitter::stop() { + count = 0; +} + +bool Emitter::isDead() const { + return count == 0; +} + +const EmitterOrigin& Emitter::getOrigin() const { + return origin; +} + +void Emitter::setOrigin(const EmitterOrigin& origin) { + this->origin = origin; +} diff --git a/src/graphics/render/Emitter.hpp b/src/graphics/render/Emitter.hpp new file mode 100644 index 00000000..c1d6c168 --- /dev/null +++ b/src/graphics/render/Emitter.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include "typedefs.hpp" + +#include "maths/UVRegion.hpp" +#include "maths/util.hpp" +#include "presets/ParticlesPreset.hpp" + +class Level; +class Emitter; + +struct Particle { + /// @brief Pointer used to access common behaviour. + /// Emitter must be utilized after all related particles despawn. + Emitter* emitter; + /// @brief Some random integer for visuals configuration. + int random; + /// @brief Global position + glm::vec3 position; + /// @brief Linear velocity + glm::vec3 velocity; + /// @brief Remaining life time + float lifetime; + /// @brief UV region + UVRegion region; +}; + +class Texture; + +using EmitterOrigin = std::variant; + +class Emitter { + const Level& level; + /// @brief Static position or entity + EmitterOrigin origin; + /// @brief Particle prototype + Particle prototype; + /// @brief Particle + const Texture* texture; + /// @brief Number of particles should be spawned before emitter deactivation. + /// -1 is infinite. + int count; + /// @brief Spawn timer used to determine number of particles + /// to spawn on update. May be innacurate. + float timer = 0.0f; + + util::PseudoRandom random; +public: + ParticlesPreset preset; + + Emitter( + const Level& level, + std::variant origin, + ParticlesPreset preset, + const Texture* texture, + const UVRegion& region, + int count + ); + + explicit Emitter(const Emitter&) = delete; + + /// @return Emitter particles texture + const Texture* getTexture() const; + + /// @brief Update emitter and spawn particles + /// @param delta delta time + /// @param cameraPosition current camera global position + /// @param particles destination particles vector + void update( + float delta, + const glm::vec3& cameraPosition, + std::vector& particles + ); + + /// @brief Set remaining particles count to 0 + void stop(); + + /// @return true if the emitter has spawned all particles + bool isDead() const; + + const EmitterOrigin& getOrigin() const; + + void setOrigin(const EmitterOrigin& origin); +}; diff --git a/src/graphics/render/MainBatch.cpp b/src/graphics/render/MainBatch.cpp new file mode 100644 index 00000000..1cbb6c42 --- /dev/null +++ b/src/graphics/render/MainBatch.cpp @@ -0,0 +1,135 @@ +#include "MainBatch.hpp" + +#include "graphics/core/Texture.hpp" +#include "graphics/core/Mesh.hpp" +#include "graphics/core/ImageData.hpp" +#include "voxels/Chunks.hpp" +#include "voxels/Chunk.hpp" + +static const vattr attrs[] = { + {3}, {2}, {3}, {1}, {0} +}; + +MainBatch::MainBatch(size_t capacity) + : buffer(std::make_unique(capacity * VERTEX_SIZE)), + capacity(capacity), + index(0), + mesh(std::make_unique(buffer.get(), 0, attrs)) { + + const ubyte pixels[] = { + 255, 255, 255, 255, + }; + ImageData image(ImageFormat::rgba8888, 1, 1, pixels); + blank = Texture::from(&image); +} + +MainBatch::~MainBatch() = default; + +void MainBatch::setTexture(const Texture* texture) { + if (texture == nullptr) { + texture = blank.get(); + } + if (texture != this->texture) { + flush(); + } + this->texture = texture; + region = UVRegion {0.0f, 0.0f, 1.0f, 1.0f}; +} + +void MainBatch::setTexture(const Texture* texture, const UVRegion& region) { + setTexture(texture); + this->region = region; +} + +void MainBatch::flush() { + if (index == 0) { + return; + } + if (texture == nullptr) { + texture = blank.get(); + } + texture->bind(); + mesh->reload(buffer.get(), index / VERTEX_SIZE); + mesh->draw(); + index = 0; +} + +void MainBatch::begin() { + texture = nullptr; + blank->bind(); +} + +void MainBatch::prepare(int vertices) { + if (index + VERTEX_SIZE * vertices > capacity * VERTEX_SIZE) { + flush(); + } +} + +glm::vec4 MainBatch::sampleLight( + const glm::vec3& pos, const Chunks& chunks, bool backlight +) { + light_t light = chunks.getLight( + std::floor(pos.x), + std::floor(std::min(CHUNK_H-1.0f, pos.y)), + std::floor(pos.z)); + light_t minIntensity = backlight ? 1 : 0; + return glm::vec4( + glm::max(Lightmap::extract(light, 0), minIntensity) / 15.0f, + glm::max(Lightmap::extract(light, 1), minIntensity) / 15.0f, + glm::max(Lightmap::extract(light, 2), minIntensity) / 15.0f, + glm::max(Lightmap::extract(light, 3), minIntensity) / 15.0f + ); +} + +inline glm::vec4 do_tint(float value) { + return glm::vec4(value, value, value, 1.0f); +} + +void MainBatch::cube( + const glm::vec3& coord, + const glm::vec3& size, + const UVRegion(&texfaces)[6], + const glm::vec4& tint, + bool shading +) { + const glm::vec3 X(1.0f, 0.0f, 0.0f); + const glm::vec3 Y(0.0f, 1.0f, 0.0f); + const glm::vec3 Z(0.0f, 0.0f, 1.0f); + + quad( + coord + glm::vec3(0.0f, 0.0f, 0.0f), + X, Y, glm::vec2(size.x, size.y), + (shading ? do_tint(0.8) * tint : tint), + glm::vec3(1.0f), texfaces[5] + ); + quad( + coord + glm::vec3(size.x, 0.0f, -size.z), + -X, Y, glm::vec2(size.x, size.y), + (shading ? do_tint(0.8) * tint : tint), + glm::vec3(1.0f), texfaces[4] + ); + quad( + coord + glm::vec3(0.0f, size.y, 0.0f), + X, -Z, glm::vec2(size.x, size.z), + (shading ? do_tint(1.0f) * tint : tint), + glm::vec3(1.0f), texfaces[3] + ); + quad( + coord + glm::vec3(0.0f, 0.0f, -size.z), + X, Z, glm::vec2(size.x, size.z), + (shading ? do_tint(0.7f) * tint : tint), + glm::vec3(1.0f), texfaces[2] + ); + quad( + coord + glm::vec3(0.0f, 0.0f, -size.z), + Z, Y, glm::vec2(size.z, size.y), + (shading ? do_tint(0.9f) * tint : tint), + glm::vec3(1.0f), texfaces[0] + ); + quad( + coord + glm::vec3(size.x, 0.0f, 0.0f), + -Z, Y, glm::vec2(size.z, size.y), + (shading ? do_tint(0.9f) * tint : tint), + glm::vec3(1.0f), texfaces[1] + ); +} diff --git a/src/graphics/render/MainBatch.hpp b/src/graphics/render/MainBatch.hpp new file mode 100644 index 00000000..36bf7a8e --- /dev/null +++ b/src/graphics/render/MainBatch.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include + +#include "typedefs.hpp" +#include "maths/UVRegion.hpp" + +class Mesh; +class Texture; +class Chunks; + +class MainBatch { + std::unique_ptr const buffer; + size_t const capacity; + size_t index; + + UVRegion region {0.0f, 0.0f, 1.0f, 1.0f}; + + std::unique_ptr mesh; + std::unique_ptr blank; + + const Texture* texture = nullptr; +public: + /// xyz, uv, color, compressed lights + static inline constexpr uint VERTEX_SIZE = 9; + + MainBatch(size_t capacity); + + ~MainBatch(); + + void begin(); + + void prepare(int vertices); + void setTexture(const Texture* texture); + void setTexture(const Texture* texture, const UVRegion& region); + void flush(); + + static glm::vec4 sampleLight( + const glm::vec3& pos, const Chunks& chunks, bool backlight + ); + + inline void vertex( + const glm::vec3& pos, + const glm::vec2& uv, + const glm::vec4& light, + const glm::vec3& tint + ) { + float* buffer = this->buffer.get(); + buffer[index++] = pos.x; + buffer[index++] = pos.y; + buffer[index++] = pos.z; + buffer[index++] = uv.x * region.getWidth() + region.u1; + buffer[index++] = uv.y * region.getHeight() + region.v1; + buffer[index++] = tint.x; + buffer[index++] = tint.y; + buffer[index++] = tint.z; + + union { + float floating; + uint32_t integer; + } compressed; + + compressed.integer = (static_cast(light.r * 255) & 0xff) << 24; + compressed.integer |= (static_cast(light.g * 255) & 0xff) << 16; + compressed.integer |= (static_cast(light.b * 255) & 0xff) << 8; + compressed.integer |= (static_cast(light.a * 255) & 0xff); + + buffer[index++] = compressed.floating; + } + + inline void quad( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec2& size, + const glm::vec4& light, + const glm::vec3& tint, + const UVRegion& subregion + ) { + prepare(6); + vertex( + pos - right * size.x * 0.5f - up * size.y * 0.5f, + {subregion.u1, subregion.v1}, + light, + tint + ); + vertex( + pos + right * size.x * 0.5f - up * size.y * 0.5f, + {subregion.u2, subregion.v1}, + light, + tint + ); + vertex( + pos + right * size.x * 0.5f + up * size.y * 0.5f, + {subregion.u2, subregion.v2}, + light, + tint + ); + + vertex( + pos - right * size.x * 0.5f - up * size.y * 0.5f, + {subregion.u1, subregion.v1}, + light, + tint + ); + vertex( + pos + right * size.x * 0.5f + up * size.y * 0.5f, + {subregion.u2, subregion.v2}, + light, + tint + ); + vertex( + pos - right * size.x * 0.5f + up * size.y * 0.5f, + {subregion.u1, subregion.v2}, + light, + tint + ); + } + + void cube( + const glm::vec3& coord, + const glm::vec3& size, + const UVRegion(&texfaces)[6], + const glm::vec4& tint, + bool shading + ); +}; diff --git a/src/graphics/render/ModelBatch.cpp b/src/graphics/render/ModelBatch.cpp index f6a1b58b..42ceb921 100644 --- a/src/graphics/render/ModelBatch.cpp +++ b/src/graphics/render/ModelBatch.cpp @@ -10,6 +10,7 @@ #include "voxels/Chunks.hpp" #include "lighting/Lightmap.hpp" #include "settings.hpp" +#include "MainBatch.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -18,13 +19,6 @@ #include -/// xyz, uv, color, compressed lights -inline constexpr uint VERTEX_SIZE = 9; - -static const vattr attrs[] = { - {3}, {2}, {3}, {1}, {0} -}; - inline constexpr glm::vec3 X(1, 0, 0); inline constexpr glm::vec3 Y(0, 1, 0); inline constexpr glm::vec3 Z(0, 0, 1); @@ -57,18 +51,10 @@ ModelBatch::ModelBatch( Chunks* chunks, const EngineSettings* settings ) - : buffer(std::make_unique(capacity * VERTEX_SIZE)), - capacity(capacity), - index(0), - mesh(std::make_unique(buffer.get(), 0, attrs)), + : batch(std::make_unique(capacity)), assets(assets), chunks(chunks), settings(settings) { - const ubyte pixels[] = { - 255, 255, 255, 255, - }; - ImageData image(ImageFormat::rgba8888, 1, 1, pixels); - blank = Texture::from(&image); } ModelBatch::~ModelBatch() = default; @@ -77,32 +63,29 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix, const glm::mat3& rotation, glm::vec3 tint, const texture_names_map* varTextures, bool backlight) { - glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - gpos += lightsOffset; - light_t light = chunks->getLight( - std::floor(gpos.x), - std::floor(std::min(CHUNK_H-1.0f, gpos.y)), - std::floor(gpos.z)); - light_t minIntensity = backlight ? 1 : 0; - glm::vec4 lights( - glm::max(Lightmap::extract(light, 0), minIntensity) / 15.0f, - glm::max(Lightmap::extract(light, 1), minIntensity) / 15.0f, - glm::max(Lightmap::extract(light, 2), minIntensity) / 15.0f, - glm::max(Lightmap::extract(light, 3), minIntensity) / 15.0f - ); + + setTexture(mesh.texture, varTextures); size_t vcount = mesh.vertices.size(); const auto& vertexData = mesh.vertices.data(); + + glm::vec4 lights(1, 1, 1, 0); + if (mesh.lighting) { + glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + gpos += lightsOffset; + lights = MainBatch::sampleLight(gpos, *chunks, backlight); + } for (size_t i = 0; i < vcount / 3; i++) { - if (index + VERTEX_SIZE * 3 > capacity * VERTEX_SIZE) { - flush(); - } + batch->prepare(3); for (size_t j = 0; j < 3; j++) { const auto vert = vertexData[i * 3 + j]; - auto norm = rotation * vert.normal; - float d = glm::dot(norm, SUN_VECTOR); - d = 0.8f + d * 0.2f; - vertex(matrix * glm::vec4(vert.coord, 1.0f), vert.uv, lights*d, tint); + float d = 1.0f; + if (mesh.lighting) { + auto norm = rotation * vert.normal; + d = glm::dot(norm, SUN_VECTOR); + d = 0.8f + d * 0.2f; + } + batch->vertex(matrix * glm::vec4(vert.coord, 1.0f), vert.uv, lights*d, tint); } } } @@ -135,7 +118,7 @@ void ModelBatch::render() { backlight ); } - flush(); + batch->flush(); entries.clear(); } @@ -148,37 +131,11 @@ void ModelBatch::setTexture(const std::string& name, if (varTextures && name.at(0) == '$') { const auto& found = varTextures->find(name); if (found == varTextures->end()) { - return setTexture(nullptr); + return batch->setTexture(nullptr); } else { return setTexture(found->second, varTextures); } } - - auto textureRegion = util::get_texture_region(*assets, name, "blocks:notfound"); - setTexture(textureRegion.texture); - region = textureRegion.region; -} - -void ModelBatch::setTexture(const Texture* texture) { - if (texture == nullptr) { - texture = blank.get(); - } - if (texture != this->texture) { - flush(); - } - this->texture = texture; - region = UVRegion {0.0f, 0.0f, 1.0f, 1.0f}; -} - -void ModelBatch::flush() { - if (index == 0) { - return; - } - if (texture == nullptr) { - texture = blank.get(); - } - texture->bind(); - mesh->reload(buffer.get(), index / VERTEX_SIZE); - mesh->draw(); - index = 0; + auto region = util::get_texture_region(*assets, name, "blocks:notfound"); + batch->setTexture(region.texture, region.region); } diff --git a/src/graphics/render/ModelBatch.hpp b/src/graphics/render/ModelBatch.hpp index 523eb300..5781baa1 100644 --- a/src/graphics/render/ModelBatch.hpp +++ b/src/graphics/render/ModelBatch.hpp @@ -13,6 +13,7 @@ class Texture; class Chunks; class Assets; struct EngineSettings; +class MainBatch; namespace model { struct Mesh; @@ -22,47 +23,15 @@ namespace model { using texture_names_map = std::unordered_map; class ModelBatch { - std::unique_ptr const buffer; - size_t const capacity; - size_t index; - - std::unique_ptr mesh; - std::unique_ptr blank; - Assets* assets; Chunks* chunks; - const Texture* texture = nullptr; - UVRegion region {0.0f, 0.0f, 1.0f, 1.0f}; + const EngineSettings* settings; glm::vec3 lightsOffset {}; static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f}; - inline void vertex( - glm::vec3 pos, glm::vec2 uv, glm::vec4 light, glm::vec3 tint - ) { - float* buffer = this->buffer.get(); - buffer[index++] = pos.x; - buffer[index++] = pos.y; - buffer[index++] = pos.z; - buffer[index++] = uv.x * region.getWidth() + region.u1; - buffer[index++] = uv.y * region.getHeight() + region.v1; - buffer[index++] = tint.x; - buffer[index++] = tint.y; - buffer[index++] = tint.z; - - union { - float floating; - uint32_t integer; - } compressed; - - compressed.integer = (static_cast(light.r * 255) & 0xff) << 24; - compressed.integer |= (static_cast(light.g * 255) & 0xff) << 16; - compressed.integer |= (static_cast(light.b * 255) & 0xff) << 8; - compressed.integer |= (static_cast(light.a * 255) & 0xff); - - buffer[index++] = compressed.floating; - } + std::unique_ptr batch; void draw(const model::Mesh& mesh, const glm::mat4& matrix, @@ -72,8 +41,6 @@ class ModelBatch { bool backlight); void setTexture(const std::string& name, const texture_names_map* varTextures); - void setTexture(const Texture* texture); - void flush(); struct DrawEntry { glm::mat4 matrix; diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index edec061c..705cabd3 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -1,6 +1,7 @@ #include "ModelsGenerator.hpp" #include "assets/Assets.hpp" +#include "assets/assets_util.hpp" #include "items/ItemDef.hpp" #include "voxels/Block.hpp" #include "content/Content.hpp" @@ -40,6 +41,13 @@ static model::Model create_flat_model( return model; } +static inline UVRegion get_region_for( + const std::string& texture, const Assets& assets +) { + auto texreg = util::get_texture_region(assets, "blocks:" + texture, ""); + return texreg.region; +} + model::Model ModelsGenerator::generate( const ItemDef& def, const Content& content, const Assets& assets ) { @@ -50,8 +58,63 @@ model::Model ModelsGenerator::generate( return create_flat_model( "blocks:" + blockDef.textureFaces.at(0), assets ); + } else if (blockDef.model == BlockModel::custom) { + model = model::Model(); + for (size_t i = 0; i < blockDef.modelBoxes.size(); i++) { + auto& mesh = + model.addMesh("blocks:" + blockDef.modelTextures[i * 6]); + mesh.lighting = !blockDef.shadeless; + const UVRegion (&boxtexfaces)[6] = { + get_region_for(blockDef.modelTextures[i * 6], assets), + get_region_for(blockDef.modelTextures[i * 6 + 1], assets), + get_region_for(blockDef.modelTextures[i * 6 + 2], assets), + get_region_for(blockDef.modelTextures[i * 6 + 3], assets), + get_region_for(blockDef.modelTextures[i * 6 + 4], assets), + get_region_for(blockDef.modelTextures[i * 6 + 5], assets) + }; + mesh.addBox( + blockDef.modelBoxes[i].center(), + blockDef.modelBoxes[i].size()*0.5f, boxtexfaces + ); + } + const auto& points = blockDef.modelExtraPoints; + glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 norm {0, 1, 0}; + for (size_t i = 0; i < blockDef.modelExtraPoints.size() / 4; i++) { + auto texture = + "blocks:" + + blockDef.modelTextures[blockDef.modelBoxes.size() * 6 + i]; + + auto& mesh = model.addMesh(texture); + mesh.lighting = !blockDef.shadeless; + + auto reg = get_region_for(texture, assets); + mesh.vertices.push_back( + {points[i * 4 + 0] - poff, glm::vec2(reg.u1, reg.v1), norm} + ); + mesh.vertices.push_back( + {points[i * 4 + 1] - poff, glm::vec2(reg.u2, reg.v1), norm} + ); + mesh.vertices.push_back( + {points[i * 4 + 2] - poff, glm::vec2(reg.u2, reg.v2), norm} + ); + mesh.vertices.push_back( + {points[i * 4 + 3] - poff, glm::vec2(reg.u1, reg.v1), norm} + ); + mesh.vertices.push_back( + {points[i * 4 + 4] - poff, glm::vec2(reg.u2, reg.v2), norm} + ); + mesh.vertices.push_back( + {points[i * 4 + 0] - poff, glm::vec2(reg.u1, reg.v2), norm} + ); + } + for (auto& mesh : model.meshes) { + mesh.scale(glm::vec3(0.3f)); + } + return model; } for (auto& mesh : model.meshes) { + mesh.lighting = !blockDef.shadeless; switch (blockDef.model) { case BlockModel::aabb: { glm::vec3 size = blockDef.hitboxes.at(0).size(); diff --git a/src/graphics/render/ParticlesRenderer.cpp b/src/graphics/render/ParticlesRenderer.cpp new file mode 100644 index 00000000..0e030dbc --- /dev/null +++ b/src/graphics/render/ParticlesRenderer.cpp @@ -0,0 +1,173 @@ +#include "ParticlesRenderer.hpp" + +#include + +#include "assets/Assets.hpp" +#include "assets/assets_util.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "graphics/render/MainBatch.hpp" +#include "window/Camera.hpp" +#include "world/Level.hpp" +#include "voxels/Chunks.hpp" +#include "settings.hpp" + +size_t ParticlesRenderer::visibleParticles = 0; +size_t ParticlesRenderer::aliveEmitters = 0; + +ParticlesRenderer::ParticlesRenderer( + const Assets& assets, const Level& level, const GraphicsSettings* settings +) + : batch(std::make_unique(1024)), + level(level), + assets(assets), + settings(settings) {} + +ParticlesRenderer::~ParticlesRenderer() = default; + +static inline void update_particle( + Particle& particle, float delta, const Chunks& chunks +) { + const auto& preset = particle.emitter->preset; + auto& pos = particle.position; + auto& vel = particle.velocity; + + vel += delta * preset.acceleration; + if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) { + vel *= 0.0f; + } + pos += vel * delta; + particle.lifetime -= delta; +} + +void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { + const auto& right = camera.right; + const auto& up = camera.up; + + const auto& chunks = *level.chunks; + bool backlight = settings->backlight.get(); + + std::vector unusedTextures; + + for (auto& [texture, vec] : particles) { + if (vec.empty()) { + unusedTextures.push_back(texture); + continue; + } + batch->setTexture(texture); + + visibleParticles += vec.size(); + + auto iter = vec.begin(); + while (iter != vec.end()) { + auto& particle = *iter; + auto& preset = particle.emitter->preset; + + if (!preset.frames.empty()) { + float time = preset.lifetime - particle.lifetime; + int framesCount = preset.frames.size(); + int frameid = time / preset.lifetime * framesCount; + int frameid2 = glm::min( + (time + delta) / preset.lifetime * framesCount, + framesCount - 1.0f + ); + if (frameid2 != frameid) { + auto tregion = util::get_texture_region( + assets, preset.frames.at(frameid2), "" + ); + if (tregion.texture == texture) { + particle.region = tregion.region; + } + } + } + update_particle(particle, delta, chunks); + + glm::vec4 light(1, 1, 1, 0); + if (preset.lighting) { + light = MainBatch::sampleLight( + particle.position, chunks, backlight + ); + light *= 0.9f + (particle.random % 100) * 0.001f; + } + batch->quad( + particle.position, + right, + preset.globalUpVector ? glm::vec3(0, 1, 0) : up, + preset.size, + light, + glm::vec3(1.0f), + particle.region + ); + if (particle.lifetime <= 0.0f) { + iter = vec.erase(iter); + } else { + iter++; + } + } + } + batch->flush(); + for (const auto& texture : unusedTextures) { + particles.erase(texture); + } +} + +void ParticlesRenderer::render(const Camera& camera, float delta) { + batch->begin(); + + aliveEmitters = emitters.size(); + visibleParticles = 0; + + renderParticles(camera, delta); + + auto iter = emitters.begin(); + while (iter != emitters.end()) { + auto& emitter = *iter->second; + auto texture = emitter.getTexture(); + const auto& found = particles.find(texture); + std::vector* vec; + if (found == particles.end()) { + if (emitter.isDead()) { + // destruct Emitter only when there is no particles spawned by it + iter = emitters.erase(iter); + continue; + } + vec = &particles[texture]; + } else { + vec = &found->second; + } + emitter.update(delta, camera.position, *vec); + iter++; + } +} + +void ParticlesRenderer::gc() { + std::set usedEmitters; + for (const auto& [_, vec] : particles) { + for (const auto& particle : vec) { + usedEmitters.insert(particle.emitter); + } + } + auto iter = emitters.begin(); + while (iter != emitters.end()) { + auto emitter = iter->second.get(); + if (usedEmitters.find(emitter) == usedEmitters.end()) { + iter = emitters.erase(iter); + } else { + iter++; + } + } +} + +Emitter* ParticlesRenderer::getEmitter(u64id_t id) const { + const auto& found = emitters.find(id); + if (found == emitters.end()) { + return nullptr; + } + return found->second.get(); +} + +u64id_t ParticlesRenderer::add(std::unique_ptr emitter) { + u64id_t uid = nextEmitter++; + emitters[uid] = std::move(emitter); + return uid; +} diff --git a/src/graphics/render/ParticlesRenderer.hpp b/src/graphics/render/ParticlesRenderer.hpp new file mode 100644 index 00000000..46509d96 --- /dev/null +++ b/src/graphics/render/ParticlesRenderer.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "Emitter.hpp" +#include "typedefs.hpp" + +class Texture; +class Assets; +class Camera; +class MainBatch; +class Level; +struct GraphicsSettings; + +class ParticlesRenderer { + const Level& level; + const Assets& assets; + const GraphicsSettings* settings; + std::unordered_map> particles; + std::unique_ptr batch; + + std::unordered_map> emitters; + u64id_t nextEmitter = 1; + + void renderParticles(const Camera& camera, float delta); +public: + ParticlesRenderer( + const Assets& assets, + const Level& level, + const GraphicsSettings* settings + ); + ~ParticlesRenderer(); + + void render(const Camera& camera, float delta); + + u64id_t add(std::unique_ptr emitter); + + /// @brief Perform garbage collection (remove extra dead emitters). + /// @note Emitters are deleting without GC when there's no particles with same + /// texture left. + /// @note Currently unused + void gc(); + + /// @brief Get emitter by UID + /// @return Emitter or nullptr + Emitter* getEmitter(u64id_t id) const; + + static size_t visibleParticles; + static size_t aliveEmitters; +}; diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index 22927b3c..b1975187 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -138,6 +138,7 @@ void Skybox::draw( } void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) { + frameid++; float dayTime = t; DrawContext ctx = pctx.sub(); ctx.setDepthMask(false); @@ -152,7 +153,31 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) glActiveTexture(GL_TEXTURE1); cubemap->bind(); shader->use(); + t *= M_PI*2.0f; + + lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); + shader->uniform1i("u_quality", quality); + shader->uniform1f("u_mie", mie); + shader->uniform1f("u_fog", mie - 1.0f); + shader->uniform3f("u_lightDir", lightDir); + shader->uniform1f("u_dayTime", dayTime); + if (glm::abs(mie-prevMie) + glm::abs(t-prevT) >= 0.01) { + for (uint face = 0; face < 6; face++) { + refreshFace(face, cubemap); + } + } else { + uint face = frameid % 6; + refreshFace(face, cubemap); + } + prevMie = mie; + prevT = t; + + cubemap->unbind(); + glActiveTexture(GL_TEXTURE0); +} + +void Skybox::refreshFace(uint face, Cubemap* cubemap) { const glm::vec3 xaxs[] = { {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, @@ -181,23 +206,11 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, }; - t *= M_PI*2.0f; - - lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); - shader->uniform1i("u_quality", quality); - shader->uniform1f("u_mie", mie); - shader->uniform1f("u_fog", mie - 1.0f); - shader->uniform3f("u_lightDir", lightDir); - shader->uniform1f("u_dayTime", dayTime); - for (uint face = 0; face < 6; face++) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap->getId(), 0); - shader->uniform3f("u_xaxis", xaxs[face]); - shader->uniform3f("u_yaxis", yaxs[face]); - shader->uniform3f("u_zaxis", zaxs[face]); - mesh->draw(); - } - cubemap->unbind(); - glActiveTexture(GL_TEXTURE0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap->getId(), 0); + shader->uniform3f("u_xaxis", xaxs[face]); + shader->uniform3f("u_yaxis", yaxs[face]); + shader->uniform3f("u_zaxis", zaxs[face]); + mesh->draw(); } void Skybox::bind() const { diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index 2781a95a..902d8fe2 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -12,6 +12,8 @@ class Shader; class Assets; class Camera; class Batch3D; +class Shader; +class Cubemap; class Framebuffer; class DrawContext; @@ -33,11 +35,16 @@ class Skybox { std::unique_ptr mesh; std::unique_ptr batch3d; std::vector sprites; + int frameid = 0; + + float prevMie = -1.0f; + float prevT = -1.0f; void drawStars(float angle, float opacity); void drawBackground( const Camera& camera, const Assets& assets, int width, int height ); + void refreshFace(uint face, Cubemap* cubemap); public: Skybox(uint size, Shader* shader); ~Skybox(); diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 4b13a23f..89b5474b 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -40,9 +40,11 @@ #include "graphics/core/PostProcessing.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" +#include "ParticlesRenderer.hpp" #include "ChunksRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" +#include "Emitter.hpp" bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showEntitiesDebug = false; @@ -56,8 +58,15 @@ WorldRenderer::WorldRenderer( frustumCulling(std::make_unique()), lineBatch(std::make_unique()), modelBatch(std::make_unique( - 20'000, engine->getAssets(), level->chunks.get(), + 20'000, + engine->getAssets(), + level->chunks.get(), &engine->getSettings() + )), + particles(std::make_unique( + *engine->getAssets(), + *frontend->getLevel(), + &engine->getSettings().graphics )) { renderer = std::make_unique( level, frontend->getContentGfxCache(), &engine->getSettings() @@ -189,7 +198,7 @@ void WorldRenderer::setupWorldShader( } void WorldRenderer::renderLevel( - const DrawContext&, + const DrawContext& ctx, const Camera& camera, const EngineSettings& settings, float delta, @@ -198,7 +207,8 @@ void WorldRenderer::renderLevel( auto assets = engine->getAssets(); bool culling = engine->getSettings().graphics.frustumCulling.get(); - float fogFactor = 15.0f / ((float)settings.chunks.loadDistance.get() - 2); + float fogFactor = + 15.0f / static_cast(settings.chunks.loadDistance.get() - 2); auto entityShader = assets->get("entity"); setupWorldShader(entityShader, camera, settings, fogFactor); @@ -211,6 +221,7 @@ void WorldRenderer::renderLevel( delta, pause ); + particles->render(camera, delta * !pause); modelBatch->render(); auto shader = assets->get("main"); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index c5d7040e..22e864c5 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -8,12 +8,15 @@ #include +#include "typedefs.hpp" + class Level; class Player; class Camera; class Batch3D; class LineBatch; class ChunksRenderer; +class ParticlesRenderer; class Shader; class Frustum; class Engine; @@ -24,6 +27,7 @@ class PostProcessing; class DrawContext; class ModelBatch; class Assets; +class Emitter; struct EngineSettings; namespace model { @@ -40,6 +44,7 @@ class WorldRenderer { std::unique_ptr skybox; std::unique_ptr batch3d; std::unique_ptr modelBatch; + float timer = 0.0f; bool drawChunk(size_t index, const Camera& camera, Shader* shader, bool culling); @@ -76,6 +81,8 @@ class WorldRenderer { float fogFactor ); public: + std::unique_ptr particles; + static bool showChunkBorders; static bool showEntitiesDebug; diff --git a/src/logic/BlocksController.hpp b/src/logic/BlocksController.hpp index 19a270f0..86bbe395 100644 --- a/src/logic/BlocksController.hpp +++ b/src/logic/BlocksController.hpp @@ -20,7 +20,7 @@ enum class BlockInteraction { step, destruction, placing }; /// @brief Player argument is nullable using on_block_interaction = std::function< - void(Player*, glm::ivec3, const Block&, BlockInteraction type)>; + void(Player*, const glm::ivec3&, const Block&, BlockInteraction type)>; /// BlocksController manages block updates and data (inventories, metadata) class BlocksController { diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index f9c8b3f8..a6edc434 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -31,6 +31,7 @@ extern const luaL_Reg itemlib[]; extern const luaL_Reg jsonlib[]; extern const luaL_Reg mat4lib[]; extern const luaL_Reg packlib[]; +extern const luaL_Reg particleslib[]; extern const luaL_Reg playerlib[]; extern const luaL_Reg quatlib[]; // quat.cpp extern const luaL_Reg timelib[]; diff --git a/src/logic/scripting/lua/libs/libparticles.cpp b/src/logic/scripting/lua/libs/libparticles.cpp new file mode 100644 index 00000000..ae2ed6d3 --- /dev/null +++ b/src/logic/scripting/lua/libs/libparticles.cpp @@ -0,0 +1,90 @@ +#include "api_lua.hpp" + +#include "logic/scripting/scripting_hud.hpp" +#include "graphics/render/WorldRenderer.hpp" +#include "graphics/render/ParticlesRenderer.hpp" +#include "graphics/render/Emitter.hpp" +#include "assets/assets_util.hpp" +#include "engine.hpp" + +using namespace scripting; + +static int l_emit(lua::State* L) { + EmitterOrigin origin; + if (lua::istable(L, 1)) { + origin = lua::tovec3(L, 1); + } else { + origin = static_cast(lua::tointeger(L, 1)); + } + int count = lua::tointeger(L, 2); + auto preset = lua::tovalue(L, 3); + auto extension = lua::tovalue(L, 4); + + ParticlesPreset particlesPreset {}; + particlesPreset.deserialize(preset); + if (extension != nullptr) { + particlesPreset.deserialize(extension); + } + auto& assets = *engine->getAssets(); + auto region = util::get_texture_region(assets, particlesPreset.texture, ""); + auto emitter = std::make_unique( + *level, + std::move(origin), + std::move(particlesPreset), + region.texture, + region.region, + count + ); + return lua::pushinteger(L, renderer->particles->add(std::move(emitter))); +} + +static int l_stop(lua::State* L) { + u64id_t id = lua::touinteger(L, 1); + if (auto emitter = renderer->particles->getEmitter(id)) { + emitter->stop(); + } + return 0; +} + +static int l_get_origin(lua::State* L) { + u64id_t id = lua::touinteger(L, 1); + if (auto emitter = renderer->particles->getEmitter(id)) { + const auto& origin = emitter->getOrigin(); + if (auto pos = std::get_if(&origin)) { + return lua::pushvec3(L, *pos); + } else if (auto entityid = std::get_if(&origin)) { + return lua::pushinteger(L, *entityid); + } + } + return 0; +} + +static int l_set_origin(lua::State* L) { + u64id_t id = lua::touinteger(L, 1); + if (auto emitter = renderer->particles->getEmitter(id)) { + EmitterOrigin origin; + if (lua::istable(L, 2)) { + emitter->setOrigin(lua::tovec3(L, 2)); + } else { + emitter->setOrigin(static_cast(lua::tointeger(L, 2))); + } + } + return 0; +} + +static int l_is_alive(lua::State* L) { + u64id_t id = lua::touinteger(L, 1); + if (auto emitter = renderer->particles->getEmitter(id)) { + return lua::pushboolean(L, !emitter->isDead()); + } + return lua::pushboolean(L, false); +} + +const luaL_Reg particleslib[] = { + {"emit", lua::wrap}, + {"stop", lua::wrap}, + {"is_alive", lua::wrap}, + {"get_origin", lua::wrap}, + {"set_origin", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index a8856837..f065a692 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -4,6 +4,7 @@ #include "engine.hpp" #include "files/files.hpp" #include "frontend/hud.hpp" +#include "graphics/render/WorldRenderer.hpp" #include "objects/Player.hpp" #include "lua/libs/api_lua.hpp" #include "lua/lua_engine.hpp" @@ -14,10 +15,14 @@ using namespace scripting; static debug::Logger logger("scripting-hud"); Hud* scripting::hud = nullptr; +WorldRenderer* scripting::renderer = nullptr; -void scripting::on_frontend_init(Hud* hud) { +void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) { scripting::hud = hud; + scripting::renderer = renderer; + lua::openlib(lua::get_main_state(), "hud", hudlib); + lua::openlib(lua::get_main_state(), "particles", particleslib); for (auto& pack : engine->getContentPacks()) { lua::emit_event( diff --git a/src/logic/scripting/scripting_hud.hpp b/src/logic/scripting/scripting_hud.hpp index c2d9b7f5..cea7e8ec 100644 --- a/src/logic/scripting/scripting_hud.hpp +++ b/src/logic/scripting/scripting_hud.hpp @@ -8,20 +8,20 @@ namespace fs = std::filesystem; class Hud; +class WorldRenderer; namespace scripting { extern Hud *hud; + extern WorldRenderer* renderer; - void on_frontend_init(Hud *hud); + void on_frontend_init(Hud* hud, WorldRenderer* renderer); void on_frontend_render(); void on_frontend_close(); - /** - * Load package-specific hud script - * @param env environment id - * @param packid content-pack id - * @param file script file path - */ + /// @brief Load package-specific hud script + /// @param env environment id + /// @param packid content-pack id + /// @param file script file path void load_hud_script( const scriptenv &env, const std::string &packid, const fs::path &file ); diff --git a/src/maths/UVRegion.hpp b/src/maths/UVRegion.hpp index ec58dfb5..f7bdb2d0 100644 --- a/src/maths/UVRegion.hpp +++ b/src/maths/UVRegion.hpp @@ -22,4 +22,15 @@ struct UVRegion { inline float getHeight() const { return fabs(v2 - v1); } + + void autoSub(float w, float h, float x, float y) { + x *= 1.0f - w; + y *= 1.0f - h; + float uvw = getWidth(); + float uvh = getHeight(); + u1 = u1 + uvw * x; + v1 = v1 + uvh * y; + u2 = u1 + uvw * w; + v2 = v1 + uvh * h; + } }; diff --git a/src/presets/ParticlesPreset.cpp b/src/presets/ParticlesPreset.cpp new file mode 100644 index 00000000..774ab553 --- /dev/null +++ b/src/presets/ParticlesPreset.cpp @@ -0,0 +1,88 @@ +#include "ParticlesPreset.hpp" + +#include "data/dv_util.hpp" + +std::string to_string(ParticleSpawnShape shape) { + static std::string names[] = { + "ball", + "sphere", + "box" + }; + return names[static_cast(shape)]; +} + +ParticleSpawnShape ParticleSpawnShape_from(std::string_view s) { + if (s == "ball") { + return ParticleSpawnShape::BALL; + } else if (s == "sphere") { + return ParticleSpawnShape::SPHERE; + } else { + return ParticleSpawnShape::BOX; + } +} + +dv::value ParticlesPreset::serialize() const { + auto root = dv::object(); + if (frames.empty()) { + root["texture"] = texture; + } else { + auto& arr = root.list("animation"); + for (const auto& frame : frames) { + arr.add(frame); + } + } + root["collision"] = collision; + root["lighting"] = lighting; + root["max_distance"] = maxDistance; + root["global_up_vector"] = globalUpVector; + root["spawn_interval"] = spawnInterval; + root["lifetime"] = lifetime; + root["lifetime_spread"] = lifetimeSpread; + root["velocity"] = dv::to_value(velocity); + root["acceleration"] = dv::to_value(acceleration); + root["explosion"] = dv::to_value(explosion); + root["size"] = dv::to_value(size); + root["spawn_spread"] = dv::to_value(size); + root["spawn_shape"] = to_string(spawnShape); + root["random_sub_uv"] = randomSubUV; + return root; +} + +void ParticlesPreset::deserialize(const dv::value& src) { + src.at("texture").get(texture); + src.at("collision").get(collision); + src.at("lighting").get(lighting); + src.at("global_up_vector").get(globalUpVector); + src.at("max_distance").get(maxDistance); + src.at("spawn_interval").get(spawnInterval); + src.at("lifetime").get(lifetime); + src.at("lifetime_spread").get(lifetimeSpread); + src.at("random_sub_uv").get(randomSubUV); + if (src.has("velocity")) { + dv::get_vec(src["velocity"], velocity); + } + if (src.has("acceleration")) { + dv::get_vec(src["acceleration"], acceleration); + } + if (src.has("size")) { + dv::get_vec(src["size"], size); + } + if (src.has("spawn_spread")) { + dv::get_vec(src["spawn_spread"], spawnSpread); + } + if (src.has("explosion")) { + dv::get_vec(src["explosion"], explosion); + } + if (src.has("spawn_shape")) { + spawnShape = ParticleSpawnShape_from(src["spawn_shape"].asString()); + } + if (src.has("frames")) { + for (const auto& frame : src["frames"]) { + frames.push_back(frame.asString()); + } + if (!frames.empty()) { + texture = frames.at(0); + randomSubUV = 1.0f; + } + } +} diff --git a/src/presets/ParticlesPreset.hpp b/src/presets/ParticlesPreset.hpp new file mode 100644 index 00000000..262b2c07 --- /dev/null +++ b/src/presets/ParticlesPreset.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "interfaces/Serializable.hpp" + +enum ParticleSpawnShape { + /// @brief Coordinates are regulary distributed within + /// the volume of a ball. + BALL = 0, + /// @brief Coordinates are regulary distributed on + /// a sphere. + SPHERE, + /// @brief Coordinates are uniform distributed within + /// the volume of a box. + BOX +}; + +std::string to_string(ParticleSpawnShape shape); +ParticleSpawnShape ParticleSpawnShape_from(std::string_view s); + +struct ParticlesPreset : public Serializable { + /// @brief Collision detection + bool collision = true; + /// @brief Apply lighting + bool lighting = true; + /// @brief Use global up vector instead of camera-dependent one + bool globalUpVector = false; + /// @brief Max distance of actually spawning particles. + float maxDistance = 16.0f; + /// @brief Particles spawn interval + float spawnInterval = 0.1f; + /// @brief Particle life time + float lifetime = 5.0f; + /// @brief Life time spread divided by lifetime + float lifetimeSpread = 0.2f; + /// @brief Initial velocity + glm::vec3 velocity {}; + /// @brief Velocity acceleration + glm::vec3 acceleration {0.0f, -16.0f, 0.0f}; + /// @brief Random velocity magnitude applying to spawned particles. + glm::vec3 explosion {2.0f}; + /// @brief Particle size + glm::vec3 size {0.1f}; + /// @brief Spawn spread shape + ParticleSpawnShape spawnShape; + /// @brief Spawn spread + glm::vec3 spawnSpread {}; + /// @brief Texture name + std::string texture = ""; + /// @brief Size of random sub-uv region + float randomSubUV = 1.0f; + /// @brief Animation frames + std::vector frames {}; + + dv::value serialize() const override; + void deserialize(const dv::value& src) override; +}; diff --git a/src/typedefs.hpp b/src/typedefs.hpp index 3925d277..b3266aea 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -12,6 +12,7 @@ using integer_t = int64_t; using number_t = double; using uint = unsigned int; +using u64id_t = uint64_t; /// @brief use for bytes arrays using ubyte = uint8_t; diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 963a0928..d94f6c82 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -5,6 +5,7 @@ #include "core_defs.hpp" #include "data/StructLayout.hpp" +#include "presets/ParticlesPreset.hpp" #include "util/stringutil.hpp" std::string to_string(BlockModel model) { @@ -142,6 +143,7 @@ void Block::cloneTo(Block& dst) { dst.inventorySize = inventorySize; dst.tickInterval = tickInterval; dst.overlayTexture = overlayTexture; + dst.particles = std::make_unique(*particles); } static std::set> RESERVED_BLOCK_FIELDS { diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 2d2cdf90..d812e48f 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -10,6 +10,8 @@ #include "maths/aabb.hpp" #include "typedefs.hpp" +struct ParticlesPreset; + namespace data { class StructLayout; } @@ -116,8 +118,8 @@ public: std::vector modelTextures = {}; std::vector modelBoxes = {}; - std::vector modelExtraPoints = - {}; // initially made for tetragons + // initially made for tetragons + std::vector modelExtraPoints = {}; std::vector modelUVs = {}; // boxes' tex-UVs also there /// @brief id of used BlockMaterial, may specify non-existing material @@ -199,6 +201,8 @@ public: std::unique_ptr dataStruct; + std::unique_ptr particles; + /// @brief Runtime indices (content indexing results) struct { /// @brief block runtime integer id diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index dbd2e17e..a380aeda 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -61,10 +61,10 @@ voxel* Chunks::get(int32_t x, int32_t y, int32_t z) const { return &chunk->voxels[(y * CHUNK_D + lz) * CHUNK_W + lx]; } -const AABB* Chunks::isObstacleAt(float x, float y, float z) { - int ix = floor(x); - int iy = floor(y); - int iz = floor(z); +const AABB* Chunks::isObstacleAt(float x, float y, float z) const { + int ix = std::floor(x); + int iy = std::floor(y); + int iz = std::floor(z); voxel* v = get(ix, iy, iz); if (v == nullptr) { if (iy >= CHUNK_H) { @@ -112,7 +112,7 @@ bool Chunks::isObstacleBlock(int32_t x, int32_t y, int32_t z) { return indices->blocks.get(v->id)->obstacle; //-V522 } -ubyte Chunks::getLight(int32_t x, int32_t y, int32_t z, int channel) { +ubyte Chunks::getLight(int32_t x, int32_t y, int32_t z, int channel) const { if (y < 0 || y >= CHUNK_H) { return 0; } @@ -132,7 +132,7 @@ ubyte Chunks::getLight(int32_t x, int32_t y, int32_t z, int channel) { return chunk->lightmap.get(lx, y, lz, channel); } -light_t Chunks::getLight(int32_t x, int32_t y, int32_t z) { +light_t Chunks::getLight(int32_t x, int32_t y, int32_t z) const { if (y < 0 || y >= CHUNK_H) { return 0; } @@ -172,8 +172,9 @@ Chunk* Chunks::getChunk(int x, int z) { } glm::ivec3 Chunks::seekOrigin( - glm::ivec3 pos, const Block& def, blockstate state -) { + const glm::ivec3& srcpos, const Block& def, blockstate state +) const { + auto pos = srcpos; const auto& rotation = def.rotations.variants[state.rotation]; auto segment = state.segment; while (true) { diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index 6e73193a..67746199 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -60,8 +60,12 @@ public: return get(pos.x, pos.y, pos.z); } - light_t getLight(int32_t x, int32_t y, int32_t z); - ubyte getLight(int32_t x, int32_t y, int32_t z, int channel); + inline const voxel* get(glm::ivec3 pos) const { + return get(pos.x, pos.y, pos.z); + } + + light_t getLight(int32_t x, int32_t y, int32_t z) const; + ubyte getLight(int32_t x, int32_t y, int32_t z, int channel) const; void set(int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state); /// @brief Seek for the extended block origin position @@ -69,7 +73,9 @@ public: /// @param def segment block definition /// @param state segment block state /// @return origin block position or `pos` if block is not extended - glm::ivec3 seekOrigin(glm::ivec3 pos, const Block& def, blockstate state); + glm::ivec3 seekOrigin( + const glm::ivec3& pos, const Block& def, blockstate state + ) const; /// @brief Check if required zone is replaceable /// @param def definition of the block that requires a replaceable zone @@ -97,7 +103,12 @@ public: glm::vec3 rayCastToObstacle(glm::vec3 start, glm::vec3 dir, float maxDist); - const AABB* isObstacleAt(float x, float y, float z); + const AABB* isObstacleAt(float x, float y, float z) const; + + const AABB* isObstacleAt(const glm::vec3& pos) const { + return isObstacleAt(pos.x, pos.y, pos.z); + } + bool isSolidBlock(int32_t x, int32_t y, int32_t z); bool isReplaceableBlock(int32_t x, int32_t y, int32_t z); bool isObstacleBlock(int32_t x, int32_t y, int32_t z);