3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
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 General Public License for more details.
15 You should have received a copy of the GNU 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.
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;
40 VoxelManipulator::VoxelManipulator():
46 VoxelManipulator::~VoxelManipulator()
55 void VoxelManipulator::clear()
57 // Reset area to volume=0
67 void VoxelManipulator::print(std::ostream &o, VoxelPrintMode mode)
69 v3s16 em = m_area.getExtent();
70 v3s16 of = m_area.MinEdge;
71 o<<"size: "<<em.X<<"x"<<em.Y<<"x"<<em.Z
72 <<" offset: ("<<of.X<<","<<of.Y<<","<<of.Z<<")"<<std::endl;
74 for(s32 y=m_area.MaxEdge.Y; y>=m_area.MinEdge.Y; y--)
76 if(em.X >= 3 && em.Y >= 3)
78 if (y==m_area.MinEdge.Y+2) o<<"^ ";
79 else if(y==m_area.MinEdge.Y+1) o<<"| ";
80 else if(y==m_area.MinEdge.Y+0) o<<"y x-> ";
84 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
86 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
88 u8 f = m_flags[m_area.index(x,y,z)];
90 if(f & VOXELFLAG_NOT_LOADED)
92 else if(f & VOXELFLAG_INEXISTENT)
97 u8 m = m_data[m_area.index(x,y,z)].d;
98 u8 pr = m_data[m_area.index(x,y,z)].pressure;
99 if(mode == VOXELPRINT_MATERIAL)
104 else if(mode == VOXELPRINT_WATERPRESSURE)
106 if(m == MATERIAL_WATER)
112 else if(m == MATERIAL_AIR)
130 void VoxelManipulator::addArea(VoxelArea area)
132 // Cancel if requested area has zero volume
133 if(area.getExtent() == v3s16(0,0,0))
136 // Cancel if m_area already contains the requested area
137 if(m_area.contains(area))
140 TimeTaker timer("addArea", g_device, &addarea_time);
142 // Calculate new area
144 // New area is the requested area if m_area has zero volume
145 if(m_area.getExtent() == v3s16(0,0,0))
149 // Else add requested area to m_area
153 new_area.addArea(area);
156 s32 new_size = new_area.getVolume();
158 /*dstream<<"adding area ";
160 dstream<<", old area ";
161 m_area.print(dstream);
162 dstream<<", new area ";
163 new_area.print(dstream);
164 dstream<<", new_size="<<new_size;
165 dstream<<std::endl;*/
167 // Allocate and clear new data
168 MapNode *new_data = new MapNode[new_size];
169 u8 *new_flags = new u8[new_size];
170 for(s32 i=0; i<new_size; i++)
172 new_flags[i] = VOXELFLAG_NOT_LOADED;
177 for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
178 for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
179 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
181 // If loaded, copy data and flags
182 if((m_flags[m_area.index(x,y,z)] & VOXELFLAG_NOT_LOADED) == false)
184 new_data[new_area.index(x,y,z)] = m_data[m_area.index(x,y,z)];
185 new_flags[new_area.index(x,y,z)] = m_flags[m_area.index(x,y,z)];
189 // Replace area, data and flags
193 MapNode *old_data = m_data;
194 u8 *old_flags = m_flags;
196 /*dstream<<"old_data="<<(int)old_data<<", new_data="<<(int)new_data
197 <<", old_flags="<<(int)m_flags<<", new_flags="<<(int)new_flags<<std::endl;*/
207 //dstream<<"addArea done"<<std::endl;
210 void VoxelManipulator::copyFrom(MapNode *src, VoxelArea src_area,
211 v3s16 from_pos, v3s16 to_pos, v3s16 size)
213 for(s16 z=0; z<size.Z; z++)
214 for(s16 y=0; y<size.Y; y++)
216 s32 i_src = src_area.index(from_pos.X, from_pos.Y+y, from_pos.Z+z);
217 s32 i_local = m_area.index(to_pos.X, to_pos.Y+y, to_pos.Z+z);
218 memcpy(&m_data[i_local], &src[i_src], size.X*sizeof(MapNode));
219 memset(&m_flags[i_local], 0, size.X);
223 void VoxelManipulator::interpolate(VoxelArea area)
225 VoxelArea emerge_area = area;
226 emerge_area.MinEdge -= v3s16(1,1,1);
227 emerge_area.MaxEdge += v3s16(1,1,1);
230 SharedBuffer<u8> buf(area.getVolume());
232 for(s32 z=area.MinEdge.Z; z<=area.MaxEdge.Z; z++)
233 for(s32 y=area.MinEdge.Y; y<=area.MaxEdge.Y; y++)
234 for(s32 x=area.MinEdge.X; x<=area.MaxEdge.X; x++)
248 //const v3s16 *dirs = g_26dirs;
252 u8 m = MATERIAL_IGNORE;
254 for(s16 i=0; i<8; i++)
255 //for(s16 i=0; i<26; i++)
257 v3s16 p2 = p + dirs[i];
259 u8 f = m_flags[m_area.index(p2)];
260 assert(!(f & VOXELFLAG_NOT_LOADED));
261 if(f & VOXELFLAG_INEXISTENT)
264 MapNode &n = m_data[m_area.index(p2)];
266 airness += (n.d == MATERIAL_AIR) ? 1 : -1;
269 if(m == MATERIAL_IGNORE && n.d != MATERIAL_AIR)
273 // 1 if air, 0 if not
274 buf[area.index(p)] = airness > -total/2 ? MATERIAL_AIR : m;
275 //buf[area.index(p)] = airness > -total ? MATERIAL_AIR : m;
276 //buf[area.index(p)] = airness >= -7 ? MATERIAL_AIR : m;
279 for(s32 z=area.MinEdge.Z; z<=area.MaxEdge.Z; z++)
280 for(s32 y=area.MinEdge.Y; y<=area.MaxEdge.Y; y++)
281 for(s32 x=area.MinEdge.X; x<=area.MaxEdge.X; x++)
284 m_data[m_area.index(p)].d = buf[area.index(p)];
289 void VoxelManipulator::clearFlag(u8 flags)
291 // 0-1ms on moderate area
292 TimeTaker timer("clearFlag", g_device, &clearflag_time);
294 v3s16 s = m_area.getExtent();
296 /*dstream<<"clearFlag clearing area of size "
297 <<""<<s.X<<"x"<<s.Y<<"x"<<s.Z<<""
302 /*for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
303 for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
304 for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
306 u8 f = m_flags[m_area.index(x,y,z)];
307 m_flags[m_area.index(x,y,z)] &= ~flags;
308 if(m_flags[m_area.index(x,y,z)] != f)
312 s32 volume = m_area.getVolume();
313 for(s32 i=0; i<volume; i++)
315 m_flags[i] &= ~flags;
318 /*s32 volume = m_area.getVolume();
319 for(s32 i=0; i<volume; i++)
322 m_flags[i] &= ~flags;
327 dstream<<"clearFlag changed "<<count<<" flags out of "
328 <<volume<<" nodes"<<std::endl;*/
331 int VoxelManipulator::getWaterPressure(v3s16 p, s16 &highest_y, int recur_count)
333 m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED2;
340 throw ProcessingLimitException
341 ("getWaterPressure recur_count limit reached");
345 v3s16(-1,0,0), // left
346 v3s16(1,0,0), // right
347 v3s16(0,0,-1), // front
348 v3s16(0,0,1), // back
349 v3s16(0,-1,0), // bottom
352 // Load neighboring nodes
353 // TODO: A bigger area would be better
354 emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)));
359 v3s16 p2 = p + dirs[i];
360 u8 f = m_flags[m_area.index(p2)];
361 // Ignore inexistent or checked nodes
362 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED2))
364 MapNode &n = m_data[m_area.index(p2)];
365 // Ignore non-liquid nodes
366 if(material_liquid(n.d) == false)
372 /*if(n.pressure == 1)
376 // Otherwise recurse more
379 pr = getWaterPressure(p2, highest_y, recur_count);
384 // If block is at top, pressure here is one higher
390 // If block is at bottom, pressure here is one lower
397 // Node is on the pressure route
398 m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED4;
404 // Nothing useful found
408 void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr,
409 VoxelArea request_area,
410 core::map<v3s16, u8> &active_nodes,
414 if(recur_count > 10000)
415 throw ProcessingLimitException
416 ("spreadWaterPressure recur_count limit reached");
418 m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED3;
419 m_data[m_area.index(p)].pressure = pr;
423 v3s16(-1,0,0), // left
424 v3s16(1,0,0), // right
425 v3s16(0,0,-1), // front
426 v3s16(0,0,1), // back
427 v3s16(0,-1,0), // bottom
430 // Load neighboring nodes
431 emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)));
436 v3s16 p2 = p + dirs[i];
438 u8 f = m_flags[m_area.index(p2)];
440 // Ignore inexistent and checked nodes
441 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED3))
444 MapNode &n = m_data[m_area.index(p2)];
448 add to active_nodes if there is flow-causing pressure.
449 NOTE: Do not remove anything from there. We cannot know
450 here if some other neighbor of it causes flow.
452 if(n.d == MATERIAL_AIR)
454 bool pressure_causes_flow = false;
455 // If block is at top
459 pressure_causes_flow = true;
461 // If block is at bottom
464 pressure_causes_flow = true;
466 // If block is at side
470 pressure_causes_flow = true;
473 if(pressure_causes_flow)
475 active_nodes[p2] = 1;
481 // Ignore non-liquid nodes
482 if(material_liquid(n.d) == false)
486 // If block is at top, pressure there is lower
492 // If block is at bottom, pressure there is higher
499 // Ignore if correct pressure is already set and is not on
501 if(n.pressure == pr2 && request_area.contains(p2) == false)
504 spreadWaterPressure(p2, pr2, request_area, active_nodes, recur_count);
508 void VoxelManipulator::updateAreaWaterPressure(VoxelArea a,
509 core::map<v3s16, u8> &active_nodes,
510 bool checked3_is_clear)
512 TimeTaker timer("updateAreaWaterPressure", g_device,
513 &updateareawaterpressure_time);
517 bool checked2_clear = false;
519 if(checked3_is_clear == false)
521 //clearFlag(VOXELFLAG_CHECKED3);
523 clearFlag(VOXELFLAG_CHECKED3 | VOXELFLAG_CHECKED2);
524 checked2_clear = true;
528 for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
529 for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
530 for(s32 x=a.MinEdge.X; x<=a.MaxEdge.X; x++)
534 u8 f = m_flags[m_area.index(p)];
535 // Ignore inexistent or checked nodes
536 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED3))
538 MapNode &n = m_data[m_area.index(p)];
539 // Ignore non-liquid nodes
540 if(material_liquid(n.d) == false)
543 if(checked2_clear == false)
545 clearFlag(VOXELFLAG_CHECKED2);
546 checked2_clear = true;
549 checked2_clear = false;
551 s16 highest_y = -32768;
557 // 0-1ms @ recur_count <= 100
558 //TimeTaker timer("getWaterPressure", g_device);
559 pr = getWaterPressure(p, highest_y, recur_count);
561 catch(ProcessingLimitException &e)
563 //dstream<<"getWaterPressure ProcessingLimitException"<<std::endl;
568 assert(highest_y != -32768);
570 pr = highest_y - p.Y + 1;
574 /*dstream<<"WARNING: Pressure at ("
575 <<p.X<<","<<p.Y<<","<<p.Z<<")"
577 //<<" and highest_y == -32768"
579 assert(highest_y != -32768);
586 //TimeTaker timer("spreadWaterPressure", g_device);
587 spreadWaterPressure(p, pr, a, active_nodes, 0);
589 catch(ProcessingLimitException &e)
591 //dstream<<"getWaterPressure ProcessingLimitException"<<std::endl;
596 bool VoxelManipulator::flowWater(v3s16 removed_pos,
597 core::map<v3s16, u8> &active_nodes,
598 int recursion_depth, bool debugprint,
599 int *counter, int counterlimit)
603 v3s16(-1,0,0), // left
604 v3s16(1,0,0), // right
605 v3s16(0,0,-1), // front
606 v3s16(0,0,1), // back
607 v3s16(0,-1,0), // bottom
614 // Randomize horizontal order
620 s16 s1 = (cs & 1) ? 1 : -1;
621 s16 s2 = (cs & 2) ? 1 : -1;
622 //dstream<<"s1="<<s1<<", s2="<<s2<<std::endl;
625 TimeTaker timer1("flowWater pre", g_device, &flowwater_pre_time);
627 // Load neighboring nodes
628 emerge(VoxelArea(removed_pos - v3s16(1,1,1), removed_pos + v3s16(1,1,1)));
630 // Ignore incorrect removed_pos
632 u8 f = m_flags[m_area.index(removed_pos)];
633 // Ignore inexistent or checked node
634 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED))
636 MapNode &n = m_data[m_area.index(removed_pos)];
637 // Water can move only to air
638 if(n.d != MATERIAL_AIR)
645 p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
647 u8 f = m_flags[m_area.index(p)];
648 // Inexistent or checked nodes can't move
649 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED))
651 MapNode &n = m_data[m_area.index(p)];
652 // Only liquid nodes can move
653 if(material_liquid(n.d) == false)
655 // If block is at top, select it always
660 // If block is at bottom, select it if it has enough pressure
667 // Else block is at some side. Select it if it has enough pressure
674 // If there is nothing to move, return
678 // Switch nodes at p and removed_pos
679 u8 m = m_data[m_area.index(p)].d;
680 u8 f = m_flags[m_area.index(p)];
681 m_data[m_area.index(p)].d = m_data[m_area.index(removed_pos)].d;
682 m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)];
683 m_data[m_area.index(removed_pos)].d = m;
684 m_flags[m_area.index(removed_pos)] = f;
686 // Mark removed_pos checked
687 m_flags[m_area.index(removed_pos)] |= VOXELFLAG_CHECKED;
688 // If block was dropped from surface, increase pressure
689 if(i == 0 && m_data[m_area.index(removed_pos)].pressure == 1)
691 m_data[m_area.index(removed_pos)].pressure = 2;
696 dstream<<"VoxelManipulator::flowWater(): Moved bubble:"<<std::endl;
697 print(dstream, VOXELPRINT_WATERPRESSURE);
702 a.addPoint(p - v3s16(1,1,1));
703 a.addPoint(p + v3s16(1,1,1));
704 a.addPoint(removed_pos - v3s16(1,1,1));
705 a.addPoint(removed_pos + v3s16(1,1,1));
706 updateAreaWaterPressure(a, active_nodes);
710 dstream<<"VoxelManipulator::flowWater(): Pressure updated:"<<std::endl;
711 print(dstream, VOXELPRINT_WATERPRESSURE);
717 dstream<<"VoxelManipulator::flowWater(): step done:"<<std::endl;
718 print(dstream, VOXELPRINT_WATERPRESSURE);
724 // Flow water to the newly created empty position
725 flowWater(p, active_nodes, recursion_depth,
726 debugprint, counter, counterlimit);
729 // Try flowing water to empty positions around removed_pos.
730 // They are checked in reverse order compared to the previous loop.
731 for(s32 i=5; i>=0; i--)
733 //v3s16 p = removed_pos + dirs[i];
734 p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
736 u8 f = m_flags[m_area.index(p)];
737 // Water can't move to inexistent nodes
738 if(f & VOXELFLAG_INEXISTENT)
740 MapNode &n = m_data[m_area.index(p)];
741 // Water can only move to air
742 if(n.d != MATERIAL_AIR)
745 // Flow water to node
747 flowWater(p, active_nodes, recursion_depth,
748 debugprint, counter, counterlimit);
752 // Search again from all neighbors
760 if((*counter) % 10 == 0)
761 dstream<<"flowWater(): moved "<<(*counter)<<" nodes"
764 if(counterlimit != -1 && (*counter) > counterlimit)
766 dstream<<"Counter limit reached; returning"<<std::endl;
767 throw ProcessingLimitException("flowWater counterlimit reached");
773 bool VoxelManipulator::flowWater(v3s16 removed_pos,
774 core::map<v3s16, u8> &active_nodes,
775 int recursion_depth, bool debugprint,
776 int *counter, int counterlimit)
780 v3s16(-1,0,0), // left
781 v3s16(1,0,0), // right
782 v3s16(0,0,-1), // front
783 v3s16(0,0,1), // back
784 v3s16(0,-1,0), // bottom
791 // Randomize horizontal order
797 s16 s1 = (cs & 1) ? 1 : -1;
798 s16 s2 = (cs & 2) ? 1 : -1;
799 //dstream<<"s1="<<s1<<", s2="<<s2<<std::endl;
802 TimeTaker timer1("flowWater pre", g_device, &flowwater_pre_time);
804 // Load neighboring nodes
805 emerge(VoxelArea(removed_pos - v3s16(1,1,1), removed_pos + v3s16(1,1,1)));
807 // Ignore incorrect removed_pos
809 u8 f = m_flags[m_area.index(removed_pos)];
810 // Ignore inexistent or checked node
811 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED))
813 MapNode &n = m_data[m_area.index(removed_pos)];
814 // Water can move only to air
815 if(n.d != MATERIAL_AIR)
822 p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
824 u8 f = m_flags[m_area.index(p)];
825 // Inexistent or checked nodes can't move
826 if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED))
828 MapNode &n = m_data[m_area.index(p)];
829 // Only liquid nodes can move
830 if(material_liquid(n.d) == false)
832 // If block is at top, select it always
837 // If block is at bottom, select it if it has enough pressure
844 // Else block is at some side. Select it if it has enough pressure
851 // If there is nothing to move, return
855 // Switch nodes at p and removed_pos
856 u8 m = m_data[m_area.index(p)].d;
857 u8 f = m_flags[m_area.index(p)];
858 m_data[m_area.index(p)].d = m_data[m_area.index(removed_pos)].d;
859 m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)];
860 m_data[m_area.index(removed_pos)].d = m;
861 m_flags[m_area.index(removed_pos)] = f;
863 // Mark removed_pos checked
864 m_flags[m_area.index(removed_pos)] |= VOXELFLAG_CHECKED;
865 // If block was dropped from surface, increase pressure
866 if(i == 0 && m_data[m_area.index(removed_pos)].pressure == 1)
868 m_data[m_area.index(removed_pos)].pressure = 2;
873 dstream<<"VoxelManipulator::flowWater(): Moved bubble:"<<std::endl;
874 print(dstream, VOXELPRINT_WATERPRESSURE);
879 a.addPoint(p - v3s16(1,1,1));
880 a.addPoint(p + v3s16(1,1,1));
881 a.addPoint(removed_pos - v3s16(1,1,1));
882 a.addPoint(removed_pos + v3s16(1,1,1));
883 updateAreaWaterPressure(a, active_nodes);
887 dstream<<"VoxelManipulator::flowWater(): Pressure updated:"<<std::endl;
888 print(dstream, VOXELPRINT_WATERPRESSURE);
894 dstream<<"VoxelManipulator::flowWater(): step done:"<<std::endl;
895 print(dstream, VOXELPRINT_WATERPRESSURE);
901 // Flow water to the newly created empty position
902 flowWater(p, active_nodes, recursion_depth,
903 debugprint, counter, counterlimit);
906 // Try flowing water to empty positions around removed_pos.
907 // They are checked in reverse order compared to the previous loop.
908 for(s32 i=5; i>=0; i--)
910 //v3s16 p = removed_pos + dirs[i];
911 p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z);
913 u8 f = m_flags[m_area.index(p)];
914 // Water can't move to inexistent nodes
915 if(f & VOXELFLAG_INEXISTENT)
917 MapNode &n = m_data[m_area.index(p)];
918 // Water can only move to air
919 if(n.d != MATERIAL_AIR)
922 // Flow water to node
924 flowWater(p, active_nodes, recursion_depth,
925 debugprint, counter, counterlimit);
929 // Search again from all neighbors
937 if((*counter) % 10 == 0)
938 dstream<<"flowWater(): moved "<<(*counter)<<" nodes"
941 if(counterlimit != -1 && (*counter) > counterlimit)
943 dstream<<"Counter limit reached; returning"<<std::endl;
944 throw ProcessingLimitException("flowWater counterlimit reached");
951 void VoxelManipulator::flowWater(
952 core::map<v3s16, u8> &active_nodes,
953 int recursion_depth, bool debugprint,
958 emerge_load_time = 0;
960 updateareawaterpressure_time = 0;
961 flowwater_pre_time = 0;
963 TimeTaker timer1("flowWater (active_nodes)", g_device);
965 dstream<<"active_nodes.size() = "<<active_nodes.size()<<std::endl;
972 // Flow water to active nodes
976 clearFlag(VOXELFLAG_CHECKED);
978 if(active_nodes.size() == 0)
981 dstream<<"Selecting a new active_node"<<std::endl;
985 core::map<v3s16, u8>::Node
986 *n = active_nodes.getIterator().getNode();
991 s32 k = (s32)rand() % (s32)active_nodes.size();
993 core::map<v3s16, u8>::Iterator
994 i = active_nodes.getIterator().getNode();
995 for(s32 j=0; j<k; j++)
999 core::map<v3s16, u8>::Node *n = i.getNode();
1002 v3s16 p = n->getKey();
1003 active_nodes.remove(p);
1004 flowWater(p, active_nodes, recursion_depth,
1005 debugprint, &counter, counterlimit);
1009 catch(ProcessingLimitException &e)
1011 //dstream<<"getWaterPressure ProcessingLimitException"<<std::endl;
1014 v3s16 e = m_area.getExtent();
1015 s32 v = m_area.getVolume();
1016 dstream<<"flowWater (active): moved "<<counter<<" nodes, "
1017 <<"area ended up as "
1018 <<e.X<<"x"<<e.Y<<"x"<<e.Z<<" = "<<v
1021 dstream<<"addarea_time: "<<addarea_time
1022 <<", emerge_time: "<<emerge_time
1023 <<", emerge_load_time: "<<emerge_load_time
1024 <<", clearflag_time: "<<clearflag_time
1025 <<", flowwater_pre_time: "<<flowwater_pre_time
1026 <<", updateareawaterpressure_time: "<<updateareawaterpressure_time