Merge pull request #341 from MihailRis/particles-first

Particles
This commit is contained in:
MihailRis 2024-11-05 04:21:25 +03:00 committed by GitHub
commit f686749ae8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 1432 additions and 182 deletions

View File

@ -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"
]
}
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -27,6 +27,7 @@
],
"atlases": [
"blocks",
"items"
"items",
"particles"
]
}

View File

@ -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)));

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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();

View File

@ -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();
}

View File

@ -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();
};

View File

@ -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;

View File

@ -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);
};

View 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++;
}
}

View 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);
};

View 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;
}

View 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);
};

View 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]
);
}

View 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
);
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View 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;
}

View 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;
};

View File

@ -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 {

View File

@ -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();

View File

@ -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");

View File

@ -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;

View File

@ -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 {

View File

@ -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[];

View 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}
};

View File

@ -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(

View File

@ -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
);

View 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;
}
};

View 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;
}
}
}

View 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;
};

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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);