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 bool MapBlock::isValidPositionParent(v3s16 p)
34 if(isValidPosition(p))
39 return m_parent->isValidPosition(getPosRelative() + p);
43 MapNode MapBlock::getNodeParent(v3s16 p)
45 if(isValidPosition(p) == false)
47 return m_parent->getNode(getPosRelative() + p);
52 throw InvalidPositionException();
53 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
57 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
59 if(isValidPosition(p) == false)
61 m_parent->setNode(getPosRelative() + p, n);
66 throw InvalidPositionException();
67 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
71 FastFace * MapBlock::makeFastFace(u16 tile, u8 light, v3f p,
72 v3f dir, v3f scale, v3f posRelative_f)
74 FastFace *f = new FastFace;
76 // Position is at the center of the cube.
81 // If looking towards z+, this is the face that is behind
82 // the center point, facing towards z+.
83 vertex_pos[0] = v3f( BS/2,-BS/2,BS/2);
84 vertex_pos[1] = v3f(-BS/2,-BS/2,BS/2);
85 vertex_pos[2] = v3f(-BS/2, BS/2,BS/2);
86 vertex_pos[3] = v3f( BS/2, BS/2,BS/2);
89 TODO: Rotate it the right way (one side comes upside down)
91 core::CMatrix4<f32> m;
92 m.buildRotateFromTo(v3f(0,0,1), dir);
94 for(u16 i=0; i<4; i++){
95 m.rotateVect(vertex_pos[i]);
96 vertex_pos[i].X *= scale.X;
97 vertex_pos[i].Y *= scale.Y;
98 vertex_pos[i].Z *= scale.Z;
99 vertex_pos[i] += pos + posRelative_f;
103 if (scale.X < 0.999 || scale.X > 1.001) abs_scale = scale.X;
104 else if(scale.Y < 0.999 || scale.Y > 1.001) abs_scale = scale.Y;
105 else if(scale.Z < 0.999 || scale.Z > 1.001) abs_scale = scale.Z;
107 v3f zerovector = v3f(0,0,0);
109 u8 li = decode_light(light);
114 //if(material == CONTENT_WATER || material == CONTENT_OCEAN)
115 if(tile == CONTENT_WATER || tile == CONTENT_OCEAN)
116 //if(tile == TILE_WATER)
121 video::SColor c = video::SColor(alpha,li,li,li);
123 /*f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
124 core::vector2d<f32>(0,1));
125 f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
126 core::vector2d<f32>(abs_scale,1));
127 f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
128 core::vector2d<f32>(abs_scale,0));
129 f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
130 core::vector2d<f32>(0,0));*/
131 f->vertices[0] = video::S3DVertex(vertex_pos[0], zerovector, c,
132 core::vector2d<f32>(0,1));
133 f->vertices[1] = video::S3DVertex(vertex_pos[1], zerovector, c,
134 core::vector2d<f32>(abs_scale,1));
135 f->vertices[2] = video::S3DVertex(vertex_pos[2], zerovector, c,
136 core::vector2d<f32>(abs_scale,0));
137 f->vertices[3] = video::S3DVertex(vertex_pos[3], zerovector, c,
138 core::vector2d<f32>(0,0));
146 Parameters must consist of air and !air.
147 Order doesn't matter.
149 If either of the nodes doesn't exist, light is 0.
151 u8 MapBlock::getFaceLight(v3s16 p, v3s16 face_dir)
154 MapNode n = getNodeParent(p);
155 MapNode n2 = getNodeParent(p + face_dir);
157 /*if(n.solidness() < n2.solidness())
158 light = n.getLight();
160 light = n2.getLight();*/
161 if(n.getLight() > n2.getLight())
162 light = n.getLight();
164 light = n2.getLight();
166 // Make some nice difference to different sides
168 /*if(face_dir.X == 1 || face_dir.Z == 1 || face_dir.Y == -1)
169 light = diminish_light(diminish_light(light));
170 else if(face_dir.X == -1 || face_dir.Z == -1)
171 light = diminish_light(light);*/
173 if(face_dir.X == 1 || face_dir.X == -1 || face_dir.Y == -1)
174 light = diminish_light(diminish_light(light));
175 else if(face_dir.Z == 1 || face_dir.Z == -1)
176 light = diminish_light(light);
180 catch(InvalidPositionException &e)
187 Gets node material from any place relative to block.
188 Returns CONTENT_IGNORE if doesn't exist or should not be drawn.
190 u8 MapBlock::getNodeTile(v3s16 p)
193 MapNode n = getNodeParent(p);
195 return content_tile(n.d);
197 catch(InvalidPositionException &e)
199 return CONTENT_IGNORE;
205 translate_dir: unit vector with only one of x, y or z
206 face_dir: unit vector with only one of x, y or z
208 void MapBlock::updateFastFaceRow(v3s16 startpos,
212 core::list<FastFace*> &dest)
215 Precalculate some variables
217 v3f translate_dir_f(translate_dir.X, translate_dir.Y,
218 translate_dir.Z); // floating point conversion
219 v3f face_dir_f(face_dir.X, face_dir.Y,
220 face_dir.Z); // floating point conversion
221 v3f posRelative_f(getPosRelative().X, getPosRelative().Y,
222 getPosRelative().Z); // floating point conversion
226 The light in the air lights the surface is taken from
227 the node that is air.
229 u8 light = getFaceLight(p, face_dir);
231 u16 continuous_tiles_count = 0;
233 u8 tile0 = getNodeTile(p);
234 u8 tile1 = getNodeTile(p + face_dir);
236 for(u16 j=0; j<length; j++)
238 bool next_is_different = true;
246 p_next = p + translate_dir;
247 tile0_next = getNodeTile(p_next);
248 tile1_next = getNodeTile(p_next + face_dir);
249 light_next = getFaceLight(p_next, face_dir);
251 if(tile0_next == tile0
252 && tile1_next == tile1
253 && light_next == light)
255 next_is_different = false;
259 continuous_tiles_count++;
261 if(next_is_different)
264 Create a face if there should be one
266 u8 mf = face_contents(tile0, tile1);
270 // Floating point conversion of the position vector
271 v3f pf(p.X, p.Y, p.Z);
272 // Center point of face (kind of)
273 v3f sp = pf - ((f32)continuous_tiles_count / 2. - 0.5) * translate_dir_f;
275 if(translate_dir.X != 0){
276 scale.X = continuous_tiles_count;
278 if(translate_dir.Y != 0){
279 scale.Y = continuous_tiles_count;
281 if(translate_dir.Z != 0){
282 scale.Z = continuous_tiles_count;
287 // If node at sp (tile0) is more solid
290 f = makeFastFace(tile0, light,
291 sp, face_dir_f, scale,
294 // If node at sp is less solid (mf == 2)
297 f = makeFastFace(tile1, light,
298 sp+face_dir_f, -1*face_dir_f, scale,
304 continuous_tiles_count = 0;
315 This is used because CMeshBuffer::append() is very slow
319 video::SMaterial material;
320 core::array<u16> indices;
321 core::array<video::S3DVertex> vertices;
328 video::SMaterial material,
329 const video::S3DVertex* const vertices,
331 const u16* const indices,
335 PreMeshBuffer *p = NULL;
336 for(u32 i=0; i<m_prebuffers.size(); i++)
338 PreMeshBuffer &pp = m_prebuffers[i];
339 if(pp.material != material)
349 pp.material = material;
350 m_prebuffers.push_back(pp);
351 p = &m_prebuffers[m_prebuffers.size()-1];
354 u32 vertex_count = p->vertices.size();
355 for(u32 i=0; i<numIndices; i++)
357 u32 j = indices[i] + vertex_count;
360 dstream<<"FIXME: Meshbuffer ran out of indices"<<std::endl;
361 // NOTE: Fix is to just add an another MeshBuffer
363 p->indices.push_back(j);
365 for(u32 i=0; i<numVertices; i++)
367 p->vertices.push_back(vertices[i]);
371 void fillMesh(scene::SMesh *mesh)
373 /*dstream<<"Filling mesh with "<<m_prebuffers.size()
374 <<" meshbuffers"<<std::endl;*/
375 for(u32 i=0; i<m_prebuffers.size(); i++)
377 PreMeshBuffer &p = m_prebuffers[i];
379 /*dstream<<"p.vertices.size()="<<p.vertices.size()
380 <<", p.indices.size()="<<p.indices.size()
385 // This is a "Standard MeshBuffer",
386 // it's a typedeffed CMeshBuffer<video::S3DVertex>
387 scene::SMeshBuffer *buf = new scene::SMeshBuffer();
389 buf->Material = p.material;
390 //((scene::SMeshBuffer*)buf)->Material = p.material;
392 //buf->setHardwareMappingHint(scene::EHM_STATIC);
394 mesh->addMeshBuffer(buf);
398 buf->append(p.vertices.pointer(), p.vertices.size(),
399 p.indices.pointer(), p.indices.size());
404 core::array<PreMeshBuffer> m_prebuffers;
407 void MapBlock::updateMesh()
409 /*v3s16 p = getPosRelative();
410 std::cout<<"MapBlock("<<p.X<<","<<p.Y<<","<<p.Z<<")"
411 <<"::updateMesh(): ";*/
412 //<<"::updateMesh()"<<std::endl;
415 TODO: Change this to directly generate the mesh (and get rid
419 core::list<FastFace*> *fastfaces_new = new core::list<FastFace*>;
422 We are including the faces of the trailing edges of the block.
423 This means that when something changes, the caller must
424 also update the meshes of the blocks at the leading edges.
428 Go through every y,z and get top faces in rows of x+
430 for(s16 y=0; y<MAP_BLOCKSIZE; y++){
431 //for(s16 y=-1; y<MAP_BLOCKSIZE; y++){
432 for(s16 z=0; z<MAP_BLOCKSIZE; z++){
433 updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
440 Go through every x,y and get right faces in rows of z+
442 for(s16 x=0; x<MAP_BLOCKSIZE; x++){
443 //for(s16 x=-1; x<MAP_BLOCKSIZE; x++){
444 for(s16 y=0; y<MAP_BLOCKSIZE; y++){
445 updateFastFaceRow(v3s16(x,y,0), MAP_BLOCKSIZE,
452 Go through every y,z and get back faces in rows of x+
454 for(s16 z=0; z<MAP_BLOCKSIZE; z++){
455 //for(s16 z=-1; z<MAP_BLOCKSIZE; z++){
456 for(s16 y=0; y<MAP_BLOCKSIZE; y++){
457 updateFastFaceRow(v3s16(0,y,z), MAP_BLOCKSIZE,
464 scene::SMesh *mesh_new = NULL;
466 mesh_new = new scene::SMesh();
468 if(fastfaces_new->getSize() > 0)
470 MeshCollector collector;
472 core::list<FastFace*>::Iterator i = fastfaces_new->begin();
474 for(; i != fastfaces_new->end(); i++)
478 const u16 indices[] = {0,1,2,2,3,0};
480 /*collector.append(g_materials[f->material], f->vertices, 4,
482 collector.append(g_materials[f->tile], f->vertices, 4,
486 collector.fillMesh(mesh_new);
488 // Use VBO for mesh (this just would set this for ever buffer)
489 //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
491 /*std::cout<<"MapBlock has "<<fastfaces_new->getSize()<<" faces "
492 <<"and uses "<<mesh_new->getMeshBufferCount()
493 <<" materials (meshbuffers)"<<std::endl;*/
497 Clear temporary FastFaces
500 core::list<FastFace*>::Iterator i;
501 i = fastfaces_new->begin();
502 for(; i != fastfaces_new->end(); i++)
506 fastfaces_new->clear();
507 delete fastfaces_new;
510 Add special graphics:
513 TODO: Optimize by using same meshbuffer for same textures
516 /*scene::ISceneManager *smgr = NULL;
517 video::IVideoDriver* driver = NULL;
520 smgr = g_device->getSceneManager();
521 driver = smgr->getVideoDriver();
524 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
525 for(s16 y=0; y<MAP_BLOCKSIZE; y++)
526 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
530 MapNode &n = getNodeRef(x,y,z);
532 if(n.d == CONTENT_LIGHT)
534 //scene::IMeshBuffer *buf = new scene::SMeshBuffer();
535 scene::SMeshBuffer *buf = new scene::SMeshBuffer();
536 video::SColor c(255,255,255,255);
538 video::S3DVertex vertices[4] =
540 video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 0,1),
541 video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 1,1),
542 video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
543 video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
546 v3s16 dir = unpackDir(n.dir);
548 for(s32 i=0; i<4; i++)
550 if(dir == v3s16(1,0,0))
551 vertices[i].Pos.rotateXZBy(0);
552 if(dir == v3s16(-1,0,0))
553 vertices[i].Pos.rotateXZBy(180);
554 if(dir == v3s16(0,0,1))
555 vertices[i].Pos.rotateXZBy(90);
556 if(dir == v3s16(0,0,-1))
557 vertices[i].Pos.rotateXZBy(-90);
558 if(dir == v3s16(0,-1,0))
559 vertices[i].Pos.rotateXZBy(45);
560 if(dir == v3s16(0,1,0))
561 vertices[i].Pos.rotateXZBy(-45);
563 vertices[i].Pos += intToFloat(p + getPosRelative());
566 u16 indices[] = {0,1,2,2,3,0};
567 buf->append(vertices, 4, indices, 6);
570 buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
571 buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
572 buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
573 //buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
574 buf->getMaterial().MaterialType
575 = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
576 if(dir == v3s16(0,-1,0))
577 buf->getMaterial().setTexture(0,
578 g_texturecache.get("torch_on_floor"));
579 else if(dir == v3s16(0,1,0))
580 buf->getMaterial().setTexture(0,
581 g_texturecache.get("torch_on_ceiling"));
582 // For backwards compatibility
583 else if(dir == v3s16(0,0,0))
584 buf->getMaterial().setTexture(0,
585 g_texturecache.get("torch_on_floor"));
587 buf->getMaterial().setTexture(0, g_texturecache.get("torch"));
590 mesh_new->addMeshBuffer(buf);
596 Do some stuff to the mesh
599 mesh_new->recalculateBoundingBox();
602 Delete new mesh if it is empty
605 if(mesh_new->getMeshBufferCount() == 0)
617 scene::SMesh *mesh_old = mesh;
623 // Remove hardware buffers of meshbuffers of mesh
624 // NOTE: No way, this runs in a different thread and everything
625 /*u32 c = mesh_old->getMeshBufferCount();
626 for(u32 i=0; i<c; i++)
628 IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
637 //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
641 Propagates sunlight down through the block.
642 Doesn't modify nodes that are not affected by sunlight.
644 Returns false if sunlight at bottom block is invalid
645 Returns true if bottom block doesn't exist.
647 If there is a block above, continues from it.
648 If there is no block above, assumes there is sunlight, unless
649 is_underground is set.
651 At the moment, all sunlighted nodes are added to light_sources.
652 TODO: This could be optimized.
654 bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources)
656 // Whether the sunlight at the top of the bottom block is valid
657 bool block_below_is_valid = true;
659 v3s16 pos_relative = getPosRelative();
661 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
663 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
665 bool no_sunlight = false;
666 bool no_top_block = false;
667 // Check if node above block has sunlight
669 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
670 if(n.getLight() != LIGHT_SUN)
679 catch(InvalidPositionException &e)
683 // TODO: This makes over-ground roofed places sunlighted
684 // Assume sunlight, unless is_underground==true
690 // TODO: There has to be some way to allow this behaviour
691 // As of now, it just makes everything dark.
693 //no_sunlight = true;
696 /*std::cout<<"("<<x<<","<<z<<"): "
697 <<"no_top_block="<<no_top_block
698 <<", is_underground="<<is_underground
699 <<", no_sunlight="<<no_sunlight
702 s16 y = MAP_BLOCKSIZE-1;
704 if(no_sunlight == false)
706 // Continue spreading sunlight downwards through transparent
712 MapNode &n = getNodeRef(pos);
714 if(n.sunlight_propagates())
716 n.setLight(LIGHT_SUN);
718 light_sources.insert(pos_relative + pos, true);
726 bool sunlight_should_go_down = (y==-1);
728 // Fill rest with black (only transparent ones)
732 MapNode &n = getNodeRef(pos);
734 if(n.light_propagates())
744 If the block below hasn't already been marked invalid:
746 Check if the node below the block has proper sunlight at top.
747 If not, the block below is invalid.
749 Ignore non-transparent nodes as they always have no light
753 if(block_below_is_valid)
755 MapNode n = getNodeParent(v3s16(x, -1, z));
756 if(n.light_propagates())
758 if(n.getLight() == LIGHT_SUN
759 && sunlight_should_go_down == false)
760 block_below_is_valid = false;
761 else if(n.getLight() != LIGHT_SUN
762 && sunlight_should_go_down == true)
763 block_below_is_valid = false;
767 catch(InvalidPositionException &e)
769 /*std::cout<<"InvalidBlockException for bottom block node"
771 // Just no block below, no need to panic.
776 return block_below_is_valid;
779 void MapBlock::copyTo(VoxelManipulator &dst)
781 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
782 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
784 dst.copyFrom(data, data_area, v3s16(0,0,0),
785 getPosRelative(), data_size);
788 /*void getPseudoObjects(v3f origin, f32 max_d,
789 core::array<DistanceSortedObject> &dest)
797 void MapBlock::serialize(std::ostream &os, u8 version)
799 if(!ser_ver_supported(version))
800 throw VersionMismatchException("ERROR: MapBlock format not supported");
804 throw SerializationError("ERROR: Not writing dummy block.");
807 // These have no compression
808 if(version <= 3 || version == 5 || version == 6)
810 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
812 u32 buflen = 1 + nodecount * MapNode::serializedLength(version);
813 SharedBuffer<u8> dest(buflen);
815 dest[0] = is_underground;
816 for(u32 i=0; i<nodecount; i++)
818 u32 s = 1 + i * MapNode::serializedLength(version);
819 data[i].serialize(&dest[s], version);
822 os.write((char*)*dest, dest.getSize());
824 else if(version <= 10)
828 Compress the materials and the params separately.
832 os.write((char*)&is_underground, 1);
834 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
836 // Get and compress materials
837 SharedBuffer<u8> materialdata(nodecount);
838 for(u32 i=0; i<nodecount; i++)
840 materialdata[i] = data[i].d;
842 compress(materialdata, os, version);
844 // Get and compress lights
845 SharedBuffer<u8> lightdata(nodecount);
846 for(u32 i=0; i<nodecount; i++)
848 lightdata[i] = data[i].param;
850 compress(lightdata, os, version);
854 // Get and compress pressure
855 SharedBuffer<u8> pressuredata(nodecount);
856 for(u32 i=0; i<nodecount; i++)
858 pressuredata[i] = data[i].pressure;
860 compress(pressuredata, os, version);
863 // All other versions (newest)
867 os.write((char*)&is_underground, 1);
869 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
875 SharedBuffer<u8> databuf(nodecount*3);
878 for(u32 i=0; i<nodecount; i++)
880 databuf[i] = data[i].d;
884 for(u32 i=0; i<nodecount; i++)
886 databuf[i+nodecount] = data[i].param;
890 for(u32 i=0; i<nodecount; i++)
892 databuf[i+nodecount*2] = data[i].pressure;
896 Compress data to output stream
899 compress(databuf, os, version);
903 void MapBlock::deSerialize(std::istream &is, u8 version)
905 if(!ser_ver_supported(version))
906 throw VersionMismatchException("ERROR: MapBlock format not supported");
908 // These have no compression
909 if(version <= 3 || version == 5 || version == 6)
911 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
915 throw SerializationError
916 ("MapBlock::deSerialize: no enough input data");
917 is_underground = tmp;
918 for(u32 i=0; i<nodecount; i++)
920 s32 len = MapNode::serializedLength(version);
921 SharedBuffer<u8> d(len);
922 is.read((char*)*d, len);
923 if(is.gcount() != len)
924 throw SerializationError
925 ("MapBlock::deSerialize: no enough input data");
926 data[i].deSerialize(*d, version);
929 else if(version <= 10)
931 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
934 is.read((char*)&t8, 1);
938 // Uncompress and set material data
939 std::ostringstream os(std::ios_base::binary);
940 decompress(is, os, version);
941 std::string s = os.str();
942 if(s.size() != nodecount)
943 throw SerializationError
944 ("MapBlock::deSerialize: invalid format");
945 for(u32 i=0; i<s.size(); i++)
951 // Uncompress and set param data
952 std::ostringstream os(std::ios_base::binary);
953 decompress(is, os, version);
954 std::string s = os.str();
955 if(s.size() != nodecount)
956 throw SerializationError
957 ("MapBlock::deSerialize: invalid format");
958 for(u32 i=0; i<s.size(); i++)
960 data[i].param = s[i];
966 // Uncompress and set pressure data
967 std::ostringstream os(std::ios_base::binary);
968 decompress(is, os, version);
969 std::string s = os.str();
970 if(s.size() != nodecount)
971 throw SerializationError
972 ("MapBlock::deSerialize: invalid format");
973 for(u32 i=0; i<s.size(); i++)
975 data[i].pressure = s[i];
979 // All other versions (newest)
982 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
985 is.read((char*)&t8, 1);
989 std::ostringstream os(std::ios_base::binary);
990 decompress(is, os, version);
991 std::string s = os.str();
992 if(s.size() != nodecount*3)
993 throw SerializationError
994 ("MapBlock::deSerialize: invalid format");
997 for(u32 i=0; i<nodecount; i++)
1002 for(u32 i=0; i<nodecount; i++)
1004 data[i].param = s[i+nodecount];
1007 for(u32 i=0; i<nodecount; i++)
1009 data[i].pressure = s[i+nodecount*2];