1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-11 17:51:04 +00:00

Animated particlespawners and more (#11545)

Co-authored-by: Lars Mueller <appgurulars@gmx.de>
Co-authored-by: sfan5 <sfan5@live.de>
Co-authored-by: Dmitry Kostenko <codeforsmile@gmail.com>
This commit is contained in:
Lexi Hale 2022-07-13 11:57:12 +02:00 committed by GitHub
parent 8724fe6e3f
commit 20bd6bdb68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1986 additions and 279 deletions

View file

@ -33,23 +33,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client.h"
#include "settings.h"
/*
Utility
*/
static f32 random_f32(f32 min, f32 max)
{
return rand() / (float)RAND_MAX * (max - min) + min;
}
static v3f random_v3f(v3f min, v3f max)
{
return v3f(
random_f32(min.X, max.X),
random_f32(min.Y, max.Y),
random_f32(min.Z, max.Z));
}
/*
Particle
*/
@ -59,25 +42,71 @@ Particle::Particle(
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
video::ITexture *texture,
const ClientTexRef& texture,
v2f texpos,
v2f texsize,
video::SColor color
):
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager())
((Client *)gamedef)->getSceneManager()),
m_texture(texture)
{
// Misc
m_gamedef = gamedef;
m_env = env;
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
const auto blendmode = texture.tex != nullptr
? texture.tex -> blendmode
: ParticleParamTypes::BlendMode::alpha;
switch (blendmode) {
case ParticleParamTypes::BlendMode::alpha:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
blendop = video::EBO_ADD;
break;
case ParticleParamTypes::BlendMode::add:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_ADD;
break;
case ParticleParamTypes::BlendMode::sub:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_REVSUBTRACT;
break;
case ParticleParamTypes::BlendMode::screen:
bfsrc = video::EBF_ONE;
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
blendop = video::EBO_ADD;
break;
default: assert(false);
}
// Texture
m_material.setFlag(video::EMF_LIGHTING, false);
m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
m_material.setFlag(video::EMF_FOG_ENABLE, true);
m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m_material.setTexture(0, texture);
// correctly render layered transparent particles -- see #10398
m_material.setFlag(video::EMF_ZWRITE_ENABLE, true);
// enable alpha blending and set blend mode
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
m_material.BlendOperation = blendop;
m_material.setTexture(0, m_texture.ref);
m_texpos = texpos;
m_texsize = texsize;
m_animation = p.animation;
@ -90,6 +119,9 @@ Particle::Particle(
m_pos = p.pos;
m_velocity = p.vel;
m_acceleration = p.acc;
m_drag = p.drag;
m_jitter = p.jitter;
m_bounce = p.bounce;
m_expiration = p.expirationtime;
m_player = player;
m_size = p.size;
@ -98,6 +130,8 @@ Particle::Particle(
m_object_collision = p.object_collision;
m_vertical = p.vertical;
m_glow = p.glow;
m_alpha = 0;
m_parent = nullptr;
// Irrlicht stuff
const float c = p.size / 2;
@ -111,6 +145,14 @@ Particle::Particle(
updateVertices();
}
Particle::~Particle()
{
/* if our textures aren't owned by a particlespawner, we need to clean
* them up ourselves when the particle dies */
if (m_parent == nullptr)
delete m_texture.tex;
}
void Particle::OnRegisterSceneNode()
{
if (IsVisible)
@ -134,6 +176,12 @@ void Particle::render()
void Particle::step(float dtime)
{
m_time += dtime;
// apply drag (not handled by collisionMoveSimple) and brownian motion
v3f av = vecAbsolute(m_velocity);
av -= av * (m_drag * dtime);
m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
if (m_collisiondetection) {
aabb3f box = m_collisionbox;
v3f p_pos = m_pos * BS;
@ -141,17 +189,41 @@ void Particle::step(float dtime)
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
m_object_collision);
if (m_collision_removal && r.collides) {
// force expiration of the particle
m_expiration = -1.0;
f32 bounciness = m_bounce.pickWithin();
if (r.collides && (m_collision_removal || bounciness > 0)) {
if (m_collision_removal) {
// force expiration of the particle
m_expiration = -1.0f;
} else if (bounciness > 0) {
/* cheap way to get a decent bounce effect is to only invert the
* largest component of the velocity vector, so e.g. you don't
* have a rock immediately bounce back in your face when you try
* to skip it across the water (as would happen if we simply
* downscaled and negated the velocity vector). this means
* bounciness will work properly for cubic objects, but meshes
* with diagonal angles and entities will not yield the correct
* visual. this is probably unavoidable */
if (av.Y > av.X && av.Y > av.Z) {
m_velocity.Y = -(m_velocity.Y * bounciness);
} else if (av.X > av.Y && av.X > av.Z) {
m_velocity.X = -(m_velocity.X * bounciness);
} else if (av.Z > av.Y && av.Z > av.X) {
m_velocity.Z = -(m_velocity.Z * bounciness);
} else { // well now we're in a bit of a pickle
m_velocity = -(m_velocity * bounciness);
}
}
} else {
m_pos = p_pos / BS;
m_velocity = p_velocity / BS;
}
m_pos = p_pos / BS;
} else {
// apply acceleration
m_velocity += m_acceleration * dtime;
m_pos += m_velocity * dtime;
}
if (m_animation.type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
@ -165,11 +237,21 @@ void Particle::step(float dtime)
}
}
// animate particle alpha in accordance with settings
if (m_texture.tex != nullptr)
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
else
m_alpha = 1.f;
// Update lighting
updateLight();
// Update model
updateVertices();
// Update position -- see #10398
v3s16 camera_offset = m_env->getCameraOffset();
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
}
void Particle::updateLight()
@ -189,7 +271,7 @@ void Particle::updateLight()
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
u8 m_light = decode_light(light + m_glow);
m_color.set(255,
m_color.set(m_alpha*255,
m_light * m_base_color.getRed() / 255,
m_light * m_base_color.getGreen() / 255,
m_light * m_base_color.getBlue() / 255);
@ -198,6 +280,12 @@ void Particle::updateLight()
void Particle::updateVertices()
{
f32 tx0, tx1, ty0, ty1;
v2f scale;
if (m_texture.tex != nullptr)
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
else
scale = v2f(1.f, 1.f);
if (m_animation.type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture(0)->getSize();
@ -218,16 +306,24 @@ void Particle::updateVertices()
ty1 = m_texpos.Y + m_texsize.Y;
}
m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
auto half = m_size * .5f,
hx = half * scale.X,
hy = half * scale.Y;
m_vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, m_color, tx0, ty1);
m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
m_vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, m_color, tx1, ty1);
m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
m_vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, m_color, tx1, ty0);
m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
m_vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, m_color, tx0, ty0);
v3s16 camera_offset = m_env->getCameraOffset();
// see #10398
// v3s16 camera_offset = m_env->getCameraOffset();
// particle position is now handled by step()
m_box.reset(v3f());
for (video::S3DVertex &vertex : m_vertices) {
if (m_vertical) {
v3f ppos = m_player->getPosition()/BS;
@ -238,7 +334,6 @@ void Particle::updateVertices()
vertex.Pos.rotateXZBy(m_player->getYaw());
}
m_box.addInternalPoint(vertex.Pos);
vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
}
}
@ -251,7 +346,8 @@ ParticleSpawner::ParticleSpawner(
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
video::ITexture *texture,
std::unique_ptr<ClientTexture[]>& texpool,
size_t texcount,
ParticleManager *p_manager
):
m_particlemanager(p_manager), p(p)
@ -259,21 +355,66 @@ ParticleSpawner::ParticleSpawner(
m_gamedef = gamedef;
m_player = player;
m_attached_id = attached_id;
m_texture = texture;
m_texpool = std::move(texpool);
m_texcount = texcount;
m_time = 0;
m_active = 0;
m_dying = false;
m_spawntimes.reserve(p.amount + 1);
for (u16 i = 0; i <= p.amount; i++) {
float spawntime = rand() / (float)RAND_MAX * p.time;
float spawntime = myrand_float() * p.time;
m_spawntimes.push_back(spawntime);
}
size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
if (p.time != 0) {
auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
max_particles = p.amount / maxGenerations;
} else {
auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
max_particles = p.amount * longestLife;
}
p_manager->reserveParticleSpace(max_particles * 1.2);
}
namespace {
GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
if (id == 0)
return nullptr;
return env->getGenericCAO(id);
}
}
void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix)
{
float fac = 0;
if (p.time != 0) { // ensure safety from divide-by-zeroes
fac = m_time / (p.time+0.1f);
}
auto r_pos = p.pos.blend(fac);
auto r_vel = p.vel.blend(fac);
auto r_acc = p.acc.blend(fac);
auto r_drag = p.drag.blend(fac);
auto r_radius = p.radius.blend(fac);
auto r_jitter = p.jitter.blend(fac);
auto r_bounce = p.bounce.blend(fac);
v3f attractor_origin = p.attractor_origin.blend(fac);
v3f attractor_direction = p.attractor_direction.blend(fac);
auto attractor_obj = findObjectByID(env, p.attractor_attachment);
auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
auto r_exp = p.exptime.blend(fac);
auto r_size = p.size.blend(fac);
auto r_attract = p.attract.blend(fac);
auto attract = r_attract.pickWithin();
v3f ppos = m_player->getPosition() / BS;
v3f pos = random_v3f(p.minpos, p.maxpos);
v3f pos = r_pos.pickWithin();
v3f sphere_radius = r_radius.pickWithin();
// Need to apply this first or the following check
// will be wrong for attached spawners
@ -287,15 +428,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
pos.Z += camera_offset.Z;
}
if (pos.getDistanceFrom(ppos) > radius)
if (pos.getDistanceFromSQ(ppos) > radius*radius)
return;
// Parameters for the single particle we're about to spawn
ParticleParameters pp;
pp.pos = pos;
pp.vel = random_v3f(p.minvel, p.maxvel);
pp.acc = random_v3f(p.minacc, p.maxacc);
pp.vel = r_vel.pickWithin();
pp.acc = r_acc.pickWithin();
pp.drag = r_drag.pickWithin();
pp.jitter = r_jitter;
pp.bounce = r_bounce;
if (attached_absolute_pos_rot_matrix) {
// Apply attachment rotation
@ -303,30 +447,137 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
}
pp.expirationtime = random_f32(p.minexptime, p.maxexptime);
if (attractor_obj)
attractor_origin += attractor_obj->getPosition() / BS;
if (attractor_direction_obj) {
auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
if (attractor_absolute_pos_rot_matrix)
attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
}
pp.expirationtime = r_exp.pickWithin();
if (sphere_radius != v3f()) {
f32 l = sphere_radius.getLength();
v3f mag = sphere_radius;
mag.normalize();
v3f ofs = v3f(l,0,0);
ofs.rotateXZBy(myrand_range(0.f,360.f));
ofs.rotateYZBy(myrand_range(0.f,360.f));
ofs.rotateXYBy(myrand_range(0.f,360.f));
pp.pos += ofs * mag;
}
if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
v3f dir;
f32 dist = 0; /* =0 necessary to silence warning */
switch (p.attractor_kind) {
case ParticleParamTypes::AttractorKind::none:
break;
case ParticleParamTypes::AttractorKind::point: {
dist = pp.pos.getDistanceFrom(attractor_origin);
dir = pp.pos - attractor_origin;
dir.normalize();
break;
}
case ParticleParamTypes::AttractorKind::line: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
const auto& lorigin = attractor_origin;
v3f ldir = attractor_direction;
ldir.normalize();
auto origin_to_point = pp.pos - lorigin;
auto scalar_projection = origin_to_point.dotProduct(ldir);
auto point_on_line = lorigin + (ldir * scalar_projection);
dist = pp.pos.getDistanceFrom(point_on_line);
dir = (point_on_line - pp.pos);
dir.normalize();
dir *= -1; // flip it around so strength=1 attracts, not repulses
break;
}
case ParticleParamTypes::AttractorKind::plane: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
const v3f& porigin = attractor_origin;
v3f normal = attractor_direction;
normal.normalize();
v3f point_to_origin = porigin - pp.pos;
f32 factor = normal.dotProduct(point_to_origin);
if (numericAbsolute(factor) == 0.0f) {
dir = normal;
} else {
factor = numericSign(factor);
dir = normal * factor;
}
dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
dir *= -1; // flip it around so strength=1 attracts, not repulses
break;
}
}
f32 speedTowards = numericAbsolute(attract) * dist;
v3f avel = dir * speedTowards;
if (attract > 0 && speedTowards > 0) {
avel *= -1;
if (p.attractor_kill) {
// make sure the particle dies after crossing the attractor threshold
f32 timeToCenter = dist / speedTowards;
if (timeToCenter < pp.expirationtime)
pp.expirationtime = timeToCenter;
}
}
pp.vel += avel;
}
p.copyCommon(pp);
video::ITexture *texture;
ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f =
m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture,
if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
texpos, texsize, &color, p.node_tile))
return;
} else {
texture = m_texture;
if (m_texcount == 0)
return;
texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
if (texture.tex->animated)
pp.animation = texture.tex->animation;
}
// synchronize animation length with particle life if desired
if (pp.animation.type != TAT_NONE) {
if (pp.animation.type == TAT_VERTICAL_FRAMES &&
pp.animation.vertical_frames.length < 0) {
auto& a = pp.animation.vertical_frames;
// we add a tiny extra value to prevent the first frame
// from flickering back on just before the particle dies
a.length = (pp.expirationtime / -a.length) + 0.1;
} else if (pp.animation.type == TAT_SHEET_2D &&
pp.animation.sheet_2d.frame_length < 0) {
auto& a = pp.animation.sheet_2d;
auto frames = a.frames_w * a.frames_h;
auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
pp.animation.sheet_2d.frame_length = frames / runtime;
}
}
// Allow keeping default random size
if (p.maxsize > 0.0f)
pp.size = random_f32(p.minsize, p.maxsize);
if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
pp.size = r_size.pickWithin();
m_particlemanager->addParticle(new Particle(
++m_active;
auto pa = new Particle(
m_gamedef,
m_player,
env,
@ -335,7 +586,9 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
texpos,
texsize,
color
));
);
pa->m_parent = this;
m_particlemanager->addParticle(pa);
}
void ParticleSpawner::step(float dtime, ClientEnvironment *env)
@ -348,7 +601,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
bool unloaded = false;
const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
if (m_attached_id) {
if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
} else {
unloaded = true;
@ -379,7 +632,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
return;
for (int i = 0; i <= p.amount; i++) {
if (rand() / (float)RAND_MAX < dtime)
if (myrand_float() < dtime)
spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
}
}
@ -408,9 +661,15 @@ void ParticleManager::stepSpawners(float dtime)
{
MutexAutoLock lock(m_spawner_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
if (i->second->get_expired()) {
delete i->second;
m_particle_spawners.erase(i++);
if (i->second->getExpired()) {
// the particlespawner owns the textures, so we need to make
// sure there are no active particles before we free it
if (i->second->m_active == 0) {
delete i->second;
m_particle_spawners.erase(i++);
} else {
++i;
}
} else {
i->second->step(dtime, m_env);
++i;
@ -423,6 +682,10 @@ void ParticleManager::stepParticles(float dtime)
MutexAutoLock lock(m_particle_list_lock);
for (auto i = m_particles.begin(); i != m_particles.end();) {
if ((*i)->get_expired()) {
if ((*i)->m_parent) {
assert((*i)->m_parent->m_active != 0);
--(*i)->m_parent->m_active;
}
(*i)->remove();
delete *i;
i = m_particles.erase(i);
@ -464,13 +727,29 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
video::ITexture *texture =
client->tsrc()->getTextureForMesh(p.texture);
// texture pool
std::unique_ptr<ClientTexture[]> texpool = nullptr;
size_t txpsz = 0;
if (!p.texpool.empty()) {
txpsz = p.texpool.size();
texpool = decltype(texpool)(new ClientTexture [txpsz]);
for (size_t i = 0; i < txpsz; ++i) {
texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
}
} else {
// no texpool in use, use fallback texture
txpsz = 1;
texpool = decltype(texpool)(new ClientTexture[1] {
ClientTexture(p.texture, client->tsrc())
});
}
auto toadd = new ParticleSpawner(client, player,
p,
event->add_particlespawner.attached_id,
texture,
texpool,
txpsz,
this);
addParticleSpawner(event->add_particlespawner.id, toadd);
@ -481,7 +760,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
case CE_SPAWN_PARTICLE: {
ParticleParameters &p = *event->spawn_particle;
video::ITexture *texture;
ClientTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
@ -489,11 +768,15 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
if (p.node.getContent() != CONTENT_IGNORE) {
const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
if (!getNodeParticleParams(p.node, f, p, &texture, texpos,
texsize, &color, p.node_tile))
texture = nullptr;
getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
texsize, &color, p.node_tile);
} else {
texture = client->tsrc()->getTextureForMesh(p.texture);
/* with no particlespawner to own the texture, we need
* to save it on the heap. it will be freed when the
* particle is destroyed */
auto texstore = new ClientTexture(p.texture, client->tsrc());
texture = ClientTexRef(*texstore);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
}
@ -502,7 +785,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
if (oldsize > 0.0f)
p.size = oldsize;
if (texture) {
if (texture.ref) {
Particle *toadd = new Particle(client, player, m_env,
p, texture, texpos, texsize, color);
@ -529,7 +812,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
if (tilenum > 0 && tilenum <= 6)
texid = tilenum - 1;
else
texid = rand() % 6;
texid = myrand_range(0,5);
const TileLayer &tile = f.tiles[texid].layers[0];
p.animation.type = TAT_NONE;
@ -539,13 +822,13 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
else
*texture = tile.texture;
float size = (rand() % 8) / 64.0f;
float size = (myrand_range(0,8)) / 64.0f;
p.size = BS * size;
if (tile.scale)
size /= tile.scale;
texsize = v2f(size * 2.0f, size * 2.0f);
texpos.X = (rand() % 64) / 64.0f - texsize.X;
texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
if (tile.has_color)
*color = tile.color;
@ -577,20 +860,20 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
{
ParticleParameters p;
video::ITexture *texture;
video::ITexture *ref = nullptr;
v2f texpos, texsize;
video::SColor color;
if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color))
if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
return;
p.expirationtime = (rand() % 100) / 100.0f;
p.expirationtime = myrand_range(0, 100) / 100.0f;
// Physics
p.vel = v3f(
(rand() % 150) / 50.0f - 1.5f,
(rand() % 150) / 50.0f,
(rand() % 150) / 50.0f - 1.5f
myrand_range(-1.5f,1.5f),
myrand_range(0.f,3.f),
myrand_range(-1.5f,1.5f)
);
p.acc = v3f(
0.0f,
@ -598,9 +881,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
0.0f
);
p.pos = v3f(
(f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
(f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
(f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
(f32)pos.X + myrand_range(0.f, .5f) - .25f,
(f32)pos.Y + myrand_range(0.f, .5f) - .25f,
(f32)pos.Z + myrand_range(0.f, .5f) - .25f
);
Particle *toadd = new Particle(
@ -608,7 +891,7 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
player,
m_env,
p,
texture,
ClientTexRef(ref),
texpos,
texsize,
color);
@ -616,6 +899,12 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
addParticle(toadd);
}
void ParticleManager::reserveParticleSpace(size_t max_estimate)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.reserve(m_particles.size() + max_estimate);
}
void ParticleManager::addParticle(Particle *toadd)
{
MutexAutoLock lock(m_particle_list_lock);
@ -634,7 +923,6 @@ void ParticleManager::deleteParticleSpawner(u64 id)
MutexAutoLock lock(m_spawner_list_lock);
auto it = m_particle_spawners.find(id);
if (it != m_particle_spawners.end()) {
delete it->second;
m_particle_spawners.erase(it);
it->second->setDying();
}
}

View file

@ -31,20 +31,53 @@ class ClientEnvironment;
struct MapNode;
struct ContentFeatures;
struct ClientTexture
{
/* per-spawner structure used to store the ParticleTexture structs
* that spawned particles will refer to through ClientTexRef */
ParticleTexture tex;
video::ITexture *ref = nullptr;
ClientTexture() = default;
ClientTexture(const ClientTexture&) = default;
ClientTexture(const ServerParticleTexture& p, ITextureSource *t):
tex(p),
ref(t->getTextureForMesh(p.string)) {};
};
struct ClientTexRef
{
/* per-particle structure used to avoid massively duplicating the
* fairly large ParticleTexture struct */
ParticleTexture* tex = nullptr;
video::ITexture* ref = nullptr;
ClientTexRef() = default;
ClientTexRef(const ClientTexRef&) = default;
/* constructor used by particles spawned from a spawner */
ClientTexRef(ClientTexture& t):
tex(&t.tex), ref(t.ref) {};
/* constructor used for node particles */
ClientTexRef(decltype(ref) tp): ref(tp) {};
};
class ParticleSpawner;
class Particle : public scene::ISceneNode
{
public:
public:
Particle(
IGameDef* gamedef,
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
video::ITexture *texture,
const ClientTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color
);
~Particle() = default;
~Particle();
virtual const aabb3f &getBoundingBox() const
{
@ -69,9 +102,12 @@ class Particle : public scene::ISceneNode
bool get_expired ()
{ return m_expiration < m_time; }
ParticleSpawner *m_parent;
private:
void updateLight();
void updateVertices();
void setVertexAlpha(float a);
video::S3DVertex m_vertices[4];
float m_time = 0.0f;
@ -81,14 +117,19 @@ private:
IGameDef *m_gamedef;
aabb3f m_box;
aabb3f m_collisionbox;
ClientTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos;
v2f m_texsize;
v3f m_pos;
v3f m_velocity;
v3f m_acceleration;
v3f m_drag;
ParticleParamTypes::v3fRange m_jitter;
ParticleParamTypes::f32Range m_bounce;
LocalPlayer *m_player;
float m_size;
//! Color without lighting
video::SColor m_base_color;
//! Final rendered color
@ -102,24 +143,27 @@ private:
float m_animation_time = 0.0f;
int m_animation_frame = 0;
u8 m_glow;
float m_alpha = 0.0f;
};
class ParticleSpawner
{
public:
ParticleSpawner(IGameDef* gamedef,
ParticleSpawner(IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
video::ITexture *texture,
std::unique_ptr<ClientTexture[]> &texpool,
size_t texcount,
ParticleManager* p_manager);
~ParticleSpawner() = default;
void step(float dtime, ClientEnvironment *env);
bool get_expired ()
{ return p.amount <= 0 && p.time != 0; }
size_t m_active;
bool getExpired() const
{ return m_dying || (p.amount <= 0 && p.time != 0); }
void setDying() { m_dying = true; }
private:
void spawnParticle(ClientEnvironment *env, float radius,
@ -127,10 +171,12 @@ private:
ParticleManager *m_particlemanager;
float m_time;
bool m_dying;
IGameDef *m_gamedef;
LocalPlayer *m_player;
ParticleSpawnerParameters p;
video::ITexture *m_texture;
std::unique_ptr<ClientTexture[]> m_texpool;
size_t m_texcount;
std::vector<float> m_spawntimes;
u16 m_attached_id;
};
@ -156,6 +202,8 @@ public:
void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
const MapNode &n, const ContentFeatures &f);
void reserveParticleSpace(size_t max_estimate);
/**
* This function is only used by client particle spawners
*

View file

@ -994,18 +994,18 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
p.amount = readU16(is);
p.time = readF32(is);
p.minpos = readV3F32(is);
p.maxpos = readV3F32(is);
p.minvel = readV3F32(is);
p.maxvel = readV3F32(is);
p.minacc = readV3F32(is);
p.maxacc = readV3F32(is);
p.minexptime = readF32(is);
p.maxexptime = readF32(is);
p.minsize = readF32(is);
p.maxsize = readF32(is);
// older protocols do not support tweening, and send only
// static ranges, so we can't just use the normal serialization
// functions for the older values.
p.pos.start.legacyDeSerialize(is);
p.vel.start.legacyDeSerialize(is);
p.acc.start.legacyDeSerialize(is);
p.exptime.start.legacyDeSerialize(is);
p.size.start.legacyDeSerialize(is);
p.collisiondetection = readU8(is);
p.texture = deSerializeString32(is);
p.texture.string = deSerializeString32(is);
server_id = readU32(is);
@ -1018,6 +1018,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
p.glow = readU8(is);
p.object_collision = readU8(is);
bool legacy_format = true;
// This is kinda awful
do {
u16 tmp_param0 = readU16(is);
@ -1026,7 +1028,70 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
p.node.param0 = tmp_param0;
p.node.param2 = readU8(is);
p.node_tile = readU8(is);
} while (0);
// v >= 5.6.0
f32 tmp_sbias = readF32(is);
if (is.eof())
break;
// initial bias must be stored separately in the stream to preserve
// backwards compatibility with older clients, which do not support
// a bias field in their range "format"
p.pos.start.bias = tmp_sbias;
p.vel.start.bias = readF32(is);
p.acc.start.bias = readF32(is);
p.exptime.start.bias = readF32(is);
p.size.start.bias = readF32(is);
p.pos.end.deSerialize(is);
p.vel.end.deSerialize(is);
p.acc.end.deSerialize(is);
p.exptime.end.deSerialize(is);
p.size.end.deSerialize(is);
// properties for legacy texture field
p.texture.deSerialize(is, m_proto_ver, true);
p.drag.deSerialize(is);
p.jitter.deSerialize(is);
p.bounce.deSerialize(is);
ParticleParamTypes::deSerializeParameterValue(is, p.attractor_kind);
using ParticleParamTypes::AttractorKind;
if (p.attractor_kind != AttractorKind::none) {
p.attract.deSerialize(is);
p.attractor_origin.deSerialize(is);
p.attractor_attachment = readU16(is);
/* we only check the first bit, in order to allow this value
* to be turned into a bit flag field later if needed */
p.attractor_kill = !!(readU8(is) & 1);
if (p.attractor_kind != AttractorKind::point) {
p.attractor_direction.deSerialize(is);
p.attractor_direction_attachment = readU16(is);
}
}
p.radius.deSerialize(is);
u16 texpoolsz = readU16(is);
p.texpool.reserve(texpoolsz);
for (u16 i = 0; i < texpoolsz; ++i) {
ServerParticleTexture newtex;
newtex.deSerialize(is, m_proto_ver);
p.texpool.push_back(newtex);
}
legacy_format = false;
} while(0);
if (legacy_format) {
// there's no tweening data to be had, so we need to set the
// legacy params to constant values, otherwise everything old
// will tween to zero
p.pos.end = p.pos.start;
p.vel.end = p.vel.start;
p.acc.end = p.acc.start;
p.exptime.end = p.exptime.start;
p.size.end = p.size.start;
}
auto event = new ClientEvent();
event->type = CE_ADD_PARTICLESPAWNER;

View file

@ -207,6 +207,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Minimap modes
PROTOCOL VERSION 40:
TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added
Added new particlespawner parameters (5.6.0)
*/
#define LATEST_PROTOCOL_VERSION 40
@ -511,11 +512,12 @@ enum ToClientCommand
TOCLIENT_SPAWN_PARTICLE = 0x46,
/*
v3f1000 pos
v3f1000 velocity
v3f1000 acceleration
f1000 expirationtime
f1000 size
-- struct range<T> { T min, T max, f32 bias };
v3f pos
v3f velocity
v3f acceleration
f32 expirationtime
f32 size
u8 bool collisiondetection
u32 len
u8[len] texture
@ -524,22 +526,26 @@ enum ToClientCommand
TileAnimation animation
u8 glow
u8 object_collision
v3f drag
range<v3f> bounce
*/
TOCLIENT_ADD_PARTICLESPAWNER = 0x47,
/*
-- struct range<T> { T min, T max, f32 bias };
-- struct tween<T> { T start, T end };
u16 amount
f1000 spawntime
v3f1000 minpos
v3f1000 maxpos
v3f1000 minvel
v3f1000 maxvel
v3f1000 minacc
v3f1000 maxacc
f1000 minexptime
f1000 maxexptime
f1000 minsize
f1000 maxsize
f32 spawntime
v3f minpos
v3f maxpos
v3f minvel
v3f maxvel
v3f minacc
v3f maxacc
f32 minexptime
f32 maxexptime
f32 minsize
f32 maxsize
u8 bool collisiondetection
u32 len
u8[len] texture
@ -549,6 +555,63 @@ enum ToClientCommand
TileAnimation animation
u8 glow
u8 object_collision
f32 pos_start_bias
f32 vel_start_bias
f32 acc_start_bias
f32 exptime_start_bias
f32 size_start_bias
range<v3f> pos_end
-- i.e v3f pos_end_min
-- v3f pos_end_max
-- f32 pos_end_bias
range<v3f> vel_end
range<v3f> acc_end
tween<range<v3f>> drag
-- i.e. v3f drag_start_min
-- v3f drag_start_max
-- f32 drag_start_bias
-- v3f drag_end_min
-- v3f drag_end_max
-- f32 drag_end_bias
tween<range<v3f>> jitter
tween<range<f32>> bounce
u8 attraction_kind
none = 0
point = 1
line = 2
plane = 3
if attraction_kind > none {
tween<range<f32>> attract_strength
tween<v3f> attractor_origin
u16 attractor_origin_attachment_object_id
u8 spawner_flags
bit 1: attractor_kill (particles dies on contact)
if attraction_mode > point {
tween<v3f> attractor_angle
u16 attractor_origin_attachment_object_id
}
}
tween<range<v3f>> radius
tween<range<v3f>> drag
u16 texpool_sz
texpool_sz.times {
u8 flags
-- bit 0: animated
-- other bits free & ignored as of proto v40
tween<f32> alpha
tween<v2f> scale
if flags.animated {
TileAnimation animation
}
}
*/
TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY = 0x48, // Obsolete

View file

@ -18,7 +18,103 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "particles.h"
#include "util/serialize.h"
#include <type_traits>
using namespace ParticleParamTypes;
#define PARAM_PVFN(n) ParticleParamTypes::n##ParameterValue
v2f PARAM_PVFN(pick) (float* f, const v2f a, const v2f b) {
return v2f(
numericalBlend(f[0], a.X, b.X),
numericalBlend(f[1], a.Y, b.Y)
);
}
v3f PARAM_PVFN(pick) (float* f, const v3f a, const v3f b) {
return v3f(
numericalBlend(f[0], a.X, b.X),
numericalBlend(f[1], a.Y, b.Y),
numericalBlend(f[2], a.Z, b.Z)
);
}
v2f PARAM_PVFN(interpolate) (float fac, const v2f a, const v2f b)
{ return b.getInterpolated(a, fac); }
v3f PARAM_PVFN(interpolate) (float fac, const v3f a, const v3f b)
{ return b.getInterpolated(a, fac); }
#define PARAM_DEF_SRZR(T, wr, rd) \
void PARAM_PVFN(serialize) (std::ostream& os, T v) {wr(os,v); } \
void PARAM_PVFN(deSerialize)(std::istream& is, T& v) {v = rd(is);}
#define PARAM_DEF_NUM(T, wr, rd) PARAM_DEF_SRZR(T, wr, rd) \
T PARAM_PVFN(interpolate)(float fac, const T a, const T b) \
{ return numericalBlend<T>(fac,a,b); } \
T PARAM_PVFN(pick) (float* f, const T a, const T b) \
{ return numericalBlend<T>(f[0],a,b); }
PARAM_DEF_NUM(u8, writeU8, readU8); PARAM_DEF_NUM(s8, writeS8, readS8);
PARAM_DEF_NUM(u16, writeU16, readU16); PARAM_DEF_NUM(s16, writeS16, readS16);
PARAM_DEF_NUM(u32, writeU32, readU32); PARAM_DEF_NUM(s32, writeS32, readS32);
PARAM_DEF_NUM(f32, writeF32, readF32);
PARAM_DEF_SRZR(v2f, writeV2F32, readV2F32);
PARAM_DEF_SRZR(v3f, writeV3F32, readV3F32);
enum class ParticleTextureFlags : u8 {
/* each value specifies a bit in a bitmask; if the maximum value
* goes above 1<<7 the type of the flags field must be changed
* from u8, which will necessitate a protocol change! */
// the first bit indicates whether the texture is animated
animated = 1,
/* the next three bits indicate the blending mode of the texture
* blendmode is encoded by (flags |= (u8)blend << 1); retrieve with
* (flags & ParticleTextureFlags::blend) >> 1. note that the third
* bit is currently reserved for adding more blend modes in the future */
blend = 0x7 << 1,
};
/* define some shorthand so we don't have to repeat ourselves or use
* decltype everywhere */
using FlagT = std::underlying_type_t<ParticleTextureFlags>;
void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly) const
{
/* newPropertiesOnly is used to de/serialize parameters of the legacy texture
* field, which are encoded separately from the texspec string */
FlagT flags = 0;
if (animated)
flags |= FlagT(ParticleTextureFlags::animated);
if (blendmode != BlendMode::alpha)
flags |= FlagT(blendmode) << 1;
serializeParameterValue(os, flags);
alpha.serialize(os);
scale.serialize(os);
if (!newPropertiesOnly)
os << serializeString32(string);
if (animated)
animation.serialize(os, protocol_ver);
}
void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly)
{
FlagT flags = 0;
deSerializeParameterValue(is, flags);
animated = !!(flags & FlagT(ParticleTextureFlags::animated));
blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1);
alpha.deSerialize(is);
scale.deSerialize(is);
if (!newPropertiesOnly)
string = deSerializeString32(is);
if (animated)
animation.deSerialize(is, protocol_ver);
}
void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
{
@ -28,7 +124,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
writeF32(os, expirationtime);
writeF32(os, size);
writeU8(os, collisiondetection);
os << serializeString32(texture);
os << serializeString32(texture.string);
writeU8(os, vertical);
writeU8(os, collision_removal);
animation.serialize(os, 6); /* NOT the protocol ver */
@ -37,6 +133,20 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
writeU16(os, node.param0);
writeU8(os, node.param2);
writeU8(os, node_tile);
writeV3F32(os, drag);
jitter.serialize(os);
bounce.serialize(os);
}
template <typename T, T (reader)(std::istream& is)>
inline bool streamEndsBeforeParam(T& val, std::istream& is)
{
// This is kinda awful
T tmp = reader(is);
if (is.eof())
return true;
val = tmp;
return false;
}
void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver)
@ -47,17 +157,20 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver)
expirationtime = readF32(is);
size = readF32(is);
collisiondetection = readU8(is);
texture = deSerializeString32(is);
texture.string = deSerializeString32(is);
vertical = readU8(is);
collision_removal = readU8(is);
animation.deSerialize(is, 6); /* NOT the protocol ver */
glow = readU8(is);
object_collision = readU8(is);
// This is kinda awful
u16 tmp_param0 = readU16(is);
if (is.eof())
if (streamEndsBeforeParam<u16, readU16>(node.param0, is))
return;
node.param0 = tmp_param0;
node.param2 = readU8(is);
node_tile = readU8(is);
if (streamEndsBeforeParam<v3f, readV3F32>(drag, is))
return;
jitter.deSerialize(is);
bounce.deSerialize(is);
}

View file

@ -20,19 +20,352 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <string>
#include <sstream>
#include <vector>
#include <ctgmath>
#include <type_traits>
#include "irrlichttypes_bloated.h"
#include "tileanimation.h"
#include "mapnode.h"
#include "util/serialize.h"
#include "util/numeric.h"
// This file defines the particle-related structures that both the server and
// client need. The ParticleManager and rendering is in client/particles.h
struct CommonParticleParams {
namespace ParticleParamTypes
{
template <bool cond, typename T>
using enableIf = typename std::enable_if<cond, T>::type;
// std::enable_if_t does not appear to be present in GCC????
// std::is_enum_v also missing. wtf. these are supposed to be
// present as of c++14
template<typename T> using BlendFunction = T(float,T,T);
#define DECL_PARAM_SRZRS(type) \
void serializeParameterValue (std::ostream& os, type v); \
void deSerializeParameterValue(std::istream& is, type& r);
#define DECL_PARAM_OVERLOADS(type) DECL_PARAM_SRZRS(type) \
type interpolateParameterValue(float fac, const type a, const type b); \
type pickParameterValue (float* facs, const type a, const type b);
DECL_PARAM_OVERLOADS(u8); DECL_PARAM_OVERLOADS(s8);
DECL_PARAM_OVERLOADS(u16); DECL_PARAM_OVERLOADS(s16);
DECL_PARAM_OVERLOADS(u32); DECL_PARAM_OVERLOADS(s32);
DECL_PARAM_OVERLOADS(f32);
DECL_PARAM_OVERLOADS(v2f);
DECL_PARAM_OVERLOADS(v3f);
/* C++ is a strongly typed language. this means that enums cannot be implicitly
* cast to integers, as they can be in C. while this may sound good in principle,
* it means that our normal serialization functions cannot be called on
* enumerations unless they are explicitly cast to a particular type first. this
* is problematic, because in C++ enums can have any integral type as an underlying
* type, and that type would need to be named everywhere an enumeration is
* de/serialized.
*
* this is obviously not cool, both in terms of writing legible, succinct code,
* and in terms of robustness: the underlying type might be changed at some point,
* e.g. if a bitmask gets too big for its britches. we could use an equivalent of
* `std::to_underlying(value)` everywhere we need to deal with enumerations, but
* that's hideous and unintuitive. instead, we supply the following functions to
* transparently map enumeration types to their underlying values. */
template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
void serializeParameterValue(std::ostream& os, E k) {
serializeParameterValue(os, (std::underlying_type_t<E>)k);
}
template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
void deSerializeParameterValue(std::istream& is, E& k) {
std::underlying_type_t<E> v;
deSerializeParameterValue(is, v);
k = (E)v;
}
/* this is your brain on C++. */
template <typename T, size_t PN>
struct Parameter
{
using ValType = T;
using pickFactors = float[PN];
T val;
using This = Parameter<T, PN>;
Parameter() = default;
Parameter(const This& a) = default;
template <typename... Args>
Parameter(Args... args) : val(args...) {}
virtual void serialize(std::ostream &os) const
{ serializeParameterValue (os, this->val); }
virtual void deSerialize(std::istream &is)
{ deSerializeParameterValue(is, this->val); }
virtual T interpolate(float fac, const This& against) const
{
return interpolateParameterValue(fac, this->val, against.val);
}
static T pick(float* f, const This& a, const This& b)
{
return pickParameterValue(f, a.val, b.val);
}
operator T() const { return val; }
T operator=(T b) { return val = b; }
};
template <typename T> T numericalBlend(float fac, T min, T max)
{ return min + ((max - min) * fac); }
template <typename T, size_t N>
struct VectorParameter : public Parameter<T,N> {
using This = VectorParameter<T,N>;
template <typename... Args>
VectorParameter(Args... args) : Parameter<T,N>(args...) {}
};
template <typename T, size_t PN>
inline std::string dump(const Parameter<T,PN>& p)
{
return std::to_string(p.val);
}
template <typename T, size_t N>
inline std::string dump(const VectorParameter<T,N>& v)
{
std::ostringstream oss;
if (N == 3)
oss << PP(v.val);
else
oss << PP2(v.val);
return oss.str();
}
using u8Parameter = Parameter<u8, 1>; using s8Parameter = Parameter<s8, 1>;
using u16Parameter = Parameter<u16, 1>; using s16Parameter = Parameter<s16, 1>;
using u32Parameter = Parameter<u32, 1>; using s32Parameter = Parameter<s32, 1>;
using f32Parameter = Parameter<f32, 1>;
using v2fParameter = VectorParameter<v2f, 2>;
using v3fParameter = VectorParameter<v3f, 3>;
template <typename T>
struct RangedParameter
{
using ValType = T;
using This = RangedParameter<T>;
T min, max;
f32 bias = 0;
RangedParameter() = default;
RangedParameter(const This& a) = default;
RangedParameter(T _min, T _max) : min(_min), max(_max) {}
template <typename M> RangedParameter(M b) : min(b), max(b) {}
// these functions handle the old range serialization "format"; bias must
// be manually encoded in a separate part of the stream. NEVER ADD FIELDS
// TO THESE FUNCTIONS
void legacySerialize(std::ostream& os) const
{
min.serialize(os);
max.serialize(os);
}
void legacyDeSerialize(std::istream& is)
{
min.deSerialize(is);
max.deSerialize(is);
}
// these functions handle the format used by new fields. new fields go here
void serialize(std::ostream &os) const
{
legacySerialize(os);
writeF32(os, bias);
}
void deSerialize(std::istream &is)
{
legacyDeSerialize(is);
bias = readF32(is);
}
This interpolate(float fac, const This against) const
{
This r;
r.min = min.interpolate(fac, against.min);
r.max = max.interpolate(fac, against.max);
r.bias = bias;
return r;
}
T pickWithin() const
{
typename T::pickFactors values;
auto p = numericAbsolute(bias) + 1;
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
if (bias < 0)
values[i] = 1.0f - pow(myrand_float(), p);
else
values[i] = pow(myrand_float(), p);
}
return T::pick(values, min, max);
}
};
template <typename T>
inline std::string dump(const RangedParameter<T>& r)
{
std::ostringstream s;
s << "range<" << dump(r.min) << " ~ " << dump(r.max);
if (r.bias != 0)
s << " :: " << r.bias;
s << ">";
return s.str();
}
enum class TweenStyle : u8 { fwd, rev, pulse, flicker };
template <typename T>
struct TweenedParameter
{
using ValType = T;
using This = TweenedParameter<T>;
TweenStyle style = TweenStyle::fwd;
u16 reps = 1;
f32 beginning = 0.0f;
T start, end;
TweenedParameter() = default;
TweenedParameter(const This& a) = default;
TweenedParameter(T _start, T _end) : start(_start), end(_end) {}
template <typename M> TweenedParameter(M b) : start(b), end(b) {}
T blend(float fac) const
{
// warp time coordinates in accordance w/ settings
if (fac > beginning) {
// remap for beginning offset
auto len = 1 - beginning;
fac -= beginning;
fac /= len;
// remap for repetitions
fac *= reps;
if (fac > 1) // poor man's modulo
fac -= (decltype(reps))fac;
// remap for style
switch (style) {
case TweenStyle::fwd: /* do nothing */ break;
case TweenStyle::rev: fac = 1.0f - fac; break;
case TweenStyle::pulse:
case TweenStyle::flicker: {
if (fac > 0.5f) {
fac = 1.f - (fac*2.f - 1.f);
} else {
fac = fac * 2;
}
if (style == TweenStyle::flicker) {
fac *= myrand_range(0.7f, 1.0f);
}
}
}
if (fac>1.f)
fac = 1.f;
else if (fac<0.f)
fac = 0.f;
} else {
fac = (style == TweenStyle::rev) ? 1.f : 0.f;
}
return start.interpolate(fac, end);
}
void serialize(std::ostream &os) const
{
writeU8(os, static_cast<u8>(style));
writeU16(os, reps);
writeF32(os, beginning);
start.serialize(os);
end.serialize(os);
}
void deSerialize(std::istream &is)
{
style = static_cast<TweenStyle>(readU8(is));
reps = readU16(is);
beginning = readF32(is);
start.deSerialize(is);
end.deSerialize(is);
}
};
template <typename T>
inline std::string dump(const TweenedParameter<T>& t)
{
std::ostringstream s;
const char* icon;
switch (t.style) {
case TweenStyle::fwd: icon = ""; break;
case TweenStyle::rev: icon = ""; break;
case TweenStyle::pulse: icon = ""; break;
case TweenStyle::flicker: icon = ""; break;
}
s << "tween<";
if (t.reps != 1)
s << t.reps << "x ";
s << dump(t.start) << " "<<icon<<" " << dump(t.end) << ">";
return s.str();
}
enum class AttractorKind : u8 { none, point, line, plane };
enum class BlendMode : u8 { alpha, add, sub, screen };
// these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations
using v3fRange = RangedParameter<v3fParameter>;
using f32Range = RangedParameter<f32Parameter>;
using v2fTween = TweenedParameter<v2fParameter>;
using v3fTween = TweenedParameter<v3fParameter>;
using f32Tween = TweenedParameter<f32Parameter>;
using v3fRangeTween = TweenedParameter<v3fRange>;
using f32RangeTween = TweenedParameter<f32Range>;
#undef DECL_PARAM_SRZRS
#undef DECL_PARAM_OVERLOADS
}
struct ParticleTexture
{
bool animated = false;
ParticleParamTypes::BlendMode blendmode = ParticleParamTypes::BlendMode::alpha;
TileAnimationParams animation;
ParticleParamTypes::f32Tween alpha{1.0f};
ParticleParamTypes::v2fTween scale{v2f(1.0f)};
};
struct ServerParticleTexture : public ParticleTexture
{
std::string string;
void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false) const;
void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false);
};
struct CommonParticleParams
{
bool collisiondetection = false;
bool collision_removal = false;
bool object_collision = false;
bool vertical = false;
std::string texture;
ServerParticleTexture texture;
struct TileAnimationParams animation;
u8 glow = 0;
MapNode node;
@ -58,22 +391,42 @@ struct CommonParticleParams {
}
};
struct ParticleParameters : CommonParticleParams {
v3f pos;
v3f vel;
v3f acc;
f32 expirationtime = 1;
f32 size = 1;
struct ParticleParameters : CommonParticleParams
{
v3f pos, vel, acc, drag;
f32 size = 1, expirationtime = 1;
ParticleParamTypes::f32Range bounce;
ParticleParamTypes::v3fRange jitter;
void serialize(std::ostream &os, u16 protocol_ver) const;
void deSerialize(std::istream &is, u16 protocol_ver);
};
struct ParticleSpawnerParameters : CommonParticleParams {
struct ParticleSpawnerParameters : CommonParticleParams
{
u16 amount = 1;
v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
f32 time = 1;
f32 minexptime = 1, maxexptime = 1, minsize = 1, maxsize = 1;
std::vector<ServerParticleTexture> texpool;
ParticleParamTypes::v3fRangeTween
pos, vel, acc, drag, radius, jitter;
ParticleParamTypes::AttractorKind
attractor_kind;
ParticleParamTypes::v3fTween
attractor_origin, attractor_direction;
// object IDs
u16 attractor_attachment = 0,
attractor_direction_attachment = 0;
// do particles disappear when they cross the attractor threshold?
bool attractor_kill = true;
ParticleParamTypes::f32RangeTween
exptime{1.0f},
size {1.0f},
attract{0.0f},
bounce {0.0f};
// For historical reasons no (de-)serialization methods here
};

View file

@ -42,7 +42,7 @@ struct EnumString es_TileAnimationType[] =
{TAT_NONE, "none"},
{TAT_VERTICAL_FRAMES, "vertical_frames"},
{TAT_SHEET_2D, "sheet_2d"},
{0, NULL},
{0, nullptr},
};
/******************************************************************************/

View file

@ -100,6 +100,7 @@ void setboolfield(lua_State *L, int table,
const char *fieldname, bool value);
v3f checkFloatPos (lua_State *L, int index);
v2f check_v2f (lua_State *L, int index);
v3f check_v3f (lua_State *L, int index);
v3s16 check_v3s16 (lua_State *L, int index);

View file

@ -0,0 +1,279 @@
/*
Minetest
Copyright (C) 2021 velartrill, Lexi Hale <lexi@hale.su>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "lua_api/l_particles.h"
#include "lua_api/l_object.h"
#include "lua_api/l_internal.h"
#include "common/c_converter.h"
#include "common/c_content.h"
#include "server.h"
#include "particles.h"
namespace LuaParticleParams
{
using namespace ParticleParamTypes;
template<typename T>
inline void readNumericLuaValue(lua_State* L, T& ret)
{
if (lua_isnil(L,-1))
return;
if (std::is_integral<T>())
ret = lua_tointeger(L, -1);
else
ret = lua_tonumber(L, -1);
}
template <typename T, size_t N>
inline void readNumericLuaValue(lua_State* L, Parameter<T,N>& ret)
{
readNumericLuaValue<T>(L, ret.val);
}
// these are unfortunately necessary as C++ intentionally disallows function template
// specialization and there's no way to make template overloads reliably resolve correctly
inline void readLuaValue(lua_State* L, f32Parameter& ret) { readNumericLuaValue(L, ret); }
inline void readLuaValue(lua_State* L, f32& ret) { readNumericLuaValue(L, ret); }
inline void readLuaValue(lua_State* L, u16& ret) { readNumericLuaValue(L, ret); }
inline void readLuaValue(lua_State* L, u8& ret) { readNumericLuaValue(L, ret); }
inline void readLuaValue(lua_State* L, v3fParameter& ret)
{
if (lua_isnil(L, -1))
return;
if (lua_isnumber(L, -1)) { // shortcut for uniform vectors
auto n = lua_tonumber(L, -1);
ret = v3fParameter(n,n,n);
} else {
ret = (v3fParameter)check_v3f(L, -1);
}
}
inline void readLuaValue(lua_State* L, v2fParameter& ret)
{
if (lua_isnil(L, -1))
return;
if (lua_isnumber(L, -1)) { // shortcut for uniform vectors
auto n = lua_tonumber(L, -1);
ret = v2fParameter(n,n);
} else {
ret = (v2fParameter)check_v2f(L, -1);
}
}
inline void readLuaValue(lua_State* L, TweenStyle& ret)
{
if (lua_isnil(L, -1))
return;
static const EnumString opts[] = {
{(int)TweenStyle::fwd, "fwd"},
{(int)TweenStyle::rev, "rev"},
{(int)TweenStyle::pulse, "pulse"},
{(int)TweenStyle::flicker, "flicker"},
{0, nullptr},
};
luaL_checktype(L, -1, LUA_TSTRING);
int v = (int)TweenStyle::fwd;
if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
throw LuaError("tween style must be one of ('fwd', 'rev', 'pulse', 'flicker')");
}
ret = (TweenStyle)v;
}
inline void readLuaValue(lua_State* L, AttractorKind& ret)
{
if (lua_isnil(L, -1))
return;
static const EnumString opts[] = {
{(int)AttractorKind::none, "none"},
{(int)AttractorKind::point, "point"},
{(int)AttractorKind::line, "line"},
{(int)AttractorKind::plane, "plane"},
{0, nullptr},
};
luaL_checktype(L, -1, LUA_TSTRING);
int v = (int)AttractorKind::none;
if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
throw LuaError("attractor kind must be one of ('none', 'point', 'line', 'plane')");
}
ret = (AttractorKind)v;
}
inline void readLuaValue(lua_State* L, BlendMode& ret)
{
if (lua_isnil(L, -1))
return;
static const EnumString opts[] = {
{(int)BlendMode::alpha, "alpha"},
{(int)BlendMode::add, "add"},
{(int)BlendMode::sub, "sub"},
{(int)BlendMode::screen, "screen"},
{0, nullptr},
};
luaL_checktype(L, -1, LUA_TSTRING);
int v = (int)BlendMode::alpha;
if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')");
}
ret = (BlendMode)v;
}
template <typename T> void
readLuaValue(lua_State* L, RangedParameter<T>& field)
{
if (lua_isnil(L,-1))
return;
if (!lua_istable(L,-1)) // is this is just a literal value?
goto set_uniform;
lua_getfield(L, -1, "min");
// handle convenience syntax for non-range values
if (lua_isnil(L,-1)) {
lua_pop(L, 1);
goto set_uniform;
}
readLuaValue(L,field.min);
lua_pop(L, 1);
lua_getfield(L, -1, "max");
readLuaValue(L,field.max);
lua_pop(L, 1);
lua_getfield(L, -1, "bias");
if (!lua_isnil(L,-1))
readLuaValue(L,field.bias);
lua_pop(L, 1);
return;
set_uniform:
readLuaValue(L, field.min);
readLuaValue(L, field.max);
}
template <typename T> void
readLegacyValue(lua_State* L, const char* name, T& field) {}
template <typename T> void
readLegacyValue(lua_State* L, const char* name, RangedParameter<T>& field)
{
int tbl = lua_gettop(L);
lua_pushliteral(L, "min");
lua_pushstring(L, name);
lua_concat(L, 2);
lua_gettable(L, tbl);
if (!lua_isnil(L, -1)) {
readLuaValue(L, field.min);
}
lua_settop(L, tbl);
lua_pushliteral(L, "max");
lua_pushstring(L, name);
lua_concat(L, 2);
lua_gettable(L, tbl);
if (!lua_isnil(L, -1)) {
readLuaValue(L, field.max);
}
lua_settop(L, tbl);
}
template <typename T> void
readTweenTable(lua_State* L, const char* name, TweenedParameter<T>& field)
{
int tbl = lua_gettop(L);
lua_pushstring(L, name);
lua_pushliteral(L, "_tween");
lua_concat(L, 2);
lua_gettable(L, tbl);
if(lua_istable(L, -1)) {
int tween = lua_gettop(L);
// get the starting value
lua_pushinteger(L, 1), lua_gettable(L, tween);
readLuaValue(L, field.start);
lua_pop(L, 1);
// get the final value -- use len instead of 2 so that this
// gracefully degrades if keyframe support is later added
lua_pushinteger(L, (lua_Integer)lua_objlen(L, -1)), lua_gettable(L, tween);
readLuaValue(L, field.end);
lua_pop(L, 1);
// get the effect settings
lua_getfield(L, -1, "style");
lua_isnil(L,-1) || (readLuaValue(L, field.style), true);
lua_pop(L, 1);
lua_getfield(L, -1, "reps");
lua_isnil(L,-1) || (readLuaValue(L, field.reps), true);
lua_pop(L, 1);
lua_getfield(L, -1, "start");
lua_isnil(L,-1) || (readLuaValue(L, field.beginning), true);
lua_pop(L, 1);
goto done;
} else {
lua_pop(L,1);
}
// the table is not present; check for nonanimated values
lua_getfield(L, tbl, name);
if(!lua_isnil(L, -1)) {
readLuaValue(L, field.start);
lua_settop(L, tbl);
goto set_uniform;
} else {
lua_pop(L,1);
}
// the goto did not trigger, so this table is not present either
// check for pre-5.6.0 legacy values
readLegacyValue(L, name, field.start);
set_uniform:
field.end = field.start;
done:
lua_settop(L, tbl); // clean up after ourselves
}
inline u16 readAttachmentID(lua_State* L, const char* name)
{
u16 id = 0;
lua_getfield(L, -1, name);
if (!lua_isnil(L, -1)) {
ObjectRef *ref = ObjectRef::checkobject(L, -1);
if (auto obj = ObjectRef::getobject(ref))
id = obj->getId();
}
lua_pop(L, 1);
return id;
}
void readTexValue(lua_State* L, ServerParticleTexture& tex);
}

View file

@ -20,30 +20,50 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_particles.h"
#include "lua_api/l_object.h"
#include "lua_api/l_internal.h"
#include "lua_api/l_particleparams.h"
#include "common/c_converter.h"
#include "common/c_content.h"
#include "server.h"
#include "particles.h"
// add_particle({pos=, velocity=, acceleration=, expirationtime=,
// size=, collisiondetection=, collision_removal=, object_collision=,
// vertical=, texture=, player=})
// pos/velocity/acceleration = {x=num, y=num, z=num}
// expirationtime = num (seconds)
// size = num
// collisiondetection = bool
// collision_removal = bool
// object_collision = bool
// vertical = bool
// texture = e.g."default_wood.png"
// animation = TileAnimation definition
// glow = num
void LuaParticleParams::readTexValue(lua_State* L, ServerParticleTexture& tex)
{
StackUnroller unroll(L);
tex.animated = false;
if (lua_isstring(L, -1)) {
tex.string = lua_tostring(L, -1);
return;
}
luaL_checktype(L, -1, LUA_TTABLE);
lua_getfield(L, -1, "name");
tex.string = luaL_checkstring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "animation");
if (! lua_isnil(L, -1)) {
tex.animated = true;
tex.animation = read_animation_definition(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, -1, "blend");
LuaParticleParams::readLuaValue(L, tex.blendmode);
lua_pop(L, 1);
LuaParticleParams::readTweenTable(L, "alpha", tex.alpha);
LuaParticleParams::readTweenTable(L, "scale", tex.scale);
}
// add_particle({...})
int ModApiParticles::l_add_particle(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
// Get parameters
struct ParticleParameters p;
ParticleParameters p;
std::string playername;
if (lua_gettop(L) > 1) // deprecated
@ -56,7 +76,7 @@ int ModApiParticles::l_add_particle(lua_State *L)
p.expirationtime = luaL_checknumber(L, 4);
p.size = luaL_checknumber(L, 5);
p.collisiondetection = readParam<bool>(L, 6);
p.texture = luaL_checkstring(L, 7);
p.texture.string = luaL_checkstring(L, 7);
if (lua_gettop(L) == 8) // only spawn for a single player
playername = luaL_checkstring(L, 8);
}
@ -108,7 +128,12 @@ int ModApiParticles::l_add_particle(lua_State *L)
p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
p.texture = getstringfield_default(L, 1, "texture", p.texture);
lua_getfield(L, 1, "texture");
if (!lua_isnil(L, -1)) {
LuaParticleParams::readTexValue(L, p.texture);
}
lua_pop(L, 1);
p.glow = getintfield_default(L, 1, "glow", p.glow);
lua_getfield(L, 1, "node");
@ -119,34 +144,26 @@ int ModApiParticles::l_add_particle(lua_State *L)
p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
playername = getstringfield_default(L, 1, "playername", "");
lua_getfield(L, 1, "drag");
if (lua_istable(L, -1))
p.drag = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "jitter");
LuaParticleParams::readLuaValue(L, p.jitter);
lua_pop(L, 1);
lua_getfield(L, 1, "bounce");
LuaParticleParams::readLuaValue(L, p.bounce);
lua_pop(L, 1);
}
getServer(L)->spawnParticle(playername, p);
return 1;
}
// add_particlespawner({amount=, time=,
// minpos=, maxpos=,
// minvel=, maxvel=,
// minacc=, maxacc=,
// minexptime=, maxexptime=,
// minsize=, maxsize=,
// collisiondetection=,
// collision_removal=,
// object_collision=,
// vertical=,
// texture=,
// player=})
// minpos/maxpos/minvel/maxvel/minacc/maxacc = {x=num, y=num, z=num}
// minexptime/maxexptime = num (seconds)
// minsize/maxsize = num
// collisiondetection = bool
// collision_removal = bool
// object_collision = bool
// vertical = bool
// texture = e.g."default_wood.png"
// animation = TileAnimation definition
// glow = num
// add_particlespawner({...})
int ModApiParticles::l_add_particlespawner(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
@ -156,24 +173,31 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
ServerActiveObject *attached = NULL;
std::string playername;
using namespace ParticleParamTypes;
if (lua_gettop(L) > 1) //deprecated
{
log_deprecated(L, "Deprecated add_particlespawner call with "
"individual parameters instead of definition");
p.amount = luaL_checknumber(L, 1);
p.time = luaL_checknumber(L, 2);
p.minpos = check_v3f(L, 3);
p.maxpos = check_v3f(L, 4);
p.minvel = check_v3f(L, 5);
p.maxvel = check_v3f(L, 6);
p.minacc = check_v3f(L, 7);
p.maxacc = check_v3f(L, 8);
p.minexptime = luaL_checknumber(L, 9);
p.maxexptime = luaL_checknumber(L, 10);
p.minsize = luaL_checknumber(L, 11);
p.maxsize = luaL_checknumber(L, 12);
auto minpos = check_v3f(L, 3);
auto maxpos = check_v3f(L, 4);
auto minvel = check_v3f(L, 5);
auto maxvel = check_v3f(L, 6);
auto minacc = check_v3f(L, 7);
auto maxacc = check_v3f(L, 8);
auto minexptime = luaL_checknumber(L, 9);
auto maxexptime = luaL_checknumber(L, 10);
auto minsize = luaL_checknumber(L, 11);
auto maxsize = luaL_checknumber(L, 12);
p.pos = v3fRange(minpos, maxpos);
p.vel = v3fRange(minvel, maxvel);
p.acc = v3fRange(minacc, maxacc);
p.exptime = f32Range(minexptime, maxexptime);
p.size = f32Range(minsize, maxsize);
p.collisiondetection = readParam<bool>(L, 13);
p.texture = luaL_checkstring(L, 14);
p.texture.string = luaL_checkstring(L, 14);
if (lua_gettop(L) == 15) // only spawn for a single player
playername = luaL_checkstring(L, 15);
}
@ -182,40 +206,46 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
p.amount = getintfield_default(L, 1, "amount", p.amount);
p.time = getfloatfield_default(L, 1, "time", p.time);
lua_getfield(L, 1, "minpos");
if (lua_istable(L, -1))
p.minpos = check_v3f(L, -1);
lua_pop(L, 1);
// set default values
p.exptime = 1;
p.size = 1;
lua_getfield(L, 1, "maxpos");
if (lua_istable(L, -1))
p.maxpos = check_v3f(L, -1);
lua_pop(L, 1);
// read spawner parameters from the table
LuaParticleParams::readTweenTable(L, "pos", p.pos);
LuaParticleParams::readTweenTable(L, "vel", p.vel);
LuaParticleParams::readTweenTable(L, "acc", p.acc);
LuaParticleParams::readTweenTable(L, "size", p.size);
LuaParticleParams::readTweenTable(L, "exptime", p.exptime);
LuaParticleParams::readTweenTable(L, "drag", p.drag);
LuaParticleParams::readTweenTable(L, "jitter", p.jitter);
LuaParticleParams::readTweenTable(L, "bounce", p.bounce);
lua_getfield(L, 1, "attract");
if (!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
lua_getfield(L, -1, "kind");
LuaParticleParams::readLuaValue(L, p.attractor_kind);
lua_pop(L,1);
lua_getfield(L, 1, "minvel");
if (lua_istable(L, -1))
p.minvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "die_on_contact");
if (!lua_isnil(L, -1))
p.attractor_kill = readParam<bool>(L, -1);
lua_pop(L,1);
lua_getfield(L, 1, "maxvel");
if (lua_istable(L, -1))
p.maxvel = check_v3f(L, -1);
lua_pop(L, 1);
if (p.attractor_kind != AttractorKind::none) {
LuaParticleParams::readTweenTable(L, "strength", p.attract);
LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin);
p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached");
if (p.attractor_kind != AttractorKind::point) {
LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction);
p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached");
}
}
} else {
p.attractor_kind = AttractorKind::none;
}
lua_pop(L,1);
LuaParticleParams::readTweenTable(L, "radius", p.radius);
lua_getfield(L, 1, "minacc");
if (lua_istable(L, -1))
p.minacc = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxacc");
if (lua_istable(L, -1))
p.maxacc = check_v3f(L, -1);
lua_pop(L, 1);
p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
p.collisiondetection = getboolfield_default(L, 1,
"collisiondetection", p.collisiondetection);
p.collision_removal = getboolfield_default(L, 1,
@ -234,11 +264,29 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
attached = ObjectRef::getobject(ref);
}
lua_getfield(L, 1, "texture");
if (!lua_isnil(L, -1)) {
LuaParticleParams::readTexValue(L, p.texture);
}
lua_pop(L, 1);
p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
p.texture = getstringfield_default(L, 1, "texture", p.texture);
playername = getstringfield_default(L, 1, "playername", "");
p.glow = getintfield_default(L, 1, "glow", p.glow);
lua_getfield(L, 1, "texpool");
if (lua_istable(L, -1)) {
size_t tl = lua_objlen(L, -1);
p.texpool.reserve(tl);
for (size_t i = 0; i < tl; ++i) {
lua_pushinteger(L, i+1), lua_gettable(L, -2);
p.texpool.emplace_back();
LuaParticleParams::readTexValue(L, p.texpool.back());
lua_pop(L,1);
}
}
lua_pop(L, 1);
lua_getfield(L, 1, "node");
if (lua_istable(L, -1))
p.node = readnode(L, -1, getGameDef(L)->ndef());

View file

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_converter.h"
#include "lua_api/l_internal.h"
#include "lua_api/l_object.h"
#include "lua_api/l_particleparams.h"
#include "client/particles.h"
#include "client/client.h"
#include "client/clientevent.h"
@ -49,6 +50,19 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
p.acc = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "drag");
if (lua_istable(L, -1))
p.drag = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "jitter");
LuaParticleParams::readLuaValue(L, p.jitter);
lua_pop(L, 1);
lua_getfield(L, 1, "bounce");
LuaParticleParams::readLuaValue(L, p.bounce);
lua_pop(L, 1);
p.expirationtime = getfloatfield_default(L, 1, "expirationtime",
p.expirationtime);
p.size = getfloatfield_default(L, 1, "size", p.size);
@ -64,7 +78,11 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
p.texture = getstringfield_default(L, 1, "texture", p.texture);
lua_getfield(L, 1, "texture");
if (!lua_isnil(L, -1)) {
LuaParticleParams::readTexValue(L,p.texture);
}
lua_pop(L, 1);
p.glow = getintfield_default(L, 1, "glow", p.glow);
lua_getfield(L, 1, "node");
@ -88,44 +106,50 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L)
// Get parameters
ParticleSpawnerParameters p;
p.amount = getintfield_default(L, 1, "amount", p.amount);
p.time = getfloatfield_default(L, 1, "time", p.time);
lua_getfield(L, 1, "minpos");
if (lua_istable(L, -1))
p.minpos = check_v3f(L, -1);
lua_pop(L, 1);
// set default values
p.exptime = 1;
p.size = 1;
lua_getfield(L, 1, "maxpos");
if (lua_istable(L, -1))
p.maxpos = check_v3f(L, -1);
lua_pop(L, 1);
// read spawner parameters from the table
using namespace ParticleParamTypes;
LuaParticleParams::readTweenTable(L, "pos", p.pos);
LuaParticleParams::readTweenTable(L, "vel", p.vel);
LuaParticleParams::readTweenTable(L, "acc", p.acc);
LuaParticleParams::readTweenTable(L, "size", p.size);
LuaParticleParams::readTweenTable(L, "exptime", p.exptime);
LuaParticleParams::readTweenTable(L, "drag", p.drag);
LuaParticleParams::readTweenTable(L, "jitter", p.jitter);
LuaParticleParams::readTweenTable(L, "bounce", p.bounce);
lua_getfield(L, 1, "attract");
if (!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TTABLE);
lua_getfield(L, -1, "kind");
LuaParticleParams::readLuaValue(L, p.attractor_kind);
lua_pop(L,1);
lua_getfield(L, 1, "minvel");
if (lua_istable(L, -1))
p.minvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "die_on_contact");
if (!lua_isnil(L, -1))
p.attractor_kill = readParam<bool>(L, -1);
lua_pop(L,1);
lua_getfield(L, 1, "maxvel");
if (lua_istable(L, -1))
p.maxvel = check_v3f(L, -1);
lua_pop(L, 1);
if (p.attractor_kind != AttractorKind::none) {
LuaParticleParams::readTweenTable(L, "strength", p.attract);
LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin);
p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached");
if (p.attractor_kind != AttractorKind::point) {
LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction);
p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached");
}
}
} else {
p.attractor_kind = AttractorKind::none;
}
lua_pop(L,1);
LuaParticleParams::readTweenTable(L, "radius", p.radius);
lua_getfield(L, 1, "minacc");
if (lua_istable(L, -1))
p.minacc = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxacc");
if (lua_istable(L, -1))
p.maxacc = check_v3f(L, -1);
lua_pop(L, 1);
p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
p.collisiondetection = getboolfield_default(L, 1,
"collisiondetection", p.collisiondetection);
p.collision_removal = getboolfield_default(L, 1,
@ -137,10 +161,28 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L)
p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "texture");
if (!lua_isnil(L, -1)) {
LuaParticleParams::readTexValue(L, p.texture);
}
lua_pop(L, 1);
p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
p.texture = getstringfield_default(L, 1, "texture", p.texture);
p.glow = getintfield_default(L, 1, "glow", p.glow);
lua_getfield(L, 1, "texpool");
if (lua_istable(L, -1)) {
size_t tl = lua_objlen(L, -1);
p.texpool.reserve(tl);
for (size_t i = 0; i < tl; ++i) {
lua_pushinteger(L, i+1), lua_gettable(L, -2);
p.texpool.emplace_back();
LuaParticleParams::readTexValue(L, p.texpool.back());
lua_pop(L,1);
}
}
lua_pop(L, 1);
lua_getfield(L, 1, "node");
if (lua_istable(L, -1))
p.node = readnode(L, -1, getGameDef(L)->ndef());

View file

@ -160,6 +160,7 @@ v3f ServerPlayingSound::getPos(ServerEnvironment *env, bool *pos_exists) const
return sao->getBasePosition();
}
}
return v3f(0,0,0);
}
@ -1599,7 +1600,12 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
if (peer_id == PEER_ID_INEXISTENT) {
std::vector<session_t> clients = m_clients.getClientIDs();
const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
const v3f pos = (
p.pos.start.min.val +
p.pos.start.max.val +
p.pos.end.min.val +
p.pos.end.max.val
) / 4.0f * BS;
const float radius_sq = radius * radius;
/* Don't send short-lived spawners to distant players.
* This could be replaced with proper tracking at some point. */
@ -1627,11 +1633,19 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id);
pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel
<< p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime
<< p.minsize << p.maxsize << p.collisiondetection;
pkt << p.amount << p.time;
{ // serialize legacy fields
std::ostringstream os(std::ios_base::binary);
p.pos.start.legacySerialize(os);
p.vel.start.legacySerialize(os);
p.acc.start.legacySerialize(os);
p.exptime.start.legacySerialize(os);
p.size.start.legacySerialize(os);
pkt.putRawString(os.str());
}
pkt << p.collisiondetection;
pkt.putLongString(p.texture);
pkt.putLongString(p.texture.string);
pkt << id << p.vertical << p.collision_removal << attached_id;
{
@ -1642,6 +1656,51 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
pkt << p.glow << p.object_collision;
pkt << p.node.param0 << p.node.param2 << p.node_tile;
{ // serialize new fields
// initial bias for older properties
pkt << p.pos.start.bias
<< p.vel.start.bias
<< p.acc.start.bias
<< p.exptime.start.bias
<< p.size.start.bias;
std::ostringstream os(std::ios_base::binary);
// final tween frames of older properties
p.pos.end.serialize(os);
p.vel.end.serialize(os);
p.acc.end.serialize(os);
p.exptime.end.serialize(os);
p.size.end.serialize(os);
// properties for legacy texture field
p.texture.serialize(os, protocol_version, true);
// new properties
p.drag.serialize(os);
p.jitter.serialize(os);
p.bounce.serialize(os);
ParticleParamTypes::serializeParameterValue(os, p.attractor_kind);
if (p.attractor_kind != ParticleParamTypes::AttractorKind::none) {
p.attract.serialize(os);
p.attractor_origin.serialize(os);
writeU16(os, p.attractor_attachment); /* object ID */
writeU8(os, p.attractor_kill);
if (p.attractor_kind != ParticleParamTypes::AttractorKind::point) {
p.attractor_direction.serialize(os);
writeU16(os, p.attractor_direction_attachment);
}
}
p.radius.serialize(os);
ParticleParamTypes::serializeParameterValue(os, (u16)p.texpool.size());
for (const auto& tex : p.texpool) {
tex.serialize(os, protocol_version);
}
pkt.putRawString(os.str());
}
Send(&pkt);
}
@ -3267,7 +3326,7 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask)
u32 new_hud_flags = (player->hud_flags & ~mask) | flags;
if (new_hud_flags == player->hud_flags) // no change
return true;
SendHUDSetFlags(player->getPeerId(), flags, mask);
player->hud_flags = new_hud_flags;
@ -3692,8 +3751,8 @@ v3f Server::findSpawnPos()
s32 range = MYMIN(1 + i, range_max);
// We're going to try to throw the player to this position
v2s16 nodepos2d = v2s16(
-range + (myrand() % (range * 2)),
-range + (myrand() % (range * 2)));
-range + myrand_range(0, range*2),
-range + myrand_range(0, range*2));
// Get spawn level at point
s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
// Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to

