commit
f686749ae8
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
12
res/content/base/scripts/world.lua
Normal file
12
res/content/base/scripts/world.lua
Normal file
@ -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
|
||||
BIN
res/content/base/textures/particles/fire_0.png
Normal file
BIN
res/content/base/textures/particles/fire_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
res/content/base/textures/particles/smoke_0.png
Normal file
BIN
res/content/base/textures/particles/smoke_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
BIN
res/content/base/textures/particles/smoke_1.png
Normal file
BIN
res/content/base/textures/particles/smoke_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@ -27,6 +27,7 @@
|
||||
],
|
||||
"atlases": [
|
||||
"blocks",
|
||||
"items"
|
||||
"items",
|
||||
"particles"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
|
||||
@ -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<ParticlesPreset>();
|
||||
def.particles->deserialize(root["particles"]);
|
||||
}
|
||||
|
||||
if (def.tickInterval == 0) {
|
||||
def.tickInterval = 1;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<UINode> 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);
|
||||
|
||||
@ -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> level)
|
||||
LevelScreen::LevelScreen(Engine* engine, std::unique_ptr<Level> levelPtr)
|
||||
: Screen(engine), postProcessing(std::make_unique<PostProcessing>())
|
||||
{
|
||||
Level* level = levelPtr.get();
|
||||
|
||||
auto& settings = engine->getSettings();
|
||||
auto assets = engine->getAssets();
|
||||
auto menu = engine->getGUI()->getMenu();
|
||||
menu->reset();
|
||||
|
||||
controller = std::make_unique<LevelController>(engine, std::move(level));
|
||||
controller = std::make_unique<LevelController>(engine, std::move(levelPtr));
|
||||
frontend = std::make_unique<LevelFrontend>(controller->getPlayer(), controller.get(), assets);
|
||||
|
||||
worldRenderer = std::make_unique<WorldRenderer>(engine, frontend.get(), controller->getPlayer());
|
||||
hud = std::make_unique<Hud>(engine, frontend.get(), controller->getPlayer());
|
||||
|
||||
|
||||
decorator = std::make_unique<Decorator>(
|
||||
*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) {
|
||||
|
||||
@ -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> worldRenderer;
|
||||
std::unique_ptr<TextureAnimator> animator;
|
||||
std::unique_ptr<PostProcessing> postProcessing;
|
||||
std::unique_ptr<Decorator> decorator;
|
||||
std::unique_ptr<Hud> hud;
|
||||
|
||||
void saveWorldPreview();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "maths/UVRegion.hpp"
|
||||
|
||||
namespace model {
|
||||
struct Vertex {
|
||||
glm::vec3 coord;
|
||||
@ -14,9 +16,27 @@ namespace model {
|
||||
struct Mesh {
|
||||
std::string texture;
|
||||
std::vector<Vertex> 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);
|
||||
};
|
||||
|
||||
|
||||
111
src/graphics/render/Decorator.cpp
Normal file
111
src/graphics/render/Decorator.cpp
Normal file
@ -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<Emitter>(
|
||||
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++;
|
||||
}
|
||||
}
|
||||
34
src/graphics/render/Decorator.hpp
Normal file
34
src/graphics/render/Decorator.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/hash.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
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<glm::ivec3, uint64_t> 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);
|
||||
};
|
||||
123
src/graphics/render/Emitter.cpp
Normal file
123
src/graphics/render/Emitter.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "Emitter.hpp"
|
||||
|
||||
#include <glm/gtc/random.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "window/Camera.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
Emitter::Emitter(
|
||||
const Level& level,
|
||||
std::variant<glm::vec3, entityid_t> 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<Particle>& particles
|
||||
) {
|
||||
const float spawnInterval = preset.spawnInterval;
|
||||
if (count == 0 || (count == -1 && spawnInterval < FLT_EPSILON)) {
|
||||
return;
|
||||
}
|
||||
glm::vec3 position {};
|
||||
if (auto staticPos = std::get_if<glm::vec3>(&origin)) {
|
||||
position = *staticPos;
|
||||
} else if (auto entityId = std::get_if<entityid_t>(&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;
|
||||
}
|
||||
88
src/graphics/render/Emitter.hpp
Normal file
88
src/graphics/render/Emitter.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#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<glm::vec3, entityid_t>;
|
||||
|
||||
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<glm::vec3, entityid_t> 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<Particle>& 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);
|
||||
};
|
||||
135
src/graphics/render/MainBatch.cpp
Normal file
135
src/graphics/render/MainBatch.cpp
Normal file
@ -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<float[]>(capacity * VERTEX_SIZE)),
|
||||
capacity(capacity),
|
||||
index(0),
|
||||
mesh(std::make_unique<Mesh>(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]
|
||||
);
|
||||
}
|
||||
129
src/graphics/render/MainBatch.hpp
Normal file
129
src/graphics/render/MainBatch.hpp
Normal file
@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "maths/UVRegion.hpp"
|
||||
|
||||
class Mesh;
|
||||
class Texture;
|
||||
class Chunks;
|
||||
|
||||
class MainBatch {
|
||||
std::unique_ptr<float[]> const buffer;
|
||||
size_t const capacity;
|
||||
size_t index;
|
||||
|
||||
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
|
||||
std::unique_ptr<Mesh> mesh;
|
||||
std::unique_ptr<Texture> 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<uint32_t>(light.r * 255) & 0xff) << 24;
|
||||
compressed.integer |= (static_cast<uint32_t>(light.g * 255) & 0xff) << 16;
|
||||
compressed.integer |= (static_cast<uint32_t>(light.b * 255) & 0xff) << 8;
|
||||
compressed.integer |= (static_cast<uint32_t>(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
|
||||
);
|
||||
};
|
||||
@ -10,6 +10,7 @@
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "lighting/Lightmap.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MainBatch.hpp"
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
@ -18,13 +19,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/// 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<float[]>(capacity * VERTEX_SIZE)),
|
||||
capacity(capacity),
|
||||
index(0),
|
||||
mesh(std::make_unique<Mesh>(buffer.get(), 0, attrs)),
|
||||
: batch(std::make_unique<MainBatch>(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);
|
||||
}
|
||||
|
||||
@ -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<std::string, std::string>;
|
||||
|
||||
class ModelBatch {
|
||||
std::unique_ptr<float[]> const buffer;
|
||||
size_t const capacity;
|
||||
size_t index;
|
||||
|
||||
std::unique_ptr<Mesh> mesh;
|
||||
std::unique_ptr<Texture> 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<uint32_t>(light.r * 255) & 0xff) << 24;
|
||||
compressed.integer |= (static_cast<uint32_t>(light.g * 255) & 0xff) << 16;
|
||||
compressed.integer |= (static_cast<uint32_t>(light.b * 255) & 0xff) << 8;
|
||||
compressed.integer |= (static_cast<uint32_t>(light.a * 255) & 0xff);
|
||||
|
||||
buffer[index++] = compressed.floating;
|
||||
}
|
||||
std::unique_ptr<MainBatch> 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;
|
||||
|
||||
@ -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();
|
||||
|
||||
173
src/graphics/render/ParticlesRenderer.cpp
Normal file
173
src/graphics/render/ParticlesRenderer.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
#include "ParticlesRenderer.hpp"
|
||||
|
||||
#include <set>
|
||||
|
||||
#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<MainBatch>(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<const Texture*> 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<Particle>* 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<Emitter*> 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> emitter) {
|
||||
u64id_t uid = nextEmitter++;
|
||||
emitters[uid] = std::move(emitter);
|
||||
return uid;
|
||||
}
|
||||
52
src/graphics/render/ParticlesRenderer.hpp
Normal file
52
src/graphics/render/ParticlesRenderer.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<const Texture*, std::vector<Particle>> particles;
|
||||
std::unique_ptr<MainBatch> batch;
|
||||
|
||||
std::unordered_map<u64id_t, std::unique_ptr<Emitter>> 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> 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;
|
||||
};
|
||||
@ -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 {
|
||||
|
||||
@ -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> mesh;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
std::vector<skysprite> 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();
|
||||
|
||||
@ -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<Frustum>()),
|
||||
lineBatch(std::make_unique<LineBatch>()),
|
||||
modelBatch(std::make_unique<ModelBatch>(
|
||||
20'000, engine->getAssets(), level->chunks.get(),
|
||||
20'000,
|
||||
engine->getAssets(),
|
||||
level->chunks.get(),
|
||||
&engine->getSettings()
|
||||
)),
|
||||
particles(std::make_unique<ParticlesRenderer>(
|
||||
*engine->getAssets(),
|
||||
*frontend->getLevel(),
|
||||
&engine->getSettings().graphics
|
||||
)) {
|
||||
renderer = std::make_unique<ChunksRenderer>(
|
||||
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<float>(settings.chunks.loadDistance.get() - 2);
|
||||
|
||||
auto entityShader = assets->get<Shader>("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<Shader>("main");
|
||||
|
||||
@ -8,12 +8,15 @@
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#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> skybox;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
std::unique_ptr<ModelBatch> 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<ParticlesRenderer> particles;
|
||||
|
||||
static bool showChunkBorders;
|
||||
static bool showEntitiesDebug;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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[];
|
||||
|
||||
90
src/logic/scripting/lua/libs/libparticles.cpp
Normal file
90
src/logic/scripting/lua/libs/libparticles.cpp
Normal file
@ -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<entityid_t>(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<Emitter>(
|
||||
*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<glm::vec3>(&origin)) {
|
||||
return lua::pushvec3(L, *pos);
|
||||
} else if (auto entityid = std::get_if<entityid_t>(&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<entityid_t>(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<l_emit>},
|
||||
{"stop", lua::wrap<l_stop>},
|
||||
{"is_alive", lua::wrap<l_is_alive>},
|
||||
{"get_origin", lua::wrap<l_get_origin>},
|
||||
{"set_origin", lua::wrap<l_set_origin>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
88
src/presets/ParticlesPreset.cpp
Normal file
88
src/presets/ParticlesPreset.cpp
Normal file
@ -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<int>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/presets/ParticlesPreset.hpp
Normal file
58
src/presets/ParticlesPreset.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#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<std::string> frames {};
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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<ParticlesPreset>(*particles);
|
||||
}
|
||||
|
||||
static std::set<std::string, std::less<>> RESERVED_BLOCK_FIELDS {
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
#include "maths/aabb.hpp"
|
||||
#include "typedefs.hpp"
|
||||
|
||||
struct ParticlesPreset;
|
||||
|
||||
namespace data {
|
||||
class StructLayout;
|
||||
}
|
||||
@ -116,8 +118,8 @@ public:
|
||||
|
||||
std::vector<std::string> modelTextures = {};
|
||||
std::vector<BoxModel> modelBoxes = {};
|
||||
std::vector<glm::vec3> modelExtraPoints =
|
||||
{}; // initially made for tetragons
|
||||
// initially made for tetragons
|
||||
std::vector<glm::vec3> modelExtraPoints = {};
|
||||
std::vector<UVRegion> modelUVs = {}; // boxes' tex-UVs also there
|
||||
|
||||
/// @brief id of used BlockMaterial, may specify non-existing material
|
||||
@ -199,6 +201,8 @@ public:
|
||||
|
||||
std::unique_ptr<data::StructLayout> dataStruct;
|
||||
|
||||
std::unique_ptr<ParticlesPreset> particles;
|
||||
|
||||
/// @brief Runtime indices (content indexing results)
|
||||
struct {
|
||||
/// @brief block runtime integer id
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user