diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua
index 64dcbbdf..cb424cb5 100644
--- a/games/minimal/mods/default/init.lua
+++ b/games/minimal/mods/default/init.lua
@@ -926,7 +926,7 @@ minetest.register_node("default:rail", {
 	walkable = false,
 	selection_box = {
 		type = "fixed",
-		--fixed = <default>
+		fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
 	},
 	groups = {bendy=2,snappy=1,dig_immediate=2},
 })
diff --git a/games/minimal/mods/stairs/depends.txt b/games/minimal/mods/stairs/depends.txt
new file mode 100644
index 00000000..4ad96d51
--- /dev/null
+++ b/games/minimal/mods/stairs/depends.txt
@@ -0,0 +1 @@
+default
diff --git a/games/minimal/mods/stairs/init.lua b/games/minimal/mods/stairs/init.lua
new file mode 100644
index 00000000..4929d137
--- /dev/null
+++ b/games/minimal/mods/stairs/init.lua
@@ -0,0 +1,93 @@
+stairs = {}
+
+-- Node will be called stairs:stair_<subname>
+function stairs.register_stair(subname, recipeitem, groups, images, description)
+	minetest.register_node("stairs:stair_" .. subname, {
+		description = description,
+		drawtype = "nodebox",
+		tile_images = images,
+		paramtype = "light",
+		paramtype2 = "facedir",
+		is_ground_content = true,
+		groups = groups,
+		node_box = {
+			type = "fixed",
+			fixed = {
+				{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+				{-0.5, 0, 0, 0.5, 0.5, 0.5},
+			},
+		},
+	})
+
+	minetest.register_craft({
+		output = 'stairs:stair_' .. subname .. ' 4',
+		recipe = {
+			{recipeitem, "", ""},
+			{recipeitem, recipeitem, ""},
+			{recipeitem, recipeitem, recipeitem},
+		},
+	})
+end
+
+-- Node will be called stairs:slab_<subname>
+function stairs.register_slab(subname, recipeitem, groups, images, description)
+	minetest.register_node("stairs:slab_" .. subname, {
+		description = description,
+		drawtype = "nodebox",
+		tile_images = images,
+		paramtype = "light",
+		is_ground_content = true,
+		groups = groups,
+		node_box = {
+			type = "fixed",
+			fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+		},
+		selection_box = {
+			type = "fixed",
+			fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+		},
+	})
+
+	minetest.register_craft({
+		output = 'stairs:slab_' .. subname .. ' 3',
+		recipe = {
+			{recipeitem, recipeitem, recipeitem},
+		},
+	})
+end
+
+-- Nodes will be called stairs:{stair,slab}_<subname>
+function stairs.register_stair_and_slab(subname, recipeitem, groups, images, desc_stair, desc_slab)
+	stairs.register_stair(subname, recipeitem, groups, images, desc_stair)
+	stairs.register_slab(subname, recipeitem, groups, images, desc_slab)
+end
+
+stairs.register_stair_and_slab("wood", "default:wood",
+		{snappy=2,choppy=2,oddly_breakable_by_hand=2},
+		{"default_wood.png"},
+		"Wooden stair",
+		"Wooden slab")
+
+stairs.register_stair_and_slab("stone", "default:stone",
+		{cracky=3},
+		{"default_stone.png"},
+		"Stone stair",
+		"Stone slab")
+
+stairs.register_stair_and_slab("cobble", "default:cobble",
+		{cracky=3},
+		{"default_cobble.png"},
+		"Cobble stair",
+		"Cobble slab")
+
+stairs.register_stair_and_slab("brick", "default:brick",
+		{cracky=3},
+		{"default_brick.png"},
+		"Brick stair",
+		"Brick slab")
+
+stairs.register_stair_and_slab("sandstone", "default:sandstone",
+		{crumbly=2,cracky=2},
+		{"default_sandstone.png"},
+		"Sandstone stair",
+		"Sandstone slab")
diff --git a/src/camera.cpp b/src/camera.cpp
index 1ef594ee..32dd85e5 100644
--- a/src/camera.cpp
+++ b/src/camera.cpp
@@ -213,8 +213,22 @@ void Camera::step(f32 dtime)
 void Camera::update(LocalPlayer* player, f32 frametime, v2u32 screensize,
 		f32 tool_reload_ratio)
 {
+	// Get player position
+	// Smooth the movement when walking up stairs
+	v3f old_player_position = m_playernode->getPosition();
+	v3f player_position = player->getPosition();
+	//if(player->touching_ground && player_position.Y > old_player_position.Y)
+	if(player->touching_ground &&
+			player_position.Y > old_player_position.Y)
+	{
+		f32 oldy = old_player_position.Y;
+		f32 newy = player_position.Y;
+		f32 t = exp(-23*frametime);
+		player_position.Y = oldy * t + newy * (1-t);
+	}
+
 	// Set player node transformation
-	m_playernode->setPosition(player->getPosition());
+	m_playernode->setPosition(player_position);
 	m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0));
 	m_playernode->updateAbsolutePosition();
 
diff --git a/src/collision.cpp b/src/collision.cpp
index f8db42d2..09a7df7c 100644
--- a/src/collision.cpp
+++ b/src/collision.cpp
@@ -22,32 +22,249 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "map.h"
 #include "nodedef.h"
 #include "gamedef.h"
+#include "log.h"
+#include <vector>
+#include "util/timetaker.h"
+#include "main.h" // g_profiler
+#include "profiler.h"
+
+// Helper function:
+// Checks for collision of a moving aabbox with a static aabbox
+// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
+// The time after which the collision occurs is stored in dtime.
+int axisAlignedCollision(
+		const aabb3f &staticbox, const aabb3f &movingbox,
+		const v3f &speed, f32 d, f32 &dtime)
+{
+	//TimeTaker tt("axisAlignedCollision");
+
+	f32 xsize = (staticbox.MaxEdge.X - staticbox.MinEdge.X);
+	f32 ysize = (staticbox.MaxEdge.Y - staticbox.MinEdge.Y);
+	f32 zsize = (staticbox.MaxEdge.Z - staticbox.MinEdge.Z);
+
+	aabb3f relbox(
+			movingbox.MinEdge.X - staticbox.MinEdge.X,
+			movingbox.MinEdge.Y - staticbox.MinEdge.Y,
+			movingbox.MinEdge.Z - staticbox.MinEdge.Z,
+			movingbox.MaxEdge.X - staticbox.MinEdge.X,
+			movingbox.MaxEdge.Y - staticbox.MinEdge.Y,
+			movingbox.MaxEdge.Z - staticbox.MinEdge.Z
+	);
+
+	if(speed.X > 0) // Check for collision with X- plane
+	{
+		if(relbox.MaxEdge.X <= d)
+		{
+			dtime = - relbox.MaxEdge.X / speed.X;
+			if((relbox.MinEdge.Y + speed.Y * dtime < ysize) &&
+					(relbox.MaxEdge.Y + speed.Y * dtime > 0) &&
+					(relbox.MinEdge.Z + speed.Z * dtime < zsize) &&
+					(relbox.MaxEdge.Z + speed.Z * dtime > 0))
+				return 0;
+		}
+		else if(relbox.MinEdge.X > xsize)
+		{
+			return -1;
+		}
+	}
+	else if(speed.X < 0) // Check for collision with X+ plane
+	{
+		if(relbox.MinEdge.X >= xsize - d)
+		{
+			dtime = (xsize - relbox.MinEdge.X) / speed.X;
+			if((relbox.MinEdge.Y + speed.Y * dtime < ysize) &&
+					(relbox.MaxEdge.Y + speed.Y * dtime > 0) &&
+					(relbox.MinEdge.Z + speed.Z * dtime < zsize) &&
+					(relbox.MaxEdge.Z + speed.Z * dtime > 0))
+				return 0;
+		}
+		else if(relbox.MaxEdge.X < 0)
+		{
+			return -1;
+		}
+	}
+
+	// NO else if here
+
+	if(speed.Y > 0) // Check for collision with Y- plane
+	{
+		if(relbox.MaxEdge.Y <= d)
+		{
+			dtime = - relbox.MaxEdge.Y / speed.Y;
+			if((relbox.MinEdge.X + speed.X * dtime < xsize) &&
+					(relbox.MaxEdge.X + speed.X * dtime > 0) &&
+					(relbox.MinEdge.Z + speed.Z * dtime < zsize) &&
+					(relbox.MaxEdge.Z + speed.Z * dtime > 0))
+				return 1;
+		}
+		else if(relbox.MinEdge.Y > ysize)
+		{
+			return -1;
+		}
+	}
+	else if(speed.Y < 0) // Check for collision with Y+ plane
+	{
+		if(relbox.MinEdge.Y >= ysize - d)
+		{
+			dtime = (ysize - relbox.MinEdge.Y) / speed.Y;
+			if((relbox.MinEdge.X + speed.X * dtime < xsize) &&
+					(relbox.MaxEdge.X + speed.X * dtime > 0) &&
+					(relbox.MinEdge.Z + speed.Z * dtime < zsize) &&
+					(relbox.MaxEdge.Z + speed.Z * dtime > 0))
+				return 1;
+		}
+		else if(relbox.MaxEdge.Y < 0)
+		{
+			return -1;
+		}
+	}
+
+	// NO else if here
+
+	if(speed.Z > 0) // Check for collision with Z- plane
+	{
+		if(relbox.MaxEdge.Z <= d)
+		{
+			dtime = - relbox.MaxEdge.Z / speed.Z;
+			if((relbox.MinEdge.X + speed.X * dtime < xsize) &&
+					(relbox.MaxEdge.X + speed.X * dtime > 0) &&
+					(relbox.MinEdge.Y + speed.Y * dtime < ysize) &&
+					(relbox.MaxEdge.Y + speed.Y * dtime > 0))
+				return 2;
+		}
+		//else if(relbox.MinEdge.Z > zsize)
+		//{
+		//	return -1;
+		//}
+	}
+	else if(speed.Z < 0) // Check for collision with Z+ plane
+	{
+		if(relbox.MinEdge.Z >= zsize - d)
+		{
+			dtime = (zsize - relbox.MinEdge.Z) / speed.Z;
+			if((relbox.MinEdge.X + speed.X * dtime < xsize) &&
+					(relbox.MaxEdge.X + speed.X * dtime > 0) &&
+					(relbox.MinEdge.Y + speed.Y * dtime < ysize) &&
+					(relbox.MaxEdge.Y + speed.Y * dtime > 0))
+				return 2;
+		}
+		//else if(relbox.MaxEdge.Z < 0)
+		//{
+		//	return -1;
+		//}
+	}
+
+	return -1;
+}
+
+// Helper function:
+// Checks if moving the movingbox up by the given distance would hit a ceiling.
+bool wouldCollideWithCeiling(
+		const std::vector<aabb3f> &staticboxes,
+		const aabb3f &movingbox,
+		f32 y_increase, f32 d)
+{
+	//TimeTaker tt("wouldCollideWithCeiling");
+
+	assert(y_increase >= 0);
+
+	for(std::vector<aabb3f>::const_iterator
+			i = staticboxes.begin();
+			i != staticboxes.end(); i++)
+	{
+		const aabb3f& staticbox = *i;
+		if((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) &&
+				(movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) &&
+				(movingbox.MinEdge.X < staticbox.MaxEdge.X) &&
+				(movingbox.MaxEdge.X > staticbox.MinEdge.X) &&
+				(movingbox.MinEdge.Z < staticbox.MaxEdge.Z) &&
+				(movingbox.MaxEdge.Z > staticbox.MinEdge.Z))
+			return true;
+	}
+
+	return false;
+}
+
 
 collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
