Large increase in performance
[oweals/minetest.git] / src / voxel.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 "voxel.h"
21 #include "map.h"
22 #include "gettime.h"
23 #include "nodedef.h"
24 #include "util/timetaker.h"
25 #include <string.h>  // memcpy, memset
26
27 /*
28         Debug stuff
29 */
30 u32 addarea_time = 0;
31 u32 emerge_time = 0;
32 u32 emerge_load_time = 0;
33 u32 clearflag_time = 0;
34 //u32 getwaterpressure_time = 0;
35 //u32 spreadwaterpressure_time = 0;
36 u32 updateareawaterpressure_time = 0;
37 u32 flowwater_pre_time = 0;
38
39
40 VoxelManipulator::VoxelManipulator():
41         m_data(NULL),
42         m_flags(NULL)
43 {
44 }
45
46 VoxelManipulator::~VoxelManipulator()
47 {
48         clear();
49         if(m_data)
50                 delete[] m_data;
51         if(m_flags)
52                 delete[] m_flags;
53 }
54
55 void VoxelManipulator::clear()
56 {
57         // Reset area to volume=0
58         m_area = VoxelArea();
59         if(m_data)
60                 delete[] m_data;
61         m_data = NULL;
62         if(m_flags)
63                 delete[] m_flags;
64         m_flags = NULL;
65 }
66
67 void VoxelManipulator::print(std::ostream &o, INodeDefManager *ndef,
68                 VoxelPrintMode mode)
69 {
70         v3s16 em = m_area.getExtent();
71         v3s16 of = m_area.MinEdge;
72         o<<"size: "<<em.X<<"x"<<em.Y<<"x"<<em.Z
73          <<" offset: ("<<of.X<<","<<of.Y<<","<<of.Z<<")"<<std::endl;
74
75         for(s32 y=m_area.MaxEdge.Y; y>=m_area.MinEdge.Y; y--)
76         {
77                 if(em.X >= 3 && em.Y >= 3)
78                 {
79                         if     (y==m_area.MinEdge.Y+2) o<<"^     ";
80                         else if(y==m_area.MinEdge.Y+1) o<<"|     ";
81                         else if(y==m_area.MinEdge.Y+0) o<<"y x-> ";
82                         else                           o<<"      ";
83                 }
84
85                 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
86                 {
87                         for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
88                         {
89                                 u8 f = m_flags[m_area.index(x,y,z)];
90                                 char c;
91                                 if(f & VOXELFLAG_NO_DATA)
92                                         c = 'N';
93                                 else
94                                 {
95                                         c = 'X';
96                                         MapNode n = m_data[m_area.index(x,y,z)];
97                                         content_t m = n.getContent();
98                                         u8 pr = n.param2;
99                                         if(mode == VOXELPRINT_MATERIAL)
100                                         {
101                                                 if(m <= 9)
102                                                         c = m + '0';
103                                         }
104                                         else if(mode == VOXELPRINT_WATERPRESSURE)
105                                         {
106                                                 if(ndef->get(m).isLiquid())
107                                                 {
108                                                         c = 'w';
109                                                         if(pr <= 9)
110                                                                 c = pr + '0';
111                                                 }
112                                                 else if(m == CONTENT_AIR)
113                                                 {
114                                                         c = ' ';
115                                                 }
116                                                 else
117                                                 {
118                                                         c = '#';
119                                                 }
120                                         }
121                                         else if(mode == VOXELPRINT_LIGHT_DAY)
122                                         {
123                                                 if(ndef->get(m).light_source != 0)
124                                                         c = 'S';
125                                                 else if(ndef->get(m).light_propagates == false)
126                                                         c = 'X';
127                                                 else
128                                                 {
129                                                         u8 light = n.getLight(LIGHTBANK_DAY, ndef);
130                                                         if(light < 10)
131                                                                 c = '0' + light;
132                                                         else
133                                                                 c = 'a' + (light-10);
134                                                 }
135                                         }
136                                 }
137                                 o<<c;
138                         }
139                         o<<' ';
140                 }
141                 o<<std::endl;
142         }
143 }
144
145 void VoxelManipulator::addArea(const VoxelArea &area)
146 {
147         // Cancel if requested area has zero volume
148         if(area.getExtent() == v3s16(0,0,0))
149                 return;
150
151         // Cancel if m_area already contains the requested area
152         if(m_area.contains(area))
153                 return;
154
155         TimeTaker timer("addArea", &addarea_time);
156
157         // Calculate new area
158         VoxelArea new_area;
159         // New area is the requested area if m_area has zero volume
160         if(m_area.getExtent() == v3s16(0,0,0))
161         {
162                 new_area = area;
163         }
164         // Else add requested area to m_area
165         else
166         {
167                 new_area = m_area;
168                 new_area.addArea(area);
169         }
170
171         s32 new_size = new_area.getVolume();
172
173         /*dstream<<"adding area ";
174         area.print(dstream);
175         dstream<<", old area ";
176         m_area.print(dstream);
177         dstream<<", new area ";
178         new_area.print(dstream);
179         dstream<<", new_size="<<new_size;
180         dstream<<std::endl;*/
181
182         // Allocate and clear new data
183         // FIXME: UGLY KLUDGE because MapNode default constructor is FUBAR; it
184         //        initialises data that is going to be overwritten anyway
185         MapNode *new_data = (MapNode*)new char[new_size * sizeof (*new_data)];
186         assert(new_data);
187         u8 *new_flags = new u8[new_size];
188         assert(new_flags);
189         memset(new_flags, VOXELFLAG_NO_DATA, new_size);
190
191         // Copy old data
192         s32 old_x_width = m_area.MaxEdge.X - m_area.MinEdge.X + 1;
193         for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
194         for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
195         {
196                 unsigned int old_index = m_area.index(m_area.MinEdge.X,y,z);
197                 unsigned int new_index = new_area.index(m_area.MinEdge.X,y,z);
198
199                 memcpy(&new_data[new_index], &m_data[old_index],
200                                 old_x_width * sizeof(MapNode));
201                 memcpy(&new_flags[new_index], &m_flags[old_index],
202                                 old_x_width * sizeof(u8));
203         }
204
205         // Replace area, data and flags
206
207         m_area = new_area;
208
209         MapNode *old_data = m_data;
210         u8 *old_flags = m_flags;
211
212         /*dstream<<"old_data="<<(int)old_data<<", new_data="<<(int)new_data
213         <<", old_flags="<<(int)m_flags<<", new_flags="<<(int)new_flags<<std::endl;*/
214
215         m_data = new_data;
216         m_flags = new_flags;
217
218         if(old_data)
219                 delete[] old_data;
220         if(old_flags)
221                 delete[] old_flags;
222
223         //dstream<<"addArea done"<<std::endl;
224 }
225
226 void VoxelManipulator::copyFrom(MapNode *src, const VoxelArea& src_area,
227                 v3s16 from_pos, v3s16 to_pos, v3s16 size)
228 {
229         /* The reason for this optimised code is that we're a member function
230          * and the data type/layout of m_data is know to us: it's stored as
231          * [z*h*w + y*h + x]. Therefore we can take the calls to m_area index
232          * (which performs the preceding mapping/indexing of m_data) out of the
233          * inner loop and calculate the next index as we're iterating to gain
234          * performance.
235          *
236          * src_step and dest_step is the amount required to be added to our index
237          * every time y increments. Because the destination area may be larger
238          * than the source area we need one additional variable (otherwise we could
239          * just continue adding dest_step as is done for the source data): dest_mod.
240          * dest_mod is the difference in size between a "row" in the source data
241          * and a "row" in the destination data (I am using the term row loosely
242          * and for illustrative purposes). E.g.
243          *
244          * src       <-------------------->|'''''' dest mod ''''''''
245          * dest      <--------------------------------------------->
246          *
247          * dest_mod (it's essentially a modulus) is added to the destination index
248          * after every full iteration of the y span.
249          *
250          * This method falls under the category "linear array and incrementing
251          * index".
252          */
253
254         s32 src_step = src_area.getExtent().X;
255         s32 dest_step = m_area.getExtent().X;
256         s32 dest_mod = m_area.index(to_pos.X, to_pos.Y, to_pos.Z + 1)
257                         - m_area.index(to_pos.X, to_pos.Y, to_pos.Z)
258                         - dest_step * size.Y;
259
260         s32 i_src = src_area.index(from_pos.X, from_pos.Y, from_pos.Z);
261         s32 i_local = m_area.index(to_pos.X, to_pos.Y, to_pos.Z);
262
263         for (s16 z = 0; z < size.Z; z++) {
264                 for (s16 y = 0; y < size.Y; y++) {
265                         memcpy(&m_data[i_local], &src[i_src], size.X * sizeof(*m_data));
266                         memset(&m_flags[i_local], 0, size.X);
267                         i_src += src_step;
268                         i_local += dest_step;
269                 }
270                 i_local += dest_mod;
271         }
272 }
273
274 void VoxelManipulator::copyTo(MapNode *dst, const VoxelArea& dst_area,
275                 v3s16 dst_pos, v3s16 from_pos, v3s16 size)
276 {
277         for(s16 z=0; z<size.Z; z++)
278         for(s16 y=0; y<size.Y; y++)
279         {
280                 s32 i_dst = dst_area.index(dst_pos.X, dst_pos.Y+y, dst_pos.Z+z);
281                 s32 i_local = m_area.index(from_pos.X, from_pos.Y+y, from_pos.Z+z);
282                 for (s16 x = 0; x < size.X; x++) {
283                         if (m_data[i_local].getContent() != CONTENT_IGNORE)
284                                 dst[i_dst] = m_data[i_local];
285                         i_dst++;
286                         i_local++;
287                 }
288         }
289 }
290
291 /*
292         Algorithms
293         -----------------------------------------------------
294 */
295
296 void VoxelManipulator::clearFlag(u8 flags)
297 {
298         // 0-1ms on moderate area
299         TimeTaker timer("clearFlag", &clearflag_time);
300
301         //v3s16 s = m_area.getExtent();
302
303         /*dstream<<"clearFlag clearing area of size "
304                         <<""<<s.X<<"x"<<s.Y<<"x"<<s.Z<<""
305                         <<std::endl;*/
306
307         //s32 count = 0;
308
309         /*for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
310         for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
311         for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
312         {
313                 u8 f = m_flags[m_area.index(x,y,z)];
314                 m_flags[m_area.index(x,y,z)] &= ~flags;
315                 if(m_flags[m_area.index(x,y,z)] != f)
316                         count++;
317         }*/
318
319         s32 volume = m_area.getVolume();
320         for(s32 i=0; i<volume; i++)
321         {
322                 m_flags[i] &= ~flags;
323         }
324
325         /*s32 volume = m_area.getVolume();
326         for(s32 i=0; i<volume; i++)
327         {
328                 u8 f = m_flags[i];
329                 m_flags[i] &= ~flags;
330                 if(m_flags[i] != f)
331                         count++;
332         }
333
334         dstream<<"clearFlag changed "<<count<<" flags out of "
335                         <<volume<<" nodes"<<std::endl;*/
336 }
337
338 void VoxelManipulator::unspreadLight(enum LightBank bank, v3s16 p, u8 oldlight,
339                 std::set<v3s16> & light_sources, INodeDefManager *nodemgr)
340 {
341         v3s16 dirs[6] = {
342                 v3s16(0,0,1), // back
343                 v3s16(0,1,0), // top
344                 v3s16(1,0,0), // right
345                 v3s16(0,0,-1), // front
346                 v3s16(0,-1,0), // bottom
347                 v3s16(-1,0,0), // left
348         };
349
350         VoxelArea voxel_area(p - v3s16(1,1,1), p + v3s16(1,1,1));
351         addArea(voxel_area);
352
353         // Loop through 6 neighbors
354         for(u16 i=0; i<6; i++)
355         {
356                 // Get the position of the neighbor node
357                 v3s16 n2pos = p + dirs[i];
358
359                 u32 n2i = m_area.index(n2pos);
360
361                 if(m_flags[n2i] & VOXELFLAG_NO_DATA)
362                         continue;
363
364                 MapNode &n2 = m_data[n2i];
365
366                 /*
367                         If the neighbor is dimmer than what was specified
368                         as oldlight (the light of the previous node)
369                 */
370                 u8 light2 = n2.getLight(bank, nodemgr);
371                 if(light2 < oldlight)
372                 {
373                         /*
374                                 And the neighbor is transparent and it has some light
375                         */
376                         if(nodemgr->get(n2).light_propagates && light2 != 0)
377                         {
378                                 /*
379                                         Set light to 0 and add to queue
380                                 */
381
382                                 n2.setLight(bank, 0, nodemgr);
383
384                                 unspreadLight(bank, n2pos, light2, light_sources, nodemgr);
385
386                                 /*
387                                         Remove from light_sources if it is there
388                                         NOTE: This doesn't happen nearly at all
389                                 */
390                                 /*if(light_sources.find(n2pos))
391                                 {
392                                         std::cout<<"Removed from light_sources"<<std::endl;
393                                         light_sources.remove(n2pos);
394                                 }*/
395                         }
396                 }
397                 else{
398                         light_sources.insert(n2pos);
399                 }
400         }
401 }
402
403 #if 1
404 /*
405         Goes recursively through the neighbours of the node.
406
407         Alters only transparent nodes.
408
409         If the lighting of the neighbour is lower than the lighting of
410         the node was (before changing it to 0 at the step before), the
411         lighting of the neighbour is set to 0 and then the same stuff
412         repeats for the neighbour.
413
414         The ending nodes of the routine are stored in light_sources.
415         This is useful when a light is removed. In such case, this
416         routine can be called for the light node and then again for
417         light_sources to re-light the area without the removed light.
418
419         values of from_nodes are lighting values.
420 */
421 void VoxelManipulator::unspreadLight(enum LightBank bank,
422                 std::map<v3s16, u8> & from_nodes,
423                 std::set<v3s16> & light_sources, INodeDefManager *nodemgr)
424 {
425         if(from_nodes.empty())
426                 return;
427
428         for(std::map<v3s16, u8>::iterator j = from_nodes.begin();
429                 j != from_nodes.end(); ++j)
430         {
431                 unspreadLight(bank, j->first, j->second, light_sources, nodemgr);
432         }
433 }
434 #endif
435
436 #if 0
437 /*
438         Goes recursively through the neighbours of the node.
439
440         Alters only transparent nodes.
441
442         If the lighting of the neighbour is lower than the lighting of
443         the node was (before changing it to 0 at the step before), the
444         lighting of the neighbour is set to 0 and then the same stuff
445         repeats for the neighbour.
446
447         The ending nodes of the routine are stored in light_sources.
448         This is useful when a light is removed. In such case, this
449         routine can be called for the light node and then again for
450         light_sources to re-light the area without the removed light.
451
452         values of from_nodes are lighting values.
453 */
454 void VoxelManipulator::unspreadLight(enum LightBank bank,
455                 core::map<v3s16, u8> & from_nodes,
456                 core::map<v3s16, bool> & light_sources)
457 {
458         v3s16 dirs[6] = {
459                 v3s16(0,0,1), // back
460                 v3s16(0,1,0), // top
461                 v3s16(1,0,0), // right
462                 v3s16(0,0,-1), // front
463                 v3s16(0,-1,0), // bottom
464                 v3s16(-1,0,0), // left
465         };
466
467         if(from_nodes.size() == 0)
468                 return;
469
470         core::map<v3s16, u8> unlighted_nodes;
471         core::map<v3s16, u8>::Iterator j;
472         j = from_nodes.getIterator();
473
474         for(; j.atEnd() == false; j++)
475         {
476                 v3s16 pos = j.getNode()->getKey();
477
478                 addArea(VoxelArea(pos - v3s16(1,1,1), pos + v3s16(1,1,1)));
479
480                 //MapNode &n = m_data[m_area.index(pos)];
481
482                 u8 oldlight = j.getNode()->getValue();
483
484                 // Loop through 6 neighbors
485                 for(u16 i=0; i<6; i++)
486                 {
487                         // Get the position of the neighbor node
488                         v3s16 n2pos = pos + dirs[i];
489
490                         u32 n2i = m_area.index(n2pos);
491
492                         if(m_flags[n2i] & VOXELFLAG_NO_DATA)
493                                 continue;
494
495                         MapNode &n2 = m_data[n2i];
496
497                         /*
498                                 If the neighbor is dimmer than what was specified
499                                 as oldlight (the light of the previous node)
500                         */
501                         if(n2.getLight(bank, nodemgr) < oldlight)
502                         {
503                                 /*
504                                         And the neighbor is transparent and it has some light
505                                 */
506                                 if(nodemgr->get(n2).light_propagates && n2.getLight(bank, nodemgr) != 0)
507                                 {
508                                         /*
509                                                 Set light to 0 and add to queue
510                                         */
511
512                                         u8 current_light = n2.getLight(bank, nodemgr);
513                                         n2.setLight(bank, 0);
514
515                                         unlighted_nodes.insert(n2pos, current_light);
516
517                                         /*
518                                                 Remove from light_sources if it is there
519                                                 NOTE: This doesn't happen nearly at all
520                                         */
521                                         /*if(light_sources.find(n2pos))
522                                         {
523                                                 std::cout<<"Removed from light_sources"<<std::endl;
524                                                 light_sources.remove(n2pos);
525                                         }*/
526                                 }
527                         }
528                         else{
529                                 light_sources.insert(n2pos, true);
530                         }
531                 }
532         }
533
534         /*dstream<<"unspreadLight(): Changed block "
535                         <<blockchangecount<<" times"
536                         <<" for "<<from_nodes.size()<<" nodes"
537                         <<std::endl;*/
538
539         if(unlighted_nodes.size() > 0)
540                 unspreadLight(bank, unlighted_nodes, light_sources);
541 }
542 #endif
543
544 void VoxelManipulator::spreadLight(enum LightBank bank, v3s16 p,
545                 INodeDefManager *nodemgr)
546 {
547         const v3s16 dirs[6] = {
548                 v3s16(0,0,1), // back
549                 v3s16(0,1,0), // top
550                 v3s16(1,0,0), // right
551                 v3s16(0,0,-1), // front
552                 v3s16(0,-1,0), // bottom
553                 v3s16(-1,0,0), // left
554         };
555
556         VoxelArea voxel_area(p - v3s16(1,1,1), p + v3s16(1,1,1));
557         addArea(voxel_area);
558
559         u32 i = m_area.index(p);
560
561         if(m_flags[i] & VOXELFLAG_NO_DATA)
562                 return;
563
564         MapNode &n = m_data[i];
565
566         u8 oldlight = n.getLight(bank, nodemgr);
567         u8 newlight = diminish_light(oldlight);
568
569         // Loop through 6 neighbors
570         for(u16 i=0; i<6; i++)
571         {
572                 // Get the position of the neighbor node
573                 v3s16 n2pos = p + dirs[i];
574
575                 u32 n2i = m_area.index(n2pos);
576
577                 if(m_flags[n2i] & VOXELFLAG_NO_DATA)
578                         continue;
579
580                 MapNode &n2 = m_data[n2i];
581
582                 u8 light2 = n2.getLight(bank, nodemgr);
583
584                 /*
585                         If the neighbor is brighter than the current node,
586                         add to list (it will light up this node on its turn)
587                 */
588                 if(light2 > undiminish_light(oldlight))
589                 {
590                         spreadLight(bank, n2pos, nodemgr);
591                 }
592                 /*
593                         If the neighbor is dimmer than how much light this node
594                         would spread on it, add to list
595                 */
596                 if(light2 < newlight)
597                 {
598                         if(nodemgr->get(n2).light_propagates)
599                         {
600                                 n2.setLight(bank, newlight, nodemgr);
601                                 spreadLight(bank, n2pos, nodemgr);
602                         }
603                 }
604         }
605 }
606
607 #if 0
608 /*
609         Lights neighbors of from_nodes, collects all them and then
610         goes on recursively.
611
612         NOTE: This is faster on small areas but will overflow the
613               stack on large areas. Thus it is not used.
614 */
615 void VoxelManipulator::spreadLight(enum LightBank bank,
616                 core::map<v3s16, bool> & from_nodes)
617 {
618         if(from_nodes.size() == 0)
619                 return;
620
621         core::map<v3s16, bool> lighted_nodes;
622         core::map<v3s16, bool>::Iterator j;
623         j = from_nodes.getIterator();
624
625         for(; j.atEnd() == false; j++)
626         {
627                 v3s16 pos = j.getNode()->getKey();
628
629                 spreadLight(bank, pos);
630         }
631 }
632 #endif
633
634 #if 1
635 /*
636         Lights neighbors of from_nodes, collects all them and then
637         goes on recursively.
638 */
639 void VoxelManipulator::spreadLight(enum LightBank bank,
640                 std::set<v3s16> & from_nodes, INodeDefManager *nodemgr)
641 {
642         const v3s16 dirs[6] = {
643                 v3s16(0,0,1), // back
644                 v3s16(0,1,0), // top
645                 v3s16(1,0,0), // right
646                 v3s16(0,0,-1), // front
647                 v3s16(0,-1,0), // bottom
648                 v3s16(-1,0,0), // left
649         };
650
651         if(from_nodes.empty())
652                 return;
653
654         std::set<v3s16> lighted_nodes;
655
656         for(std::set<v3s16>::iterator j = from_nodes.begin();
657                 j != from_nodes.end(); ++j)
658         {
659                 v3s16 pos = *j;
660
661                 VoxelArea voxel_area(pos - v3s16(1,1,1), pos + v3s16(1,1,1));
662                 addArea(voxel_area);
663
664                 u32 i = m_area.index(pos);
665
666                 if(m_flags[i] & VOXELFLAG_NO_DATA)
667                         continue;
668
669                 MapNode &n = m_data[i];
670
671                 u8 oldlight = n.getLight(bank, nodemgr);
672                 u8 newlight = diminish_light(oldlight);
673
674                 // Loop through 6 neighbors
675                 for(u16 i=0; i<6; i++)
676                 {
677                         // Get the position of the neighbor node
678                         v3s16 n2pos = pos + dirs[i];
679
680                         try
681                         {
682                                 u32 n2i = m_area.index(n2pos);
683
684                                 if(m_flags[n2i] & VOXELFLAG_NO_DATA)
685                                         continue;
686
687                                 MapNode &n2 = m_data[n2i];
688
689                                 u8 light2 = n2.getLight(bank, nodemgr);
690
691                                 /*
692                                         If the neighbor is brighter than the current node,
693                                         add to list (it will light up this node on its turn)
694                                 */
695                                 if(light2 > undiminish_light(oldlight))
696                                 {
697                                         lighted_nodes.insert(n2pos);
698                                 }
699                                 /*
700                                         If the neighbor is dimmer than how much light this node
701                                         would spread on it, add to list
702                                 */
703                                 if(light2 < newlight)
704                                 {
705                                         if(nodemgr->get(n2).light_propagates)
706                                         {
707                                                 n2.setLight(bank, newlight, nodemgr);
708                                                 lighted_nodes.insert(n2pos);
709                                         }
710                                 }
711                         }
712                         catch(InvalidPositionException &e)
713                         {
714                                 continue;
715                         }
716                 }
717         }
718
719         /*dstream<<"spreadLight(): Changed block "
720                         <<blockchangecount<<" times"
721                         <<" for "<<from_nodes.size()<<" nodes"
722                         <<std::endl;*/
723
724         if(!lighted_nodes.empty())
725                 spreadLight(bank, lighted_nodes, nodemgr);
726 }
727 #endif
728
729 //END