View file

@ -46,11 +46,22 @@ void myrand_bytes(void *out, size_t len)
g_pcgrand.bytes(out, len);
}
float myrand_float()
{
u32 uv = g_pcgrand.next();
return (float)uv / (float)U32_MAX;
}
int myrand_range(int min, int max)
{
return g_pcgrand.range(min, max);
}
float myrand_range(float min, float max)
{
return (max-min) * myrand_float() + min;
}
/*
64-bit unaligned version of MurmurHash

View file

@ -223,6 +223,8 @@ u32 myrand();
void mysrand(unsigned int seed);
void myrand_bytes(void *out, size_t len);
int myrand_range(int min, int max);
float myrand_range(float min, float max);
float myrand_float();
/*
Miscellaneous functions
@ -446,3 +448,24 @@ inline irr::video::SColor multiplyColorValue(const irr::video::SColor &color, fl
core::clamp<u32>(color.getGreen() * mod, 0, 255),
core::clamp<u32>(color.getBlue() * mod, 0, 255));
}
template <typename T> inline T numericAbsolute(T v) { return v < 0 ? T(-v) : v; }
template <typename T> inline T numericSign(T v) { return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); }
inline v3f vecAbsolute(v3f v)
{
return v3f(
numericAbsolute(v.X),
numericAbsolute(v.Y),
numericAbsolute(v.Z)
);
}
inline v3f vecSign(v3f v)
{
return v3f(
numericSign(v.X),
numericSign(v.Y),
numericSign(v.Z)
);
}

View file

@ -45,7 +45,7 @@ public:
Buffer()
{
m_size = 0;
data = NULL;
data = nullptr;
}
Buffer(unsigned int size)
{
@ -53,7 +53,7 @@ public:
if(size != 0)
data = new T[size];
else
data = NULL;
data = nullptr;
}
// Disable class copy
@ -82,7 +82,7 @@ public:
memcpy(data, t, size);
}
else
data = NULL;
data = nullptr;
}
~Buffer()
@ -166,7 +166,7 @@ public:
if(m_size != 0)
data = new T[m_size];
else
data = NULL;
data = nullptr;
refcount = new unsigned int;
memset(data,0,sizeof(T)*m_size);
(*refcount) = 1;
@ -201,7 +201,7 @@ public:
memcpy(data, t, m_size);
}
else
data = NULL;
data = nullptr;
refcount = new unsigned int;
(*refcount) = 1;
}
@ -216,7 +216,7 @@ public:
memcpy(data, *buffer, buffer.getSize());
}
else
data = NULL;
data = nullptr;
refcount = new unsigned int;
(*refcount) = 1;
}
@ -256,3 +256,4 @@ private:
unsigned int m_size;
unsigned int *refcount;
};