-		f32 pos_max_d, const core::aabbox3d<f32> &box_0,
-		f32 dtime, v3f &pos_f, v3f &speed_f)
+		f32 pos_max_d, const aabb3f &box_0,
+		f32 stepheight, f32 dtime,
+		v3f &pos_f, v3f &speed_f, v3f &accel_f)
 {
+	//TimeTaker tt("collisionMoveSimple");
+    ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);
+
 	collisionMoveResult result;
 
-	// If there is no speed, there are no collisions
+	/*
+		Calculate new velocity
+	*/
+	speed_f += accel_f * dtime;
+
+    // If there is no speed, there are no collisions
 	if(speed_f.getLength() == 0)
 		return result;
 
-	v3f oldpos_f = pos_f;
-	v3s16 oldpos_i = floatToInt(oldpos_f, BS);
-
 	/*
-		Calculate new position
+		Collect node boxes in movement range
 	*/
-	pos_f += speed_f * dtime;
+	std::vector<aabb3f> cboxes;
+	std::vector<bool> is_unloaded;
+	std::vector<bool> is_step_up;
+	{
+	//TimeTaker tt2("collisionMoveSimple collect boxes");
+    ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG);
+
+	v3s16 oldpos_i = floatToInt(pos_f, BS);
+	v3s16 newpos_i = floatToInt(pos_f + speed_f * dtime, BS);
+	s16 min_x = MYMIN(oldpos_i.X, newpos_i.X) + (box_0.MinEdge.X / BS) - 1;
+	s16 min_y = MYMIN(oldpos_i.Y, newpos_i.Y) + (box_0.MinEdge.Y / BS) - 1;
+	s16 min_z = MYMIN(oldpos_i.Z, newpos_i.Z) + (box_0.MinEdge.Z / BS) - 1;
+	s16 max_x = MYMAX(oldpos_i.X, newpos_i.X) + (box_0.MaxEdge.X / BS) + 1;
+	s16 max_y = MYMAX(oldpos_i.Y, newpos_i.Y) + (box_0.MaxEdge.Y / BS) + 1;
+	s16 max_z = MYMAX(oldpos_i.Z, newpos_i.Z) + (box_0.MaxEdge.Z / BS) + 1;
+
+	for(s16 x = min_x; x <= max_x; x++)
+	for(s16 y = min_y; y <= max_y; y++)
+	for(s16 z = min_z; z <= max_z; z++)
+	{
+		try{
+			// Object collides into walkable nodes
+			MapNode n = map->getNode(v3s16(x,y,z));
+			if(gamedef->getNodeDefManager()->get(n).walkable == false)
+				continue;
+
+			std::vector<aabb3f> nodeboxes = n.getNodeBoxes(gamedef->ndef());
+			for(std::vector<aabb3f>::iterator
+					i = nodeboxes.begin();
+					i != nodeboxes.end(); i++)
+			{
+				aabb3f box = *i;
+				box.MinEdge += v3f(x, y, z)*BS;
+				box.MaxEdge += v3f(x, y, z)*BS;
+				cboxes.push_back(box);
+				is_unloaded.push_back(false);
+				is_step_up.push_back(false);
+			}
+		}
+		catch(InvalidPositionException &e)
+		{
+			// Collide with unloaded nodes
+			aabb3f box = getNodeBox(v3s16(x,y,z), BS);
+			cboxes.push_back(box);
+			is_unloaded.push_back(true);
+			is_step_up.push_back(false);
+		}
+	}
+	} // tt2
+
+	assert(cboxes.size() == is_unloaded.size());
+	assert(cboxes.size() == is_step_up.size());
 
 	/*
 		Collision detection
 	*/
-	
-	// position in nodes
-	v3s16 pos_i = floatToInt(pos_f, BS);
-	
+
 	/*
 		Collision uncertainty radius
 		Make it a bit larger than the maximum distance of movement
@@ -58,49 +275,129 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
 
 	// This should always apply, otherwise there are glitches
 	assert(d > pos_max_d);
-	
-	/*
-		Calculate collision box
-	*/
-	core::aabbox3d<f32> box = box_0;
-	box.MaxEdge += pos_f;
-	box.MinEdge += pos_f;
-	core::aabbox3d<f32> oldbox = box_0;
-	oldbox.MaxEdge += oldpos_f;
-	oldbox.MinEdge += oldpos_f;
 
-	/*
-		If the object lies on a walkable node, this is set to true.
-	*/
-	result.touching_ground = false;
-	
-	/*
-		Go through every node around the object
-	*/
-	s16 min_x = (box_0.MinEdge.X / BS) - 2;
-	s16 min_y = (box_0.MinEdge.Y / BS) - 2;
-	s16 min_z = (box_0.MinEdge.Z / BS) - 2;
-	s16 max_x = (box_0.MaxEdge.X / BS) + 1;
-	s16 max_y = (box_0.MaxEdge.Y / BS) + 1;
-	s16 max_z = (box_0.MaxEdge.Z / BS) + 1;
-	for(s16 y = oldpos_i.Y + min_y; y <= oldpos_i.Y + max_y; y++)
-	for(s16 z = oldpos_i.Z + min_z; z <= oldpos_i.Z + max_z; z++)
-	for(s16 x = oldpos_i.X + min_x; x <= oldpos_i.X + max_x; x++)
+	int loopcount = 0;
+
+	while(dtime > BS*1e-10)
 	{
-		try{
-			// Object collides into walkable nodes
-			MapNode n = map->getNode(v3s16(x,y,z));
-			if(gamedef->getNodeDefManager()->get(n).walkable == false)
-				continue;
-		}
-		catch(InvalidPositionException &e)
+		//TimeTaker tt3("collisionMoveSimple dtime loop");
+        ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);
+
+		// Avoid infinite loop
+		loopcount++;
+		if(loopcount >= 100)
 		{
-			// Doing nothing here will block the object from
-			// walking over map borders
+			infostream<<"collisionMoveSimple: WARNING: Loop count exceeded, aborting to avoid infiniite loop"<<std::endl;
+			dtime = 0;
+			break;
 		}
 
