From 392485aa454e871566a67afc61e11331f9d27397 Mon Sep 17 00:00:00 2001
From: Kahrl <kahrl@gmx.net>
Date: Sat, 3 Dec 2011 08:40:32 +0100
Subject: [PATCH] inventorycube: use all three specified textures; also moved
 mesh creation / modification functions to mesh.cpp; in lua, inventorycube is
 now called minetest.inventorycube

---
 data/builtin.lua                |  13 ++
 data/mods/default/init.lua      |  66 +++---
 data/mods/experimental/init.lua |   5 +-
 src/CMakeLists.txt              |   1 +
 src/camera.cpp                  | 293 ++------------------------
 src/camera.h                    |   9 +-
 src/content_cao.cpp             |  13 +-
 src/mesh.cpp                    | 362 ++++++++++++++++++++++++++++++++
 src/mesh.h                      |  66 ++++++
 src/tile.cpp                    |  38 ++--
 src/utility.cpp                 |  21 --
 src/utility.h                   |   5 -
 12 files changed, 517 insertions(+), 375 deletions(-)
 create mode 100644 src/mesh.cpp
 create mode 100644 src/mesh.h

diff --git a/data/builtin.lua b/data/builtin.lua
index a9412a97d..5d6936d61 100644
--- a/data/builtin.lua
+++ b/data/builtin.lua
@@ -303,6 +303,19 @@ function test_stackstring()
 end
 test_stackstring()
 
+--
+-- nodeitem helpers
+--
+
+minetest.inventorycube = function(img1, img2, img3)
+	img2 = img2 or img1
+	img3 = img3 or img1
+	return "[inventorycube"
+			.. "{" .. img1:gsub("%^", "&")
+			.. "{" .. img2:gsub("%^", "&")
+			.. "{" .. img3:gsub("%^", "&")
+end
+
 --
 -- craftitem helpers
 --
diff --git a/data/mods/default/init.lua b/data/mods/default/init.lua
index 3baafff63..2e07b5412 100644
--- a/data/mods/default/init.lua
+++ b/data/mods/default/init.lua
@@ -824,20 +824,11 @@ function digprop_glasslike(toughness)
 	}
 end
 
