Esempio n. 1
0
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
	m_mesh(new scene::SMesh()),
	m_gamedef(data->m_gamedef),
	m_animation_force_timer(0), // force initial animation
	m_last_crack(-1),
	m_crack_materials(),
	m_last_daynight_ratio((u32) -1),
	m_daynight_diffs()
{
	// 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
	// 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
	//TimeTaker timer1("MapBlockMesh()");

	std::vector<FastFace> fastfaces_new;

	/*
		We are including the faces of the trailing edges of the block.
		This means that when something changes, the caller must
		also update the meshes of the blocks at the leading edges.

		NOTE: This is the slowest part of this method.
	*/
	{
		// 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
		//TimeTaker timer2("updateAllFastFaceRows()");
		updateAllFastFaceRows(data, fastfaces_new);
	}
	// End of slow part

	/*
		Convert FastFaces to MeshCollector
	*/

	MeshCollector collector;

	{
		// avg 0ms (100ms spikes when loading textures the first time)
		// (NOTE: probably outdated)
		//TimeTaker timer2("MeshCollector building");

		for(u32 i=0; i<fastfaces_new.size(); i++)
		{
			FastFace &f = fastfaces_new[i];

			const u16 indices[] = {0,1,2,2,3,0};
			const u16 indices_alternate[] = {0,1,3,2,3,1};
			
			if(f.tile.texture == NULL)
				continue;

			const u16 *indices_p = indices;
			
			/*
				Revert triangles for nicer looking gradient if vertices
				1 and 3 have same color or 0 and 2 have different color.
				getRed() is the day color.
			*/
			if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed()
					|| f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed())
				indices_p = indices_alternate;
			
			collector.append(f.tile, f.vertices, 4, indices_p, 6);
		}
	}

	/*
		Add special graphics:
		- torches
		- flowing water
		- fences
		- whatever
	*/

	mapblock_mesh_generate_special(data, collector);
	

	/*
		Convert MeshCollector to SMesh
	*/
	bool enable_shaders     = g_settings->getBool("enable_shaders");
	bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping");
	bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");

	video::E_MATERIAL_TYPE  shadermat1, shadermat2, shadermat3,
							shadermat4, shadermat5;
	shadermat1 = shadermat2 = shadermat3 = shadermat4 = shadermat5 = 
		video::EMT_SOLID;

	if (enable_shaders) {
		IShaderSource *shdrsrc = m_gamedef->getShaderSource();
		shadermat1 = shdrsrc->getShader("solids_shader").material;
		shadermat2 = shdrsrc->getShader("liquids_shader").material;
		shadermat3 = shdrsrc->getShader("alpha_shader").material;
		shadermat4 = shdrsrc->getShader("leaves_shader").material;
		shadermat5 = shdrsrc->getShader("plants_shader").material;
	}

	for(u32 i = 0; i < collector.prebuffers.size(); i++)
	{
		PreMeshBuffer &p = collector.prebuffers[i];
		/*dstream<<"p.vertices.size()="<<p.vertices.size()
				<<", p.indices.size()="<<p.indices.size()
				<<std::endl;*/

		// Generate animation data
		// - Cracks
		if(p.tile.material_flags & MATERIAL_FLAG_CRACK)
		{
			ITextureSource *tsrc = data->m_gamedef->tsrc();
			// Find the texture name plus ^[crack:N:
			std::ostringstream os(std::ios::binary);
			os<<tsrc->getTextureName(p.tile.texture_id)<<"^[crack";
			if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
				os<<"o";  // use ^[cracko
			os<<":"<<(u32)p.tile.animation_frame_count<<":";
			m_crack_materials.insert(std::make_pair(i, os.str()));
			// Replace tile texture with the cracked one
			p.tile.texture = tsrc->getTexture(
					os.str()+"0",
					&p.tile.texture_id);
		}
		// - Texture animation
		if(p.tile.material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
		{
			ITextureSource *tsrc = data->m_gamedef->tsrc();
			// Add to MapBlockMesh in order to animate these tiles
			m_animation_tiles[i] = p.tile;
			m_animation_frames[i] = 0;
			if(g_settings->getBool("desynchronize_mapblock_texture_animation")){
				// Get starting position from noise
				m_animation_frame_offsets[i] = 100000 * (2.0 + noise3d(
						data->m_blockpos.X, data->m_blockpos.Y,
						data->m_blockpos.Z, 0));
			} else {
				// Play all synchronized
				m_animation_frame_offsets[i] = 0;
			}
			// Replace tile texture with the first animation frame
			std::ostringstream os(std::ios::binary);
			os<<tsrc->getTextureName(p.tile.texture_id);
			os<<"^[verticalframe:"<<(int)p.tile.animation_frame_count<<":0";
			p.tile.texture = tsrc->getTexture(
					os.str(),
					&p.tile.texture_id);
		}
		// - Classic lighting (shaders handle this by themselves)
		if(!enable_shaders)
		{
			for(u32 j = 0; j < p.vertices.size(); j++)
			{
				video::SColor &vc = p.vertices[j].Color;
				// Set initial real color and store for later updates
				u8 day = vc.getRed();
				u8 night = vc.getGreen();
				finalColorBlend(vc, day, night, 1000);
				if(day != night)
					m_daynight_diffs[i][j] = std::make_pair(day, night);
				// Brighten topside (no shaders)
				if(p.vertices[j].Normal.Y > 0.5)
				{
					vc.setRed  (srgb_linear_multiply(vc.getRed(),   1.3, 255.0));
					vc.setGreen(srgb_linear_multiply(vc.getGreen(), 1.3, 255.0));
					vc.setBlue (srgb_linear_multiply(vc.getBlue(),  1.3, 255.0));
				}
			}
		}

		// Create material
		video::SMaterial material;
		material.setFlag(video::EMF_LIGHTING, false);
		material.setFlag(video::EMF_BACK_FACE_CULLING, true);
		material.setFlag(video::EMF_BILINEAR_FILTER, false);
		material.setFlag(video::EMF_FOG_ENABLE, true);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_SIMPLE);
		//material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
		material.setTexture(0, p.tile.texture);

		if (enable_shaders) {
			ITextureSource *tsrc = data->m_gamedef->tsrc();
			material.setTexture(2, tsrc->getTexture("disable_img.png"));
			if (enable_bumpmapping || enable_parallax_occlusion) {
				std::string fname_base = tsrc->getTextureName(p.tile.texture_id);
				std::string normal_ext = "_normal.png";
				size_t pos = fname_base.find(".");
				std::string fname_normal = fname_base.substr(0, pos) + normal_ext;

				if (tsrc->isKnownSourceImage(fname_normal)) {
					// look for image extension and replace it 
					size_t i = 0;
					while ((i = fname_base.find(".", i)) != std::string::npos) {
						fname_base.replace(i, 4, normal_ext);
						i += normal_ext.length();
					}
					material.setTexture(1, tsrc->getTexture(fname_base));
					material.setTexture(2, tsrc->getTexture("enable_img.png"));
				}
			}
			p.tile.applyMaterialOptionsWithShaders(material,
				shadermat1, shadermat2, shadermat3, shadermat4, shadermat5);
		} else {
			p.tile.applyMaterialOptions(material);
		}
		// Create meshbuffer

		// This is a "Standard MeshBuffer",
		// it's a typedeffed CMeshBuffer<video::S3DVertex>
		scene::SMeshBuffer *buf = new scene::SMeshBuffer();
		// Set material
		buf->Material = material;
		// Add to mesh
		m_mesh->addMeshBuffer(buf);
		// Mesh grabbed it
		buf->drop();
		buf->append(&p.vertices[0], p.vertices.size(),
				&p.indices[0], p.indices.size());
	}

	m_camera_offset = camera_offset;
	
	/*
		Do some stuff to the mesh
	*/

	translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));

	if(m_mesh)
	{
#if 0
		// Usually 1-700 faces and 1-7 materials
		std::cout<<"Updated MapBlock has "<<fastfaces_new.size()<<" faces "
				<<"and uses "<<m_mesh->getMeshBufferCount()
				<<" materials (meshbuffers)"<<std::endl;
#endif

		// Use VBO for mesh (this just would set this for ever buffer)
		// This will lead to infinite memory usage because or irrlicht.
		//m_mesh->setHardwareMappingHint(scene::EHM_STATIC);

		/*
			NOTE: If that is enabled, some kind of a queue to the main
			thread should be made which would call irrlicht to delete
			the hardware buffer and then delete the mesh
		*/
	}
	
	//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;

	// Check if animation is required for this mesh
	m_has_animation =
		!m_crack_materials.empty() ||
		!m_daynight_diffs.empty() ||
		!m_animation_tiles.empty();
}
scene::SMesh* makeMapBlockMesh(MeshMakeData *data)
{
	// 4-21ms for MAP_BLOCKSIZE=16
	// 24-155ms for MAP_BLOCKSIZE=32
	//TimeTaker timer1("makeMapBlockMesh()");

	core::array<FastFace> fastfaces_new;

	v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE;
	
	// floating point conversion
	v3f posRelative_f(blockpos_nodes.X, blockpos_nodes.Y, blockpos_nodes.Z);
	
	/*
		Some settings
	*/
	//bool new_style_water = g_settings.getBool("new_style_water");
	//bool new_style_leaves = g_settings.getBool("new_style_leaves");
	bool smooth_lighting = g_settings.getBool("smooth_lighting");
	
	/*
		We are including the faces of the trailing edges of the block.
		This means that when something changes, the caller must
		also update the meshes of the blocks at the leading edges.

		NOTE: This is the slowest part of this method.
	*/
	
	{
		// 4-23ms for MAP_BLOCKSIZE=16
		//TimeTaker timer2("updateMesh() collect");

		/*
			Go through every y,z and get top(y+) faces in rows of x+
		*/
		for(s16 y=0; y<MAP_BLOCKSIZE; y++){
			for(s16 z=0; z<MAP_BLOCKSIZE; z++){
				updateFastFaceRow(data->m_daynight_ratio, posRelative_f,
						v3s16(0,y,z), MAP_BLOCKSIZE,
						v3s16(1,0,0), //dir
						v3f  (1,0,0),
						v3s16(0,1,0), //face dir
						v3f  (0,1,0),
						fastfaces_new,
						data->m_temp_mods,
						data->m_vmanip,
						blockpos_nodes,
						smooth_lighting);
			}
		}
		/*
			Go through every x,y and get right(x+) faces in rows of z+
		*/
		for(s16 x=0; x<MAP_BLOCKSIZE; x++){
			for(s16 y=0; y<MAP_BLOCKSIZE; y++){
				updateFastFaceRow(data->m_daynight_ratio, posRelative_f,
						v3s16(x,y,0), MAP_BLOCKSIZE,
						v3s16(0,0,1),
						v3f  (0,0,1),
						v3s16(1,0,0),
						v3f  (1,0,0),
						fastfaces_new,
						data->m_temp_mods,
						data->m_vmanip,
						blockpos_nodes,
						smooth_lighting);
			}
		}
		/*
			Go through every y,z and get back(z+) faces in rows of x+
		*/
		for(s16 z=0; z<MAP_BLOCKSIZE; z++){
			for(s16 y=0; y<MAP_BLOCKSIZE; y++){
				updateFastFaceRow(data->m_daynight_ratio, posRelative_f,
						v3s16(0,y,z), MAP_BLOCKSIZE,
						v3s16(1,0,0),
						v3f  (1,0,0),
						v3s16(0,0,1),
						v3f  (0,0,1),
						fastfaces_new,
						data->m_temp_mods,
						data->m_vmanip,
						blockpos_nodes,
						smooth_lighting);
			}
		}
	}

	// End of slow part

	/*
		Convert FastFaces to SMesh
	*/

	MeshCollector collector;

	if(fastfaces_new.size() > 0)
	{
		// avg 0ms (100ms spikes when loading textures the first time)
		//TimeTaker timer2("updateMesh() mesh building");

		video::SMaterial material;
		material.setFlag(video::EMF_LIGHTING, false);
		material.setFlag(video::EMF_BILINEAR_FILTER, false);
		material.setFlag(video::EMF_FOG_ENABLE, true);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_SIMPLE);

		for(u32 i=0; i<fastfaces_new.size(); i++)
		{
			FastFace &f = fastfaces_new[i];

			const u16 indices[] = {0,1,2,2,3,0};
			const u16 indices_alternate[] = {0,1,3,2,3,1};
			
			video::ITexture *texture = f.tile.texture.atlas;
			if(texture == NULL)
				continue;

			material.setTexture(0, texture);
			
			f.tile.applyMaterialOptions(material);

			const u16 *indices_p = indices;
			
			/*
				Revert triangles for nicer looking gradient if vertices
				1 and 3 have same color or 0 and 2 have different color.
			*/
			if(f.vertices[0].Color != f.vertices[2].Color
					|| f.vertices[1].Color == f.vertices[3].Color)
				indices_p = indices_alternate;
			
			collector.append(material, f.vertices, 4, indices_p, 6);
		}
	}

	/*
		Add special graphics:
		- torches
		- flowing water
		- fences
		- whatever
	*/

	mapblock_mesh_generate_special(data, collector);
	
	/*
		Add stuff from collector to mesh
	*/
	
	scene::SMesh *mesh_new = NULL;
	mesh_new = new scene::SMesh();
	
	collector.fillMesh(mesh_new);

	/*
		Do some stuff to the mesh
	*/

	mesh_new->recalculateBoundingBox();

	/*
		Delete new mesh if it is empty
	*/

	if(mesh_new->getMeshBufferCount() == 0)
	{
		mesh_new->drop();
		mesh_new = NULL;
	}

	if(mesh_new)
	{
#if 0
		// Usually 1-700 faces and 1-7 materials
		std::cout<<"Updated MapBlock has "<<fastfaces_new.size()<<" faces "
				<<"and uses "<<mesh_new->getMeshBufferCount()
				<<" materials (meshbuffers)"<<std::endl;
#endif

		// Use VBO for mesh (this just would set this for ever buffer)
		// This will lead to infinite memory usage because or irrlicht.
		//mesh_new->setHardwareMappingHint(scene::EHM_STATIC);

		/*
			NOTE: If that is enabled, some kind of a queue to the main
			thread should be made which would call irrlicht to delete
			the hardware buffer and then delete the mesh
		*/
	}

	return mesh_new;
	
	//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
}
Esempio n. 3
0
void mapblock_mesh_generate_special(MeshMakeData *data,
		MeshCollector &collector)
{
	INodeDefManager *nodedef = data->m_gamedef->ndef();

	// 0ms
	//TimeTaker timer("mapblock_mesh_generate_special()");

	/*
		Some settings
	*/
	bool new_style_water = g_settings->getBool("new_style_water");
	
	float node_liquid_level = 1.0;
	if(new_style_water)
		node_liquid_level = 0.85;
	
	v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE;

	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
	{
		v3s16 p(x,y,z);

		MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes+p);
		const ContentFeatures &f = nodedef->get(n);

		// Only solidness=0 stuff is drawn here
		if(f.solidness != 0)
			continue;
		
		switch(f.drawtype){
		default:
			infostream<<"Got "<<f.drawtype<<std::endl;
			assert(0);
			break;
		case NDT_AIRLIKE:
			break;
		case NDT_LIQUID:
		{
			/*
				Add water sources to mesh if using new style
			*/
			TileSpec tile_liquid = f.special_tiles[0];
			AtlasPointer &pa_liquid = tile_liquid.texture;

			bool top_is_air = false;
			MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			if(n.getContent() == CONTENT_AIR)
				top_is_air = true;
			
			if(top_is_air == false)
				continue;

			u16 l = getInteriorLight(n, 0, data);
			video::SColor c = MapBlock_LightColor(f.alpha, l);
			
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
						pa_liquid.x0(), pa_liquid.y1()),
				video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
						pa_liquid.x1(), pa_liquid.y1()),
				video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
						pa_liquid.x1(), pa_liquid.y0()),
				video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
						pa_liquid.x0(), pa_liquid.y0()),
			};

			v3f offset(p.X, p.Y + (-0.5+node_liquid_level)*BS, p.Z);
			for(s32 i=0; i<4; i++)
			{
				vertices[i].Pos += offset;
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(tile_liquid, vertices, 4, indices, 6);
		break;}
		case NDT_FLOWINGLIQUID:
		{
			/*
				Add flowing liquid to mesh
			*/
			TileSpec tile_liquid = f.special_tiles[0];
			TileSpec tile_liquid_bfculled = f.special_tiles[1];
			AtlasPointer &pa_liquid = tile_liquid.texture;

			bool top_is_same_liquid = false;
			MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			content_t c_flowing = nodedef->getId(f.liquid_alternative_flowing);
			content_t c_source = nodedef->getId(f.liquid_alternative_source);
			if(ntop.getContent() == c_flowing || ntop.getContent() == c_source)
				top_is_same_liquid = true;
			
			u16 l = 0;
			// If this liquid emits light and doesn't contain light, draw
			// it at what it emits, for an increased effect
			u8 light_source = nodedef->get(n).light_source;
			if(light_source != 0){
				//l = decode_light(undiminish_light(light_source));
				l = decode_light(light_source);
				l = l | (l<<8);
			}
			// Use the light of the node on top if possible
			else if(nodedef->get(ntop).param_type == CPT_LIGHT)
				l = getInteriorLight(ntop, 0, data);
			// Otherwise use the light of this node (the liquid)
			else
				l = getInteriorLight(n, 0, data);
			video::SColor c = MapBlock_LightColor(f.alpha, l);
			
			// Neighbor liquid levels (key = relative position)
			// Includes current node
			core::map<v3s16, f32> neighbor_levels;
			core::map<v3s16, content_t> neighbor_contents;
			core::map<v3s16, u8> neighbor_flags;
			const u8 neighborflag_top_is_same_liquid = 0x01;
			v3s16 neighbor_dirs[9] = {
				v3s16(0,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(1,0,1),
				v3s16(-1,0,-1),
				v3s16(1,0,-1),
				v3s16(-1,0,1),
			};
			for(u32 i=0; i<9; i++)
			{
				content_t content = CONTENT_AIR;
				float level = -0.5 * BS;
				u8 flags = 0;
				// Check neighbor
				v3s16 p2 = p + neighbor_dirs[i];
				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
				if(n2.getContent() != CONTENT_IGNORE)
				{
					content = n2.getContent();

					if(n2.getContent() == c_source)
						level = (-0.5+node_liquid_level) * BS;
					else if(n2.getContent() == c_flowing)
						level = (-0.5 + ((float)(n2.param2&LIQUID_LEVEL_MASK)
								+ 0.5) / 8.0 * node_liquid_level) * BS;

					// Check node above neighbor.
					// NOTE: This doesn't get executed if neighbor
					//       doesn't exist
					p2.Y += 1;
					n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
					if(n2.getContent() == c_source ||
							n2.getContent() == c_flowing)
						flags |= neighborflag_top_is_same_liquid;
				}
				
				neighbor_levels.insert(neighbor_dirs[i], level);
				neighbor_contents.insert(neighbor_dirs[i], content);
				neighbor_flags.insert(neighbor_dirs[i], flags);
			}

			// Corner heights (average between four liquids)
			f32 corner_levels[4];
			
			v3s16 halfdirs[4] = {
				v3s16(0,0,0),
				v3s16(1,0,0),
				v3s16(1,0,1),
				v3s16(0,0,1),
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 cornerdir = halfdirs[i];
				float cornerlevel = 0;
				u32 valid_count = 0;
				u32 air_count = 0;
				for(u32 j=0; j<4; j++)
				{
					v3s16 neighbordir = cornerdir - halfdirs[j];
					content_t content = neighbor_contents[neighbordir];
					// If top is liquid, draw starting from top of node
					if(neighbor_flags[neighbordir] &
							neighborflag_top_is_same_liquid)
					{
						cornerlevel = 0.5*BS;
						valid_count = 1;
						break;
					}
					// Source is always the same height
					else if(content == c_source)
					{
						cornerlevel = (-0.5+node_liquid_level)*BS;
						valid_count = 1;
						break;
					}
					// Flowing liquid has level information
					else if(content == c_flowing)
					{
						cornerlevel += neighbor_levels[neighbordir];
						valid_count++;
					}
					else if(content == CONTENT_AIR)
					{
						air_count++;
					}
				}
				if(air_count >= 2)
					cornerlevel = -0.5*BS;
				else if(valid_count > 0)
					cornerlevel /= valid_count;
				corner_levels[i] = cornerlevel;
			}

			/*
				Generate sides
			*/

			v3s16 side_dirs[4] = {
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
			};
			s16 side_corners[4][2] = {
				{1, 2},
				{3, 0},
				{2, 3},
				{0, 1},
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 dir = side_dirs[i];

				/*
					If our topside is liquid and neighbor's topside
					is liquid, don't draw side face
				*/
				if(top_is_same_liquid &&
						neighbor_flags[dir] & neighborflag_top_is_same_liquid)
					continue;

				content_t neighbor_content = neighbor_contents[dir];
				const ContentFeatures &n_feat = nodedef->get(neighbor_content);
				
				// Don't draw face if neighbor is blocking the view
				if(n_feat.solidness == 2)
					continue;
				
				bool neighbor_is_same_liquid = (neighbor_content == c_source
						|| neighbor_content == c_flowing);
				
				// Don't draw any faces if neighbor same is liquid and top is
				// same liquid
				if(neighbor_is_same_liquid == true
						&& top_is_same_liquid == false)
					continue;

				// Use backface culled material if neighbor doesn't have a
				// solidness of 0
				const TileSpec *current_tile = &tile_liquid;
				if(n_feat.solidness != 0 || n_feat.visual_solidness != 0)
					current_tile = &tile_liquid_bfculled;
				
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x0(), pa_liquid.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x1(), pa_liquid.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x1(), pa_liquid.y0()),
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x0(), pa_liquid.y0()),
				};
				
				/*
					If our topside is liquid, set upper border of face
					at upper border of node
				*/
				if(top_is_same_liquid)
				{
					vertices[2].Pos.Y = 0.5*BS;
					vertices[3].Pos.Y = 0.5*BS;
				}
				/*
					Otherwise upper position of face is corner levels
				*/
				else
				{
					vertices[2].Pos.Y = corner_levels[side_corners[i][0]];
					vertices[3].Pos.Y = corner_levels[side_corners[i][1]];
				}
				
				/*
					If neighbor is liquid, lower border of face is corner
					liquid levels
				*/
				if(neighbor_is_same_liquid)
				{
					vertices[0].Pos.Y = corner_levels[side_corners[i][1]];
					vertices[1].Pos.Y = corner_levels[side_corners[i][0]];
				}
				/*
					If neighbor is not liquid, lower border of face is
					lower border of node
				*/
				else
				{
					vertices[0].Pos.Y = -0.5*BS;
					vertices[1].Pos.Y = -0.5*BS;
				}
				
				for(s32 j=0; j<4; j++)
				{
					if(dir == v3s16(0,0,1))
						vertices[j].Pos.rotateXZBy(0);
					if(dir == v3s16(0,0,-1))
						vertices[j].Pos.rotateXZBy(180);
					if(dir == v3s16(-1,0,0))
						vertices[j].Pos.rotateXZBy(90);
					if(dir == v3s16(1,0,-0))
						vertices[j].Pos.rotateXZBy(-90);
						
					// Do this to not cause glitches when two liquids are
					// side-by-side
					/*if(neighbor_is_same_liquid == false){
						vertices[j].Pos.X *= 0.98;
						vertices[j].Pos.Z *= 0.98;
					}*/

					vertices[j].Pos += intToFloat(p, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(*current_tile, vertices, 4, indices, 6);
			}
			
			/*
				Generate top side, if appropriate
			*/
			
			if(top_is_same_liquid == false)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x0(), pa_liquid.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid.x1(), pa_liquid.y1()),
					video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
							pa_liquid.x1(), pa_liquid.y0()),
					video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
							pa_liquid.x0(), pa_liquid.y0()),
				};
				
				// To get backface culling right, the vertices need to go
				// clockwise around the front of the face. And we happened to
				// calculate corner levels in exact reverse order.
				s32 corner_resolve[4] = {3,2,1,0};

				for(s32 i=0; i<4; i++)
				{
					//vertices[i].Pos.Y += liquid_level;
					//vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)];
					s32 j = corner_resolve[i];
					vertices[i].Pos.Y += corner_levels[j];
					vertices[i].Pos += intToFloat(p, BS);
				}
				
				// Default downwards-flowing texture animation goes from 
				// -Z towards +Z, thus the direction is +Z.
				// Rotate texture to make animation go in flow direction
				// Positive if liquid moves towards +Z
				int dz = (corner_levels[side_corners[2][0]] +
						corner_levels[side_corners[2][1]] <
						corner_levels[side_corners[3][0]] +
						corner_levels[side_corners[3][1]]);
				// Positive if liquid moves towards +X
				int dx = (corner_levels[side_corners[0][0]] +
						corner_levels[side_corners[0][1]] <
						corner_levels[side_corners[1][0]] +
						corner_levels[side_corners[1][1]]);
				// -X
				if(-dx >= abs(dz))
				{
					v2f t = vertices[0].TCoords;
					vertices[0].TCoords = vertices[1].TCoords;
					vertices[1].TCoords = vertices[2].TCoords;
					vertices[2].TCoords = vertices[3].TCoords;
					vertices[3].TCoords = t;
				}
				// +X
				if(dx >= abs(dz))
				{
					v2f t = vertices[0].TCoords;
					vertices[0].TCoords = vertices[3].TCoords;
					vertices[3].TCoords = vertices[2].TCoords;
					vertices[2].TCoords = vertices[1].TCoords;
					vertices[1].TCoords = t;
				}
				// -Z
				if(-dz >= abs(dx))
				{
					v2f t = vertices[0].TCoords;
					vertices[0].TCoords = vertices[3].TCoords;
					vertices[3].TCoords = vertices[2].TCoords;
					vertices[2].TCoords = vertices[1].TCoords;
					vertices[1].TCoords = t;
					t = vertices[0].TCoords;
					vertices[0].TCoords = vertices[3].TCoords;
					vertices[3].TCoords = vertices[2].TCoords;
					vertices[2].TCoords = vertices[1].TCoords;
					vertices[1].TCoords = t;
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(tile_liquid, vertices, 4, indices, 6);
			}
		break;}
		case NDT_GLASSLIKE:
		{
			TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data);
			AtlasPointer ap = tile.texture;

			u16 l = getInteriorLight(n, 1, data);
			video::SColor c = MapBlock_LightColor(255, l);

			for(u32 j=0; j<6; j++)
			{
				// Check this neighbor
				v3s16 n2p = blockpos_nodes + p + g_6dirs[j];
				MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
				// Don't make face if neighbor is of same type
				if(n2.getContent() == n.getContent())
					continue;

				// The face at Z+
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y0()),
				};
				
				// Rotations in the g_6dirs format
				if(j == 0) // Z+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				else if(j == 1) // Y+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(-90);
				else if(j == 2) // X+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				else if(j == 3) // Z-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				else if(j == 4) // Y-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(90);
				else if(j == 5) // X-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);

				for(u16 i=0; i<4; i++){
					vertices[i].Pos += intToFloat(p, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(tile, vertices, 4, indices, 6);
			}
		break;}
		case NDT_ALLFACES:
		{
			TileSpec tile_leaves = getNodeTile(n, p,
					v3s16(0,0,0), data);
			AtlasPointer pa_leaves = tile_leaves.texture;

			u16 l = getInteriorLight(n, 1, data);
			video::SColor c = MapBlock_LightColor(255, l);

			v3f pos = intToFloat(p, BS);
			aabb3f box(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2);
			box.MinEdge += pos;
			box.MaxEdge += pos;
			makeCuboid(&collector, box, &tile_leaves, 1, c, NULL);
		break;}
		case NDT_ALLFACES_OPTIONAL:
			// This is always pre-converted to something else
			assert(0);
			break;
		case NDT_TORCHLIKE:
		{
			v3s16 dir = n.getWallMountedDir(nodedef);
			
			u8 tileindex = 0;
			if(dir == v3s16(0,-1,0)){
				tileindex = 0; // floor
			} else if(dir == v3s16(0,1,0)){
				tileindex = 1; // ceiling
			// For backwards compatibility
			} else if(dir == v3s16(0,0,0)){
				tileindex = 0; // floor
			} else {
				tileindex = 2; // side
			}

			TileSpec tile = getNodeTileN(n, p, tileindex, data);
			tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
			tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;

			AtlasPointer ap = tile.texture;

			video::SColor c(255,255,255,255);

			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c,
						ap.x0(), ap.y1()),
				video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c,
						ap.x1(), ap.y1()),
				video::S3DVertex(BS/2,BS/2,0, 0,0,0, c,
						ap.x1(), ap.y0()),
				video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c,
						ap.x0(), ap.y0()),
			};

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXZBy(45);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXZBy(-45);

				vertices[i].Pos += intToFloat(p, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(tile, vertices, 4, indices, 6);
		break;}
		case NDT_SIGNLIKE:
		{
			TileSpec tile = getNodeTileN(n, p, 0, data);
			tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
			tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
			AtlasPointer ap = tile.texture;

			u16 l = getInteriorLight(n, 0, data);
			video::SColor c = MapBlock_LightColor(255, l);
				
			float d = (float)BS/16;
			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(BS/2-d,BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y0()),
				video::S3DVertex(BS/2-d,BS/2,-BS/2, 0,0,0, c,
						ap.x1(), ap.y0()),
				video::S3DVertex(BS/2-d,-BS/2,-BS/2, 0,0,0, c,
						ap.x1(), ap.y1()),
				video::S3DVertex(BS/2-d,-BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y1()),
			};

			v3s16 dir = n.getWallMountedDir(nodedef);

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXYBy(-90);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXYBy(90);

				vertices[i].Pos += intToFloat(p, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(tile, vertices, 4, indices, 6);
		break;}
		case NDT_PLANTLIKE:
		{
			TileSpec tile = getNodeTileN(n, p, 0, data);
			tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
			AtlasPointer ap = tile.texture;
			
			u16 l = getInteriorLight(n, 1, data);
			video::SColor c = MapBlock_LightColor(255, l);

			for(u32 j=0; j<4; j++)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
						ap.x0(), ap.y1()),
					video::S3DVertex( BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
						ap.x1(), ap.y1()),
					video::S3DVertex( BS/2*f.visual_scale,
						-BS/2 + f.visual_scale*BS,0, 0,0,0, c,
						ap.x1(), ap.y0()),
					video::S3DVertex(-BS/2*f.visual_scale,
						-BS/2 + f.visual_scale*BS,0, 0,0,0, c,
						ap.x0(), ap.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(45);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-45);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(135);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-135);
				}

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos *= f.visual_scale;
					vertices[i].Pos += intToFloat(p, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(tile, vertices, 4, indices, 6);
			}
		break;}
		case NDT_FENCELIKE:
		{
			TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data);
			TileSpec tile_nocrack = tile;
			tile_nocrack.material_flags &= ~MATERIAL_FLAG_CRACK;
			
			// A hack to put wood the right way around in the posts
			ITextureSource *tsrc = data->m_gamedef->tsrc();
			TileSpec tile_rot = tile;
			tile_rot.texture = tsrc->getTexture(tsrc->getTextureName(
					tile.texture.id) + "^[transformR90");
					
			u16 l = getInteriorLight(n, 1, data);
			video::SColor c = MapBlock_LightColor(255, l);

			const f32 post_rad=(f32)BS/8;
			const f32 bar_rad=(f32)BS/16;
			const f32 bar_len=(f32)(BS/2)-post_rad;

			v3f pos = intToFloat(p, BS);

			// The post - always present
			aabb3f post(-post_rad,-BS/2,-post_rad,post_rad,BS/2,post_rad);
			post.MinEdge += pos;
			post.MaxEdge += pos;
			f32 postuv[24]={
					6/16.,6/16.,10/16.,10/16.,
					6/16.,6/16.,10/16.,10/16.,
					0/16.,0,4/16.,1,
					4/16.,0,8/16.,1,
					8/16.,0,12/16.,1,
					12/16.,0,16/16.,1};
			makeCuboid(&collector, post, &tile_rot, 1, c, postuv);

			// Now a section of fence, +X, if there's a post there
			v3s16 p2 = p;
			p2.X++;
			MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			const ContentFeatures *f2 = &nodedef->get(n2);
			if(f2->drawtype == NDT_FENCELIKE)
			{
				aabb3f bar(-bar_len+BS/2,-bar_rad+BS/4,-bar_rad,
						bar_len+BS/2,bar_rad+BS/4,bar_rad);
				bar.MinEdge += pos;
				bar.MaxEdge += pos;
				f32 xrailuv[24]={
					0/16.,2/16.,16/16.,4/16.,
					0/16.,4/16.,16/16.,6/16.,
					6/16.,6/16.,8/16.,8/16.,
					10/16.,10/16.,12/16.,12/16.,
					0/16.,8/16.,16/16.,10/16.,
					0/16.,14/16.,16/16.,16/16.};
				makeCuboid(&collector, bar, &tile_nocrack, 1,
						c, xrailuv);
				bar.MinEdge.Y -= BS/2;
				bar.MaxEdge.Y -= BS/2;
				makeCuboid(&collector, bar, &tile_nocrack, 1,
						c, xrailuv);
			}

			// Now a section of fence, +Z, if there's a post there
			p2 = p;
			p2.Z++;
			n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			f2 = &nodedef->get(n2);
			if(f2->drawtype == NDT_FENCELIKE)
			{
				aabb3f bar(-bar_rad,-bar_rad+BS/4,-bar_len+BS/2,
						bar_rad,bar_rad+BS/4,bar_len+BS/2);
				bar.MinEdge += pos;
				bar.MaxEdge += pos;
				f32 zrailuv[24]={
					3/16.,1/16.,5/16.,5/16., // cannot rotate; stretch
					4/16.,1/16.,6/16.,5/16., // for wood texture instead
					0/16.,9/16.,16/16.,11/16.,
					0/16.,6/16.,16/16.,8/16.,
					6/16.,6/16.,8/16.,8/16.,
					10/16.,10/16.,12/16.,12/16.};
				makeCuboid(&collector, bar, &tile_nocrack, 1,
						c, zrailuv);
				bar.MinEdge.Y -= BS/2;
				bar.MaxEdge.Y -= BS/2;
				makeCuboid(&collector, bar, &tile_nocrack, 1,
						c, zrailuv);
			}
		break;}
		case NDT_RAILLIKE:
		{
			bool is_rail_x [] = { false, false };  /* x-1, x+1 */
			bool is_rail_z [] = { false, false };  /* z-1, z+1 */

			bool is_rail_z_minus_y [] = { false, false };  /* z-1, z+1; y-1 */
			bool is_rail_x_minus_y [] = { false, false };  /* x-1, z+1; y-1 */
			bool is_rail_z_plus_y [] = { false, false };  /* z-1, z+1; y+1 */
			bool is_rail_x_plus_y [] = { false, false };  /* x-1, x+1; y+1 */

			MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z));
			MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z));
			MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1));
			MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1));
			MapNode n_plus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y+1, z));
			MapNode n_plus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y-1, z));
			MapNode n_minus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y+1, z));
			MapNode n_minus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y-1, z));
			MapNode n_plus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z+1));
			MapNode n_minus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z-1));
			MapNode n_plus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z+1));
			MapNode n_minus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z-1));
			
			content_t thiscontent = n.getContent();
			if(n_minus_x.getContent() == thiscontent)
				is_rail_x[0] = true;
			if (n_minus_x_minus_y.getContent() == thiscontent)
				is_rail_x_minus_y[0] = true;
			if(n_minus_x_plus_y.getContent() == thiscontent)
				is_rail_x_plus_y[0] = true;

			if(n_plus_x.getContent() == thiscontent)
				is_rail_x[1] = true;
			if (n_plus_x_minus_y.getContent() == thiscontent)
				is_rail_x_minus_y[1] = true;
			if(n_plus_x_plus_y.getContent() == thiscontent)
				is_rail_x_plus_y[1] = true;

			if(n_minus_z.getContent() == thiscontent)
				is_rail_z[0] = true;
			if (n_minus_z_minus_y.getContent() == thiscontent)
				is_rail_z_minus_y[0] = true;
			if(n_minus_z_plus_y.getContent() == thiscontent)
				is_rail_z_plus_y[0] = true;

			if(n_plus_z.getContent() == thiscontent)
				is_rail_z[1] = true;
			if (n_plus_z_minus_y.getContent() == thiscontent)
				is_rail_z_minus_y[1] = true;
			if(n_plus_z_plus_y.getContent() == thiscontent)
				is_rail_z_plus_y[1] = true;

			bool is_rail_x_all[] = {false, false};
			bool is_rail_z_all[] = {false, false};
			is_rail_x_all[0]=is_rail_x[0] || is_rail_x_minus_y[0] || is_rail_x_plus_y[0];
			is_rail_x_all[1]=is_rail_x[1] || is_rail_x_minus_y[1] || is_rail_x_plus_y[1];
			is_rail_z_all[0]=is_rail_z[0] || is_rail_z_minus_y[0] || is_rail_z_plus_y[0];
			is_rail_z_all[1]=is_rail_z[1] || is_rail_z_minus_y[1] || is_rail_z_plus_y[1];

			// reasonable default, flat straight unrotated rail
			bool is_straight = true;
			int adjacencies = 0;
			int angle = 0;
			u8 tileindex = 0;

			// check for sloped rail
			if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1])
			{
				adjacencies = 5; //5 means sloped
				is_straight = true; // sloped is always straight
			}
			else
			{
				// is really straight, rails on both sides
				is_straight = (is_rail_x_all[0] && is_rail_x_all[1]) || (is_rail_z_all[0] && is_rail_z_all[1]);
				adjacencies = is_rail_x_all[0] + is_rail_x_all[1] + is_rail_z_all[0] + is_rail_z_all[1];
			}

			switch (adjacencies) {
			case 1:
				if(is_rail_x_all[0] || is_rail_x_all[1])
					angle = 90;
				break;
			case 2:
				if(!is_straight)
					tileindex = 1; // curved
				if(is_rail_x_all[0] && is_rail_x_all[1])
					angle = 90;
				if(is_rail_z_all[0] && is_rail_z_all[1]){
					if (n_minus_z_plus_y.getContent() == thiscontent) angle = 180;
				}
				else if(is_rail_x_all[0] && is_rail_z_all[0])
					angle = 270;
				else if(is_rail_x_all[0] && is_rail_z_all[1])
					angle = 180;
				else if(is_rail_x_all[1] && is_rail_z_all[1])
					angle = 90;
				break;
			case 3:
				// here is where the potential to 'switch' a junction is, but not implemented at present
				tileindex = 2; // t-junction
				if(!is_rail_x_all[1])
					angle=180;
				if(!is_rail_z_all[0])
					angle=90;
				if(!is_rail_z_all[1])
					angle=270;
				break;
			case 4:
				tileindex = 3; // crossing
				break;
			case 5: //sloped
				if(is_rail_z_plus_y[0])
					angle = 180;
				if(is_rail_x_plus_y[0])
					angle = 90;
				if(is_rail_x_plus_y[1])
					angle = -90;
				break;
			default:
				break;
			}

			TileSpec tile = getNodeTileN(n, p, tileindex, data);
			tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
			tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;

			AtlasPointer ap = tile.texture;
			
			u16 l = getInteriorLight(n, 0, data);
			video::SColor c = MapBlock_LightColor(255, l);

			float d = (float)BS/64;
			
			char g=-1;
			if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1])
				g=1; //Object is at a slope

			video::S3DVertex vertices[4] =
			{
					video::S3DVertex(-BS/2,-BS/2+d,-BS/2, 0,0,0, c,
							ap.x0(), ap.y1()),
					video::S3DVertex(BS/2,-BS/2+d,-BS/2, 0,0,0, c,
							ap.x1(), ap.y1()),
					video::S3DVertex(BS/2,g*BS/2+d,BS/2, 0,0,0, c,
							ap.x1(), ap.y0()),
					video::S3DVertex(-BS/2,g*BS/2+d,BS/2, 0,0,0, c,
							ap.x0(), ap.y0()),
			};

			for(s32 i=0; i<4; i++)
			{
				if(angle != 0)
					vertices[i].Pos.rotateXZBy(angle);
				vertices[i].Pos += intToFloat(p, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			collector.append(tile, vertices, 4, indices, 6);
		break;}
		case NDT_NODEBOX:
		{
			TileSpec tiles[6];
			for(int i = 0; i < 6; i++)
			{
				tiles[i] = getNodeTileN(n, p, i, data);
			}

			u16 l = getInteriorLight(n, 0, data);
			video::SColor c = MapBlock_LightColor(255, l);

			v3f pos = intToFloat(p, BS);

			std::vector<aabb3f> boxes = n.getNodeBoxes(nodedef);
			for(std::vector<aabb3f>::iterator
					i = boxes.begin();
					i != boxes.end(); i++)
			{
				aabb3f box = *i;
				box.MinEdge += pos;
				box.MaxEdge += pos;

				// Compute texture coords
				f32 tx1 = (i->MinEdge.X/BS)+0.5;
				f32 ty1 = (i->MinEdge.Y/BS)+0.5;
				f32 tz1 = (i->MinEdge.Z/BS)+0.5;
				f32 tx2 = (i->MaxEdge.X/BS)+0.5;
				f32 ty2 = (i->MaxEdge.Y/BS)+0.5;
				f32 tz2 = (i->MaxEdge.Z/BS)+0.5;
				f32 txc[24] = {
					// up
					tx1, 1-tz2, tx2, 1-tz1,
					// down
					tx1, tz1, tx2, tz2,
					// right
					tz1, 1-ty2, tz2, 1-ty1,
					// left
					1-tz2, 1-ty2, 1-tz1, 1-ty1,
					// back
					1-tx2, 1-ty2, 1-tx1, 1-ty1,
					// front
					tx1, 1-ty2, tx2, 1-ty1,
				};

				makeCuboid(&collector, box, tiles, 6, c, txc);
			}
		break;}
		}
	}
}
Esempio n. 4
0
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
	clearHardwareBuffer(false),
	step(data->step),
	timestamp(data->timestamp),
	m_mesh(new scene::SMesh()),
	m_gamedef(data->m_gamedef),
	m_animation_force_timer(0), // force initial animation
	m_last_crack(-1),
	m_crack_materials(),
	m_highlighted_materials(),
	m_last_daynight_ratio((u32) -1),
	m_daynight_diffs(),
	m_usage_timer(0)
{
	m_enable_shaders = g_settings->getBool("enable_shaders");
	m_enable_highlighting = g_settings->getBool("enable_node_highlighting");

	// 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
	// 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
	//TimeTaker timer1("MapBlockMesh()");

	data->fill_data();

	std::vector<FastFace> fastfaces_new;

	/*
		We are including the faces of the trailing edges of the block.
		This means that when something changes, the caller must
		also update the meshes of the blocks at the leading edges.

		NOTE: This is the slowest part of this method.
	*/
	{
		// 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
		//TimeTaker timer2("updateAllFastFaceRows()");
		updateAllFastFaceRows(data, fastfaces_new, step);
	}
	// End of slow part

	//if (data->debug) infostream<<" step="<<step<<" fastfaces_new.size="<<fastfaces_new.size()<<std::endl;

	/*
		Convert FastFaces to MeshCollector
	*/

	MeshCollector collector;

	{
		// avg 0ms (100ms spikes when loading textures the first time)
		// (NOTE: probably outdated)
		//TimeTaker timer2("MeshCollector building");

		for(u32 i=0; i<fastfaces_new.size(); i++)
		{
			FastFace &f = fastfaces_new[i];

			const u16 indices[] = {0,1,2,2,3,0};
			const u16 indices_alternate[] = {0,1,3,2,3,1};

			if(f.tile.texture == NULL)
				continue;

			const u16 *indices_p = indices;

			/*
				Revert triangles for nicer looking gradient if vertices
				1 and 3 have same color or 0 and 2 have different color.
				getRed() is the day color.
			*/
			if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed()
					|| f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed())
				indices_p = indices_alternate;

			collector.append(f.tile, f.vertices, 4, indices_p, 6);
		}
	}

	/*
		Add special graphics:
		- torches
		- flowing water
		- fences
		- whatever
	*/

	if(step <= 1)
	mapblock_mesh_generate_special(data, collector);

	m_highlight_mesh_color = data->m_highlight_mesh_color;

	/*
		Convert MeshCollector to SMesh
	*/
	ITextureSource *tsrc = m_gamedef->tsrc();
	IShaderSource *shdrsrc = m_gamedef->getShaderSource();

	for(u32 i = 0; i < collector.prebuffers.size(); i++)
	{
		PreMeshBuffer &p = collector.prebuffers[i];

		if (step <= data->draw_control.farmesh || !data->draw_control.farmesh) {
		// Generate animation data
		// - Cracks
		if(p.tile.material_flags & MATERIAL_FLAG_CRACK)
		{
			// Find the texture name plus ^[crack:N:
			std::ostringstream os(std::ios::binary);
			os<<tsrc->getTextureName(p.tile.texture_id)<<"^[crack";
			if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
				os<<"o";  // use ^[cracko
			os<<":"<<(u32)p.tile.animation_frame_count<<":";
			m_crack_materials.insert(std::make_pair(i, os.str()));
			// Replace tile texture with the cracked one
			p.tile.texture = tsrc->getTexture(
					os.str()+"0",
					&p.tile.texture_id);
		}
		}
		// - Texture animation
		if(p.tile.material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
		{
			// Add to MapBlockMesh in order to animate these tiles
			m_animation_tiles[i] = p.tile;
			m_animation_frames[i] = 0;
			if(g_settings->getBool("desynchronize_mapblock_texture_animation")){
				// Get starting position from noise
				m_animation_frame_offsets[i] = 100000 * (2.0 + noise3d(
						data->m_blockpos.X, data->m_blockpos.Y,
						data->m_blockpos.Z, 0));
			} else {
				// Play all synchronized
				m_animation_frame_offsets[i] = 0;
			}
			// Replace tile texture with the first animation frame
			FrameSpec animation_frame = p.tile.frames.find(0)->second;
			p.tile.texture = animation_frame.texture;
		}

		if(m_enable_highlighting && p.tile.material_flags & MATERIAL_FLAG_HIGHLIGHTED)
			m_highlighted_materials.push_back(i);	

		for(u32 j = 0; j < p.vertices.size(); j++)
		{
			// Note applyFacesShading second parameter is precalculated sqrt
			// value for speed improvement
			// Skip it for lightsources and top faces.
			video::SColor &vc = p.vertices[j].Color;
			if (!vc.getBlue()) {
				if (p.vertices[j].Normal.Y < -0.5) {
					applyFacesShading (vc, 0.447213);
				} else if (p.vertices[j].Normal.X > 0.5) {
					applyFacesShading (vc, 0.670820);
				} else if (p.vertices[j].Normal.X < -0.5) {
					applyFacesShading (vc, 0.670820);
				} else if (p.vertices[j].Normal.Z > 0.5) {
					applyFacesShading (vc, 0.836660);
				} else if (p.vertices[j].Normal.Z < -0.5) {
					applyFacesShading (vc, 0.836660);
				}
			}
			if(!m_enable_shaders)
			{
				// - Classic lighting (shaders handle this by themselves)
				// Set initial real color and store for later updates
				u8 day = vc.getRed();
				u8 night = vc.getGreen();
				finalColorBlend(vc, day, night, 1000);
				if(day != night)
					m_daynight_diffs[i][j] = std::make_pair(day, night);
			}
		}

		// Create material
		video::SMaterial material;
		material.setFlag(video::EMF_LIGHTING, false);
		material.setFlag(video::EMF_BACK_FACE_CULLING, true);
		material.setFlag(video::EMF_BILINEAR_FILTER, false);
		material.setFlag(video::EMF_FOG_ENABLE, true);
		//material.setFlag(video::EMF_WIREFRAME, true);
		material.setTexture(0, p.tile.texture);

		if (p.tile.material_flags & MATERIAL_FLAG_HIGHLIGHTED) {
			material.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
		} else {
			if (m_enable_shaders) {
				material.MaterialType = shdrsrc->getShaderInfo(p.tile.shader_id).material;
				p.tile.applyMaterialOptionsWithShaders(material);
				if (p.tile.normal_texture) {
					material.setTexture(1, p.tile.normal_texture);
					material.setTexture(2, tsrc->getTexture("enable_img.png"));
				} else {
					material.setTexture(2, tsrc->getTexture("disable_img.png"));
				}
			} else {
				p.tile.applyMaterialOptions(material);
			}
		}

		// Create meshbuffer
		// This is a "Standard MeshBuffer",
		// it's a typedeffed CMeshBuffer<video::S3DVertex>
		scene::SMeshBuffer *buf = new scene::SMeshBuffer();
		// Set material
		buf->Material = material;
		// Add to mesh
		m_mesh->addMeshBuffer(buf);
		// Mesh grabbed it
		buf->drop();
		buf->append(&p.vertices[0], p.vertices.size(),
				&p.indices[0], p.indices.size());
	}

	m_camera_offset = camera_offset;

	/*
		Do some stuff to the mesh
	*/

	v3f t = v3f(0,0,0);
	if (step>1) {
		scaleMesh(m_mesh, v3f(step,step,step));
		// TODO: remove this wrong numbers, find formula   good test: fly above ocean
		if (step == 2)	t = v3f(BS/2,		 BS/2,		BS/2);
		if (step == 4)	t = v3f(BS*1.666,	-BS/3.0,	BS*1.666);
		if (step == 8)	t = v3f(BS*2.666,	-BS*2.4,	BS*2.666);
		if (step == 16)	t = v3f(BS*6.4,		-BS*6.4,	BS*6.4);
	}
	translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS) + t);

	if(m_mesh)
	{
#if 0
		// Usually 1-700 faces and 1-7 materials
		infostream<<"Updated MapBlock mesh p="<<data->m_blockpos<<" has "<<fastfaces_new.size()<<" faces "
				<<"and uses "<<m_mesh->getMeshBufferCount()
				<<" materials "<<" step="<<step<<" range="<<data->range<< " mesh="<<m_mesh<<std::endl;
#endif
	}

	//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;

	// Check if animation is required for this mesh
	m_has_animation =
		!m_crack_materials.empty() ||
		!m_daynight_diffs.empty() ||
		!m_animation_tiles.empty() ||
		!m_highlighted_materials.empty();
}
Esempio n. 5
0
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
	m_mesh(new scene::SMesh()),
	m_minimap_mapblock(NULL),
	m_gamedef(data->m_gamedef),
	m_tsrc(m_gamedef->getTextureSource()),
	m_shdrsrc(m_gamedef->getShaderSource()),
	m_animation_force_timer(0), // force initial animation
	m_last_crack(-1),
	m_crack_materials(),
	m_highlighted_materials(),
	m_last_daynight_ratio((u32) -1),
	m_daynight_diffs()
{
	m_enable_shaders = data->m_use_shaders;
	m_enable_highlighting = g_settings->getBool("enable_node_highlighting");

	if (g_settings->getBool("enable_minimap")) {
		m_minimap_mapblock = new MinimapMapblock;
		m_minimap_mapblock->getMinimapNodes(
			&data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
	}

	// 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
	// 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
	//TimeTaker timer1("MapBlockMesh()");

	std::vector<FastFace> fastfaces_new;
	fastfaces_new.reserve(512);

	/*
		We are including the faces of the trailing edges of the block.
		This means that when something changes, the caller must
		also update the meshes of the blocks at the leading edges.

		NOTE: This is the slowest part of this method.
	*/
	{
		// 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
		//TimeTaker timer2("updateAllFastFaceRows()");
		updateAllFastFaceRows(data, fastfaces_new);
	}
	// End of slow part

	/*
		Convert FastFaces to MeshCollector
	*/

	MeshCollector collector;

	{
		// avg 0ms (100ms spikes when loading textures the first time)
		// (NOTE: probably outdated)
		//TimeTaker timer2("MeshCollector building");

		for(u32 i=0; i<fastfaces_new.size(); i++)
		{
			FastFace &f = fastfaces_new[i];

			const u16 indices[] = {0,1,2,2,3,0};
			const u16 indices_alternate[] = {0,1,3,2,3,1};

			if(f.tile.texture == NULL)
				continue;

			const u16 *indices_p = indices;

			/*
				Revert triangles for nicer looking gradient if vertices
				1 and 3 have same color or 0 and 2 have different color.
				getRed() is the day color.
			*/
			if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed()
					|| f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed())
				indices_p = indices_alternate;

			collector.append(f.tile, f.vertices, 4, indices_p, 6);
		}
	}

	/*
		Add special graphics:
		- torches
		- flowing water
		- fences
		- whatever
	*/

	mapblock_mesh_generate_special(data, collector);

	m_highlight_mesh_color = data->m_highlight_mesh_color;

	/*
		Convert MeshCollector to SMesh
	*/

	for(u32 i = 0; i < collector.prebuffers.size(); i++)
	{
		PreMeshBuffer &p = collector.prebuffers[i];

		// Generate animation data
		// - Cracks
		if(p.tile.material_flags & MATERIAL_FLAG_CRACK)
		{
			// Find the texture name plus ^[crack:N:
			std::ostringstream os(std::ios::binary);
			os<<m_tsrc->getTextureName(p.tile.texture_id)<<"^[crack";
			if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
				os<<"o";  // use ^[cracko
			os<<":"<<(u32)p.tile.animation_frame_count<<":";
			m_crack_materials.insert(std::make_pair(i, os.str()));
			// Replace tile texture with the cracked one
			p.tile.texture = m_tsrc->getTextureForMesh(
					os.str()+"0",
					&p.tile.texture_id);
		}
		// - Texture animation
		if(p.tile.material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
		{
			// Add to MapBlockMesh in order to animate these tiles
			m_animation_tiles[i] = p.tile;
			m_animation_frames[i] = 0;
			if(g_settings->getBool("desynchronize_mapblock_texture_animation")){
				// Get starting position from noise
				m_animation_frame_offsets[i] = 100000 * (2.0 + noise3d(
						data->m_blockpos.X, data->m_blockpos.Y,
						data->m_blockpos.Z, 0));
			} else {
				// Play all synchronized
				m_animation_frame_offsets[i] = 0;
			}
			// Replace tile texture with the first animation frame
			FrameSpec animation_frame = p.tile.frames[0];
			p.tile.texture = animation_frame.texture;
		}

		if(m_enable_highlighting && p.tile.material_flags & MATERIAL_FLAG_HIGHLIGHTED)
			m_highlighted_materials.push_back(i);

		for(u32 j = 0; j < p.vertices.size(); j++)
		{
			video::S3DVertexTangents *vertex = &p.vertices[j];
			// Note applyFacesShading second parameter is precalculated sqrt
			// value for speed improvement
			// Skip it for lightsources and top faces.
			video::SColor &vc = vertex->Color;
			if (!vc.getBlue()) {
				if (vertex->Normal.Y < -0.5) {
					applyFacesShading (vc, 0.447213);
				} else if (vertex->Normal.X > 0.5) {
					applyFacesShading (vc, 0.670820);
				} else if (vertex->Normal.X < -0.5) {
					applyFacesShading (vc, 0.670820);
				} else if (vertex->Normal.Z > 0.5) {
					applyFacesShading (vc, 0.836660);
				} else if (vertex->Normal.Z < -0.5) {
					applyFacesShading (vc, 0.836660);
				}
			}
			if(!m_enable_shaders)
			{
				// - Classic lighting (shaders handle this by themselves)
				// Set initial real color and store for later updates
				u8 day = vc.getRed();
				u8 night = vc.getGreen();
				finalColorBlend(vc, day, night, 1000);
				if(day != night)
					m_daynight_diffs[i][j] = std::make_pair(day, night);
			}
		}

		// Create material
		video::SMaterial material;
		material.setFlag(video::EMF_LIGHTING, false);
		material.setFlag(video::EMF_BACK_FACE_CULLING, true);
		material.setFlag(video::EMF_BILINEAR_FILTER, false);
		material.setFlag(video::EMF_FOG_ENABLE, true);
		material.setTexture(0, p.tile.texture);

		if (p.tile.material_flags & MATERIAL_FLAG_HIGHLIGHTED) {
			material.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
		} else {
			if (m_enable_shaders) {
				material.MaterialType = m_shdrsrc->getShaderInfo(p.tile.shader_id).material;
				p.tile.applyMaterialOptionsWithShaders(material);
				if (p.tile.normal_texture) {
					material.setTexture(1, p.tile.normal_texture);
				}
				material.setTexture(2, p.tile.flags_texture);
			} else {
				p.tile.applyMaterialOptions(material);
			}
		}

	// Create meshbuffer
	scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
	// Set material
	buf->Material = material;
	// Add to mesh
	m_mesh->addMeshBuffer(buf);
	// Mesh grabbed it
	buf->drop();
	buf->append(&p.vertices[0], p.vertices.size(),
		&p.indices[0], p.indices.size());
}
	m_camera_offset = camera_offset;

	/*
		Do some stuff to the mesh
	*/

	translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));

	if (m_enable_shaders) {
		scene::IMeshManipulator* meshmanip = m_gamedef->getSceneManager()->getMeshManipulator();
		meshmanip->recalculateTangents(m_mesh, true, false, false);
	}

	if(m_mesh)
	{
#if 0
		// Usually 1-700 faces and 1-7 materials
		std::cout<<"Updated MapBlock has "<<fastfaces_new.size()<<" faces "
				<<"and uses "<<m_mesh->getMeshBufferCount()
				<<" materials (meshbuffers)"<<std::endl;
#endif

		// Use VBO for mesh (this just would set this for ever buffer)
		// This will lead to infinite memory usage because or irrlicht.
		//m_mesh->setHardwareMappingHint(scene::EHM_STATIC);

		/*
			NOTE: If that is enabled, some kind of a queue to the main
			thread should be made which would call irrlicht to delete
			the hardware buffer and then delete the mesh
		*/
	}

	//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;

	// Check if animation is required for this mesh
	m_has_animation =
		!m_crack_materials.empty() ||
		!m_daynight_diffs.empty() ||
		!m_animation_tiles.empty() ||
		!m_highlighted_materials.empty();
}
void mapblock_mesh_generate_special(MeshMakeData *data,
		MeshCollector &collector)
{
	// 0ms
	//TimeTaker timer("mapblock_mesh_generate_special()");

	/*
		Some settings
	*/
	bool new_style_water = g_settings.getBool("new_style_water");
	bool new_style_leaves = g_settings.getBool("new_style_leaves");
	//bool smooth_lighting = g_settings.getBool("smooth_lighting");
	bool invisible_stone = g_settings.getBool("invisible_stone");
	
	float node_water_level = 1.0;
	if(new_style_water)
		node_water_level = 0.85;
	
	v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE;

	// Flowing water material
	video::SMaterial material_water1;
	material_water1.setFlag(video::EMF_LIGHTING, false);
	material_water1.setFlag(video::EMF_BACK_FACE_CULLING, false);
	material_water1.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_water1.setFlag(video::EMF_FOG_ENABLE, true);
	material_water1.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
	AtlasPointer pa_water1 = g_texturesource->getTexture(
			g_texturesource->getTextureId("water.png"));
	material_water1.setTexture(0, pa_water1.atlas);

	// New-style leaves material
	video::SMaterial material_leaves1;
	material_leaves1.setFlag(video::EMF_LIGHTING, false);
	//material_leaves1.setFlag(video::EMF_BACK_FACE_CULLING, false);
	material_leaves1.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_leaves1.setFlag(video::EMF_FOG_ENABLE, true);
	material_leaves1.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
	AtlasPointer pa_leaves1 = g_texturesource->getTexture(
			g_texturesource->getTextureId("leaves.png"));
	material_leaves1.setTexture(0, pa_leaves1.atlas);

	// Glass material
	video::SMaterial material_glass;
	material_glass.setFlag(video::EMF_LIGHTING, false);
	material_glass.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_glass.setFlag(video::EMF_FOG_ENABLE, true);
	material_glass.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
	AtlasPointer pa_glass = g_texturesource->getTexture(
			g_texturesource->getTextureId("glass.png"));
	material_glass.setTexture(0, pa_glass.atlas);

	// Wood material
	video::SMaterial material_wood;
	material_wood.setFlag(video::EMF_LIGHTING, false);
	material_wood.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_wood.setFlag(video::EMF_FOG_ENABLE, true);
	material_wood.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
	AtlasPointer pa_wood = g_texturesource->getTexture(
			g_texturesource->getTextureId("wood.png"));
	material_wood.setTexture(0, pa_wood.atlas);

	// General ground material for special output
	// Texture is modified just before usage
	video::SMaterial material_general;
	material_general.setFlag(video::EMF_LIGHTING, false);
	material_general.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_general.setFlag(video::EMF_FOG_ENABLE, true);
	material_general.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;


	// Papyrus material
	video::SMaterial material_papyrus;
	material_papyrus.setFlag(video::EMF_LIGHTING, false);
	material_papyrus.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_papyrus.setFlag(video::EMF_FOG_ENABLE, true);
	material_papyrus.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
	AtlasPointer pa_papyrus = g_texturesource->getTexture(
			g_texturesource->getTextureId("papyrus.png"));
	material_papyrus.setTexture(0, pa_papyrus.atlas);
	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
	{
		v3s16 p(x,y,z);

		MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes+p);
		
		/*
			Add torches to mesh
		*/
		if(n.d == CONTENT_TORCH)
		{
			video::SColor c(255,255,255,255);

			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 0,1),
				video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 1,1),
				video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 1,0),
				video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 0,0),
			};

			v3s16 dir = unpackDir(n.dir);

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXZBy(45);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXZBy(-45);

				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			// Set material
			video::SMaterial material;
			material.setFlag(video::EMF_LIGHTING, false);
			material.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material.setFlag(video::EMF_BILINEAR_FILTER, false);
			//material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
			material.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;

			if(dir == v3s16(0,-1,0))
				material.setTexture(0,
						g_texturesource->getTextureRaw("torch_on_floor.png"));
			else if(dir == v3s16(0,1,0))
				material.setTexture(0,
						g_texturesource->getTextureRaw("torch_on_ceiling.png"));
			// For backwards compatibility
			else if(dir == v3s16(0,0,0))
				material.setTexture(0,
						g_texturesource->getTextureRaw("torch_on_floor.png"));
			else
				material.setTexture(0, 
						g_texturesource->getTextureRaw("torch.png"));

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(material, vertices, 4, indices, 6);
		}
		/*
			Signs on walls
		*/
		else if(n.d == CONTENT_SIGN_WALL)
		{
			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
			video::SColor c(255,l,l,l);
				
			float d = (float)BS/16;
			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(BS/2-d,-BS/2,-BS/2, 0,0,0, c, 0,1),
				video::S3DVertex(BS/2-d,-BS/2,BS/2, 0,0,0, c, 1,1),
				video::S3DVertex(BS/2-d,BS/2,BS/2, 0,0,0, c, 1,0),
				video::S3DVertex(BS/2-d,BS/2,-BS/2, 0,0,0, c, 0,0),
			};

			v3s16 dir = unpackDir(n.dir);

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXYBy(-90);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXYBy(90);

				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			// Set material
			video::SMaterial material;
			material.setFlag(video::EMF_LIGHTING, false);
			material.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material.setFlag(video::EMF_BILINEAR_FILTER, false);
			material.setFlag(video::EMF_FOG_ENABLE, true);
			//material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
			material.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;

			material.setTexture(0, 
					g_texturesource->getTextureRaw("sign_wall.png"));

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(material, vertices, 4, indices, 6);
		}
		/*
			Add flowing water to mesh
		*/
		else if(n.d == CONTENT_WATER)
		{
			bool top_is_water = false;
			MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			if(ntop.d == CONTENT_WATER || ntop.d == CONTENT_WATERSOURCE)
				top_is_water = true;
			
			u8 l = 0;
			// Use the light of the node on top if possible
			if(content_features(ntop.d).param_type == CPT_LIGHT)
				l = decode_light(ntop.getLightBlend(data->m_daynight_ratio));
			// Otherwise use the light of this node (the water)
			else
				l = decode_light(n.getLightBlend(data->m_daynight_ratio));
			video::SColor c(WATER_ALPHA,l,l,l);
			
			// Neighbor water levels (key = relative position)
			// Includes current node
			core::map<v3s16, f32> neighbor_levels;
			core::map<v3s16, u8> neighbor_contents;
			core::map<v3s16, u8> neighbor_flags;
			const u8 neighborflag_top_is_water = 0x01;
			v3s16 neighbor_dirs[9] = {
				v3s16(0,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(1,0,1),
				v3s16(-1,0,-1),
				v3s16(1,0,-1),
				v3s16(-1,0,1),
			};
			for(u32 i=0; i<9; i++)
			{
				u8 content = CONTENT_AIR;
				float level = -0.5 * BS;
				u8 flags = 0;
				// Check neighbor
				v3s16 p2 = p + neighbor_dirs[i];
				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
				if(n2.d != CONTENT_IGNORE)
				{
					content = n2.d;

					if(n2.d == CONTENT_WATERSOURCE)
						level = (-0.5+node_water_level) * BS;
					else if(n2.d == CONTENT_WATER)
						level = (-0.5 + ((float)n2.param2 + 0.5) / 8.0
								* node_water_level) * BS;

					// Check node above neighbor.
					// NOTE: This doesn't get executed if neighbor
					//       doesn't exist
					p2.Y += 1;
					n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
					if(n2.d == CONTENT_WATERSOURCE || n2.d == CONTENT_WATER)
						flags |= neighborflag_top_is_water;
				}
				
				neighbor_levels.insert(neighbor_dirs[i], level);
				neighbor_contents.insert(neighbor_dirs[i], content);
				neighbor_flags.insert(neighbor_dirs[i], flags);
			}

			//float water_level = (-0.5 + ((float)n.param2 + 0.5) / 8.0) * BS;
			//float water_level = neighbor_levels[v3s16(0,0,0)];

			// Corner heights (average between four waters)
			f32 corner_levels[4];
			
			v3s16 halfdirs[4] = {
				v3s16(0,0,0),
				v3s16(1,0,0),
				v3s16(1,0,1),
				v3s16(0,0,1),
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 cornerdir = halfdirs[i];
				float cornerlevel = 0;
				u32 valid_count = 0;
				for(u32 j=0; j<4; j++)
				{
					v3s16 neighbordir = cornerdir - halfdirs[j];
					u8 content = neighbor_contents[neighbordir];
					// Special case for source nodes
					if(content == CONTENT_WATERSOURCE)
					{
						cornerlevel = (-0.5+node_water_level)*BS;
						valid_count = 1;
						break;
					}
					else if(content == CONTENT_WATER)
					{
						cornerlevel += neighbor_levels[neighbordir];
						valid_count++;
					}
					else if(content == CONTENT_AIR)
					{
						cornerlevel += -0.5*BS;
						valid_count++;
					}
				}
				if(valid_count > 0)
					cornerlevel /= valid_count;
				corner_levels[i] = cornerlevel;
			}

			/*
				Generate sides
			*/

			v3s16 side_dirs[4] = {
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
			};
			s16 side_corners[4][2] = {
				{1, 2},
				{3, 0},
				{2, 3},
				{0, 1},
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 dir = side_dirs[i];

				/*
					If our topside is water and neighbor's topside
					is water, don't draw side face
				*/
				if(top_is_water &&
						neighbor_flags[dir] & neighborflag_top_is_water)
					continue;

				u8 neighbor_content = neighbor_contents[dir];
				
				// Don't draw face if neighbor is not air or water
				if(neighbor_content != CONTENT_AIR
						&& neighbor_content != CONTENT_WATER)
					continue;
				
				bool neighbor_is_water = (neighbor_content == CONTENT_WATER);
				
				// Don't draw any faces if neighbor is water and top is water
				if(neighbor_is_water == true && top_is_water == false)
					continue;
				
				video::S3DVertex vertices[4] =
				{
					/*video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0),
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0),*/
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x0(), pa_water1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x1(), pa_water1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x1(), pa_water1.y0()),
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x0(), pa_water1.y0()),
				};
				
				/*
					If our topside is water, set upper border of face
					at upper border of node
				*/
				if(top_is_water)
				{
					vertices[2].Pos.Y = 0.5*BS;
					vertices[3].Pos.Y = 0.5*BS;
				}
				/*
					Otherwise upper position of face is corner levels
				*/
				else
				{
					vertices[2].Pos.Y = corner_levels[side_corners[i][0]];
					vertices[3].Pos.Y = corner_levels[side_corners[i][1]];
				}
				
				/*
					If neighbor is water, lower border of face is corner
					water levels
				*/
				if(neighbor_is_water)
				{
					vertices[0].Pos.Y = corner_levels[side_corners[i][1]];
					vertices[1].Pos.Y = corner_levels[side_corners[i][0]];
				}
				/*
					If neighbor is not water, lower border of face is
					lower border of node
				*/
				else
				{
					vertices[0].Pos.Y = -0.5*BS;
					vertices[1].Pos.Y = -0.5*BS;
				}
				
				for(s32 j=0; j<4; j++)
				{
					if(dir == v3s16(0,0,1))
						vertices[j].Pos.rotateXZBy(0);
					if(dir == v3s16(0,0,-1))
						vertices[j].Pos.rotateXZBy(180);
					if(dir == v3s16(-1,0,0))
						vertices[j].Pos.rotateXZBy(90);
					if(dir == v3s16(1,0,-0))
						vertices[j].Pos.rotateXZBy(-90);

					vertices[j].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_water1, vertices, 4, indices, 6);
			}
			
			/*
				Generate top side, if appropriate
			*/
			
			if(top_is_water == false)
			{
				video::S3DVertex vertices[4] =
				{
					/*video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,1),
					video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,1),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0),
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0),*/
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x0(), pa_water1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_water1.x1(), pa_water1.y1()),
					video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
							pa_water1.x1(), pa_water1.y0()),
					video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
							pa_water1.x0(), pa_water1.y0()),
				};
				
				// This fixes a strange bug
				s32 corner_resolve[4] = {3,2,1,0};

				for(s32 i=0; i<4; i++)
				{
					//vertices[i].Pos.Y += water_level;
					//vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)];
					s32 j = corner_resolve[i];
					vertices[i].Pos.Y += corner_levels[j];
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_water1, vertices, 4, indices, 6);
			}
		}
		/*
			Add water sources to mesh if using new style
		*/
		else if(n.d == CONTENT_WATERSOURCE && new_style_water)
		{
			//bool top_is_water = false;
			bool top_is_air = false;
			MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			/*if(n.d == CONTENT_WATER || n.d == CONTENT_WATERSOURCE)
				top_is_water = true;*/
			if(n.d == CONTENT_AIR)
				top_is_air = true;
			
			/*if(top_is_water == true)
				continue;*/
			if(top_is_air == false)
				continue;

			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
			video::SColor c(WATER_ALPHA,l,l,l);
			
			video::S3DVertex vertices[4] =
			{
				/*video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,1),
				video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,1),
				video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0),
				video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0),*/
				video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
						pa_water1.x0(), pa_water1.y1()),
				video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
						pa_water1.x1(), pa_water1.y1()),
				video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
						pa_water1.x1(), pa_water1.y0()),
				video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
						pa_water1.x0(), pa_water1.y0()),
			};

			for(s32 i=0; i<4; i++)
			{
				vertices[i].Pos.Y += (-0.5+node_water_level)*BS;
				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(material_water1, vertices, 4, indices, 6);
		}
		/*
			Add leaves if using new style
		*/
		else if(n.d == CONTENT_LEAVES && new_style_leaves)
		{
			/*u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));*/
			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
			video::SColor c(255,l,l,l);

			for(u32 j=0; j<6; j++)
			{
				video::S3DVertex vertices[4] =
				{
					/*video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c, 0,1),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c, 1,1),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c, 1,0),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c, 0,0),*/
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x0(), pa_leaves1.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x1(), pa_leaves1.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x1(), pa_leaves1.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x0(), pa_leaves1.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);
				}
				else if(j == 4)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(-90);
				}
				else if(j == 5)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(90);
				}

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_leaves1, vertices, 4, indices, 6);
			}
		}
		/*
			Add glass
		*/
		else if(n.d == CONTENT_GLASS)
		{
			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
			video::SColor c(255,l,l,l);

			for(u32 j=0; j<6; j++)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_glass.x0(), pa_glass.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_glass.x1(), pa_glass.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						pa_glass.x1(), pa_glass.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						pa_glass.x0(), pa_glass.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);
				}
				else if(j == 4)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(-90);
				}
				else if(j == 5)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(90);
				}

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_glass, vertices, 4, indices, 6);
			}
		}
		/*
			Add fence
		*/
		else if(n.d == CONTENT_FENCE)
		{
			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
			video::SColor c(255,l,l,l);

			const f32 post_rad=(f32)BS/10;
			const f32 bar_rad=(f32)BS/20;
			const f32 bar_len=(f32)(BS/2)-post_rad;

			// The post - always present
			v3f pos = intToFloat(p+blockpos_nodes, BS);
			f32 postuv[24]={
					0.4,0.4,0.6,0.6,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.4,0.4,0.6,0.6};
			makeCuboid(material_wood, &collector,
				&pa_wood, c, pos,
				post_rad,BS/2,post_rad, postuv);

			// Now a section of fence, +X, if there's a post there
			v3s16 p2 = p;
			p2.X++;
			MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			if(n2.d == CONTENT_FENCE)
			{
				pos = intToFloat(p+blockpos_nodes, BS);
				pos.X += BS/2;
				pos.Y += BS/4;
				f32 xrailuv[24]={
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6};
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_len,bar_rad,bar_rad, xrailuv);

				pos.Y -= BS/2;
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_len,bar_rad,bar_rad, xrailuv);
			}

			// Now a section of fence, +Z, if there's a post there
			p2 = p;
			p2.Z++;
			n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			if(n2.d == CONTENT_FENCE)
			{
				pos = intToFloat(p+blockpos_nodes, BS);
				pos.Z += BS/2;
				pos.Y += BS/4;
				f32 zrailuv[24]={
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6};
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_rad,bar_rad,bar_len, zrailuv);
				pos.Y -= BS/2;
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_rad,bar_rad,bar_len, zrailuv);

			}

		}
