Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Particles upgrade & fixes #421

Merged
merged 11 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/en/particles.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Particles are a table, all fields of which are optional.
| acceleration | Particles acceleration. | {0, -16, 0} |
| explosion | Force of particles explosion on spawn. | {2, 2, 2} |
| size | Size of particles. | {0.1, 0.1, 0.1} |
| size_spread | Maximum particle size spread over time. | 0.2 |
| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 |
| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 |
| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 |
| spawn_shape | Shape of particle spawn area. (ball/sphere/box) | ball |
| spawn_spread | Size of particle spawn area. | {0, 0, 0} |
| random_sub_uv | Size of random texture subregion (1 - entire texture will be used). | 1.0 |
Expand Down
3 changes: 3 additions & 0 deletions doc/ru/particles.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
| explosion | Сила разлёта частиц при спавне. | {2, 2, 2} |
| size | Размер частиц. | {0.1, 0.1, 0.1} |
| size_spread | Максимальное отклонение времени размера частиц. | 0.2 |
| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 |
| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
| spawn_shape | Форма области спавна частиц. (ball/sphere/box) | ball |
| spawn_spread | Размер области спавна частиц. | {0, 0, 0} |
| random_sub_uv | Размер случайного подрегиона текстуры (1 - будет использована вся текстура). | 1.0 |
Expand Down
19 changes: 19 additions & 0 deletions res/content/base/blocks/leaves.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,24 @@
"material": "base:grass",
"draw-group": 5,
"culling": "optional",
"particles": {
"lifetime": 4.0,
"spawn_interval": 1000.0,
"acceleration": [0, -0.1, 0],
"velocity": [0.2, -2.5, 0.3],
"explosion": [0, 0, 0],
"collision": false,
"size": [0.3, 0.3, 0.3],
"size_spread": 0.2,
"spawn_shape": "box",
"spawn_spread": [0.2, 0.2, 0.2],
"angle_spread": 1.0,
"min_angular_vel": 0.5,
"max_angular_vel": 2.0,
"lighting": true,
"frames": [
"particles:leaf_0"
]
},
"base:durability": 0.7
}
Binary file added res/content/base/textures/particles/leaf_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions src/graphics/render/Decorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@ void Decorator::update(

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);
update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos);
}
const auto& chunks = *level.chunks;
const auto& indices = *level.content->getIndices();
Expand Down
23 changes: 21 additions & 2 deletions src/graphics/render/Emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ Emitter::Emitter(
)
: level(level),
origin(std::move(origin)),
prototype({this, 0, glm::vec3(), preset.velocity, preset.lifetime, region}),
prototype({this, 0, {}, preset.velocity, preset.lifetime, region}),
texture(texture),
count(count),
preset(std::move(preset)) {
random.setSeed(reinterpret_cast<ptrdiff_t>(this));
this->prototype.emitter = this;
timer = preset.spawnInterval;
timer = preset.spawnInterval * random.randFloat();
}

const Texture* Emitter::getTexture() const {
Expand Down Expand Up @@ -76,13 +77,26 @@ void Emitter::update(
count = std::max(0, count - skipped);
timer -= skipped * spawnInterval;
}
if (count < 0) {
int skipped = timer / spawnInterval;
timer -= skipped * spawnInterval;
}
return;
}
while (count && timer > spawnInterval) {
// spawn particle
Particle particle = prototype;
particle.emitter = this;
particle.random = random.rand32();
if (glm::abs(preset.angleSpread) >= 0.005f) {
particle.angle =
random.randFloat() * preset.angleSpread * glm::pi<float>() * 2;
}
particle.angularVelocity =
(preset.minAngularVelocity +
random.randFloat() *
(preset.maxAngularVelocity - preset.minAngularVelocity)) *
((random.rand() % 2) * 2 - 1);

glm::vec3 spawnOffset = generate_coord(preset.spawnShape);
spawnOffset *= preset.spawnSpread;
Expand All @@ -103,6 +117,7 @@ void Emitter::update(
if (count > 0) {
count--;
}
refCount++;
}
}

Expand All @@ -114,6 +129,10 @@ bool Emitter::isDead() const {
return count == 0;
}

bool Emitter::isReferred() const {
return refCount > 0;
}

const EmitterOrigin& Emitter::getOrigin() const {
return origin;
}
Expand Down
12 changes: 11 additions & 1 deletion src/graphics/render/Emitter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ struct Particle {
float lifetime;
/// @brief UV region
UVRegion region;
/// @brief Current rotation angle
float angle;
/// @brief Angular velocity
float angularVelocity;
};