-function inventorycube(img1, img2, img3)
-	img2 = img2 or img1
-	img3 = img3 or img1
-	return "[inventorycube"
-			.. "{" .. img1:gsub("%^", "&")
-			.. "{" .. img2:gsub("%^", "&")
-			.. "{" .. img3:gsub("%^", "&")
-end
-
 -- Legacy nodes
 
 minetest.register_node(":stone", {
 	tile_images = {"stone.png"},
-	inventory_image = inventorycube("stone.png"),
+	inventory_image = minetest.inventorycube("stone.png"),
 	paramtype = "mineral",
 	is_ground_content = true,
 	often_contains_mineral = true, -- Texture atlas hint
@@ -847,7 +838,7 @@ minetest.register_node(":stone", {
 
 minetest.register_node(":dirt_with_grass", {
 	tile_images = {"grass.png", "mud.png", "mud.png^grass_side.png"},
-	inventory_image = inventorycube("mud.png^grass_side.png"),
+	inventory_image = minetest.inventorycube("mud.png^grass_side.png"),
 	is_ground_content = true,
 	material = digprop_dirtlike(1.0),
 	dug_item = 'node "dirt" 1',
@@ -863,14 +854,14 @@ minetest.register_node(":dirt_with_grass_footsteps", {
 
 minetest.register_node(":dirt", {
 	tile_images = {"mud.png"},
-	inventory_image = inventorycube("mud.png"),
+	inventory_image = minetest.inventorycube("mud.png"),
 	is_ground_content = true,
 	material = digprop_dirtlike(1.0),
 })
 
 minetest.register_node(":sand", {
 	tile_images = {"sand.png"},
-	inventory_image = inventorycube("sand.png"),
+	inventory_image = minetest.inventorycube("sand.png"),
 	is_ground_content = true,
 	material = digprop_dirtlike(1.0),
 	cookresult_item = 'node "glass" 1',
@@ -878,14 +869,14 @@ minetest.register_node(":sand", {
 
 minetest.register_node(":gravel", {
 	tile_images = {"gravel.png"},
-	inventory_image = inventorycube("gravel.png"),
+	inventory_image = minetest.inventorycube("gravel.png"),
 	is_ground_content = true,
 	material = digprop_gravellike(1.0),
 })
 
 minetest.register_node(":sandstone", {
 	tile_images = {"sandstone.png"},
-	inventory_image = inventorycube("sandstone.png"),
+	inventory_image = minetest.inventorycube("sandstone.png"),
 	is_ground_content = true,
 	material = digprop_dirtlike(1.0),  -- FIXME should this be stonelike?
 	dug_item = 'node "sand" 1',  -- FIXME is this intentional?
@@ -893,7 +884,7 @@ minetest.register_node(":sandstone", {
 
 minetest.register_node(":clay", {
 	tile_images = {"clay.png"},
-	inventory_image = inventorycube("clay.png"),
+	inventory_image = minetest.inventorycube("clay.png"),
 	is_ground_content = true,
 	material = digprop_dirtlike(1.0),
 	dug_item = 'craft "lump_of_clay" 4',
@@ -901,7 +892,7 @@ minetest.register_node(":clay", {
 
 minetest.register_node(":brick", {
 	tile_images = {"brick.png"},
-	inventory_image = inventorycube("brick.png"),
+	inventory_image = minetest.inventorycube("brick.png"),
 	is_ground_content = true,
 	material = digprop_stonelike(1.0),
 	dug_item = 'craft "clay_brick" 4',
@@ -909,7 +900,7 @@ minetest.register_node(":brick", {
 
 minetest.register_node(":tree", {
 	tile_images = {"tree_top.png", "tree_top.png", "tree.png"},
-	inventory_image = inventorycube("tree_top.png", "tree.png", "tree.png"),
+	inventory_image = minetest.inventorycube("tree_top.png", "tree.png", "tree.png"),
 	is_ground_content = true,
 	material = digprop_woodlike(1.0),
 	cookresult_item = 'craft "lump_of_coal" 1',
@@ -918,7 +909,7 @@ minetest.register_node(":tree", {
 
 minetest.register_node(":jungletree", {
 	tile_images = {"jungletree_top.png", "jungletree_top.png", "jungletree.png"},
-	inventory_image = inventorycube("jungletree_top.png", "jungletree.png", "jungletree.png"),
+	inventory_image = minetest.inventorycube("jungletree_top.png", "jungletree.png", "jungletree.png"),
 	is_ground_content = true,
 	material = digprop_woodlike(1.0),
 	cookresult_item = 'craft "lump_of_coal" 1',
@@ -941,7 +932,7 @@ minetest.register_node(":leaves", {
 	drawtype = "allfaces_optional",
 	visual_scale = 1.3,
 	tile_images = {"leaves.png"},
-	inventory_image = "leaves.png",
+	inventory_image = minetest.inventorycube("leaves.png"),
 	light_propagates = true,
 	paramtype = "light",
 	material = digprop_leaveslike(1.0),
@@ -952,7 +943,7 @@ minetest.register_node(":leaves", {
 
 minetest.register_node(":cactus", {
 	tile_images = {"cactus_top.png", "cactus_top.png", "cactus_side.png"},
-	inventory_image = inventorycube("cactus_top.png", "cactus_side.png", "cactus_side.png"),
+	inventory_image = minetest.inventorycube("cactus_top.png", "cactus_side.png", "cactus_side.png"),
 	is_ground_content = true,
 	material = digprop_woodlike(0.75),
 	furnace_burntime = 15,
@@ -972,9 +963,7 @@ minetest.register_node(":papyrus", {
 
 minetest.register_node(":bookshelf", {
 	tile_images = {"wood.png", "wood.png", "bookshelf.png"},
-	-- FIXME: inventorycube only cares for the first texture
-	--inventory_image = inventorycube("wood.png", "bookshelf.png", "bookshelf.png")
-	inventory_image = inventorycube("bookshelf.png"),
+	inventory_image = minetest.inventorycube("wood.png", "bookshelf.png", "bookshelf.png"),
 	is_ground_content = true,
 	material = digprop_woodlike(0.75),
 	furnace_burntime = 30,
@@ -983,7 +972,7 @@ minetest.register_node(":bookshelf", {
 minetest.register_node(":glass", {
 	drawtype = "glasslike",
 	tile_images = {"glass.png"},
-	inventory_image = inventorycube("glass.png"),
+	inventory_image = minetest.inventorycube("glass.png"),
 	light_propagates = true,
 	paramtype = "light",
 	sunlight_propagates = true,
@@ -1050,7 +1039,7 @@ minetest.register_node(":coalstone", {
 
 minetest.register_node(":wood", {
 	tile_images = {"wood.png"},
-	inventory_image = inventorycube("wood.png"),
+	inventory_image = minetest.inventorycube("wood.png"),
 	is_ground_content = true,
 	furnace_burntime = 7,
 	material = digprop_woodlike(0.75),
@@ -1058,7 +1047,7 @@ minetest.register_node(":wood", {
 
 minetest.register_node(":mese", {
 	tile_images = {"mese.png"},
-	inventory_image = inventorycube("mese.png"),
+	inventory_image = minetest.inventorycube("mese.png"),
 	is_ground_content = true,
 	furnace_burntime = 30,
 	material = digprop_stonelike(0.5),
@@ -1066,7 +1055,7 @@ minetest.register_node(":mese", {
 
 minetest.register_node(":cloud", {
 	tile_images = {"cloud.png"},
-	inventory_image = inventorycube("cloud.png"),
+	inventory_image = minetest.inventorycube("cloud.png"),
 	is_ground_content = true,
 })
 
@@ -1074,7 +1063,7 @@ minetest.register_node(":water_flowing", {
 	drawtype = "flowingliquid",
 	tile_images = {"water.png"},
 	alpha = WATER_ALPHA,
-	inventory_image = inventorycube("water.png"),
+	inventory_image = minetest.inventorycube("water.png"),
 	paramtype = "light",
 	light_propagates = true,
 	walkable = false,
@@ -1096,7 +1085,7 @@ minetest.register_node(":water_source", {
 	drawtype = "liquid",
 	tile_images = {"water.png"},
 	alpha = WATER_ALPHA,
-	inventory_image = inventorycube("water.png"),
+	inventory_image = minetest.inventorycube("water.png"),
 	paramtype = "light",
 	light_propagates = true,
 	walkable = false,
@@ -1117,7 +1106,7 @@ minetest.register_node(":water_source", {
 minetest.register_node(":lava_flowing", {
 	drawtype = "flowingliquid",
 	tile_images = {"lava.png"},
-	inventory_image = inventorycube("lava.png"),
+	inventory_image = minetest.inventorycube("lava.png"),
 	paramtype = "light",
 	light_propagates = false,
 	light_source = LIGHT_MAX - 1,
@@ -1140,7 +1129,7 @@ minetest.register_node(":lava_flowing", {
 minetest.register_node(":lava_source", {
 	drawtype = "liquid",
 	tile_images = {"lava.png"},
-	inventory_image = inventorycube("lava.png"),
+	inventory_image = minetest.inventorycube("lava.png"),
 	paramtype = "light",
 	light_propagates = false,
 	light_source = LIGHT_MAX - 1,
@@ -1204,8 +1193,7 @@ minetest.register_node(":sign_wall", {
 minetest.register_node(":chest", {
 	tile_images = {"chest_top.png", "chest_top.png", "chest_side.png",
 		"chest_side.png", "chest_side.png", "chest_front.png"},
-	inventory_image = "chest_top.png",
-	--inventory_image = inventorycube("chest_top.png", "chest_side.png", "chest_front.png"),
+	inventory_image = minetest.inventorycube("chest_top.png", "chest_front.png", "chest_side.png"),
 	paramtype = "facedir_simple",
 	metadata_name = "chest",
 	material = digprop_woodlike(1.0),
@@ -1215,7 +1203,7 @@ minetest.register_node(":chest", {
 minetest.register_node(":locked_chest", {
 	tile_images = {"chest_top.png", "chest_top.png", "chest_side.png",
 		"chest_side.png", "chest_side.png", "chest_lock.png"},
-	inventory_image = "chest_lock.png",
+	inventory_image = minetest.inventorycube("chest_top.png", "chest_lock.png", "chest_side.png"),
 	paramtype = "facedir_simple",
 	metadata_name = "locked_chest",
 	material = digprop_woodlike(1.0),
@@ -1225,7 +1213,7 @@ minetest.register_node(":locked_chest", {
 minetest.register_node(":furnace", {
 	tile_images = {"furnace_side.png", "furnace_side.png", "furnace_side.png",
 		"furnace_side.png", "furnace_side.png", "furnace_front.png"},
-	inventory_image = "furnace_front.png",
+	inventory_image = minetest.inventorycube("furnace_side.png", "furnace_front.png", "furnace_side.png"),
 	paramtype = "facedir_simple",
 	metadata_name = "furnace",
 	material = digprop_stonelike(3.0),
@@ -1233,7 +1221,7 @@ minetest.register_node(":furnace", {
 
 minetest.register_node(":cobble", {
 	tile_images = {"cobble.png"},
-	inventory_image = inventorycube("cobble.png"),
+	inventory_image = minetest.inventorycube("cobble.png"),
 	is_ground_content = true,
 	cookresult_item = 'node "stone" 1',
 	material = digprop_stonelike(0.9),
@@ -1241,14 +1229,14 @@ minetest.register_node(":cobble", {
 
 minetest.register_node(":mossycobble", {
 	tile_images = {"mossycobble.png"},
-	inventory_image = inventorycube("mossycobble.png"),
+	inventory_image = minetest.inventorycube("mossycobble.png"),
 	is_ground_content = true,
 	material = digprop_stonelike(0.8),
 })
 
 minetest.register_node(":steelblock", {
 	tile_images = {"steel_block.png"},
-	inventory_image = inventorycube("steel_block.png"),
+	inventory_image = minetest.inventorycube("steel_block.png"),
 	is_ground_content = true,
 	material = digprop_stonelike(5.0),
 })
diff --git a/data/mods/experimental/init.lua b/data/mods/experimental/init.lua
index 326fe1b75..b63bf76fc 100644
--- a/data/mods/experimental/init.lua
+++ b/data/mods/experimental/init.lua
@@ -8,7 +8,7 @@ minetest.register_node("experimental:luafurnace", {
 	tile_images = {"lava.png", "furnace_side.png", "furnace_side.png",
 		"furnace_side.png", "furnace_side.png", "furnace_front.png"},
 	--inventory_image = "furnace_front.png",
-	inventory_image = inventorycube("furnace_front.png"),
+	inventory_image = minetest.inventorycube("furnace_front.png"),
 	paramtype = "facedir_simple",
 	metadata_name = "generic",
 	material = digprop_stonelike(3.0),
@@ -134,7 +134,8 @@ minetest.register_craft({
 
 minetest.register_node("experimental:somenode", {
 	tile_images = {"lava.png", "mese.png", "stone.png", "grass.png", "cobble.png", "tree_top.png"},
-	inventory_image = "treeprop.png",
+	inventory_image = minetest.inventorycube("lava.png", "mese.png", "stone.png"),
+	--inventory_image = "treeprop.png",
 	material = {
 		diggability = "normal",
 		weight = 0,
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a0e063465..61a0b1be8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -161,6 +161,7 @@ set(minetest_SRCS
 	MyBillboardSceneNode.cpp
 	content_mapblock.cpp
 	content_cao.cpp
+	mesh.cpp
 	mapblock_mesh.cpp
 	farmesh.cpp
 	keycode.cpp
diff --git a/src/camera.cpp b/src/camera.cpp
index 8f421e691..c0e171468 100644
--- a/src/camera.cpp
+++ b/src/camera.cpp
@@ -22,21 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "client.h"
 #include "main.h" // for g_settings
 #include "map.h"
+#include "mesh.h"
 #include "player.h"
 #include "tile.h"
 #include <cmath>
-#include <SAnimatedMesh.h>
 #include "settings.h"
 #include "nodedef.h" // For wield visualization
 
-// In Irrlicht 1.8 the signature of ITexture::lock was changed from
-// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
-#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
-#define MY_ETLM_READ_ONLY true
-#else
-#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
-#endif
-
 Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control):
 	m_smgr(smgr),
 	m_playernode(NULL),
@@ -480,7 +472,6 @@ void Camera::wield(const InventoryItem* item, IGameDef *gamedef)
 			case NDT_ALLFACES:
 			case NDT_ALLFACES_OPTIONAL:
 				m_wieldnode->setCube(ndef->get(content).tiles);
-				m_wieldnode->setScale(v3f(30));
 				isCube = true;
 				break;
 			default:
@@ -492,7 +483,6 @@ void Camera::wield(const InventoryItem* item, IGameDef *gamedef)
 		if (!isCube)
 		{
 			m_wieldnode->setSprite(item->getImageRaw());
-			m_wieldnode->setScale(v3f(40));
 		}
 
 		m_wieldnode->setVisible(true);
@@ -501,7 +491,6 @@ void Camera::wield(const InventoryItem* item, IGameDef *gamedef)
 	{
 		// Bare hands
 		m_wieldnode->setSprite(gamedef->tsrc()->getTextureRaw("wieldhand.png"));
-		m_wieldnode->setScale(v3f(40));
 		m_wieldnode->setVisible(true);
 	}
 }
@@ -536,7 +525,6 @@ ExtrudedSpriteSceneNode::ExtrudedSpriteSceneNode(
 	ISceneNode(parent, mgr, id, position, rotation, scale)
 {
 	m_meshnode = mgr->addMeshSceneNode(NULL, this, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
-	m_thickness = 0.1;
 	m_cubemesh = NULL;
 	m_is_cube = false;
 	m_light = LIGHT_MAX;
@@ -551,6 +539,8 @@ ExtrudedSpriteSceneNode::~ExtrudedSpriteSceneNode()
 
 void ExtrudedSpriteSceneNode::setSprite(video::ITexture* texture)
 {
+	const v3f sprite_scale(40.0, 40.0, 4.0); // width, height, thickness
+
 	if (texture == NULL)
 	{
 		m_meshnode->setVisible(false);
@@ -568,7 +558,9 @@ void ExtrudedSpriteSceneNode::setSprite(video::ITexture* texture)
 	else
 	{
 		// Texture was not yet extruded, do it now and save in cache
-		mesh = extrude(texture);
+		mesh = createExtrudedMesh(texture,
+				SceneManager->getVideoDriver(),
+				sprite_scale);
 		if (mesh == NULL)
 		{
 			dstream << "Warning: failed to extrude sprite" << std::endl;
@@ -580,7 +572,6 @@ void ExtrudedSpriteSceneNode::setSprite(video::ITexture* texture)
 		mesh->drop();
 	}
 
-	m_meshnode->setScale(v3f(1, 1, m_thickness));
 	m_meshnode->getMaterial(0).setTexture(0, texture);
 	m_meshnode->getMaterial(0).setFlag(video::EMF_LIGHTING, false);
 	m_meshnode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, false);
@@ -592,11 +583,14 @@ void ExtrudedSpriteSceneNode::setSprite(video::ITexture* texture)
 
 void ExtrudedSpriteSceneNode::setCube(const TileSpec tiles[6])
 {
+	const v3f cube_scale(30.0, 30.0, 30.0);
+
 	if (m_cubemesh == NULL)
-		m_cubemesh = createCubeMesh();
+	{
+		m_cubemesh = createCubeMesh(cube_scale);
+	}
 
 	m_meshnode->setMesh(m_cubemesh);
-	m_meshnode->setScale(v3f(1));
 	for (int i = 0; i < 6; ++i)
 	{
 		// Get the tile texture and atlas transformation
@@ -626,7 +620,7 @@ void ExtrudedSpriteSceneNode::updateLight(u8 light)
 	// Set brightness one lower than incoming light
 	diminish_light(li);
 	video::SColor color(255,li,li,li);
-	setMeshVerticesColor(m_meshnode->getMesh(), color);
+	setMeshColor(m_meshnode->getMesh(), color);
 }
 
 void ExtrudedSpriteSceneNode::removeSpriteFromCache(video::ITexture* texture)
@@ -637,13 +631,6 @@ void ExtrudedSpriteSceneNode::removeSpriteFromCache(video::ITexture* texture)
 		cache->removeMesh(mesh);
 }
 
-void ExtrudedSpriteSceneNode::setSpriteThickness(f32 thickness)
-{
-	m_thickness = thickness;
-	if (!m_is_cube)
-		m_meshnode->setScale(v3f(1, 1, thickness));
-}
-
 const core::aabbox3d<f32>& ExtrudedSpriteSceneNode::getBoundingBox() const
 {
 	return m_meshnode->getBoundingBox();
@@ -667,259 +654,3 @@ io::path ExtrudedSpriteSceneNode::getExtrudedName(video::ITexture* texture)
 	path.append("/[extruded]");
 	return path;
 }
-
-scene::IAnimatedMesh* ExtrudedSpriteSceneNode::extrudeARGB(u32 width, u32 height, u8* data)
-{
-	const s32 argb_wstep = 4 * width;
-	const s32 alpha_threshold = 1;
-
-	scene::IMeshBuffer* buf = new scene::SMeshBuffer();
-	video::SColor c(255,255,255,255);
-
-	// Front and back
-	{
-		video::S3DVertex vertices[8] =
-		{
-			video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
-			video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
-			video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
-			video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
-			video::S3DVertex(+0.5,-0.5,+0.5, 0,0,+1, c, 1,1),
-			video::S3DVertex(+0.5,+0.5,+0.5, 0,0,+1, c, 1,0),
-			video::S3DVertex(-0.5,+0.5,+0.5, 0,0,+1, c, 0,0),
-			video::S3DVertex(-0.5,-0.5,+0.5, 0,0,+1, c, 0,1),
-		};
-		u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
-		buf->append(vertices, 8, indices, 12);
-	}
-
-	// "Interior"
-	// (add faces where a solid pixel is next to a transparent one)
-	u8* solidity = new u8[(width+2) * (height+2)];
-	u32 wstep = width + 2;
-	for (u32 y = 0; y < height + 2; ++y)
-	{
-		u8* scanline = solidity + y * wstep;
-		if (y == 0 || y == height + 1)
-		{
-			for (u32 x = 0; x < width + 2; ++x)
-				scanline[x] = 0;
-		}
-		else
-		{
-			scanline[0] = 0;
-			u8* argb_scanline = data + (y - 1) * argb_wstep;
-			for (u32 x = 0; x < width; ++x)
-				scanline[x+1] = (argb_scanline[x*4+3] >= alpha_threshold);
-			scanline[width + 1] = 0;
-		}
-	}
-
-	// without this, there would be occasional "holes" in the mesh
-	f32 eps = 0.01;
-
-	for (u32 y = 0; y <= height; ++y)
-	{
-		u8* scanline = solidity + y * wstep + 1;
-		for (u32 x = 0; x <= width; ++x)
-		{
-			if (scanline[x] && !scanline[x + wstep])
-			{
-				u32 xx = x + 1;
-				while (scanline[xx] && !scanline[xx + wstep])
-					++xx;
-				f32 vx1 = (x - eps) / (f32) width - 0.5;
-				f32 vx2 = (xx + eps) / (f32) width - 0.5;
-				f32 vy = 0.5 - (y - eps) / (f32) height;
-				f32 tx1 = x / (f32) width;
-				f32 tx2 = xx / (f32) width;
-				f32 ty = (y - 0.5) / (f32) height;
-				video::S3DVertex vertices[8] =
-				{
-					video::S3DVertex(vx1,vy,-0.5, 0,-1,0, c, tx1,ty),
-					video::S3DVertex(vx2,vy,-0.5, 0,-1,0, c, tx2,ty),
-					video::S3DVertex(vx2,vy,+0.5, 0,-1,0, c, tx2,ty),
-					video::S3DVertex(vx1,vy,+0.5, 0,-1,0, c, tx1,ty),
-				};
-				u16 indices[6] = {0,1,2,2,3,0};
-				buf->append(vertices, 4, indices, 6);
-				x = xx - 1;
-			}
-			if (!scanline[x] && scanline[x + wstep])
-			{
-				u32 xx = x + 1;
-				while (!scanline[xx] && scanline[xx + wstep])
-					++xx;
-				f32 vx1 = (x - eps) / (f32) width - 0.5;
-				f32 vx2 = (xx + eps) / (f32) width - 0.5;
-				f32 vy = 0.5 - (y + eps) / (f32) height;
-				f32 tx1 = x / (f32) width;
-				f32 tx2 = xx / (f32) width;
-				f32 ty = (y + 0.5) / (f32) height;
-				video::S3DVertex vertices[8] =
-				{
-					video::S3DVertex(vx1,vy,-0.5, 0,1,0, c, tx1,ty),
-					video::S3DVertex(vx1,vy,+0.5, 0,1,0, c, tx1,ty),
-					video::S3DVertex(vx2,vy,+0.5, 0,1,0, c, tx2,ty),
-					video::S3DVertex(vx2,vy,-0.5, 0,1,0, c, tx2,ty),
-				};
-				u16 indices[6] = {0,1,2,2,3,0};
-				buf->append(vertices, 4, indices, 6);
-				x = xx - 1;
-			}
-		}
-	}
-
-	for (u32 x = 0; x <= width; ++x)
-	{
-		u8* scancol = solidity + x + wstep;
-		for (u32 y = 0; y <= height; ++y)
-		{
-			if (scancol[y * wstep] && !scancol[y * wstep + 1])
-			{
-				u32 yy = y + 1;
-				while (scancol[yy * wstep] && !scancol[yy * wstep + 1])
-					++yy;
-				f32 vx = (x - eps) / (f32) width - 0.5;
-				f32 vy1 = 0.5 - (y - eps) / (f32) height;
-				f32 vy2 = 0.5 - (yy + eps) / (f32) height;
-				f32 tx = (x - 0.5) / (f32) width;
-				f32 ty1 = y / (f32) height;
-				f32 ty2 = yy / (f32) height;
-				video::S3DVertex vertices[8] =
-				{
-					video::S3DVertex(vx,vy1,-0.5, 1,0,0, c, tx,ty1),
-					video::S3DVertex(vx,vy1,+0.5, 1,0,0, c, tx,ty1),
-					video::S3DVertex(vx,vy2,+0.5, 1,0,0, c, tx,ty2),
-					video::S3DVertex(vx,vy2,-0.5, 1,0,0, c, tx,ty2),
-				};
-				u16 indices[6] = {0,1,2,2,3,0};
-				buf->append(vertices, 4, indices, 6);
-				y = yy - 1;
-			}
-			if (!scancol[y * wstep] && scancol[y * wstep + 1])
-			{
-				u32 yy = y + 1;
-				while (!scancol[yy * wstep] && scancol[yy * wstep + 1])
-					++yy;
-				f32 vx = (x + eps) / (f32) width - 0.5;
-				f32 vy1 = 0.5 - (y - eps) / (f32) height;
-				f32 vy2 = 0.5 - (yy + eps) / (f32) height;
-				f32 tx = (x + 0.5) / (f32) width;
-				f32 ty1 = y / (f32) height;
-				f32 ty2 = yy / (f32) height;
-				video::S3DVertex vertices[8] =
-				{
-					video::S3DVertex(vx,vy1,-0.5, -1,0,0, c, tx,ty1),
-					video::S3DVertex(vx,vy2,-0.5, -1,0,0, c, tx,ty2),
-					video::S3DVertex(vx,vy2,+0.5, -1,0,0, c, tx,ty2),
-					video::S3DVertex(vx,vy1,+0.5, -1,0,0, c, tx,ty1),
-				};
-				u16 indices[6] = {0,1,2,2,3,0};
-				buf->append(vertices, 4, indices, 6);
-				y = yy - 1;
-			}
-		}
-	}
-
-	// Add to mesh
-	scene::SMesh* mesh = new scene::SMesh();
-	buf->recalculateBoundingBox();
-	mesh->addMeshBuffer(buf);
-	buf->drop();
-	mesh->recalculateBoundingBox();
-	scene::SAnimatedMesh* anim_mesh = new scene::SAnimatedMesh(mesh);
-	mesh->drop();
-	return anim_mesh;
-}
-
-scene::IAnimatedMesh* ExtrudedSpriteSceneNode::extrude(video::ITexture* texture)
-{
-	scene::IAnimatedMesh* mesh = NULL;
-	core::dimension2d<u32> size = texture->getSize();
-	video::ECOLOR_FORMAT format = texture->getColorFormat();
-	if (format == video::ECF_A8R8G8B8)
-	{
-		// Texture is in the correct color format, we can pass it
-		// to extrudeARGB right away.
-		void* data = texture->lock(MY_ETLM_READ_ONLY);
-		if (data == NULL)
-			return NULL;
-		mesh = extrudeARGB(size.Width, size.Height, (u8*) data);
-		texture->unlock();
-	}
-	else
-	{
-		video::IVideoDriver* driver = SceneManager->getVideoDriver();
-
-		video::IImage* img1 = driver->createImageFromData(format, size, texture->lock(MY_ETLM_READ_ONLY));
-		if (img1 == NULL)
-			return NULL;
-
-		// img1 is in the texture's color format, convert to 8-bit ARGB
-		video::IImage* img2 = driver->createImage(video::ECF_A8R8G8B8, size);
-		if (img2 != NULL)
-		{
-			img1->copyTo(img2);
-			img1->drop();
-
-			mesh = extrudeARGB(size.Width, size.Height, (u8*) img2->lock());
-			img2->unlock();
-			img2->drop();
-		}
-		img1->drop();
-	}
-	return mesh;
-}
-
-scene::IMesh* ExtrudedSpriteSceneNode::createCubeMesh()
-{
-	video::SColor c(255,255,255,255);
-	video::S3DVertex vertices[24] =
-	{
-		// Up
-		video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1),
-		video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0),
-		video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0),
-		video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1),
-		// Down
-		video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0),
-		video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0),
-		video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1),
-		video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1),
-		// Right
-		video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1),
-		video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0),
-		video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0),
-		video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1),
-		// Left
-		video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1),
-		video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1),
-		video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0),
-		video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0),
-		// Back
-		video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1),
-		video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1),
-		video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0),
-		video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0),
-		// Front
-		video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
-		video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
-		video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
-		video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
-	};
-
-	u16 indices[6] = {0,1,2,2,3,0};
-
-	scene::SMesh* mesh = new scene::SMesh();
-	for (u32 i=0; i<6; ++i)
-	{
-		scene::IMeshBuffer* buf = new scene::SMeshBuffer();
-		buf->append(vertices + 4 * i, 4, indices, 6);
-		buf->recalculateBoundingBox();
-		mesh->addMeshBuffer(buf);
-		buf->drop();
-	}
-	mesh->recalculateBoundingBox();
-	return mesh;
-}
diff --git a/src/camera.h b/src/camera.h
index 973ae3f31..d5789d807 100644
--- a/src/camera.h
+++ b/src/camera.h
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "common_irrlicht.h"
 #include "inventory.h"
+#include "mesh.h"
 #include "tile.h"
 #include "utility.h"
 #include <ICameraSceneNode.h>
@@ -206,9 +207,6 @@ class ExtrudedSpriteSceneNode: public scene::ISceneNode
 	void setSprite(video::ITexture* texture);
 	void setCube(const TileSpec tiles[6]);
 
-	f32 getSpriteThickness() const { return m_thickness; }
-	void setSpriteThickness(f32 thickness);
-
 	void updateLight(u8 light);
 
 	void removeSpriteFromCache(video::ITexture* texture);
@@ -219,16 +217,11 @@ class ExtrudedSpriteSceneNode: public scene::ISceneNode
 
 private:
 	scene::IMeshSceneNode* m_meshnode;
-	f32 m_thickness;
 	scene::IMesh* m_cubemesh;
 	bool m_is_cube;
 	u8 m_light;
 
-	// internal extrusion helper methods
 	io::path getExtrudedName(video::ITexture* texture);
-	scene::IAnimatedMesh* extrudeARGB(u32 width, u32 height, u8* data);
-	scene::IAnimatedMesh* extrude(video::ITexture* texture);
-	scene::IMesh* createCubeMesh();
 };
 
 #endif
diff --git a/src/content_cao.cpp b/src/content_cao.cpp
index b9dc91e63..da68004ce 100644
--- a/src/content_cao.cpp
+++ b/src/content_cao.cpp
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gamedef.h"
 #include "clientobject.h"
 #include "content_object.h"
+#include "mesh.h"
 #include "utility.h" // For IntervalLimiter
 class Settings;
 #include "MyBillboardSceneNode.h"
@@ -630,7 +631,7 @@ void ItemCAO::updateLight(u8 light_at_pos)
 
 	u8 li = decode_light(light_at_pos);
 	video::SColor color(255,li,li,li);
-	setMeshVerticesColor(m_node->getMesh(), color);
+	setMeshColor(m_node->getMesh(), color);
 }
 
 v3s16 ItemCAO::getLightPosition()
@@ -778,7 +779,7 @@ void RatCAO::updateLight(u8 light_at_pos)
 
 	u8 li = decode_light(light_at_pos);
 	video::SColor color(255,li,li,li);
-	setMeshVerticesColor(m_node->getMesh(), color);
+	setMeshColor(m_node->getMesh(), color);
 }
 
 v3s16 RatCAO::getLightPosition()
@@ -934,7 +935,7 @@ void Oerkki1CAO::updateLight(u8 light_at_pos)
 
 	u8 li = decode_light(light_at_pos);
 	video::SColor color(255,li,li,li);
-	setMeshVerticesColor(m_node->getMesh(), color);
+	setMeshColor(m_node->getMesh(), color);
 }
 
 v3s16 Oerkki1CAO::getLightPosition()
@@ -1165,7 +1166,7 @@ void FireflyCAO::updateLight(u8 light_at_pos)
 
 	u8 li = 255;
 	video::SColor color(255,li,li,li);
-	setMeshVerticesColor(m_node->getMesh(), color);
+	setMeshColor(m_node->getMesh(), color);
 }
 
 v3s16 FireflyCAO::getLightPosition()
@@ -1866,7 +1867,7 @@ class LuaEntityCAO : public ClientActiveObject
 		u8 li = decode_light(light_at_pos);
 		video::SColor color(255,li,li,li);
 		if(m_meshnode){
-			setMeshVerticesColor(m_meshnode->getMesh(), color);
+			setMeshColor(m_meshnode->getMesh(), color);
 			m_meshnode->setVisible(true);
 		}
 		if(m_spritenode){
@@ -2250,7 +2251,7 @@ class PlayerCAO : public ClientActiveObject
 
 		u8 li = decode_light(light_at_pos);
 		video::SColor color(255,li,li,li);
-		setMeshVerticesColor(m_node->getMesh(), color);
+		setMeshColor(m_node->getMesh(), color);
 	}
 
 	v3s16 getLightPosition()
diff --git a/src/mesh.cpp b/src/mesh.cpp
new file mode 100644
index 000000000..1d347a09f
--- /dev/null
+++ b/src/mesh.cpp
@@ -0,0 +1,362 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mesh.h"
+#include <IAnimatedMesh.h>
+#include <SAnimatedMesh.h>
+
+// In Irrlicht 1.8 the signature of ITexture::lock was changed from
+// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+#define MY_ETLM_READ_ONLY true
+#else
+#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
+#endif
+
+scene::IAnimatedMesh* createCubeMesh(v3f scale)
+{
+	video::SColor c(255,255,255,255);
+	video::S3DVertex vertices[24] =
+	{
+		// Up
+		video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1),
+		video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0),
+		video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0),
+		video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1),
+		// Down
+		video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0),
+		video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0),
+		video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1),
+		video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1),
+		// Right
+		video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1),
+		video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0),
+		video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0),
+		video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1),
+		// Left
+		video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1),
+		video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1),
+		video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0),
+		video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0),
+		// Back
+		video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1),
+		video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1),
+		video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0),
+		video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0),
+		// Front
+		video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
+		video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
+		video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
+		video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
+	};
+
+	u16 indices[6] = {0,1,2,2,3,0};
+
+	scene::SMesh *mesh = new scene::SMesh();
+	for (u32 i=0; i<6; ++i)
+	{
+		scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+		buf->append(vertices + 4 * i, 4, indices, 6);
+		mesh->addMeshBuffer(buf);
+		buf->drop();
+	}
+	scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
+	mesh->drop();
+	scaleMesh(anim_mesh, scale);  // also recalculates bounding box
+	return anim_mesh;
+}
+
+static scene::IAnimatedMesh* extrudeARGB(u32 twidth, u32 theight, u8 *data)
+{
+	const s32 argb_wstep = 4 * twidth;
+	const s32 alpha_threshold = 1;
+
+	scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+	video::SColor c(255,255,255,255);
+
+	// Front and back
+	{
+		video::S3DVertex vertices[8] =
+		{
+			video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
+			video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
+			video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
+			video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
+			video::S3DVertex(+0.5,-0.5,+0.5, 0,0,+1, c, 1,1),
+			video::S3DVertex(+0.5,+0.5,+0.5, 0,0,+1, c, 1,0),
+			video::S3DVertex(-0.5,+0.5,+0.5, 0,0,+1, c, 0,0),
+			video::S3DVertex(-0.5,-0.5,+0.5, 0,0,+1, c, 0,1),
+		};
+		u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
+		buf->append(vertices, 8, indices, 12);
+	}
+
+	// "Interior"
+	// (add faces where a solid pixel is next to a transparent one)
+	u8 *solidity = new u8[(twidth+2) * (theight+2)];
+	u32 wstep = twidth + 2;
+	for (u32 y = 0; y < theight + 2; ++y)
+	{
+		u8 *scanline = solidity + y * wstep;
+		if (y == 0 || y == theight + 1)
+		{
+			for (u32 x = 0; x < twidth + 2; ++x)
+				scanline[x] = 0;
+		}
+		else
+		{
+			scanline[0] = 0;
+			u8 *argb_scanline = data + (y - 1) * argb_wstep;
+			for (u32 x = 0; x < twidth; ++x)
+				scanline[x+1] = (argb_scanline[x*4+3] >= alpha_threshold);
+			scanline[twidth + 1] = 0;
+		}
+	}
+
+	// without this, there would be occasional "holes" in the mesh
+	f32 eps = 0.01;
+
+	for (u32 y = 0; y <= theight; ++y)
+	{
+		u8 *scanline = solidity + y * wstep + 1;
+		for (u32 x = 0; x <= twidth; ++x)
+		{
+			if (scanline[x] && !scanline[x + wstep])
+			{
+				u32 xx = x + 1;
+				while (scanline[xx] && !scanline[xx + wstep])
+					++xx;
+				f32 vx1 = (x - eps) / (f32) twidth - 0.5;
+				f32 vx2 = (xx + eps) / (f32) twidth - 0.5;
+				f32 vy = 0.5 - (y - eps) / (f32) theight;
+				f32 tx1 = x / (f32) twidth;
+				f32 tx2 = xx / (f32) twidth;
+				f32 ty = (y - 0.5) / (f32) theight;
+				video::S3DVertex vertices[8] =
+				{
+					video::S3DVertex(vx1,vy,-0.5, 0,-1,0, c, tx1,ty),
+					video::S3DVertex(vx2,vy,-0.5, 0,-1,0, c, tx2,ty),
+					video::S3DVertex(vx2,vy,+0.5, 0,-1,0, c, tx2,ty),
+					video::S3DVertex(vx1,vy,+0.5, 0,-1,0, c, tx1,ty),
+				};
+				u16 indices[6] = {0,1,2,2,3,0};
+				buf->append(vertices, 4, indices, 6);
+				x = xx - 1;
+			}
+			if (!scanline[x] && scanline[x + wstep])
+			{
+				u32 xx = x + 1;
+				while (!scanline[xx] && scanline[xx + wstep])
+					++xx;
+				f32 vx1 = (x - eps) / (f32) twidth - 0.5;
+				f32 vx2 = (xx + eps) / (f32) twidth - 0.5;
+				f32 vy = 0.5 - (y + eps) / (f32) theight;
+				f32 tx1 = x / (f32) twidth;
+				f32 tx2 = xx / (f32) twidth;
+				f32 ty = (y + 0.5) / (f32) theight;
+				video::S3DVertex vertices[8] =
+				{
+					video::S3DVertex(vx1,vy,-0.5, 0,1,0, c, tx1,ty),
+					video::S3DVertex(vx1,vy,+0.5, 0,1,0, c, tx1,ty),
+					video::S3DVertex(vx2,vy,+0.5, 0,1,0, c, tx2,ty),
+					video::S3DVertex(vx2,vy,-0.5, 0,1,0, c, tx2,ty),
+				};
+				u16 indices[6] = {0,1,2,2,3,0};
+				buf->append(vertices, 4, indices, 6);
+				x = xx - 1;
+			}
+		}
+	}
+
+	for (u32 x = 0; x <= twidth; ++x)
+	{
+		u8 *scancol = solidity + x + wstep;
+		for (u32 y = 0; y <= theight; ++y)
+		{
+			if (scancol[y * wstep] && !scancol[y * wstep + 1])
+			{
+				u32 yy = y + 1;
+				while (scancol[yy * wstep] && !scancol[yy * wstep + 1])
+					++yy;
+				f32 vx = (x - eps) / (f32) twidth - 0.5;
+				f32 vy1 = 0.5 - (y - eps) / (f32) theight;
+				f32 vy2 = 0.5 - (yy + eps) / (f32) theight;
+				f32 tx = (x - 0.5) / (f32) twidth;
+				f32 ty1 = y / (f32) theight;
+				f32 ty2 = yy / (f32) theight;
+				video::S3DVertex vertices[8] =
+				{
+					video::S3DVertex(vx,vy1,-0.5, 1,0,0, c, tx,ty1),
+					video::S3DVertex(vx,vy1,+0.5, 1,0,0, c, tx,ty1),
+					video::S3DVertex(vx,vy2,+0.5, 1,0,0, c, tx,ty2),
+					video::S3DVertex(vx,vy2,-0.5, 1,0,0, c, tx,ty2),
+				};
+				u16 indices[6] = {0,1,2,2,3,0};
+				buf->append(vertices, 4, indices, 6);
+				y = yy - 1;
+			}
+			if (!scancol[y * wstep] && scancol[y * wstep + 1])
+			{
+				u32 yy = y + 1;
+				while (!scancol[yy * wstep] && scancol[yy * wstep + 1])
+					++yy;
+				f32 vx = (x + eps) / (f32) twidth - 0.5;
+				f32 vy1 = 0.5 - (y - eps) / (f32) theight;
+				f32 vy2 = 0.5 - (yy + eps) / (f32) theight;
+				f32 tx = (x + 0.5) / (f32) twidth;
+				f32 ty1 = y / (f32) theight;
+				f32 ty2 = yy / (f32) theight;
+				video::S3DVertex vertices[8] =
+				{
+					video::S3DVertex(vx,vy1,-0.5, -1,0,0, c, tx,ty1),
+					video::S3DVertex(vx,vy2,-0.5, -1,0,0, c, tx,ty2),
+					video::S3DVertex(vx,vy2,+0.5, -1,0,0, c, tx,ty2),
+					video::S3DVertex(vx,vy1,+0.5, -1,0,0, c, tx,ty1),
+				};
+				u16 indices[6] = {0,1,2,2,3,0};
+				buf->append(vertices, 4, indices, 6);
+				y = yy - 1;
+			}
+		}
+	}
+
+	// Add to mesh
+	scene::SMesh *mesh = new scene::SMesh();
+	mesh->addMeshBuffer(buf);
+	buf->drop();
+	scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
+	mesh->drop();
+	return anim_mesh;
+}
+
+scene::IAnimatedMesh* createExtrudedMesh(video::ITexture *texture,
+		video::IVideoDriver *driver, v3f scale)
+{
+	scene::IAnimatedMesh *mesh = NULL;
+	core::dimension2d<u32> size = texture->getSize();
+	video::ECOLOR_FORMAT format = texture->getColorFormat();
+	if (format == video::ECF_A8R8G8B8)
+	{
+		// Texture is in the correct color format, we can pass it
+		// to extrudeARGB right away.
+		void *data = texture->lock(MY_ETLM_READ_ONLY);
+		if (data == NULL)
+			return NULL;
+		mesh = extrudeARGB(size.Width, size.Height, (u8*) data);
+		texture->unlock();
+	}
+	else
+	{
+		video::IImage *img1 = driver->createImageFromData(format, size, texture->lock(MY_ETLM_READ_ONLY));
+		if (img1 == NULL)
+			return NULL;
+
+		// img1 is in the texture's color format, convert to 8-bit ARGB
+		video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, size);
+		if (img2 != NULL)
+		{
+			img1->copyTo(img2);
+			img1->drop();
+
+			mesh = extrudeARGB(size.Width, size.Height, (u8*) img2->lock());
+			img2->unlock();
+			img2->drop();
+		}
+		img1->drop();
+	}
+	scaleMesh(mesh, scale);  // also recalculates bounding box
+	return mesh;
+}
+
+void scaleMesh(scene::IMesh *mesh, v3f scale)
+{
+	if(mesh == NULL)
+		return;
+
+	core::aabbox3d<f32> bbox;
+	bbox.reset(0,0,0);
+
+	u16 mc = mesh->getMeshBufferCount();
+	for(u16 j=0; j<mc; j++)
+	{
+		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
+		u16 vc = buf->getVertexCount();
+		for(u16 i=0; i<vc; i++)
+		{
+			vertices[i].Pos *= scale;
+		}
+		buf->recalculateBoundingBox();
+
+		// calculate total bounding box
+		if(j == 0)
+			bbox = buf->getBoundingBox();
+		else
+			bbox.addInternalBox(buf->getBoundingBox());
+	}
+	mesh->setBoundingBox(bbox);
+}
+
+void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
+{
+	if(mesh == NULL)
+		return;
+	
+	u16 mc = mesh->getMeshBufferCount();
+	for(u16 j=0; j<mc; j++)
+	{
+		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
+		u16 vc = buf->getVertexCount();
+		for(u16 i=0; i<vc; i++)
+		{
+			vertices[i].Color = color;
+		}
+	}
+}
+
+void setMeshColorByNormalXYZ(scene::IMesh *mesh,
+		const video::SColor &colorX,
+		const video::SColor &colorY,
+		const video::SColor &colorZ)
+{
+	if(mesh == NULL)
+		return;
+	
+	u16 mc = mesh->getMeshBufferCount();
+	for(u16 j=0; j<mc; j++)
+	{
+		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
+		u16 vc = buf->getVertexCount();
+		for(u16 i=0; i<vc; i++)
+		{
+			f32 x = fabs(vertices[i].Normal.X);
+			f32 y = fabs(vertices[i].Normal.Y);
+			f32 z = fabs(vertices[i].Normal.Z);
+			if(x >= y && x >= z)
+				vertices[i].Color = colorX;
+			else if(y >= z)
+				vertices[i].Color = colorY;
+			else
+				vertices[i].Color = colorZ;
+
+		}
+	}
+}
diff --git a/src/mesh.h b/src/mesh.h
new file mode 100644
index 000000000..33e072927
--- /dev/null
+++ b/src/mesh.h
@@ -0,0 +1,66 @@
+/*
+Minetest-c55
+Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MESH_HEADER
+#define MESH_HEADER
+
+#include "common_irrlicht.h"
+
+/*
+	Create a new cube mesh.
+	Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2).
+
+	The resulting mesh has 6 materials (up, down, right, left, back, front)
+	which must be defined by the caller.
+*/
+scene::IAnimatedMesh* createCubeMesh(v3f scale);
+
+/*
+	Create a new extruded mesh from a texture.
+	Maximum bounding box is (+-scale.X/2, +-scale.Y/2, +-scale.Z).
+	Thickness is in Z direction.
+
+	The resulting mesh has 1 material which must be defined by the caller.
+*/
+scene::IAnimatedMesh* createExtrudedMesh(video::ITexture *texture,
+		video::IVideoDriver *driver, v3f scale);
+
+/*
+	Multiplies each vertex coordinate by the specified scaling factors
+	(componentwise vector multiplication).
+*/
+void scaleMesh(scene::IMesh *mesh, v3f scale);
+
+/*
+	Set a constant color for all vertices in the mesh
+*/
+void setMeshColor(scene::IMesh *mesh, const video::SColor &color);
+
+/*
+	Set the color of all vertices in the mesh.
+	For each vertex, determine the largest absolute entry in
+	the normal vector, and choose one of colorX, colorY or
+	colorZ accordingly.
+*/
+void setMeshColorByNormalXYZ(scene::IMesh *mesh,
+		const video::SColor &colorX,
+		const video::SColor &colorY,
+		const video::SColor &colorZ);
+
+#endif
diff --git a/src/tile.cpp b/src/tile.cpp
index 27454c321..89f345197 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "utility.h"
 #include "settings.h"
+#include "mesh.h"
 #include <ICameraSceneNode.h>
 #include "log.h"
 #include "mapnode.h" // For texture atlas making
@@ -1468,11 +1469,14 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 			assert(img_top && img_left && img_right);
 
 			// Create textures from images
-			// TODO: Use them all
 			video::ITexture *texture_top = driver->addTexture(
 					(imagename_top + "__temp__").c_str(), img_top);
-			assert(texture_top);
-			
+			video::ITexture *texture_left = driver->addTexture(
+					(imagename_left + "__temp__").c_str(), img_left);
+			video::ITexture *texture_right = driver->addTexture(
+					(imagename_right + "__temp__").c_str(), img_right);
+			assert(texture_top && texture_left && texture_right);
+
 			// Drop images
 			img_top->drop();
 			img_left->drop();
@@ -1499,17 +1503,24 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 				Create scene:
 				- An unit cube is centered at 0,0,0
 				- Camera looks at cube from Y+, Z- towards Y-, Z+
-				NOTE: Cube has to be changed to something else because
-				the textures cannot be set individually (or can they?)
 			*/
 
-			scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
-					v3f(0,0,0), v3f(0, 45, 0));
+			scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
+			setMeshColor(cube, video::SColor(255, 255, 255, 255));
+
+			scene::IMeshSceneNode* cubenode = smgr->addMeshSceneNode(cube, NULL, -1, v3f(0,0,0), v3f(0,45,0), v3f(1,1,1), true);
+			cube->drop();
+
 			// Set texture of cube
-			cube->setMaterialTexture(0, texture_top);
-			//cube->setMaterialFlag(video::EMF_LIGHTING, false);
-			cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
-			cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
+			cubenode->getMaterial(0).setTexture(0, texture_top);
+			cubenode->getMaterial(1).setTexture(0, texture_top);
+			cubenode->getMaterial(2).setTexture(0, texture_right);
+			cubenode->getMaterial(3).setTexture(0, texture_right);
+			cubenode->getMaterial(4).setTexture(0, texture_left);
+			cubenode->getMaterial(5).setTexture(0, texture_left);
+			cubenode->setMaterialFlag(video::EMF_LIGHTING, true);
+			cubenode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
+			cubenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, true);
 
 			scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
 					v3f(0, 1.0, -1.5), v3f(0, 0, 0));
@@ -1519,7 +1530,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 			camera->setProjectionMatrix(pm, true);
 
 			/*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
-					v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
+					v3f(-50, 100, -75), video::SColorf(0.5,0.5,0.5), 1000);
 
 			smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
 
@@ -1540,8 +1551,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
 			driver->setRenderTarget(0, true, true, 0);
 
 			// Free textures of images
-			// TODO: When all are used, free them all
 			driver->removeTexture(texture_top);
+			driver->removeTexture(texture_left);
+			driver->removeTexture(texture_right);
 			
 			// Create image of render target
 			video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
diff --git a/src/utility.cpp b/src/utility.cpp
index 87c81cb2a..4e9f307d8 100644
--- a/src/utility.cpp
+++ b/src/utility.cpp
@@ -172,27 +172,6 @@ int myrand_range(int min, int max)
 	return (myrand()%(max-min+1))+min;
 }
 
-#ifndef SERVER
-// Sets the color of all vertices in the mesh
-void setMeshVerticesColor(scene::IMesh* mesh, video::SColor& color)
-{
-	if(mesh == NULL)
-		return;
-	
-	u16 mc = mesh->getMeshBufferCount();
-	for(u16 j=0; j<mc; j++)
-	{
-		scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-		video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
-		u16 vc = buf->getVertexCount();
-		for(u16 i=0; i<vc; i++)
-		{
-			vertices[i].Color = color;
-		}
-	}
-}
-#endif
-
 /*
 	blockpos: position of block in block coordinates
 	camera_pos: position of camera in nodes
diff --git a/src/utility.h b/src/utility.h
index 47696cbf8..14b49772b 100644
--- a/src/utility.h
+++ b/src/utility.h
@@ -694,11 +694,6 @@ class TimeTaker
 	u32 *m_result;
 };
 
-#ifndef SERVER
-// Sets the color of all vertices in the mesh
-void setMeshVerticesColor(scene::IMesh* mesh, video::SColor& color);
-#endif
-
 // Calculates the borders of a "d-radius" cube
 inline void getFacePositions(core::list<v3s16> &list, u16 d)
 {
-- 
GitLab