-		core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
-		
+		aabb3f movingbox = box_0;
+		movingbox.MinEdge += pos_f;
+		movingbox.MaxEdge += pos_f;
+
+		int nearest_collided = -1;
+		f32 nearest_dtime = dtime;
+		u32 nearest_boxindex = -1;
+
+		/*
+			Go through every nodebox, find nearest collision
+		*/
+		for(u32 boxindex = 0; boxindex < cboxes.size(); boxindex++)
+		{
+			// Ignore if already stepped up this nodebox.
+			if(is_step_up[boxindex])
+				continue;
+
+			// Find nearest collision of the two boxes (raytracing-like)
+			f32 dtime_tmp;
+			int collided = axisAlignedCollision(
+					cboxes[boxindex], movingbox, speed_f, d, dtime_tmp);
+
+			if(collided == -1 || dtime_tmp >= nearest_dtime)
+				continue;
+
+			nearest_dtime = dtime_tmp;
+			nearest_collided = collided;
+			nearest_boxindex = boxindex;
+		}
+
+		if(nearest_collided == -1)
+		{
+			// No collision with any collision box.
+			pos_f += speed_f * dtime;
+			dtime = 0;  // Set to 0 to avoid "infinite" loop due to small FP numbers
+		}
+		else
+		{
+			// Otherwise, a collision occurred.
+
+			const aabb3f& cbox = cboxes[nearest_boxindex];
+
+			// Check for stairs.
+			bool step_up = (nearest_collided != 1) && // must not be Y direction
+					(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
+					(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
+					(!wouldCollideWithCeiling(cboxes, movingbox,
+							cbox.MaxEdge.Y - movingbox.MinEdge.Y,
+							d));
+
+			// Move to the point of collision and reduce dtime by nearest_dtime
+			if(nearest_dtime < 0)
+			{
+				// Handle negative nearest_dtime (can be caused by the d allowance)
+				if(!step_up)
+				{
+					if(nearest_collided == 0)
+						pos_f.X += speed_f.X * nearest_dtime;
+					if(nearest_collided == 1)
+						pos_f.Y += speed_f.Y * nearest_dtime;
+					if(nearest_collided == 2)
+						pos_f.Z += speed_f.Z * nearest_dtime;
+				}
+			}
+			else
+			{
+				pos_f += speed_f * nearest_dtime;
+				dtime -= nearest_dtime;
+			}
+
+			// Set the speed component that caused the collision to zero
+			if(step_up)
+			{
+				// Special case: Handle stairs
+				is_step_up[nearest_boxindex] = true;
+			}
+			else if(nearest_collided == 0) // X
+			{
+				speed_f.X = 0;
+				result.collides = true;
+				result.collides_xz = true;
+			}
+			else if(nearest_collided == 1) // Y
+			{
+				speed_f.Y = 0;
+				result.collides = true;
+			}
+			else if(nearest_collided == 2) // Z
+			{
+				speed_f.Z = 0;
+				result.collides = true;
+				result.collides_xz = true;
+			}
+		}
+	}
+
+	/*
+		Final touches: Check if standing on ground, step up stairs.
+	*/
+	aabb3f box = box_0;
+	box.MinEdge += pos_f;
+	box.MaxEdge += pos_f;
+	for(u32 boxindex = 0; boxindex < cboxes.size(); boxindex++)
+	{
+		const aabb3f& cbox = cboxes[boxindex];
+
 		/*
 			See if the object is touching ground.
 
@@ -111,112 +408,50 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
 			Use 0.15*BS so that it is easier to get on a node.
 		*/
 		if(
-				//fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < d
-				fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS
-				&& nodebox.MaxEdge.X-d > box.MinEdge.X
-				&& nodebox.MinEdge.X+d < box.MaxEdge.X
-				&& nodebox.MaxEdge.Z-d > box.MinEdge.Z
-				&& nodebox.MinEdge.Z+d < box.MaxEdge.Z
+				cbox.MaxEdge.X-d > box.MinEdge.X &&
+				cbox.MinEdge.X+d < box.MaxEdge.X &&
+				cbox.MaxEdge.Z-d > box.MinEdge.Z &&
+				cbox.MinEdge.Z+d < box.MaxEdge.Z
 		){
-			result.touching_ground = true;
-		}
-		
-		// If object doesn't intersect with node, ignore node.
-		if(box.intersectsWithBox(nodebox) == false)
-			continue;
-		
-		/*
-			Go through every axis
-		*/
-		v3f dirs[3] = {
-			v3f(0,0,1), // back-front
-			v3f(0,1,0), // top-bottom
-			v3f(1,0,0), // right-left
-		};
-		for(u16 i=0; i<3; i++)
-		{
-			/*
-				Calculate values along the axis
-			*/
-			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
-			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
-			f32 objectmax = box.MaxEdge.dotProduct(dirs[i]);
-			f32 objectmin = box.MinEdge.dotProduct(dirs[i]);
-			f32 objectmax_old = oldbox.MaxEdge.dotProduct(dirs[i]);
-			f32 objectmin_old = oldbox.MinEdge.dotProduct(dirs[i]);
-			
-			/*
-				Check collision for the axis.
-				Collision happens when object is going through a surface.
-			*/
-			bool negative_axis_collides =
-				(nodemax > objectmin && nodemax <= objectmin_old + d
-					&& speed_f.dotProduct(dirs[i]) < 0);
-			bool positive_axis_collides =
-				(nodemin < objectmax && nodemin >= objectmax_old - d
-					&& speed_f.dotProduct(dirs[i]) > 0);
-			bool main_axis_collides =
-					negative_axis_collides || positive_axis_collides;
-			
-			/*
-				Check overlap of object and node in other axes
-			*/
-			bool other_axes_overlap = true;
-			for(u16 j=0; j<3; j++)
+			if(is_step_up[boxindex])
 			{
-				if(j == i)
-					continue;
-				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
-				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
-				f32 objectmax = box.MaxEdge.dotProduct(dirs[j]);
-				f32 objectmin = box.MinEdge.dotProduct(dirs[j]);
-				if(!(nodemax - d > objectmin && nodemin + d < objectmax))
-				{
-					other_axes_overlap = false;
-					break;
-				}
+				pos_f.Y += (cbox.MaxEdge.Y - box.MinEdge.Y);
+				box = box_0;
+				box.MinEdge += pos_f;
+				box.MaxEdge += pos_f;
 			}
-			
-			/*
-				If this is a collision, revert the pos_f in the main
-				direction.
-			*/
-			if(other_axes_overlap && main_axis_collides)
+			if(fabs(cbox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS)
 			{
-				speed_f -= speed_f.dotProduct(dirs[i]) * dirs[i];
-				pos_f -= pos_f.dotProduct(dirs[i]) * dirs[i];
-				pos_f += oldpos_f.dotProduct(dirs[i]) * dirs[i];
-				result.collides = true;
+				result.touching_ground = true;
+				if(is_unloaded[boxindex])
+					result.standing_on_unloaded = true;
 			}
-		
 		}
-	} // xyz
-	
+	}
+
 	return result;
 }
 
+#if 0
+// This doesn't seem to work and isn't used
 collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef,
-		f32 pos_max_d, const core::aabbox3d<f32> &box_0,
-		f32 dtime, v3f &pos_f, v3f &speed_f)
+		f32 pos_max_d, const aabb3f &box_0,
+		f32 stepheight, f32 dtime,
+		v3f &pos_f, v3f &speed_f, v3f &accel_f)
 {
-	collisionMoveResult final_result;
+	//TimeTaker tt("collisionMovePrecise");
+    ScopeProfiler sp(g_profiler, "collisionMovePrecise avg", SPT_AVG);
 	
+	collisionMoveResult final_result;
+
 	// If there is no speed, there are no collisions
 	if(speed_f.getLength() == 0)
 		return final_result;
 
-	// Maximum time increment (for collision detection etc)
-	// time = distance / speed
-	f32 dtime_max_increment = pos_max_d / speed_f.getLength();
-	
-	// Maximum time increment is 10ms or lower
-	if(dtime_max_increment > 0.01)
-		dtime_max_increment = 0.01;
-	
 	// Don't allow overly huge dtime
 	if(dtime > 2.0)
 		dtime = 2.0;
-	
+
 	f32 dtime_downcount = dtime;
 
 	u32 loopcount = 0;
@@ -224,6 +459,16 @@ collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef,
 	{
 		loopcount++;
 
+		// Maximum time increment (for collision detection etc)
+		// time = distance / speed
+		f32 dtime_max_increment = 1.0;
+		if(speed_f.getLength() != 0)
+			dtime_max_increment = pos_max_d / speed_f.getLength();
+
+		// Maximum time increment is 10ms or lower
+		if(dtime_max_increment > 0.01)
+			dtime_max_increment = 0.01;
+
 		f32 dtime_part;
 		if(dtime_downcount > dtime_max_increment)
 		{
@@ -242,17 +487,20 @@ collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef,
 		}
 
 		collisionMoveResult result = collisionMoveSimple(map, gamedef,
-				pos_max_d, box_0, dtime_part, pos_f, speed_f);
+				pos_max_d, box_0, stepheight, dtime_part,
+				pos_f, speed_f, accel_f);
 
 		if(result.touching_ground)
 			final_result.touching_ground = true;
 		if(result.collides)
 			final_result.collides = true;
+		if(result.collides_xz)
+			final_result.collides_xz = true;
+		if(result.standing_on_unloaded)
+			final_result.standing_on_unloaded = true;
 	}
 	while(dtime_downcount > 0.001);
-		
 
 	return final_result;
 }
-
-
+#endif
diff --git a/src/collision.h b/src/collision.h
index a4eca0dd..243c4b29 100644
--- a/src/collision.h
+++ b/src/collision.h
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define COLLISION_HEADER
 
 #include "irrlichttypes_bloated.h"
+#include <vector>
 
 class Map;
 class IGameDef;
@@ -29,22 +30,47 @@ struct collisionMoveResult
 {
 	bool touching_ground;
 	bool collides;
+	bool collides_xz;
+	bool standing_on_unloaded;
 
 	collisionMoveResult():
 		touching_ground(false),
-		collides(false)
+		collides(false),
+		collides_xz(false),
+		standing_on_unloaded(false)
 	{}
 };
 
 // Moves using a single iteration; speed should not exceed pos_max_d/dtime
 collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
-		f32 pos_max_d, const core::aabbox3d<f32> &box_0,
-		f32 dtime, v3f &pos_f, v3f &speed_f);
+		f32 pos_max_d, const aabb3f &box_0,
+		f32 stepheight, f32 dtime,
+		v3f &pos_f, v3f &speed_f, v3f &accel_f);
 
+#if 0
+// This doesn't seem to work and isn't used
 // Moves using as many iterations as needed
 collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef,
