Predict param2 of facedir nodes and attachment of attached_node nodes
[oweals/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         AtlasPointer ap
61 ):
62         scene::ISceneNode(smgr->getRootSceneNode(), smgr)
63 {
64         // Misc
65         m_gamedef = gamedef;
66
67         // Texture
68         m_material.setFlag(video::EMF_LIGHTING, false);
69         m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
70         m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
71         m_material.setFlag(video::EMF_FOG_ENABLE, true);
72         m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
73         m_material.setTexture(0, ap.atlas);
74         m_ap = ap;
75
76
77         // Particle related
78         m_pos = pos;
79         m_velocity = velocity;
80         m_acceleration = acceleration;
81         m_expiration = expirationtime;
82         m_time = 0;
83         m_player = player;
84         m_size = size;
85         m_collisiondetection = collisiondetection;
86
87         // Irrlicht stuff
88         m_collisionbox = core::aabbox3d<f32>
89                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
90         this->setAutomaticCulling(scene::EAC_OFF);
91
92         // Init lighting
93         updateLight(env);
94
95         // Init model
96         updateVertices();
97
98         all_particles.push_back(this);
99 }
100
101 Particle::~Particle()
102 {
103 }
104
105 void Particle::OnRegisterSceneNode()
106 {
107         if (IsVisible)
108         {
109                 SceneManager->registerNodeForRendering
110                                 (this, scene::ESNRP_TRANSPARENT);
111                 SceneManager->registerNodeForRendering
112                                 (this, scene::ESNRP_SOLID);
113         }
114
115         ISceneNode::OnRegisterSceneNode();
116 }
117
118 void Particle::render()
119 {
120         // TODO: Render particles in front of water and the selectionbox
121
122         video::IVideoDriver* driver = SceneManager->getVideoDriver();
123         driver->setMaterial(m_material);
124         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
125
126         u16 indices[] = {0,1,2, 2,3,0};
127         driver->drawVertexPrimitiveList(m_vertices, 4,
128                         indices, 2, video::EVT_STANDARD,
129                         scene::EPT_TRIANGLES, video::EIT_16BIT);
130 }
131
132 void Particle::step(float dtime, ClientEnvironment &env)
133 {
134         m_time += dtime;
135         if (m_collisiondetection)
136         {
137                 core::aabbox3d<f32> box = m_collisionbox;
138                 v3f p_pos = m_pos*BS;
139                 v3f p_velocity = m_velocity*BS;
140                 v3f p_acceleration = m_acceleration*BS;
141                 collisionMoveSimple(&env, m_gamedef,
142                         BS*0.5, box,
143                         0, dtime,
144                         p_pos, p_velocity, p_acceleration);
145                 m_pos = p_pos/BS;
146                 m_velocity = p_velocity/BS;
147                 m_acceleration = p_acceleration/BS;
148         }
149         else
150         {
151                 m_velocity += m_acceleration * dtime;
152                 m_pos += m_velocity * dtime;
153         }
154
155         // Update lighting
156         updateLight(env);
157
158         // Update model
159         updateVertices();
160 }
161
162 void Particle::updateLight(ClientEnvironment &env)
163 {
164         u8 light = 0;
165         try{
166                 v3s16 p = v3s16(
167                         floor(m_pos.X+0.5),
168                         floor(m_pos.Y+0.5),
169                         floor(m_pos.Z+0.5)
170                 );
171                 MapNode n = env.getClientMap().getNode(p);
172                 light = n.getLightBlend(env.getDayNightRatio(), m_gamedef->ndef());
173         }
174         catch(InvalidPositionException &e){
175                 light = blend_light(env.getDayNightRatio(), LIGHT_SUN, 0);
176         }
177         m_light = decode_light(light);
178 }
179
180 void Particle::updateVertices()
181 {
182         video::SColor c(255, m_light, m_light, m_light);
183         m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
184                         c, m_ap.x0(), m_ap.y1());
185         m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
186                         c, m_ap.x1(), m_ap.y1());
187         m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
188                         c, m_ap.x1(), m_ap.y0());
189         m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
190                         c ,m_ap.x0(), m_ap.y0());
191
192         for(u16 i=0; i<4; i++)
193         {
194                 m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
195                 m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
196                 m_box.addInternalPoint(m_vertices[i].Pos);
197                 m_vertices[i].Pos += m_pos*BS;
198         }
199 }
200
201
202 /*
203         Helpers
204 */
205
206
207 void allparticles_step (float dtime, ClientEnvironment &env)
208 {
209         for(std::vector<Particle*>::iterator i = all_particles.begin();
210                         i != all_particles.end();)
211         {
212                 if ((*i)->get_expired())
213                 {
214                         (*i)->remove();
215                         delete *i;
216                         all_particles.erase(i);
217                 }
218                 else
219                 {
220                         (*i)->step(dtime, env);
221                         i++;
222                 }
223         }
224 }
225
226 void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
227                 LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
228                 const TileSpec tiles[])
229 {
230         for (u16 j = 0; j < 32; j++) // set the amount of particles here
231         {
232                 addNodeParticle(gamedef, smgr, player, env, pos, tiles);
233         }
234 }
235
236 void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
237                 LocalPlayer *player, ClientEnvironment &env,
238                 v3s16 pos, const TileSpec tiles[])
239 {
240         addNodeParticle(gamedef, smgr, player, env, pos, tiles);
241 }
242
243 // add a particle of a node
244 // used by digging and punching particles
245 void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
246                 LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
247                 const TileSpec tiles[])
248 {
249         // Texture
250         u8 texid = myrand_range(0,5);
251         AtlasPointer ap = tiles[texid].texture;
252         float size = rand()%64/512.;
253         float visual_size = BS*size;
254         float texsize = size*2;
255
256         float x1 = ap.x1();
257         float y1 = ap.y1();
258
259         ap.size.X = (ap.x1() - ap.x0()) * texsize;
260         ap.size.Y = (ap.x1() - ap.x0()) * texsize;
261
262         ap.pos.X = ap.x0() + (x1 - ap.x0()) * ((rand()%64)/64.-texsize);
263         ap.pos.Y = ap.y0() + (y1 - ap.y0()) * ((rand()%64)/64.-texsize);
264
265         // Physics
266         v3f velocity(   (rand()%100/50.-1)/1.5,
267                         rand()%100/35.,
268                         (rand()%100/50.-1)/1.5);
269
270         v3f acceleration(0,-9,0);
271         v3f particlepos = v3f(
272                 (f32)pos.X+rand()%100/200.-0.25,
273                 (f32)pos.Y+rand()%100/200.-0.25,
274                 (f32)pos.Z+rand()%100/200.-0.25
275         );
276
277         new Particle(
278                 gamedef,
279                 smgr,
280                 player,
281                 env,
282                 particlepos,
283                 velocity,
284                 acceleration,
285                 rand()%100/100., // expiration time
286                 visual_size,
287                 true,
288                 ap);
289 }
290
291 /*
292         ParticleSpawner
293 */
294
295 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
296         u16 amount, float time,
297         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
298         float minexptime, float maxexptime, float minsize, float maxsize,
299         bool collisiondetection, AtlasPointer ap, u32 id)
300 {
301         m_gamedef = gamedef;
302         m_smgr = smgr;
303         m_player = player;
304         m_amount = amount;
305         m_spawntime = time;
306         m_minpos = minpos;
307         m_maxpos = maxpos;
308         m_minvel = minvel;
309         m_maxvel = maxvel;
310         m_minacc = minacc;
311         m_maxacc = maxacc;
312         m_minexptime = minexptime;
313         m_maxexptime = maxexptime;
314         m_minsize = minsize;
315         m_maxsize = maxsize;
316         m_collisiondetection = collisiondetection;
317         m_ap = ap;
318         m_time = 0;
319
320         for (u16 i = 0; i<=m_amount; i++)
321         {
322                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
323                 m_spawntimes.push_back(spawntime);
324         }
325
326         all_particlespawners.insert(std::pair<u32, ParticleSpawner*>(id, this));
327 }
328
329 ParticleSpawner::~ParticleSpawner() {}
330
331 void ParticleSpawner::step(float dtime, ClientEnvironment &env)
332 {
333         m_time += dtime;
334
335         if (m_spawntime != 0) // Spawner exists for a predefined timespan
336         {
337                 for(std::vector<float>::iterator i = m_spawntimes.begin();
338                                 i != m_spawntimes.end();)
339                 {
340                         if ((*i) <= m_time && m_amount > 0)
341                         {
342                                 m_amount--;
343
344                                 v3f pos = random_v3f(m_minpos, m_maxpos);
345                                 v3f vel = random_v3f(m_minvel, m_maxvel);
346                                 v3f acc = random_v3f(m_minacc, m_maxacc);
347                                 float exptime = rand()/(float)RAND_MAX
348                                                 *(m_maxexptime-m_minexptime)
349                                                 +m_minexptime;
350                                 float size = rand()/(float)RAND_MAX
351                                                 *(m_maxsize-m_minsize)
352                                                 +m_minsize;
353
354                                 new Particle(
355                                         m_gamedef,
356                                         m_smgr,
357                                         m_player,
358                                         env,
359                                         pos,
360                                         vel,
361                                         acc,
362                                         exptime,
363                                         size,
364                                         m_collisiondetection,
365                                         m_ap);
366                                 m_spawntimes.erase(i);
367                         }
368                         else
369                         {
370                                 i++;
371                         }
372                 }
373         }
374         else // Spawner exists for an infinity timespan, spawn on a per-second base
375         {
376                 for (int i = 0; i <= m_amount; i++)
377                 {
378                         if (rand()/(float)RAND_MAX < dtime)
379                         {
380                                 v3f pos = random_v3f(m_minpos, m_maxpos);
381                                 v3f vel = random_v3f(m_minvel, m_maxvel);
382                                 v3f acc = random_v3f(m_minacc, m_maxacc);
383                                 float exptime = rand()/(float)RAND_MAX
384                                                 *(m_maxexptime-m_minexptime)
385                                                 +m_minexptime;
386                                 float size = rand()/(float)RAND_MAX
387                                                 *(m_maxsize-m_minsize)
388                                                 +m_minsize;
389
390                                 new Particle(
391                                         m_gamedef,
392                                         m_smgr,
393                                         m_player,
394                                         env,
395                                         pos,
396                                         vel,
397                                         acc,
398                                         exptime,
399                                         size,
400                                         m_collisiondetection,
401                                         m_ap);
402                         }
403                 }
404         }
405 }
406
407 void allparticlespawners_step (float dtime, ClientEnvironment &env)
408 {
409         for(std::map<u32, ParticleSpawner*>::iterator i = 
410                         all_particlespawners.begin();
411                         i != all_particlespawners.end();)
412         {
413                 if (i->second->get_expired())
414                 {
415                         delete i->second;
416                         all_particlespawners.erase(i++);
417                 }
418                 else
419                 {
420                         i->second->step(dtime, env);
421                         i++;
422                 }
423         }
424 }
425
426 void delete_particlespawner (u32 id)
427 {
428         if (all_particlespawners.find(id) != all_particlespawners.end())
429         {
430                 delete all_particlespawners.find(id)->second;
431                 all_particlespawners.erase(id);
432         }
433 }
434
435 void clear_particles ()
436 {
437         for(std::map<u32, ParticleSpawner*>::iterator i =
438                         all_particlespawners.begin();
439                         i != all_particlespawners.end();)
440         {
441                 delete i->second;
442                 all_particlespawners.erase(i++);
443         }
444
445         for(std::vector<Particle*>::iterator i =
446                         all_particles.begin();
447                         i != all_particles.end();)
448         {
449                 (*i)->remove();
450                 delete *i;
451                 all_particles.erase(i);
452         }       
453 }