#if 1
		/*
			Add stones with minerals if stone is invisible
		*/
		else if(n.d == CONTENT_STONE && invisible_stone && n.getMineral() != MINERAL_NONE)
		{
			for(u32 j=0; j<6; j++)
			{
				// NOTE: Hopefully g_6dirs[j] is the right direction...
				v3s16 dir = g_6dirs[j];
				/*u8 l = 0;
				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + dir);
				if(content_features(n2.d).param_type == CPT_LIGHT)
					l = decode_light(n2.getLightBlend(data->m_daynight_ratio));
				else
					l = 255;*/
				u8 l = 255;
				video::SColor c(255,l,l,l);
				
				// Get the right texture
				TileSpec ts = n.getTile(dir);
				AtlasPointer ap = ts.texture;
				material_general.setTexture(0, ap.atlas);
				video::S3DVertex vertices[4] =
 				{
					/*video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c, 0,1),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c, 1,1),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c, 1,0),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c, 0,0),*/
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						ap.x0(), ap.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);
				}
				else if(j == 4)

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_general, vertices, 4, indices, 6);
			}
		}
#endif
		else if(n.d == CONTENT_PAPYRUS)
		{
			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio)));
			video::SColor c(255,l,l,l);

			for(u32 j=0; j<4; j++)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c,
						pa_papyrus.x0(), pa_papyrus.y1()),
					video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c,
						pa_papyrus.x1(), pa_papyrus.y1()),
					video::S3DVertex(BS/2,BS/2,0, 0,0,0, c,
						pa_papyrus.x1(), pa_papyrus.y0()),
					video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c,
						pa_papyrus.x0(), pa_papyrus.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(45);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-45);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(135);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-135);
				}

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_papyrus, vertices, 4, indices, 6);
			}
		}
		else if(n.d == CONTENT_RAIL)
		{
			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio));
			video::SColor c(255,l,l,l);

			bool is_rail_x [] = { false, false };  /* x-1, x+1 */
			bool is_rail_z [] = { false, false };  /* z-1, z+1 */

			MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z));
			MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z));
			MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1));
			MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1));

			if(n_minus_x.d == CONTENT_RAIL)
				is_rail_x[0] = true;
			if(n_plus_x.d == CONTENT_RAIL)
				is_rail_x[1] = true;
			if(n_minus_z.d == CONTENT_RAIL)
				is_rail_z[0] = true;
			if(n_plus_z.d == CONTENT_RAIL)
				is_rail_z[1] = true;

			float d = (float)BS/16;
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,-BS/2+d,-BS/2, 0,0,0, c,
					0, 1),
				video::S3DVertex(BS/2,-BS/2+d,-BS/2, 0,0,0, c,
					1, 1),
				video::S3DVertex(BS/2,-BS/2+d,BS/2, 0,0,0, c,
					1, 0),
				video::S3DVertex(-BS/2,-BS/2+d,BS/2, 0,0,0, c,
					0, 0),
			};

			video::SMaterial material_rail;
			material_rail.setFlag(video::EMF_LIGHTING, false);
			material_rail.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material_rail.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_rail.setFlag(video::EMF_FOG_ENABLE, true);
			material_rail.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;

			int adjacencies = is_rail_x[0] + is_rail_x[1] + is_rail_z[0] + is_rail_z[1];

			// Assign textures
			if(adjacencies < 2)
				material_rail.setTexture(0, g_texturesource->getTextureRaw("rail.png"));
			else if(adjacencies == 2)
			{
				if((is_rail_x[0] && is_rail_x[1]) || (is_rail_z[0] && is_rail_z[1]))
					material_rail.setTexture(0, g_texturesource->getTextureRaw("rail.png"));
				else
					material_rail.setTexture(0, g_texturesource->getTextureRaw("rail_curved.png"));
			}
			else if(adjacencies == 3)
				material_rail.setTexture(0, g_texturesource->getTextureRaw("rail_t_junction.png"));
			else if(adjacencies == 4)
				material_rail.setTexture(0, g_texturesource->getTextureRaw("rail_crossing.png"));

			// Rotate textures
			int angle = 0;

			if(adjacencies == 1)
			{
				if(is_rail_x[0] || is_rail_x[1])
					angle = 90;
			}
			else if(adjacencies == 2)
			{
				if(is_rail_x[0] && is_rail_x[1])
					angle = 90;
				else if(is_rail_x[0] && is_rail_z[0])
					angle = 270;
				else if(is_rail_x[0] && is_rail_z[1])
					angle = 180;
				else if(is_rail_x[1] && is_rail_z[1])
					angle = 90;
			}
			else if(adjacencies == 3)
			{
				if(!is_rail_x[0])
					angle=0;
				if(!is_rail_x[1])
					angle=180;
				if(!is_rail_z[0])
					angle=90;
				if(!is_rail_z[1])
					angle=270;
			}

			if(angle != 0) {
				for(u16 i=0; i<4; i++)
					vertices[i].Pos.rotateXZBy(angle);
			}

			for(s32 i=0; i<4; i++)
			{
				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			collector.append(material_rail, vertices, 4, indices, 6);
		}
	}
}
Esempio n. 7
0
void mapblock_mesh_generate_special(MeshMakeData *data,
		MeshCollector &collector, IGameDef *gamedef)
{
	INodeDefManager *nodedef = gamedef->ndef();

	// 0ms
	//TimeTaker timer("mapblock_mesh_generate_special()");

	/*
		Some settings
	*/
	bool new_style_water = g_settings->getBool("new_style_water");
	
	float node_liquid_level = 1.0;
	if(new_style_water)
		node_liquid_level = 0.85;
	
	v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE;

	/*// General ground material for special output
	// Texture is modified just before usage
	video::SMaterial material_general;
	material_general.setFlag(video::EMF_LIGHTING, false);
	material_general.setFlag(video::EMF_BILINEAR_FILTER, false);
	material_general.setFlag(video::EMF_FOG_ENABLE, true);
	material_general.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;*/

	for(s16 z=0; z<MAP_BLOCKSIZE; z++)
	for(s16 y=0; y<MAP_BLOCKSIZE; y++)
	for(s16 x=0; x<MAP_BLOCKSIZE; x++)
	{
		v3s16 p(x,y,z);

		MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes+p);
		const ContentFeatures &f = nodedef->get(n);

		// Only solidness=0 stuff is drawn here
		if(f.solidness != 0)
			continue;
		
		switch(f.drawtype){
		default:
			infostream<<"Got "<<f.drawtype<<std::endl;
			assert(0);
			break;
		case NDT_AIRLIKE:
			break;
		case NDT_LIQUID:
		{
			/*
				Add water sources to mesh if using new style
			*/
			assert(nodedef->get(n).special_materials[0]);
			//assert(nodedef->get(n).special_materials[1]);
			assert(nodedef->get(n).special_aps[0]);

			video::SMaterial &liquid_material =
					*nodedef->get(n).special_materials[0];
			/*video::SMaterial &liquid_material_bfculled =
					*nodedef->get(n).special_materials[1];*/
			AtlasPointer &pa_liquid1 =
					*nodedef->get(n).special_aps[0];

			bool top_is_air = false;
			MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			if(n.getContent() == CONTENT_AIR)
				top_is_air = true;
			
			if(top_is_air == false)
				continue;

			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio, nodedef));
			video::SColor c = MapBlock_LightColor(
					nodedef->get(n).alpha, l);
			
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
						pa_liquid1.x0(), pa_liquid1.y1()),
				video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
						pa_liquid1.x1(), pa_liquid1.y1()),
				video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
						pa_liquid1.x1(), pa_liquid1.y0()),
				video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
						pa_liquid1.x0(), pa_liquid1.y0()),
			};

			for(s32 i=0; i<4; i++)
			{
				vertices[i].Pos.Y += (-0.5+node_liquid_level)*BS;
				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(liquid_material, vertices, 4, indices, 6);
		break;}
		case NDT_FLOWINGLIQUID:
		{
			/*
				Add flowing liquid to mesh
			*/
			assert(nodedef->get(n).special_materials[0]);
			assert(nodedef->get(n).special_materials[1]);
			assert(nodedef->get(n).special_aps[0]);

			video::SMaterial &liquid_material =
					*nodedef->get(n).special_materials[0];
			video::SMaterial &liquid_material_bfculled =
					*nodedef->get(n).special_materials[1];
			AtlasPointer &pa_liquid1 =
					*nodedef->get(n).special_aps[0];

			bool top_is_same_liquid = false;
			MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
			content_t c_flowing = nodedef->getId(nodedef->get(n).liquid_alternative_flowing);
			content_t c_source = nodedef->getId(nodedef->get(n).liquid_alternative_source);
			if(ntop.getContent() == c_flowing || ntop.getContent() == c_source)
				top_is_same_liquid = true;
			
			u8 l = 0;
			// Use the light of the node on top if possible
			if(nodedef->get(ntop).param_type == CPT_LIGHT)
				l = decode_light(ntop.getLightBlend(data->m_daynight_ratio, nodedef));
			// Otherwise use the light of this node (the liquid)
			else
				l = decode_light(n.getLightBlend(data->m_daynight_ratio, nodedef));
			video::SColor c = MapBlock_LightColor(
					nodedef->get(n).alpha, l);
			
			// Neighbor liquid levels (key = relative position)
			// Includes current node
			core::map<v3s16, f32> neighbor_levels;
			core::map<v3s16, content_t> neighbor_contents;
			core::map<v3s16, u8> neighbor_flags;
			const u8 neighborflag_top_is_same_liquid = 0x01;
			v3s16 neighbor_dirs[9] = {
				v3s16(0,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(1,0,1),
				v3s16(-1,0,-1),
				v3s16(1,0,-1),
				v3s16(-1,0,1),
			};
			for(u32 i=0; i<9; i++)
			{
				content_t content = CONTENT_AIR;
				float level = -0.5 * BS;
				u8 flags = 0;
				// Check neighbor
				v3s16 p2 = p + neighbor_dirs[i];
				MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
				if(n2.getContent() != CONTENT_IGNORE)
				{
					content = n2.getContent();

					if(n2.getContent() == c_source)
						level = (-0.5+node_liquid_level) * BS;
					else if(n2.getContent() == c_flowing)
						level = (-0.5 + ((float)(n2.param2&LIQUID_LEVEL_MASK)
								+ 0.5) / 8.0 * node_liquid_level) * BS;

					// Check node above neighbor.
					// NOTE: This doesn't get executed if neighbor
					//       doesn't exist
					p2.Y += 1;
					n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
					if(n2.getContent() == c_source ||
							n2.getContent() == c_flowing)
						flags |= neighborflag_top_is_same_liquid;
				}
				
				neighbor_levels.insert(neighbor_dirs[i], level);
				neighbor_contents.insert(neighbor_dirs[i], content);
				neighbor_flags.insert(neighbor_dirs[i], flags);
			}

			// Corner heights (average between four liquids)
			f32 corner_levels[4];
			
			v3s16 halfdirs[4] = {
				v3s16(0,0,0),
				v3s16(1,0,0),
				v3s16(1,0,1),
				v3s16(0,0,1),
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 cornerdir = halfdirs[i];
				float cornerlevel = 0;
				u32 valid_count = 0;
				u32 air_count = 0;
				for(u32 j=0; j<4; j++)
				{
					v3s16 neighbordir = cornerdir - halfdirs[j];
					content_t content = neighbor_contents[neighbordir];
					// If top is liquid, draw starting from top of node
					if(neighbor_flags[neighbordir] &
							neighborflag_top_is_same_liquid)
					{
						cornerlevel = 0.5*BS;
						valid_count = 1;
						break;
					}
					// Source is always the same height
					else if(content == c_source)
					{
						cornerlevel = (-0.5+node_liquid_level)*BS;
						valid_count = 1;
						break;
					}
					// Flowing liquid has level information
					else if(content == c_flowing)
					{
						cornerlevel += neighbor_levels[neighbordir];
						valid_count++;
					}
					else if(content == CONTENT_AIR)
					{
						air_count++;
					}
				}
				if(air_count >= 2)
					cornerlevel = -0.5*BS;
				else if(valid_count > 0)
					cornerlevel /= valid_count;
				corner_levels[i] = cornerlevel;
			}

			/*
				Generate sides
			*/

			v3s16 side_dirs[4] = {
				v3s16(1,0,0),
				v3s16(-1,0,0),
				v3s16(0,0,1),
				v3s16(0,0,-1),
			};
			s16 side_corners[4][2] = {
				{1, 2},
				{3, 0},
				{2, 3},
				{0, 1},
			};
			for(u32 i=0; i<4; i++)
			{
				v3s16 dir = side_dirs[i];

				/*
					If our topside is liquid and neighbor's topside
					is liquid, don't draw side face
				*/
				if(top_is_same_liquid &&
						neighbor_flags[dir] & neighborflag_top_is_same_liquid)
					continue;

				content_t neighbor_content = neighbor_contents[dir];
				const ContentFeatures &n_feat = nodedef->get(neighbor_content);
				
				// Don't draw face if neighbor is blocking the view
				if(n_feat.solidness == 2)
					continue;
				
				bool neighbor_is_same_liquid = (neighbor_content == c_source
						|| neighbor_content == c_flowing);
				
				// Don't draw any faces if neighbor same is liquid and top is
				// same liquid
				if(neighbor_is_same_liquid == true
						&& top_is_same_liquid == false)
					continue;

				// Use backface culled material if neighbor doesn't have a
				// solidness of 0
				video::SMaterial *current_material = &liquid_material;
				if(n_feat.solidness != 0 || n_feat.visual_solidness != 0)
					current_material = &liquid_material_bfculled;
				
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x0(), pa_liquid1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x1(), pa_liquid1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x1(), pa_liquid1.y0()),
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x0(), pa_liquid1.y0()),
				};
				
				/*
					If our topside is liquid, set upper border of face
					at upper border of node
				*/
				if(top_is_same_liquid)
				{
					vertices[2].Pos.Y = 0.5*BS;
					vertices[3].Pos.Y = 0.5*BS;
				}
				/*
					Otherwise upper position of face is corner levels
				*/
				else
				{
					vertices[2].Pos.Y = corner_levels[side_corners[i][0]];
					vertices[3].Pos.Y = corner_levels[side_corners[i][1]];
				}
				
				/*
					If neighbor is liquid, lower border of face is corner
					liquid levels
				*/
				if(neighbor_is_same_liquid)
				{
					vertices[0].Pos.Y = corner_levels[side_corners[i][1]];
					vertices[1].Pos.Y = corner_levels[side_corners[i][0]];
				}
				/*
					If neighbor is not liquid, lower border of face is
					lower border of node
				*/
				else
				{
					vertices[0].Pos.Y = -0.5*BS;
					vertices[1].Pos.Y = -0.5*BS;
				}
				
				for(s32 j=0; j<4; j++)
				{
					if(dir == v3s16(0,0,1))
						vertices[j].Pos.rotateXZBy(0);
					if(dir == v3s16(0,0,-1))
						vertices[j].Pos.rotateXZBy(180);
					if(dir == v3s16(-1,0,0))
						vertices[j].Pos.rotateXZBy(90);
					if(dir == v3s16(1,0,-0))
						vertices[j].Pos.rotateXZBy(-90);
						
					// Do this to not cause glitches when two liquids are
					// side-by-side
					/*if(neighbor_is_same_liquid == false){
						vertices[j].Pos.X *= 0.98;
						vertices[j].Pos.Z *= 0.98;
					}*/

					vertices[j].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(*current_material, vertices, 4, indices, 6);
			}
			
			/*
				Generate top side, if appropriate
			*/
			
			if(top_is_same_liquid == false)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x0(), pa_liquid1.y1()),
					video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
							pa_liquid1.x1(), pa_liquid1.y1()),
					video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
							pa_liquid1.x1(), pa_liquid1.y0()),
					video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
							pa_liquid1.x0(), pa_liquid1.y0()),
				};
				
				// This fixes a strange bug
				s32 corner_resolve[4] = {3,2,1,0};

				for(s32 i=0; i<4; i++)
				{
					//vertices[i].Pos.Y += liquid_level;
					//vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)];
					s32 j = corner_resolve[i];
					vertices[i].Pos.Y += corner_levels[j];
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(liquid_material, vertices, 4, indices, 6);
			}
		break;}
		case NDT_GLASSLIKE:
		{
			video::SMaterial material_glass;
			material_glass.setFlag(video::EMF_LIGHTING, false);
			material_glass.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_glass.setFlag(video::EMF_FOG_ENABLE, true);
			material_glass.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			AtlasPointer pa_glass = f.tiles[0].texture;
			material_glass.setTexture(0, pa_glass.atlas);

			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio, nodedef)));
			video::SColor c = MapBlock_LightColor(255, l);

			for(u32 j=0; j<6; j++)
			{
				// Check this neighbor
				v3s16 n2p = blockpos_nodes + p + g_6dirs[j];
				MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
				// Don't make face if neighbor is of same type
				if(n2.getContent() == n.getContent())
					continue;

				// The face at Z+
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_glass.x0(), pa_glass.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_glass.x1(), pa_glass.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						pa_glass.x1(), pa_glass.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						pa_glass.x0(), pa_glass.y0()),
				};
				
				// Rotations in the g_6dirs format
				if(j == 0) // Z+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				else if(j == 1) // Y+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(-90);
				else if(j == 2) // X+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				else if(j == 3) // Z-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				else if(j == 4) // Y-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(90);
				else if(j == 5) // X-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);

				for(u16 i=0; i<4; i++){
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_glass, vertices, 4, indices, 6);
			}
		break;}
		case NDT_ALLFACES:
		{
			video::SMaterial material_leaves1;
			material_leaves1.setFlag(video::EMF_LIGHTING, false);
			material_leaves1.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_leaves1.setFlag(video::EMF_FOG_ENABLE, true);
			material_leaves1.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			AtlasPointer pa_leaves1 = f.tiles[0].texture;
			material_leaves1.setTexture(0, pa_leaves1.atlas);

			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio, nodedef)));
			video::SColor c = MapBlock_LightColor(255, l);

			for(u32 j=0; j<6; j++)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x0(), pa_leaves1.y1()),
					video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x1(), pa_leaves1.y1()),
					video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x1(), pa_leaves1.y0()),
					video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
						pa_leaves1.x0(), pa_leaves1.y0()),
				};

				// Rotations in the g_6dirs format
				if(j == 0) // Z+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(0);
				else if(j == 1) // Y+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(-90);
				else if(j == 2) // X+
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-90);
				else if(j == 3) // Z-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(180);
				else if(j == 4) // Y-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateYZBy(90);
				else if(j == 5) // X-
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(90);

				for(u16 i=0; i<4; i++){
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_leaves1, vertices, 4, indices, 6);
			}
		break;}
		case NDT_ALLFACES_OPTIONAL:
			// This is always pre-converted to something else
			assert(0);
			break;
		case NDT_TORCHLIKE:
		{
			v3s16 dir = unpackDir(n.param2);
			
			AtlasPointer ap(0);
			if(dir == v3s16(0,-1,0)){
				ap = f.tiles[0].texture; // floor
			} else if(dir == v3s16(0,1,0)){
				ap = f.tiles[1].texture; // ceiling
			// For backwards compatibility
			} else if(dir == v3s16(0,0,0)){
				ap = f.tiles[0].texture; // floor
			} else {
				ap = f.tiles[2].texture; // side
			}

			// Set material
			video::SMaterial material;
			material.setFlag(video::EMF_LIGHTING, false);
			material.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material.setFlag(video::EMF_BILINEAR_FILTER, false);
			material.setFlag(video::EMF_FOG_ENABLE, true);
			//material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
			material.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			material.setTexture(0, ap.atlas);

			video::SColor c(255,255,255,255);

			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c,
						ap.x0(), ap.y1()),
				video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c,
						ap.x1(), ap.y1()),
				video::S3DVertex(BS/2,BS/2,0, 0,0,0, c,
						ap.x1(), ap.y0()),
				video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c,
						ap.x0(), ap.y0()),
			};

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXZBy(45);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXZBy(-45);

				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(material, vertices, 4, indices, 6);
		break;}
		case NDT_SIGNLIKE:
		{
			// Set material
			video::SMaterial material;
			material.setFlag(video::EMF_LIGHTING, false);
			material.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material.setFlag(video::EMF_BILINEAR_FILTER, false);
			material.setFlag(video::EMF_FOG_ENABLE, true);
			material.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			AtlasPointer ap = f.tiles[0].texture;
			material.setTexture(0, ap.atlas);

			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio, nodedef));
			video::SColor c = MapBlock_LightColor(255, l);
				
			float d = (float)BS/16;
			// Wall at X+ of node
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(BS/2-d,-BS/2,-BS/2, 0,0,0, c,
						ap.x0(), ap.y1()),
				video::S3DVertex(BS/2-d,-BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y1()),
				video::S3DVertex(BS/2-d,BS/2,BS/2, 0,0,0, c,
						ap.x1(), ap.y0()),
				video::S3DVertex(BS/2-d,BS/2,-BS/2, 0,0,0, c,
						ap.x0(), ap.y0()),
			};

			v3s16 dir = unpackDir(n.param2);

			for(s32 i=0; i<4; i++)
			{
				if(dir == v3s16(1,0,0))
					vertices[i].Pos.rotateXZBy(0);
				if(dir == v3s16(-1,0,0))
					vertices[i].Pos.rotateXZBy(180);
				if(dir == v3s16(0,0,1))
					vertices[i].Pos.rotateXZBy(90);
				if(dir == v3s16(0,0,-1))
					vertices[i].Pos.rotateXZBy(-90);
				if(dir == v3s16(0,-1,0))
					vertices[i].Pos.rotateXYBy(-90);
				if(dir == v3s16(0,1,0))
					vertices[i].Pos.rotateXYBy(90);

				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			// Add to mesh collector
			collector.append(material, vertices, 4, indices, 6);
		break;}
		case NDT_PLANTLIKE:
		{
			video::SMaterial material_papyrus;
			material_papyrus.setFlag(video::EMF_LIGHTING, false);
			material_papyrus.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_papyrus.setFlag(video::EMF_FOG_ENABLE, true);
			material_papyrus.MaterialType=video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			AtlasPointer pa_papyrus = f.tiles[0].texture;
			material_papyrus.setTexture(0, pa_papyrus.atlas);
			
			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio, nodedef)));
			video::SColor c = MapBlock_LightColor(255, l);

			for(u32 j=0; j<4; j++)
			{
				video::S3DVertex vertices[4] =
				{
					video::S3DVertex(-BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
						pa_papyrus.x0(), pa_papyrus.y1()),
					video::S3DVertex( BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
						pa_papyrus.x1(), pa_papyrus.y1()),
					video::S3DVertex( BS/2*f.visual_scale,
						-BS/2 + f.visual_scale*BS,0, 0,0,0, c,
						pa_papyrus.x1(), pa_papyrus.y0()),
					video::S3DVertex(-BS/2*f.visual_scale,
						-BS/2 + f.visual_scale*BS,0, 0,0,0, c,
						pa_papyrus.x0(), pa_papyrus.y0()),
				};

				if(j == 0)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(45);
				}
				else if(j == 1)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-45);
				}
				else if(j == 2)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(135);
				}
				else if(j == 3)
				{
					for(u16 i=0; i<4; i++)
						vertices[i].Pos.rotateXZBy(-135);
				}

				for(u16 i=0; i<4; i++)
				{
					vertices[i].Pos *= f.visual_scale;
					vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
				}

				u16 indices[] = {0,1,2,2,3,0};
				// Add to mesh collector
				collector.append(material_papyrus, vertices, 4, indices, 6);
			}
		break;}
		case NDT_FENCELIKE:
		{
			video::SMaterial material_wood;
			material_wood.setFlag(video::EMF_LIGHTING, false);
			material_wood.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_wood.setFlag(video::EMF_FOG_ENABLE, true);
			material_wood.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			AtlasPointer pa_wood = f.tiles[0].texture;
			material_wood.setTexture(0, pa_wood.atlas);

			u8 l = decode_light(undiminish_light(n.getLightBlend(data->m_daynight_ratio, nodedef)));
			video::SColor c = MapBlock_LightColor(255, l);

			const f32 post_rad=(f32)BS/10;
			const f32 bar_rad=(f32)BS/20;
			const f32 bar_len=(f32)(BS/2)-post_rad;

			// The post - always present
			v3f pos = intToFloat(p+blockpos_nodes, BS);
			f32 postuv[24]={
					0.4,0.4,0.6,0.6,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.35,0,0.65,1,
					0.4,0.4,0.6,0.6};
			makeCuboid(material_wood, &collector,
				&pa_wood, c, pos,
				post_rad,BS/2,post_rad, postuv);

			// Now a section of fence, +X, if there's a post there
			v3s16 p2 = p;
			p2.X++;
			MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			const ContentFeatures *f2 = &nodedef->get(n2);
			if(f2->drawtype == NDT_FENCELIKE)
			{
				pos = intToFloat(p+blockpos_nodes, BS);
				pos.X += BS/2;
				pos.Y += BS/4;
				f32 xrailuv[24]={
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6};
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_len,bar_rad,bar_rad, xrailuv);

				pos.Y -= BS/2;
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_len,bar_rad,bar_rad, xrailuv);
			}

			// Now a section of fence, +Z, if there's a post there
			p2 = p;
			p2.Z++;
			n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
			f2 = &nodedef->get(n2);
			if(f2->drawtype == NDT_FENCELIKE)
			{
				pos = intToFloat(p+blockpos_nodes, BS);
				pos.Z += BS/2;
				pos.Y += BS/4;
				f32 zrailuv[24]={
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6,
					0,0.4,1,0.6};
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_rad,bar_rad,bar_len, zrailuv);
				pos.Y -= BS/2;
				makeCuboid(material_wood, &collector,
					&pa_wood, c, pos,
					bar_rad,bar_rad,bar_len, zrailuv);

			}
		break;}
		case NDT_RAILLIKE:
		{
			bool is_rail_x [] = { false, false };  /* x-1, x+1 */
			bool is_rail_z [] = { false, false };  /* z-1, z+1 */

			MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z));
			MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z));
			MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1));
			MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1));
			
			content_t thiscontent = n.getContent();
			if(n_minus_x.getContent() == thiscontent)
				is_rail_x[0] = true;
			if(n_plus_x.getContent() == thiscontent)
				is_rail_x[1] = true;
			if(n_minus_z.getContent() == thiscontent)
				is_rail_z[0] = true;
			if(n_plus_z.getContent() == thiscontent)
				is_rail_z[1] = true;

			int adjacencies = is_rail_x[0] + is_rail_x[1] + is_rail_z[0] + is_rail_z[1];

			// Assign textures
			AtlasPointer ap = f.tiles[0].texture; // straight
			if(adjacencies < 2)
				ap = f.tiles[0].texture; // straight
			else if(adjacencies == 2)
			{
				if((is_rail_x[0] && is_rail_x[1]) || (is_rail_z[0] && is_rail_z[1]))
					ap = f.tiles[0].texture; // straight
				else
					ap = f.tiles[1].texture; // curved
			}
			else if(adjacencies == 3)
				ap = f.tiles[2].texture; // t-junction
			else if(adjacencies == 4)
				ap = f.tiles[3].texture; // crossing
			
			video::SMaterial material_rail;
			material_rail.setFlag(video::EMF_LIGHTING, false);
			material_rail.setFlag(video::EMF_BACK_FACE_CULLING, false);
			material_rail.setFlag(video::EMF_BILINEAR_FILTER, false);
			material_rail.setFlag(video::EMF_FOG_ENABLE, true);
			material_rail.MaterialType
					= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			material_rail.setTexture(0, ap.atlas);

			u8 l = decode_light(n.getLightBlend(data->m_daynight_ratio, nodedef));
			video::SColor c = MapBlock_LightColor(255, l);

			float d = (float)BS/16;
			video::S3DVertex vertices[4] =
			{
				video::S3DVertex(-BS/2,-BS/2+d,-BS/2, 0,0,0, c,
						ap.x0(), ap.y1()),
				video::S3DVertex(BS/2,-BS/2+d,-BS/2, 0,0,0, c,
						ap.x1(), ap.y1()),
				video::S3DVertex(BS/2,-BS/2+d,BS/2, 0,0,0, c,
						ap.x1(), ap.y0()),
				video::S3DVertex(-BS/2,-BS/2+d,BS/2, 0,0,0, c,
						ap.x0(), ap.y0()),
			};

			// Rotate textures
			int angle = 0;

			if(adjacencies == 1)
			{
				if(is_rail_x[0] || is_rail_x[1])
					angle = 90;
			}
			else if(adjacencies == 2)
			{
				if(is_rail_x[0] && is_rail_x[1])
					angle = 90;
				else if(is_rail_x[0] && is_rail_z[0])
					angle = 270;
				else if(is_rail_x[0] && is_rail_z[1])
					angle = 180;
				else if(is_rail_x[1] && is_rail_z[1])
					angle = 90;
			}
			else if(adjacencies == 3)
			{
				if(!is_rail_x[0])
					angle=0;
				if(!is_rail_x[1])
					angle=180;
				if(!is_rail_z[0])
					angle=90;
				if(!is_rail_z[1])
					angle=270;
			}

			if(angle != 0) {
				for(u16 i=0; i<4; i++)
					vertices[i].Pos.rotateXZBy(angle);
			}

			for(s32 i=0; i<4; i++)
			{
				vertices[i].Pos += intToFloat(p + blockpos_nodes, BS);
			}

			u16 indices[] = {0,1,2,2,3,0};
			collector.append(material_rail, vertices, 4, indices, 6);
		break;}
		}
	}
}
Esempio n. 8
0
MapBlockMesh::MapBlockMesh(MeshMakeData *data):
	m_mesh(new scene::SMesh()),
	m_gamedef(data->m_gamedef),
	m_animation_force_timer(0), // force initial animation
	m_last_crack(-1),
	m_crack_materials(),
	m_last_daynight_ratio((u32) -1),
	m_daynight_diffs()
{
	// 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
	// 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
	//TimeTaker timer1("MapBlockMesh()");

	core::array<FastFace> fastfaces_new;

	/*
		We are including the faces of the trailing edges of the block.
		This means that when something changes, the caller must
		also update the meshes of the blocks at the leading edges.

		NOTE: This is the slowest part of this method.
	*/
	{
		// 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
		//TimeTaker timer2("updateAllFastFaceRows()");
		updateAllFastFaceRows(data, fastfaces_new);
	}
	// End of slow part

	/*
		Convert FastFaces to MeshCollector
	*/

	MeshCollector collector;

	{
		// avg 0ms (100ms spikes when loading textures the first time)
		// (NOTE: probably outdated)
		//TimeTaker timer2("MeshCollector building");

		for(u32 i=0; i<fastfaces_new.size(); i++)
		{
			FastFace &f = fastfaces_new[i];

			const u16 indices[] = {0,1,2,2,3,0};
			const u16 indices_alternate[] = {0,1,3,2,3,1};
			
			if(f.tile.texture.atlas == NULL)
				continue;

			const u16 *indices_p = indices;
			
			/*
				Revert triangles for nicer looking gradient if vertices
				1 and 3 have same color or 0 and 2 have different color.
				getRed() is the day color.
			*/
			if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed()
					|| f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed())
				indices_p = indices_alternate;
			
			collector.append(f.tile, f.vertices, 4, indices_p, 6);
		}
	}

	/*
		Add special graphics:
		- torches
		- flowing water
		- fences
		- whatever
	*/

	mapblock_mesh_generate_special(data, collector);
	

	/*
		Convert MeshCollector to SMesh
		Also store animation info
	*/
	for(u32 i = 0; i < collector.prebuffers.size(); i++)
	{
		PreMeshBuffer &p = collector.prebuffers[i];
		/*dstream<<"p.vertices.size()="<<p.vertices.size()
				<<", p.indices.size()="<<p.indices.size()
				<<std::endl;*/

		// Generate animation data
		// - Cracks
		if(p.tile.material_flags & MATERIAL_FLAG_CRACK)
		{
			ITextureSource *tsrc = data->m_gamedef->tsrc();
			std::string crack_basename = tsrc->getTextureName(p.tile.texture.id);
			if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
				crack_basename += "^[cracko";
			else
				crack_basename += "^[crack";
			m_crack_materials.insert(std::make_pair(i, crack_basename));
		}
		// - Lighting
		for(u32 j = 0; j < p.vertices.size(); j++)
		{
			video::SColor &vc = p.vertices[j].Color;
			u8 day = vc.getRed();
			u8 night = vc.getGreen();
			finalColorBlend(vc, day, night, 1000);
			if(day != night)
				m_daynight_diffs[i][j] = std::make_pair(day, night);
		}


		// Create material
		video::SMaterial material;
		material.setFlag(video::EMF_LIGHTING, false);
		material.setFlag(video::EMF_BACK_FACE_CULLING, true);
		material.setFlag(video::EMF_BILINEAR_FILTER, false);
		material.setFlag(video::EMF_FOG_ENABLE, true);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
		//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_SIMPLE);
		material.MaterialType
				= video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
		material.setTexture(0, p.tile.texture.atlas);
		p.tile.applyMaterialOptions(material);

		// Create meshbuffer

		// This is a "Standard MeshBuffer",
		// it's a typedeffed CMeshBuffer<video::S3DVertex>
		scene::SMeshBuffer *buf = new scene::SMeshBuffer();
		// Set material
		buf->Material = material;
		// Add to mesh
		m_mesh->addMeshBuffer(buf);
		// Mesh grabbed it
		buf->drop();
		buf->append(p.vertices.pointer(), p.vertices.size(),
				p.indices.pointer(), p.indices.size());
	}

	/*
		Do some stuff to the mesh
	*/

	translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE, BS));
	m_mesh->recalculateBoundingBox(); // translateMesh already does this

	if(m_mesh)
	{
#if 0
		// Usually 1-700 faces and 1-7 materials
		std::cout<<"Updated MapBlock has "<<fastfaces_new.size()<<" faces "
				<<"and uses "<<m_mesh->getMeshBufferCount()
				<<" materials (meshbuffers)"<<std::endl;
#endif

		// Use VBO for mesh (this just would set this for ever buffer)
		// This will lead to infinite memory usage because or irrlicht.
		//m_mesh->setHardwareMappingHint(scene::EHM_STATIC);

		/*
			NOTE: If that is enabled, some kind of a queue to the main
			thread should be made which would call irrlicht to delete
			the hardware buffer and then delete the mesh
		*/
	}
	
	//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;

	// Check if animation is required for this mesh
	m_has_animation =
		!m_crack_materials.empty() ||
		!m_daynight_diffs.empty();
}