-		f32 pos_max_d, const core::aabbox3d<f32> &box_0,
-		f32 dtime, v3f &pos_f, v3f &speed_f);
+		f32 pos_max_d, const aabb3f &box_0,
+		f32 stepheight, f32 dtime,
+		v3f &pos_f, v3f &speed_f, v3f &accel_f);
+#endif
+
+// Helper function:
+// Checks for collision of a moving aabbox with a static aabbox
+// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
+// dtime receives time until first collision, invalid if -1 is returned
+int axisAlignedCollision(
+		const aabb3f &staticbox, const aabb3f &movingbox,
+		const v3f &speed, f32 d, f32 &dtime);
+
+// Helper function:
+// Checks if moving the movingbox up by the given distance would hit a ceiling.
+bool wouldCollideWithCeiling(
+		const std::vector<aabb3f> &staticboxes,
+		const aabb3f &movingbox,
+		f32 y_increase, f32 d);
+
 
 enum CollisionType
 {
diff --git a/src/content_cao.cpp b/src/content_cao.cpp
index c993f3f8..5702a73b 100644
--- a/src/content_cao.cpp
+++ b/src/content_cao.cpp
@@ -881,22 +881,24 @@ public:
 			box.MinEdge *= BS;
 			box.MaxEdge *= BS;
 			collisionMoveResult moveresult;
-			f32 pos_max_d = BS*0.25; // Distance per iteration
+			f32 pos_max_d = BS*0.125; // Distance per iteration
+			f32 stepheight = 0;
 			v3f p_pos = m_position;
 			v3f p_velocity = m_velocity;
+			v3f p_acceleration = m_acceleration;
 			IGameDef *gamedef = env->getGameDef();
-			moveresult = collisionMovePrecise(&env->getMap(), gamedef,
-					pos_max_d, box, dtime, p_pos, p_velocity);
+			moveresult = collisionMoveSimple(&env->getMap(), gamedef,
+					pos_max_d, box, stepheight, dtime,
+					p_pos, p_velocity, p_acceleration);
 			// Apply results
 			m_position = p_pos;
 			m_velocity = p_velocity;
+			m_acceleration = p_acceleration;
 			
 			bool is_end_position = moveresult.collides;
 			pos_translator.update(m_position, is_end_position, dtime);
 			pos_translator.translate(dtime);
 			updateNodePos();
-
-			m_velocity += dtime * m_acceleration;
 		} else {
 			m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration;
 			m_velocity += dtime * m_acceleration;
diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp
index ebf9fd59..d87ae32f 100644
--- a/src/content_mapblock.cpp
+++ b/src/content_mapblock.cpp
@@ -1027,6 +1027,53 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 			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;}
 		}
 	}
 }
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index 468a3911..e9b5782a 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -206,9 +206,12 @@ public:
 			m_speed_f *= pos_max_d / (m_speed_f.getLength()*dtime);
 		v3f pos_f = getBasePosition();
 		v3f pos_f_old = pos_f;
+		v3f accel_f = v3f(0,0,0);
+		f32 stepheight = 0;
 		IGameDef *gamedef = m_env->getGameDef();
 		moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
-				pos_max_d, box, dtime, pos_f, m_speed_f);
+				pos_max_d, box, stepheight, dtime,
+				pos_f, m_speed_f, accel_f);
 		
 		if(send_recommended == false)
 			return;
@@ -452,16 +455,18 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
 		box.MaxEdge *= BS;
 		collisionMoveResult moveresult;
 		f32 pos_max_d = BS*0.25; // Distance per iteration
-		v3f p_pos = getBasePosition();
+		f32 stepheight = 0; // Maximum climbable step height
+		v3f p_pos = m_base_position;
 		v3f p_velocity = m_velocity;
+		v3f p_acceleration = m_acceleration;
 		IGameDef *gamedef = m_env->getGameDef();
-		moveresult = collisionMovePrecise(&m_env->getMap(), gamedef,
-				pos_max_d, box, dtime, p_pos, p_velocity);
+		moveresult = collisionMoveSimple(&m_env->getMap(), gamedef,
+				pos_max_d, box, stepheight, dtime,
+				p_pos, p_velocity, p_acceleration);
 		// Apply results
-		setBasePosition(p_pos);
+		m_base_position = p_pos;
 		m_velocity = p_velocity;
