]> git.lizzy.rs Git - minetest.git/blob - src/particles.cpp
Fix rendering glitches when far from the center of the map
[minetest.git] / src / particles.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "particles.h"
21 #include "constants.h"
22 #include "debug.h"
23 #include "main.h" // For g_profiler and g_settings
24 #include "settings.h"
25 #include "tile.h"
26 #include "gamedef.h"
27 #include "collision.h"
28 #include <stdlib.h>
29 #include "util/numeric.h"
30 #include "light.h"
31 #include "environment.h"
32 #include "clientmap.h"
33 #include "mapnode.h"
34
35 /*
36         Utility
37 */
38
39 v3f random_v3f(v3f min, v3f max)
40 {
41         return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
42                         rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
43                         rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
44 }
45
46 std::vector<Particle*> all_particles;
47 std::map<u32, ParticleSpawner*> all_particlespawners;
48
49 Particle::Particle(
50         IGameDef *gamedef,
51         scene::ISceneManager* smgr,
52         LocalPlayer *player,
53         ClientEnvironment &env,
54         v3f pos,
55         v3f velocity,
56         v3f acceleration,
57         float expirationtime,
58         float size,
59         bool collisiondetection,
60         bool vertical,
61         video::ITexture *texture,
62         v2f texpos,
63         v2f texsize
64 ):
65         scene::ISceneNode(smgr->getRootSceneNode(), smgr)
66 {
67         // Misc
68         m_gamedef = gamedef;
69         m_env = &env;
70
71         // Texture
72         m_material.setFlag(video::EMF_LIGHTING, false);
73         m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
74         m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
75         m_material.setFlag(video::EMF_FOG_ENABLE, true);
76         m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
77         m_material.setTexture(0, texture);
78         m_texpos = texpos;
79         m_texsize = texsize;
80
81
82         // Particle related
83         m_pos = pos;
84         m_velocity = velocity;
85         m_acceleration = acceleration;
86         m_expiration = expirationtime;
87         m_time = 0;
88         m_player = player;
89         m_size = size;
90         m_collisiondetection = collisiondetection;
91         m_vertical = vertical;
92
93         // Irrlicht stuff
94         m_collisionbox = core::aabbox3d<f32>
95                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
96         this->setAutomaticCulling(scene::EAC_OFF);
97
98         // Init lighting
99         updateLight();
100
101         // Init model
102         updateVertices();
103
104         all_particles.push_back(this);
105 }
106
107 Particle::~Particle()
108 {
109 }
110
111 void Particle::OnRegisterSceneNode()
112 {
113         if (IsVisible)
114         {
115                 SceneManager->registerNodeForRendering
116                                 (this, scene::ESNRP_TRANSPARENT);
117                 SceneManager->registerNodeForRendering
118                                 (this, scene::ESNRP_SOLID);
119         }
120
121         ISceneNode::OnRegisterSceneNode();
122 }
123
124 void Particle::render()
125 {
126         // TODO: Render particles in front of water and the selectionbox
127
128         video::IVideoDriver* driver = SceneManager->getVideoDriver();
129         driver->setMaterial(m_material);
130         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
131
132         u16 indices[] = {0,1,2, 2,3,0};
133         driver->drawVertexPrimitiveList(m_vertices, 4,
134                         indices, 2, video::EVT_STANDARD,
135                         scene::EPT_TRIANGLES, video::EIT_16BIT);
136 }
137
138 void Particle::step(float dtime)
139 {
140         m_time += dtime;
141         if (m_collisiondetection)
142         {
143                 core::aabbox3d<f32> box = m_collisionbox;
144                 v3f p_pos = m_pos*BS;
145                 v3f p_velocity = m_velocity*BS;
146                 v3f p_acceleration = m_acceleration*BS;
147                 collisionMoveSimple(m_env, m_gamedef,
148                         BS*0.5, box,
149                         0, dtime,
150                         p_pos, p_velocity, p_acceleration);
151                 m_pos = p_pos/BS;
152                 m_velocity = p_velocity/BS;
153                 m_acceleration = p_acceleration/BS;
154         }
155         else
156         {
157                 m_velocity += m_acceleration * dtime;
158                 m_pos += m_velocity * dtime;
159         }
160
161         // Update lighting
162         updateLight();
163
164         // Update model
165         updateVertices();
166 }
167
168 void Particle::updateLight()
169 {
170         u8 light = 0;
171         try{
172                 v3s16 p = v3s16(
173                         floor(m_pos.X+0.5),
174                         floor(m_pos.Y+0.5),
175                         floor(m_pos.Z+0.5)
176                 );
177                 MapNode n = m_env->getClientMap().getNode(p);
178                 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
179         }
180         catch(InvalidPositionException &e){
181                 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
182         }
183         m_light = decode_light(light);
184 }
185
186 void Particle::updateVertices()
187 {
188         video::SColor c(255, m_light, m_light, m_light);
189         f32 tx0 = m_texpos.X;
190         f32 tx1 = m_texpos.X + m_texsize.X;
191         f32 ty0 = m_texpos.Y;
192         f32 ty1 = m_texpos.Y + m_texsize.Y;
193
194         m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
195                         c, tx0, ty1);
196         m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
197                         c, tx1, ty1);
198         m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
199                         c, tx1, ty0);
200         m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
201                         c, tx0, ty0);
202
203         v3s16 camera_offset = m_env->getCameraOffset();
204         for(u16 i=0; i<4; i++)
205         {
206                 if (m_vertical) {
207                         v3f ppos = m_player->getPosition()/BS;
208                         m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
209                 } else {
210                         m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
211                         m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
212                 }
213                 m_box.addInternalPoint(m_vertices[i].Pos);
214                 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
215         }
216 }
217
218 /*
219         Helpers
220 */
221
222
223 void allparticles_step (float dtime)
224 {
225         for(std::vector<Particle*>::iterator i = all_particles.begin();
226                         i != all_particles.end();)
227         {
228                 if ((*i)->get_expired())
229                 {
230                         (*i)->remove();
231                         delete *i;
232                         i = all_particles.erase(i);
233                 }
234                 else
235                 {
236                         (*i)->step(dtime);
237                         i++;
238                 }
239         }
240 }
241
242 void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
243                 LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
244                 const TileSpec tiles[])
245 {
246         for (u16 j = 0; j < 32; j++) // set the amount of particles here
247         {
248                 addNodeParticle(gamedef, smgr, player, env, pos, tiles);
249         }
250 }
251
252 void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
253                 LocalPlayer *player, ClientEnvironment &env,
254                 v3s16 pos, const TileSpec tiles[])
255 {
256         addNodeParticle(gamedef, smgr, player, env, pos, tiles);
257 }
258
259 // add a particle of a node
260 // used by digging and punching particles
261 void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
262                 LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
263                 const TileSpec tiles[])
264 {
265         // Texture
266         u8 texid = myrand_range(0,5);
267         video::ITexture *texture = tiles[texid].texture;
268
269         // Only use first frame of animated texture
270         f32 ymax = 1;
271         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
272                 ymax /= tiles[texid].animation_frame_count;
273
274         float size = rand()%64/512.;
275         float visual_size = BS*size;
276         v2f texsize(size*2, ymax*size*2);
277         v2f texpos;
278         texpos.X = ((rand()%64)/64.-texsize.X);
279         texpos.Y = ymax*((rand()%64)/64.-texsize.Y);
280
281         // Physics
282         v3f velocity(   (rand()%100/50.-1)/1.5,
283                         rand()%100/35.,
284                         (rand()%100/50.-1)/1.5);
285
286         v3f acceleration(0,-9,0);
287         v3f particlepos = v3f(
288                 (f32)pos.X+rand()%100/200.-0.25,
289                 (f32)pos.Y+rand()%100/200.-0.25,
290                 (f32)pos.Z+rand()%100/200.-0.25
291         );
292
293         new Particle(
294                 gamedef,
295                 smgr,
296                 player,
297                 env,
298                 particlepos,
299                 velocity,
300                 acceleration,
301                 rand()%100/100., // expiration time
302                 visual_size,
303                 true,
304                 false,
305                 texture,
306                 texpos,
307                 texsize);
308 }
309
310 /*
311         ParticleSpawner
312 */
313
314 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
315         u16 amount, float time,
316         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
317         float minexptime, float maxexptime, float minsize, float maxsize,
318         bool collisiondetection, bool vertical, video::ITexture *texture, u32 id)
319 {
320         m_gamedef = gamedef;
321         m_smgr = smgr;
322         m_player = player;
323         m_amount = amount;
324         m_spawntime = time;
325         m_minpos = minpos;
326         m_maxpos = maxpos;
327         m_minvel = minvel;
328         m_maxvel = maxvel;
329         m_minacc = minacc;
330         m_maxacc = maxacc;
331         m_minexptime = minexptime;
332         m_maxexptime = maxexptime;
333         m_minsize = minsize;
334         m_maxsize = maxsize;
335         m_collisiondetection = collisiondetection;
336         m_vertical = vertical;
337         m_texture = texture;
338         m_time = 0;
339
340         for (u16 i = 0; i<=m_amount; i++)
341         {
342                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
343                 m_spawntimes.push_back(spawntime);
344         }
345
346         all_particlespawners.insert(std::pair<u32, ParticleSpawner*>(id, this));
347 }
348
349 ParticleSpawner::~ParticleSpawner() {}
350
351 void ParticleSpawner::step(float dtime, ClientEnvironment &env)
352 {
353         m_time += dtime;
354
355         if (m_spawntime != 0) // Spawner exists for a predefined timespan
356         {
357                 for(std::vector<float>::iterator i = m_spawntimes.begin();
358                                 i != m_spawntimes.end();)
359                 {
360                         if ((*i) <= m_time && m_amount > 0)
361                         {
362                                 m_amount--;
363
364                                 v3f pos = random_v3f(m_minpos, m_maxpos);
365                                 v3f vel = random_v3f(m_minvel, m_maxvel);
366                                 v3f acc = random_v3f(m_minacc, m_maxacc);
367                                 float exptime = rand()/(float)RAND_MAX
368                                                 *(m_maxexptime-m_minexptime)
369                                                 +m_minexptime;
370                                 float size = rand()/(float)RAND_MAX
371                                                 *(m_maxsize-m_minsize)
372                                                 +m_minsize;
373
374                                 new Particle(
375                                         m_gamedef,
376                                         m_smgr,
377                                         m_player,
378                                         env,
379                                         pos,
380                                         vel,
381                                         acc,
382                                         exptime,
383                                         size,
384                                         m_collisiondetection,
385                                         m_vertical,
386                                         m_texture,
387                                         v2f(0.0, 0.0),
388                                         v2f(1.0, 1.0));
389                                 i = m_spawntimes.erase(i);
390                         }
391                         else
392                         {
393                                 i++;
394                         }
395                 }
396         }
397         else // Spawner exists for an infinity timespan, spawn on a per-second base
398         {
399                 for (int i = 0; i <= m_amount; i++)
400                 {
401                         if (rand()/(float)RAND_MAX < dtime)
402                         {
403                                 v3f pos = random_v3f(m_minpos, m_maxpos);
404                                 v3f vel = random_v3f(m_minvel, m_maxvel);
405                                 v3f acc = random_v3f(m_minacc, m_maxacc);
406                                 float exptime = rand()/(float)RAND_MAX
407                                                 *(m_maxexptime-m_minexptime)
408                                                 +m_minexptime;
409                                 float size = rand()/(float)RAND_MAX
410                                                 *(m_maxsize-m_minsize)
411                                                 +m_minsize;
412
413                                 new Particle(
414                                         m_gamedef,
415                                         m_smgr,
416                                         m_player,
417                                         env,
418                                         pos,
419                                         vel,
420                                         acc,
421                                         exptime,
422                                         size,
423                                         m_collisiondetection,
424                                         m_vertical,
425                                         m_texture,
426                                         v2f(0.0, 0.0),
427                                         v2f(1.0, 1.0));
428                         }
429                 }
430         }
431 }
432
433 void allparticlespawners_step (float dtime, ClientEnvironment &env)
434 {
435         for(std::map<u32, ParticleSpawner*>::iterator i = 
436                         all_particlespawners.begin();
437                         i != all_particlespawners.end();)
438         {
439                 if (i->second->get_expired())
440                 {
441                         delete i->second;
442                         all_particlespawners.erase(i++);
443                 }
444                 else
445                 {
446                         i->second->step(dtime, env);
447                         i++;
448                 }
449         }
450 }
451
452 void delete_particlespawner (u32 id)
453 {
454         if (all_particlespawners.find(id) != all_particlespawners.end())
455         {
456                 delete all_particlespawners.find(id)->second;
457                 all_particlespawners.erase(id);
458         }
459 }
460
461 void clear_particles ()
462 {
463         for(std::map<u32, ParticleSpawner*>::iterator i =
464                         all_particlespawners.begin();
465                         i != all_particlespawners.end();)
466         {
467                 delete i->second;
468                 all_particlespawners.erase(i++);
469         }
470
471         for(std::vector<Particle*>::iterator i =
472                         all_particles.begin();
473                         i != all_particles.end();)
474         {
475                 (*i)->remove();
476                 delete *i;
477                 i = all_particles.erase(i);
478         }
479 }