class Texture;
Expand All @@ -39,7 +43,7 @@ class Emitter {
EmitterOrigin origin;
/// @brief Particle prototype
Particle prototype;
/// @brief Particle
/// @brief Particle texture
const Texture* texture;
/// @brief Number of particles should be spawned before emitter deactivation.
/// -1 is infinite.
Expand All @@ -50,6 +54,9 @@ class Emitter {

util::PseudoRandom random;
public:
/// @brief Number of references (alive particles)
int refCount = 0;
/// @brief Particle settings
ParticlesPreset preset;

Emitter(
Expand Down Expand Up @@ -82,6 +89,9 @@ class Emitter {
/// @return true if the emitter has spawned all particles
bool isDead() const;

/// @return true if there is at least one alive referring particle left
bool isReferred() const;

const EmitterOrigin& getOrigin() const;

void setOrigin(const EmitterOrigin& origin);
Expand Down
65 changes: 49 additions & 16 deletions src/graphics/render/ParticlesRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ static inline void update_particle(
const auto& preset = particle.emitter->preset;
auto& pos = particle.position;
auto& vel = particle.velocity;
auto& angle = particle.angle;

vel += delta * preset.acceleration;
if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) {
vel *= 0.0f;
}
pos += vel * delta;
angle += particle.angularVelocity * delta;
particle.lifetime -= delta;
}

Expand All @@ -61,7 +63,8 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
auto iter = vec.begin();
while (iter != vec.end()) {
auto& particle = *iter;
auto& preset = particle.emitter->preset;
auto& emitter = *particle.emitter;
auto& preset = emitter.preset;

if (!preset.frames.empty()) {
float time = preset.lifetime - particle.lifetime;
Expand All @@ -82,26 +85,60 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
}
update_particle(particle, delta, chunks);

float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;

glm::vec4 light(1, 1, 1, 0);
if (preset.lighting) {
light = MainBatch::sampleLight(
particle.position, chunks, backlight
particle.position,
chunks,
backlight
);
auto size = glm::max(glm::vec3(0.5f), preset.size * scale);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
light = glm::max(
light,
MainBatch::sampleLight(
particle.position -
size * glm::vec3(x, y, z),
chunks,
backlight
)
);
}
}
}
light *= 0.9f + (particle.random % 100) * 0.001f;
}
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;


glm::vec3 localRight = right;
glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up;
float angle = particle.angle;
if (glm::abs(angle) >= 0.005f) {
glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f);
glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f);

localRight = right * rotatedRight.x + localUp * rotatedRight.y +
camera.front * rotatedRight.z;
localUp = right * rotatedUp.x + localUp * rotatedUp.y +
camera.front * rotatedUp.z;
}
batch->quad(
particle.position,
right,
preset.globalUpVector ? glm::vec3(0, 1, 0) : up,
localRight,
localUp,
preset.size * scale,
light,
glm::vec3(1.0f),
particle.region
);
if (particle.lifetime <= 0.0f) {
iter = vec.erase(iter);
emitter.refCount--;
} else {
iter++;
}
Expand All @@ -124,19 +161,15 @@ void ParticlesRenderer::render(const Camera& camera, float delta) {
auto iter = emitters.begin();
while (iter != emitters.end()) {
auto& emitter = *iter->second;
if (emitter.isDead() && !emitter.isReferred()) {
// destruct Emitter only when there is no particles spawned by it
iter = emitters.erase(iter);
continue;
}
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;
}
vec = &particles[texture];
emitter.update(delta, camera.position, *vec);
iter++;
}
Expand Down
6 changes: 6 additions & 0 deletions src/presets/ParticlesPreset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ dv::value ParticlesPreset::serialize() const {
root["explosion"] = dv::to_value(explosion);
root["size"] = dv::to_value(size);
root["size_spread"] = sizeSpread;
root["angle_spread"] = angleSpread;
root["min_angular_vel"] = minAngularVelocity;
root["max_angular_vel"] = maxAngularVelocity;
root["spawn_spread"] = dv::to_value(size);
root["spawn_shape"] = to_string(spawnShape);
root["random_sub_uv"] = randomSubUV;
Expand All @@ -58,6 +61,9 @@ void ParticlesPreset::deserialize(const dv::value& src) {
src.at("spawn_interval").get(spawnInterval);
src.at("lifetime").get(lifetime);
src.at("lifetime_spread").get(lifetimeSpread);
src.at("angle_spread").get(angleSpread);
src.at("min_angular_vel").get(minAngularVelocity);
src.at("max_angular_vel").get(maxAngularVelocity);
src.at("random_sub_uv").get(randomSubUV);
if (src.has("velocity")) {
dv::get_vec(src["velocity"], velocity);
Expand Down
8 changes: 7 additions & 1 deletion src/presets/ParticlesPreset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct ParticlesPreset : public Serializable {
/// @brief Use global up vector instead of camera-dependent one
bool globalUpVector = false;
/// @brief Max distance of actually spawning particles.
float maxDistance = 16.0f;
float maxDistance = 32.0f;
/// @brief Particles spawn interval
float spawnInterval = 0.1f;
/// @brief Particle life time
Expand All @@ -44,6 +44,12 @@ struct ParticlesPreset : public Serializable {
glm::vec3 size {0.1f};
/// @brief Particles size spread
float sizeSpread = 0.2f;
/// @brief Random initial angle spread
float angleSpread = 0.0f;
/// @brief Minimum angular velocity
float minAngularVelocity = 0.0f;
/// @brief Maximum angular velocity
float maxAngularVelocity = 0.0f;
/// @brief Spawn spread shape
ParticleSpawnShape spawnShape = BALL;
/// @brief Spawn spread
Expand Down
Loading