-
-		m_velocity += dtime * m_acceleration;
+		m_acceleration = p_acceleration;
 	} else {
 		m_base_position += dtime * m_velocity + 0.5 * dtime
 				* dtime * m_acceleration;
diff --git a/src/game.cpp b/src/game.cpp
index c8defd82..98ffac3d 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -61,6 +61,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #endif
 #include "event_manager.h"
 #include <list>
+#include "util/directiontables.h"
 
 /*
 	Text input system
@@ -294,14 +295,12 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 		core::line3d<f32> shootline, f32 d,
 		bool liquids_pointable,
 		bool look_for_object,
-		core::aabbox3d<f32> &hilightbox,
-		bool &should_show_hilightbox,
+		std::vector<aabb3f> &hilightboxes,
 		ClientActiveObject *&selected_object)
 {
 	PointedThing result;
 
-	hilightbox = core::aabbox3d<f32>(0,0,0,0,0,0);
-	should_show_hilightbox = false;
+	hilightboxes.clear();
 	selected_object = NULL;
 
 	INodeDefManager *nodedef = client->getNodeDefManager();
@@ -312,27 +311,27 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 	{
 		selected_object = client->getSelectedActiveObject(d*BS,
 				camera_position, shootline);
-	}
-	if(selected_object != NULL)
-	{
-		core::aabbox3d<f32> *selection_box
-			= selected_object->getSelectionBox();
-		// Box should exist because object was returned in the
-		// first place
-		assert(selection_box);
 
-		v3f pos = selected_object->getPosition();
+		if(selected_object != NULL)
+		{
+			if(selected_object->doShowSelectionBox())
+			{
+				aabb3f *selection_box = selected_object->getSelectionBox();
+				// Box should exist because object was
+				// returned in the first place
+				assert(selection_box);
 
-		hilightbox = core::aabbox3d<f32>(
-				selection_box->MinEdge + pos,
-				selection_box->MaxEdge + pos
-		);
+				v3f pos = selected_object->getPosition();
+				hilightboxes.push_back(aabb3f(
+						selection_box->MinEdge + pos,
+						selection_box->MaxEdge + pos));
+			}
 
-		should_show_hilightbox = selected_object->doShowSelectionBox();
 
-		result.type = POINTEDTHING_OBJECT;
-		result.object_id = selected_object->getId();
-		return result;
+			result.type = POINTEDTHING_OBJECT;
+			result.object_id = selected_object->getId();
+			return result;
+		}
 	}
 
 	// That didn't work, try to find a pointed at node
@@ -368,196 +367,64 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 		if(!isPointableNode(n, client, liquids_pointable))
 			continue;
 
+		std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
+
 		v3s16 np(x,y,z);
 		v3f npf = intToFloat(np, BS);
-		
-		f32 d = 0.01;
-		
-		v3s16 dirs[6] = {
-			v3s16(0,0,1), // back
-			v3s16(0,1,0), // top
-			v3s16(1,0,0), // right
-			v3s16(0,0,-1), // front
-			v3s16(0,-1,0), // bottom
-			v3s16(-1,0,0), // left
-		};
-		
-		const ContentFeatures &f = nodedef->get(n);
-		
-		if(f.selection_box.type == NODEBOX_FIXED)
+
+		for(std::vector<aabb3f>::const_iterator
+				i = boxes.begin();
+				i != boxes.end(); i++)
 		{
-			core::aabbox3d<f32> box = f.selection_box.fixed;
+			aabb3f box = *i;
 			box.MinEdge += npf;
 			box.MaxEdge += npf;
 
-			v3s16 facedirs[6] = {
-				v3s16(-1,0,0),
-				v3s16(1,0,0),
-				v3s16(0,-1,0),
-				v3s16(0,1,0),
-				v3s16(0,0,-1),
-				v3s16(0,0,1),
-			};
-
-			core::aabbox3d<f32> faceboxes[6] = {
-				// X-
-				core::aabbox3d<f32>(
-					box.MinEdge.X, box.MinEdge.Y, box.MinEdge.Z,
-					box.MinEdge.X+d, box.MaxEdge.Y, box.MaxEdge.Z
-				),
-				// X+
-				core::aabbox3d<f32>(
-					box.MaxEdge.X-d, box.MinEdge.Y, box.MinEdge.Z,
-					box.MaxEdge.X, box.MaxEdge.Y, box.MaxEdge.Z
-				),
-				// Y-
-				core::aabbox3d<f32>(
-					box.MinEdge.X, box.MinEdge.Y, box.MinEdge.Z,
-					box.MaxEdge.X, box.MinEdge.Y+d, box.MaxEdge.Z
-				),
-				// Y+
-				core::aabbox3d<f32>(
-					box.MinEdge.X, box.MaxEdge.Y-d, box.MinEdge.Z,
-					box.MaxEdge.X, box.MaxEdge.Y, box.MaxEdge.Z
-				),
-				// Z-
-				core::aabbox3d<f32>(
-					box.MinEdge.X, box.MinEdge.Y, box.MinEdge.Z,
-					box.MaxEdge.X, box.MaxEdge.Y, box.MinEdge.Z+d
-				),
-				// Z+
-				core::aabbox3d<f32>(
-					box.MinEdge.X, box.MinEdge.Y, box.MaxEdge.Z-d,
-					box.MaxEdge.X, box.MaxEdge.Y, box.MaxEdge.Z
-				),
-			};
-
-			for(u16 i=0; i<6; i++)
+			for(u16 j=0; j<6; j++)
 			{
-				v3f facedir_f(facedirs[i].X, facedirs[i].Y, facedirs[i].Z);
-				v3f centerpoint = npf + facedir_f * BS/2;
+				v3s16 facedir = g_6dirs[j];
+				aabb3f facebox = box;
+
+				f32 d = 0.001*BS;
+				if(facedir.X > 0)
+					facebox.MinEdge.X = facebox.MaxEdge.X-d;
+				else if(facedir.X < 0)
+					facebox.MaxEdge.X = facebox.MinEdge.X+d;
+				else if(facedir.Y > 0)
+					facebox.MinEdge.Y = facebox.MaxEdge.Y-d;
+				else if(facedir.Y < 0)
+					facebox.MaxEdge.Y = facebox.MinEdge.Y+d;
+				else if(facedir.Z > 0)
+					facebox.MinEdge.Z = facebox.MaxEdge.Z-d;
+				else if(facedir.Z < 0)
+					facebox.MaxEdge.Z = facebox.MinEdge.Z+d;
+
+				v3f centerpoint = facebox.getCenter();
 				f32 distance = (centerpoint - camera_position).getLength();
 				if(distance >= mindistance)
 					continue;
-				if(!faceboxes[i].intersectsWithLine(shootline))
+				if(!facebox.intersectsWithLine(shootline))
 					continue;
+
+				v3s16 np_above = np + facedir;
+
 				result.type = POINTEDTHING_NODE;
 				result.node_undersurface = np;
-				result.node_abovesurface = np+facedirs[i];
+				result.node_abovesurface = np_above;
 				mindistance = distance;
-				hilightbox = box;
-				should_show_hilightbox = true;
-			}
-		}
-		else if(f.selection_box.type == NODEBOX_WALLMOUNTED)
-		{
-			v3s16 dir = n.getWallMountedDir(nodedef);
-			v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
-			dir_f *= BS/2 - BS/6 - BS/20;
-			v3f cpf = npf + dir_f;
-			f32 distance = (cpf - camera_position).getLength();
 
-			core::aabbox3d<f32> box;
-			
-			// top
-			if(dir == v3s16(0,1,0)){
-				box = f.selection_box.wall_top;
-			}
-			// bottom
-			else if(dir == v3s16(0,-1,0)){
-				box = f.selection_box.wall_bottom;
-			}
-			// side
-			else{
-				v3f vertices[2] =
+				hilightboxes.clear();
+				for(std::vector<aabb3f>::const_iterator
+						i2 = boxes.begin();
+						i2 != boxes.end(); i2++)
 				{
-					f.selection_box.wall_side.MinEdge,
-					f.selection_box.wall_side.MaxEdge
-				};
-
-				for(s32 i=0; i<2; i++)
-				{
-					if(dir == v3s16(-1,0,0))
-						vertices[i].rotateXZBy(0);
-					if(dir == v3s16(1,0,0))
-						vertices[i].rotateXZBy(180);
-					if(dir == v3s16(0,0,-1))
-						vertices[i].rotateXZBy(90);
-					if(dir == v3s16(0,0,1))
-						vertices[i].rotateXZBy(-90);
-				}
-
-				box = core::aabbox3d<f32>(vertices[0]);
-				box.addInternalPoint(vertices[1]);
-			}
-
-			box.MinEdge += npf;
-			box.MaxEdge += npf;
-			
-			if(distance < mindistance)
-			{
-				if(box.intersectsWithLine(shootline))
-				{
-					result.type = POINTEDTHING_NODE;
-					result.node_undersurface = np;
-					result.node_abovesurface = np;
-					mindistance = distance;
-					hilightbox = box;
-					should_show_hilightbox = true;
+					aabb3f box = *i2;
+					box.MinEdge += npf + v3f(-d,-d,-d);
+					box.MaxEdge += npf + v3f(d,d,d);
+					hilightboxes.push_back(box);
 				}
 			}
 		}
-		else // NODEBOX_REGULAR
-		{
-			for(u16 i=0; i<6; i++)
-			{
-				v3f dir_f = v3f(dirs[i].X,
-						dirs[i].Y, dirs[i].Z);
-				v3f centerpoint = npf + dir_f * BS/2;
-				f32 distance =
-						(centerpoint - camera_position).getLength();
-				
-				if(distance < mindistance)
-				{
-					core::CMatrix4<f32> m;
-					m.buildRotateFromTo(v3f(0,0,1), dir_f);
-
-					// This is the back face
-					v3f corners[2] = {
-						v3f(BS/2, BS/2, BS/2),
-						v3f(-BS/2, -BS/2, BS/2+d)
-					};
-					
-					for(u16 j=0; j<2; j++)
-					{
-						m.rotateVect(corners[j]);
-						corners[j] += npf;
-					}
-
-					core::aabbox3d<f32> facebox(corners[0]);
-					facebox.addInternalPoint(corners[1]);
-
-					if(facebox.intersectsWithLine(shootline))
-					{
-						result.type = POINTEDTHING_NODE;
-						result.node_undersurface = np;
-						result.node_abovesurface = np + dirs[i];
-						mindistance = distance;
-
-						//hilightbox = facebox;
-
-						const float d = 0.502;
-						core::aabbox3d<f32> nodebox
-								(-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
-						v3f nodepos_f = intToFloat(np, BS);
-						nodebox.MinEdge += nodepos_f;
-						nodebox.MaxEdge += nodepos_f;
-						hilightbox = nodebox;
-						should_show_hilightbox = true;
-					}
-				} // if distance < mindistance
-			} // for dirs
-		} // regular block
 	} // for coords
 
 	return result;
@@ -1514,7 +1381,7 @@ void the_game(
 			hotbar_imagesize = 64;
 		
 		// Hilight boxes collected during the loop and displayed
-		core::list< core::aabbox3d<f32> > hilightboxes;
+		std::vector<aabb3f> hilightboxes;
 		
 		// Info text
 		std::wstring infotext;
@@ -2127,8 +1994,6 @@ void the_game(
 		core::line3d<f32> shootline(camera_position,
 				camera_position + camera_direction * BS * (d+1));
 
-		core::aabbox3d<f32> hilightbox;
-		bool should_show_hilightbox = false;
 		ClientActiveObject *selected_object = NULL;
 
 		PointedThing pointed = getPointedThing(
@@ -2137,7 +2002,7 @@ void the_game(
 				camera_position, shootline, d,
 				playeritem_liquids_pointable, !ldown_for_dig,
 				// output
-				hilightbox, should_show_hilightbox,
+				hilightboxes,
 				selected_object);
 
 		if(pointed != pointed_old)
@@ -2146,12 +2011,6 @@ void the_game(
 			//dstream<<"Pointing at "<<pointed.dump()<<std::endl;
 		}
 
-		/*
-			Visualize selection
-		*/
-		if(should_show_hilightbox)
-			hilightboxes.push_back(hilightbox);
-
 		/*
 			Stop digging when
 			- releasing left mouse button
@@ -2818,9 +2677,10 @@ void the_game(
 
 		if(show_hud)
 		{
-			for(core::list<aabb3f>::Iterator i=hilightboxes.begin();
-					i != hilightboxes.end(); i++)
-			{
+			for(std::vector<aabb3f>::const_iterator
+					i = hilightboxes.begin();
+ 					i != hilightboxes.end(); i++)
+	 		{
 				/*infostream<<"hilightbox min="
 						<<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
 						<<" max="
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index 2bd62dab..df668b36 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -52,24 +52,15 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 	INodeDefManager *nodemgr = m_gamedef->ndef();
 
 	v3f position = getPosition();
-	v3f oldpos = position;
-	v3s16 oldpos_i = floatToInt(oldpos, BS);
 
 	v3f old_speed = m_speed;
 
-	/*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
-			<<oldpos_i.Z<<")"<<std::endl;*/
-
-	/*
-		Calculate new position
-	*/
-	position += m_speed * dtime;
-	
 	// Skip collision detection if a special movement mode is used
 	bool fly_allowed = m_gamedef->checkLocalPrivilege("fly");
 	bool free_move = fly_allowed && g_settings->getBool("free_move");
 	if(free_move)
 	{
+        position += m_speed * dtime;
 		setPosition(position);
 		return;
 	}
@@ -78,9 +69,6 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 		Collision detection
 	*/
 	
-	// Player position in nodes
-	v3s16 pos_i = floatToInt(position, BS);
-	
 	/*
 		Check if player is in water (the oscillating value)
 	*/
@@ -147,13 +135,6 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 	// Maximum distance over border for sneaking
 	f32 sneak_max = BS*0.4;
 
-	/*
-		If sneaking, player has larger collision radius to keep from
-		falling
-	*/
-	/*if(control.sneak)
-		player_radius = sneak_max + d*1.1;*/
-	
 	/*
 		If sneaking, keep in range from the last walked node and don't
 		fall off from it
@@ -170,22 +151,8 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 		{
 			position.Y = min_y;
 
-			//v3f old_speed = m_speed;
-
 			if(m_speed.Y < 0)
 				m_speed.Y = 0;
-
-			/*if(collision_info)
-			{
-				// Report fall collision
-				if(old_speed.Y < m_speed.Y - 0.1)
-				{
-					CollisionInfo info;
-					info.t = COLLISION_FALL;
-					info.speed = m_speed.Y - old_speed.Y;
-					collision_info->push_back(info);
-				}
-			}*/
 		}
 	}
 
@@ -193,22 +160,22 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 		Calculate player collision box (new and old)
 	*/
 	core::aabbox3d<f32> playerbox(
-		position.X - player_radius,
-		position.Y - 0.0,
-		position.Z - player_radius,
-		position.X + player_radius,
-		position.Y + player_height,
-		position.Z + player_radius
-	);
-	core::aabbox3d<f32> playerbox_old(
-		oldpos.X - player_radius,
-		oldpos.Y - 0.0,
-		oldpos.Z - player_radius,
-		oldpos.X + player_radius,
-		oldpos.Y + player_height,
-		oldpos.Z + player_radius
+		-player_radius,
+		0.0,
+		-player_radius,
+		player_radius,
+		player_height,
+		player_radius
 	);
 
+	float player_stepheight = touching_ground ? (BS*0.6) : (BS*0.2);
+
+	v3f accel_f = v3f(0,0,0);
+
+	collisionMoveResult result = collisionMoveSimple(&map, m_gamedef,
+			pos_max_d, playerbox, player_stepheight, dtime,
+			position, m_speed, accel_f);
+
 	/*
 		If the player's feet touch the topside of any node, this is
 		set to true.
@@ -216,154 +183,9 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
 		Player is allowed to jump when this is true.
 	*/
 	bool touching_ground_was = touching_ground;
-	touching_ground = false;
-
-	/*std::cout<<"Checking collisions for ("
-			<<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
-			<<") -> ("
-			<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
-			<<"):"<<std::endl;*/
-	
-	bool standing_on_unloaded = false;
-	
-	/*
-		Go through every node around the player
-	*/
-	for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
-	for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
-	for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
-	{
-		bool is_unloaded = false;
-		try{
-			// Player collides into walkable nodes
-			if(nodemgr->get(map.getNode(v3s16(x,y,z))).walkable == false)
-				continue;
-		}
-		catch(InvalidPositionException &e)
-		{
-			is_unloaded = true;
-			// Doing nothing here will block the player from
-			// walking over map borders
-		}
-
-		core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
-		
-		/*
-			See if the player is touching ground.
-
-			Player touches ground if player's minimum Y is near node's
-			maximum Y and player's X-Z-area overlaps with the node's
-			X-Z-area.
-
-			Use 0.15*BS so that it is easier to get on a node.
-		*/
-		if(
-				//fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
-				fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < 0.15*BS
-				&& nodebox.MaxEdge.X-d > playerbox.MinEdge.X
-				&& nodebox.MinEdge.X+d < playerbox.MaxEdge.X
-				&& nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
-				&& nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
-		){
-			touching_ground = true;
-			if(is_unloaded)
-				standing_on_unloaded = true;
-		}
-		
-		// If player doesn't intersect with node, ignore node.
-		if(playerbox.intersectsWithBox(nodebox) == false)
-			continue;
-		
-		/*
-			Go through every axis
-		*/
-		v3f dirs[3] = {
-			v3f(0,0,1), // back-front
-			v3f(0,1,0), // top-bottom
-			v3f(1,0,0), // right-left
-		};
-		for(u16 i=0; i<3; i++)
-		{
-			/*
-				Calculate values along the axis
-			*/
-			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
-			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
-			f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
-			f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
-			f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
-			f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
-			
-			/*
-				Check collision for the axis.
-				Collision happens when player is going through a surface.
-			*/
-			/*f32 neg_d = d;
-			f32 pos_d = d;
-			// Make it easier to get on top of a node
-			if(i == 1)
-				neg_d = 0.15*BS;
-			bool negative_axis_collides =
-				(nodemax > playermin && nodemax <= playermin_old + neg_d
-					&& m_speed.dotProduct(dirs[i]) < 0);
-			bool positive_axis_collides =
-				(nodemin < playermax && nodemin >= playermax_old - pos_d
-					&& m_speed.dotProduct(dirs[i]) > 0);*/
-			bool negative_axis_collides =
-				(nodemax > playermin && nodemax <= playermin_old + d
-					&& m_speed.dotProduct(dirs[i]) < 0);
-			bool positive_axis_collides =
-				(nodemin < playermax && nodemin >= playermax_old - d
-					&& m_speed.dotProduct(dirs[i]) > 0);
-			bool main_axis_collides =
-					negative_axis_collides || positive_axis_collides;
-			
-			/*
-				Check overlap of player and node in other axes
-			*/
-			bool other_axes_overlap = true;
-			for(u16 j=0; j<3; j++)
-			{
-				if(j == i)
-					continue;
-				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
-				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
-				f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
-				f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
-				if(!(nodemax - d > playermin && nodemin + d < playermax))
-				{
-					other_axes_overlap = false;
-					break;
-				}
-			}
-			
-			/*
-				If this is a collision, revert the position in the main
-				direction.
-			*/
-			if(other_axes_overlap && main_axis_collides)
-			{
-				//v3f old_speed = m_speed;
-
-				m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
-				position -= position.dotProduct(dirs[i]) * dirs[i];
-				position += oldpos.dotProduct(dirs[i]) * dirs[i];
-				
-				/*if(collision_info)
-				{
-					// Report fall collision
-					if(old_speed.Y < m_speed.Y - 0.1)
-					{
-						CollisionInfo info;
-						info.t = COLLISION_FALL;
-						info.speed = m_speed.Y - old_speed.Y;
-						collision_info->push_back(info);
-					}
-				}*/
-			}
-		
-		}
-	} // xyz
+	touching_ground = result.touching_ground;
+    
+    bool standing_on_unloaded = result.standing_on_unloaded;
 
 	/*
 		Check the nodes under the player to see from which node the
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index 4de84dd1..e12f252f 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -134,7 +134,98 @@ v3s16 MapNode::getWallMountedDir(INodeDefManager *nodemgr) const
 	}
 }
 
+static std::vector<aabb3f> transformNodeBox(const MapNode &n,
+		const NodeBox &nodebox, INodeDefManager *nodemgr)
+{
+	std::vector<aabb3f> boxes;
+	if(nodebox.type == NODEBOX_FIXED)
+	{
+		const std::vector<aabb3f> &fixed = nodebox.fixed;
+		int facedir = n.getFaceDir(nodemgr);
+		for(std::vector<aabb3f>::const_iterator
+				i = fixed.begin();
+				i != fixed.end(); i++)
+		{
+			aabb3f box = *i;
+			if(facedir == 1)
+			{
+				box.MinEdge.rotateXZBy(-90);
+				box.MaxEdge.rotateXZBy(-90);
+				box.repair();
+			}
+			else if(facedir == 2)
+			{
+				box.MinEdge.rotateXZBy(180);
+				box.MaxEdge.rotateXZBy(180);
+				box.repair();
+			}
+			else if(facedir == 3)
+			{
+				box.MinEdge.rotateXZBy(90);
+				box.MaxEdge.rotateXZBy(90);
+				box.repair();
+			}
+			boxes.push_back(box);
+		}
+	}
+	else if(nodebox.type == NODEBOX_WALLMOUNTED)
+	{
+		v3s16 dir = n.getWallMountedDir(nodemgr);
 
+		// top
+		if(dir == v3s16(0,1,0))
+		{
+			boxes.push_back(nodebox.wall_top);
+		}
+		// bottom
+		else if(dir == v3s16(0,-1,0))
+		{
+			boxes.push_back(nodebox.wall_bottom);
+		}
+		// side
+		else
+		{
+			v3f vertices[2] =
+			{
+				nodebox.wall_side.MinEdge,
+				nodebox.wall_side.MaxEdge
+			};
+
+			for(s32 i=0; i<2; i++)
+			{
+				if(dir == v3s16(-1,0,0))
+					vertices[i].rotateXZBy(0);
+				if(dir == v3s16(1,0,0))
+					vertices[i].rotateXZBy(180);
+				if(dir == v3s16(0,0,-1))
+					vertices[i].rotateXZBy(90);
+				if(dir == v3s16(0,0,1))
+					vertices[i].rotateXZBy(-90);
+			}
+
+			aabb3f box = aabb3f(vertices[0]);
+			box.addInternalPoint(vertices[1]);
+			boxes.push_back(box);
+		}
+	}
+	else // NODEBOX_REGULAR
+	{
+		boxes.push_back(aabb3f(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2));
+	}
+	return boxes;
+}
+
+std::vector<aabb3f> MapNode::getNodeBoxes(INodeDefManager *nodemgr) const
+{
+	const ContentFeatures &f = nodemgr->get(*this);
+	return transformNodeBox(*this, f.node_box, nodemgr);
+}
+
+std::vector<aabb3f> MapNode::getSelectionBoxes(INodeDefManager *nodemgr) const
+{
+	const ContentFeatures &f = nodemgr->get(*this);
+	return transformNodeBox(*this, f.selection_box, nodemgr);
+}
 
 u32 MapNode::serializedLength(u8 version)
 {
diff --git a/src/mapnode.h b/src/mapnode.h
index 32e46b63..75156313 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -22,7 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "irrlichttypes.h"
 #include "irr_v3d.h"
+#include "irr_aabb3d.h"
 #include "light.h"
+#include <vector>
 
 class INodeDefManager;
 
@@ -196,6 +198,17 @@ struct MapNode
 	u8 getWallMounted(INodeDefManager *nodemgr) const;
 	v3s16 getWallMountedDir(INodeDefManager *nodemgr) const;
 
+	/*
+		Gets list of node boxes (used for rendering (NDT_NODEBOX)
+		and collision)
+	*/
+	std::vector<aabb3f> getNodeBoxes(INodeDefManager *nodemgr) const;
+
+	/*
+		Gets list of selection boxes
+	*/
+	std::vector<aabb3f> getSelectionBoxes(INodeDefManager *nodemgr) const;
+
 	/*
 		Serialization functions
 	*/
diff --git a/src/nodedef.cpp b/src/nodedef.cpp
index 80bfae3e..1b85a955 100644
--- a/src/nodedef.cpp
+++ b/src/nodedef.cpp
@@ -33,34 +33,74 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	NodeBox
 */
 
+void NodeBox::reset()
+{
+	type = NODEBOX_REGULAR;
+	// default is empty
+	fixed.clear();
+	// default is sign/ladder-like
+	wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2);
+	wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2);
+	wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2);
+}
+
 void NodeBox::serialize(std::ostream &os) const
 {
-	writeU8(os, 0); // version
+	writeU8(os, 1); // version
 	writeU8(os, type);
-	writeV3F1000(os, fixed.MinEdge);
-	writeV3F1000(os, fixed.MaxEdge);
-	writeV3F1000(os, wall_top.MinEdge);
-	writeV3F1000(os, wall_top.MaxEdge);
-	writeV3F1000(os, wall_bottom.MinEdge);
-	writeV3F1000(os, wall_bottom.MaxEdge);
-	writeV3F1000(os, wall_side.MinEdge);
-	writeV3F1000(os, wall_side.MaxEdge);
+
+	if(type == NODEBOX_FIXED)
+	{
+		writeU16(os, fixed.size());
+		for(std::vector<aabb3f>::const_iterator
+				i = fixed.begin();
+				i != fixed.end(); i++)
+		{
+			writeV3F1000(os, i->MinEdge);
+			writeV3F1000(os, i->MaxEdge);
+		}
+	}
+	else if(type == NODEBOX_WALLMOUNTED)
+	{
+		writeV3F1000(os, wall_top.MinEdge);
+		writeV3F1000(os, wall_top.MaxEdge);
+		writeV3F1000(os, wall_bottom.MinEdge);
+		writeV3F1000(os, wall_bottom.MaxEdge);
+		writeV3F1000(os, wall_side.MinEdge);
+		writeV3F1000(os, wall_side.MaxEdge);
+	}
 }
 
 void NodeBox::deSerialize(std::istream &is)
 {
 	int version = readU8(is);
-	if(version != 0)
+	if(version != 1)
 		throw SerializationError("unsupported NodeBox version");
+
+	reset();
+
 	type = (enum NodeBoxType)readU8(is);
-	fixed.MinEdge = readV3F1000(is);
-	fixed.MaxEdge = readV3F1000(is);
-	wall_top.MinEdge = readV3F1000(is);
-	wall_top.MaxEdge = readV3F1000(is);
-	wall_bottom.MinEdge = readV3F1000(is);
-	wall_bottom.MaxEdge = readV3F1000(is);
-	wall_side.MinEdge = readV3F1000(is);
-	wall_side.MaxEdge = readV3F1000(is);
+
+	if(type == NODEBOX_FIXED)
+	{
+		u16 fixed_count = readU16(is);
+		while(fixed_count--)
+		{
+			aabb3f box;
+			box.MinEdge = readV3F1000(is);
+			box.MaxEdge = readV3F1000(is);
+			fixed.push_back(box);
+		}
+	}
+	else if(type == NODEBOX_WALLMOUNTED)
+	{
+		wall_top.MinEdge = readV3F1000(is);
+		wall_top.MaxEdge = readV3F1000(is);
+		wall_bottom.MinEdge = readV3F1000(is);
+		wall_bottom.MaxEdge = readV3F1000(is);
+		wall_side.MinEdge = readV3F1000(is);
+		wall_side.MaxEdge = readV3F1000(is);
+	}
 }
 
 /*
@@ -165,6 +205,7 @@ void ContentFeatures::reset()
 	liquid_viscosity = 0;
 	light_source = 0;
 	damage_per_second = 0;
+	node_box = NodeBox();
 	selection_box = NodeBox();
 	legacy_facedir_simple = false;
 	legacy_wallmounted = false;
@@ -214,6 +255,7 @@ void ContentFeatures::serialize(std::ostream &os)
 	writeU8(os, liquid_viscosity);
 	writeU8(os, light_source);
 	writeU32(os, damage_per_second);
+	node_box.serialize(os);
 	selection_box.serialize(os);
 	writeU8(os, legacy_facedir_simple);
 	writeU8(os, legacy_wallmounted);
@@ -277,6 +319,7 @@ void ContentFeatures::deSerialize(std::istream &is)
 	liquid_viscosity = readU8(is);
 	light_source = readU8(is);
 	damage_per_second = readU32(is);
+	node_box.deSerialize(is);
 	selection_box.deSerialize(is);
 	legacy_facedir_simple = readU8(is);
 	legacy_wallmounted = readU8(is);
@@ -577,6 +620,7 @@ public:
 			case NDT_PLANTLIKE:
 			case NDT_FENCELIKE:
 			case NDT_RAILLIKE:
+			case NDT_NODEBOX:
 				f->solidness = 0;
 				break;
 			}
diff --git a/src/nodedef.h b/src/nodedef.h
index fcd06be7..7c693183 100644
--- a/src/nodedef.h
+++ b/src/nodedef.h
@@ -65,7 +65,7 @@ enum LiquidType
 enum NodeBoxType
 {
 	NODEBOX_REGULAR, // Regular block; allows buildable_to
-	NODEBOX_FIXED, // Static separately defined box
+	NODEBOX_FIXED, // Static separately defined box(es)
 	NODEBOX_WALLMOUNTED, // Box for wall mounted nodes; (top, bottom, side)
 };
 
@@ -74,22 +74,16 @@ struct NodeBox
 	enum NodeBoxType type;
 	// NODEBOX_REGULAR (no parameters)
 	// NODEBOX_FIXED
-	core::aabbox3d<f32> fixed;
+	std::vector<aabb3f> fixed;
 	// NODEBOX_WALLMOUNTED
-	core::aabbox3d<f32> wall_top;
-	core::aabbox3d<f32> wall_bottom;
-	core::aabbox3d<f32> wall_side; // being at the -X side
+	aabb3f wall_top;
+	aabb3f wall_bottom;
+	aabb3f wall_side; // being at the -X side
 
-	NodeBox():
-		type(NODEBOX_REGULAR),
-		// default is rail-like
-		fixed(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2),
-		// default is sign/ladder-like
-		wall_top(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2),
-		wall_bottom(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2),
-		wall_side(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2)
-	{}
+	NodeBox()
+	{ reset(); }
 
+	void reset();
 	void serialize(std::ostream &os) const;
 	void deSerialize(std::istream &is);
 };
@@ -143,6 +137,7 @@ enum NodeDrawType
 	NDT_PLANTLIKE,
 	NDT_FENCELIKE,
 	NDT_RAILLIKE,
+	NDT_NODEBOX,
 };
 
 #define CF_SPECIAL_COUNT 2
@@ -217,6 +212,7 @@ struct ContentFeatures
 	// Amount of light the node emits
 	u8 light_source;
 	u32 damage_per_second;
+	NodeBox node_box;
 	NodeBox selection_box;
 	// Compatibility with old maps
 	// Set to true if paramtype used to be 'facedir_simple'
diff --git a/src/player.cpp b/src/player.cpp
index d470fa6f..999d842f 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -166,6 +166,10 @@ void Player::deSerialize(std::istream &is)
 	RemotePlayer
 */
 
+
+
+
+
 void RemotePlayer::setPosition(const v3f &position)
 {
 	Player::setPosition(position);
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index 289ad31f..cf2a01c6 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -382,6 +382,7 @@ struct EnumString es_DrawType[] =
 	{NDT_PLANTLIKE, "plantlike"},
 	{NDT_FENCELIKE, "fencelike"},
 	{NDT_RAILLIKE, "raillike"},
+	{NDT_NODEBOX, "nodebox"},
 	{0, NULL},
 };
 
@@ -595,52 +596,89 @@ static video::SColor readARGB8(lua_State *L, int index)
 	return color;
 }
 
-static core::aabbox3d<f32> read_aabbox3df32(lua_State *L, int index, f32 scale)
+static aabb3f read_aabb3f(lua_State *L, int index, f32 scale)
 {
-	core::aabbox3d<f32> box;
-	if(lua_istable(L, -1)){
-		lua_rawgeti(L, -1, 1);
+	aabb3f box;
+	if(lua_istable(L, index)){
+		lua_rawgeti(L, index, 1);
 		box.MinEdge.X = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
-		lua_rawgeti(L, -1, 2);
+		lua_rawgeti(L, index, 2);
 		box.MinEdge.Y = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
-		lua_rawgeti(L, -1, 3);
+		lua_rawgeti(L, index, 3);
 		box.MinEdge.Z = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
-		lua_rawgeti(L, -1, 4);
+		lua_rawgeti(L, index, 4);
 		box.MaxEdge.X = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
-		lua_rawgeti(L, -1, 5);
+		lua_rawgeti(L, index, 5);
 		box.MaxEdge.Y = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
-		lua_rawgeti(L, -1, 6);
+		lua_rawgeti(L, index, 6);
 		box.MaxEdge.Z = lua_tonumber(L, -1) * scale;
 		lua_pop(L, 1);
 	}
 	return box;
 }
 
-#if 0
-/*
-	MaterialProperties
-*/
-
-static MaterialProperties read_material_properties(
-		lua_State *L, int table)
+static std::vector<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale)
 {
-	MaterialProperties prop;
-	prop.diggability = (Diggability)getenumfield(L, -1, "diggability",
-			es_Diggability, DIGGABLE_NORMAL);
-	getfloatfield(L, -1, "constant_time", prop.constant_time);
-	getfloatfield(L, -1, "weight", prop.weight);
-	getfloatfield(L, -1, "crackiness", prop.crackiness);
-	getfloatfield(L, -1, "crumbliness", prop.crumbliness);
-	getfloatfield(L, -1, "cuttability", prop.cuttability);
-	getfloatfield(L, -1, "flammability", prop.flammability);
-	return prop;
+	std::vector<aabb3f> boxes;
+	if(lua_istable(L, index)){
+		int n = lua_objlen(L, index);
+		// Check if it's a single box or a list of boxes
+		bool possibly_single_box = (n == 6);
+		for(int i = 1; i <= n && possibly_single_box; i++){
+			lua_rawgeti(L, index, i);
+			if(!lua_isnumber(L, -1))
+				possibly_single_box = false;
+			lua_pop(L, 1);
+		}
+		if(possibly_single_box){
+			// Read a single box
+			boxes.push_back(read_aabb3f(L, index, scale));
+		} else {
+			// Read a list of boxes
+			for(int i = 1; i <= n; i++){
+				lua_rawgeti(L, index, i);
+				boxes.push_back(read_aabb3f(L, -1, scale));
+				lua_pop(L, 1);
+			}
+		}
+	}
+	return boxes;
+}
+
+static NodeBox read_nodebox(lua_State *L, int index)
+{
+	NodeBox nodebox;
+	if(lua_istable(L, -1)){
+		nodebox.type = (NodeBoxType)getenumfield(L, index, "type",
+				es_NodeBoxType, NODEBOX_REGULAR);
+
+		lua_getfield(L, index, "fixed");
+		if(lua_istable(L, -1))
+			nodebox.fixed = read_aabb3f_vector(L, -1, BS);
+		lua_pop(L, 1);
+
+		lua_getfield(L, index, "wall_top");
+		if(lua_istable(L, -1))
+			nodebox.wall_top = read_aabb3f(L, -1, BS);
+		lua_pop(L, 1);
+
+		lua_getfield(L, index, "wall_bottom");
+		if(lua_istable(L, -1))
+			nodebox.wall_bottom = read_aabb3f(L, -1, BS);
+		lua_pop(L, 1);
+
+		lua_getfield(L, index, "wall_side");
+		if(lua_istable(L, -1))
+			nodebox.wall_side = read_aabb3f(L, -1, BS);
+		lua_pop(L, 1);
+	}
+	return nodebox;
 }
-#endif
 
 /*
 	Groups
@@ -891,7 +929,7 @@ static void read_object_properties(lua_State *L, int index,
 
 	lua_getfield(L, -1, "collisionbox");
 	if(lua_istable(L, -1))
-		prop->collisionbox = read_aabbox3df32(L, -1, 1.0);
+		prop->collisionbox = read_aabb3f(L, -1, 1.0);
 	lua_pop(L, 1);
 
 	getstringfield(L, -1, "visual", prop->visual);
@@ -1203,33 +1241,16 @@ static ContentFeatures read_content_features(lua_State *L, int index)
 	f.damage_per_second = getintfield_default(L, index,
 			"damage_per_second", f.damage_per_second);
 	
-	lua_getfield(L, index, "selection_box");
-	if(lua_istable(L, -1)){
-		f.selection_box.type = (NodeBoxType)getenumfield(L, -1, "type",
-				es_NodeBoxType, NODEBOX_REGULAR);
-
-		lua_getfield(L, -1, "fixed");
-		if(lua_istable(L, -1))
-			f.selection_box.fixed = read_aabbox3df32(L, -1, BS);
-		lua_pop(L, 1);
-
-		lua_getfield(L, -1, "wall_top");
-		if(lua_istable(L, -1))
-			f.selection_box.wall_top = read_aabbox3df32(L, -1, BS);
-		lua_pop(L, 1);
-
-		lua_getfield(L, -1, "wall_bottom");
-		if(lua_istable(L, -1))
-			f.selection_box.wall_bottom = read_aabbox3df32(L, -1, BS);
-		lua_pop(L, 1);
-
-		lua_getfield(L, -1, "wall_side");
-		if(lua_istable(L, -1))
-			f.selection_box.wall_side = read_aabbox3df32(L, -1, BS);
-		lua_pop(L, 1);
-	}
+	lua_getfield(L, index, "node_box");
+	if(lua_istable(L, -1))
+		f.node_box = read_nodebox(L, -1);
 	lua_pop(L, 1);
 
+	lua_getfield(L, index, "selection_box");
+	if(lua_istable(L, -1))
+		f.selection_box = read_nodebox(L, -1);
+ 	lua_pop(L, 1);
+
 	// Set to true if paramtype used to be 'facedir_simple'
 	getboolfield(L, index, "legacy_facedir_simple", f.legacy_facedir_simple);
 	// Set to true if wall_mounted used to be set to true
@@ -5604,6 +5625,21 @@ void scriptapi_luaentity_get_properties(lua_State *L, u16 id,
 	// Set default values that differ from ObjectProperties defaults
 	prop->hp_max = 10;
 	
+	/* Read stuff */
+	
+	prop->hp_max = getintfield_default(L, -1, "hp_max", 10);
+
+	getboolfield(L, -1, "physical", prop->physical);
+
+	getfloatfield(L, -1, "weight", prop->weight);
+
+	lua_getfield(L, -1, "collisionbox");
+	if(lua_istable(L, -1))
+		prop->collisionbox = read_aabb3f(L, -1, 1.0);
+	lua_pop(L, 1);
+
+	getstringfield(L, -1, "visual", prop->visual);
+	
 	// Deprecated: read object properties directly
 	read_object_properties(L, -1, prop);
 	
diff --git a/src/test.cpp b/src/test.cpp
index 3dc6814b..9b134627 100644
--- a/src/test.cpp
+++ b/src/test.cpp
@@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "connection.h"
 #include "serialization.h"
 #include "voxel.h"
+#include "collision.h"
 #include <sstream>
 #include "porting.h"
 #include "content_mapnode.h"
@@ -1075,6 +1076,153 @@ struct TestMapSector
 };
 #endif
 
+struct TestCollision
+{
+	void Run()
+	{
+		/*
+			axisAlignedCollision
+		*/
+
+		for(s16 bx = -3; bx <= 3; bx++)
+		for(s16 by = -3; by <= 3; by++)
+		for(s16 bz = -3; bz <= 3; bz++)
+		{
+			// X-
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
+				v3f v(1, 0, 0);
+				f32 dtime = 0;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 1.000) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
+				v3f v(-1, 0, 0);
+				f32 dtime = 0;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == -1);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx-2, by+1.5, bz, bx-1, by+2.5, bz-1);
+				v3f v(1, 0, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == -1);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
+				v3f v(0.5, 0.1, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 3.000) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
+				v3f v(0.5, 0.1, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 3.000) < 0.001);
+			}
+
+			// X+
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
+				v3f v(-1, 0, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 1.000) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
+				v3f v(1, 0, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == -1);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx+2, by, bz+1.5, bx+3, by+1, bz+3.5);
+				v3f v(-1, 0, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == -1);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
+				v3f v(-0.5, 0.2, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 1);  // Y, not X!
+				assert(fabs(dtime - 2.500) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
+				aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
+				v3f v(-0.5, 0.3, 0);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 2.000) < 0.001);
+			}
+
+			// TODO: Y-, Y+, Z-, Z+
+
+			// misc
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx+2.3, by+2.29, bz+2.29, bx+4.2, by+4.2, bz+4.2);
+				v3f v(-1./3, -1./3, -1./3);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 0.9) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx+2.29, by+2.3, bz+2.29, bx+4.2, by+4.2, bz+4.2);
+				v3f v(-1./3, -1./3, -1./3);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 1);
+				assert(fabs(dtime - 0.9) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx+2.29, by+2.29, bz+2.3, bx+4.2, by+4.2, bz+4.2);
+				v3f v(-1./3, -1./3, -1./3);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 2);
+				assert(fabs(dtime - 0.9) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.3, by-2.29, bz-2.29);
+				v3f v(1./7, 1./7, 1./7);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 0);
+				assert(fabs(dtime - 16.1) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.3, bz-2.29);
+				v3f v(1./7, 1./7, 1./7);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 1);
+				assert(fabs(dtime - 16.1) < 0.001);
+			}
+			{
+				aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
+				aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.29, bz-2.3);
+				v3f v(1./7, 1./7, 1./7);
+				f32 dtime;
+				assert(axisAlignedCollision(s, m, v, 0, dtime) == 2);
+				assert(fabs(dtime - 16.1) < 0.001);
+			}
+		}
+	}
+};
+
 struct TestSocket
 {
 	void Run()
@@ -1544,6 +1692,7 @@ void run_tests()
 	TESTPARAMS(TestInventory, idef);
 	//TEST(TestMapBlock);
 	//TEST(TestMapSector);
+	TEST(TestCollision);
 	if(INTERNET_SIMULATOR == false){
 		TEST(TestSocket);
 		dout_con<<"=== BEGIN RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl;
diff --git a/src/tile.h b/src/tile.h
index 8c1f42e0..ae986e79 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -61,6 +61,14 @@ struct AtlasPointer
 	v2f size; // Size in atlas
 	u16 tiled; // X-wise tiling count. If 0, width of atlas is width of image.
 
+	AtlasPointer():
+		id(0),
+		atlas(NULL),
+		pos(0,0),
+		size(1,1),
+		tiled(1)
+	{}
+
 	AtlasPointer(
 			u16 id_,
 			video::ITexture *atlas_=NULL,