Add cancel button to password change menu. (#5720)
[oweals/minetest.git] / src / mesh.cpp
index 38b3d97bc44d0bde6d6b9322c2c631e681bb699c..824d6891bdc1113d4917aa33c4c747e2cbb2ee49 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mesh.h"
 #include "debug.h"
 #include "log.h"
+#include "irrMap.h"
 #include <iostream>
 #include <IAnimatedMesh.h>
 #include <SAnimatedMesh.h>
@@ -32,6 +33,33 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
 #endif
 
+inline static void applyShadeFactor(video::SColor& color, float factor)
+{
+       color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
+       color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255));
+       color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255));
+}
+
+void applyFacesShading(video::SColor &color, const v3f &normal)
+{
+       /*
+               Some drawtypes have normals set to (0, 0, 0), this must result in
+               maximum brightness: shade factor 1.0.
+               Shade factors for aligned cube faces are:
+               +Y 1.000000 sqrt(1.0)
+               -Y 0.447213 sqrt(0.2)
+               +-X 0.670820 sqrt(0.45)
+               +-Z 0.836660 sqrt(0.7)
+       */
+       float x2 = normal.X * normal.X;
+       float y2 = normal.Y * normal.Y;
+       float z2 = normal.Z * normal.Z;
+       if (normal.Y < 0)
+               applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2);
+       else if ((x2 > 1e-3) || (z2 > 1e-3))
+               applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2);
+}
+
 scene::IAnimatedMesh* createCubeMesh(v3f scale)
 {
        video::SColor c(255,255,255,255);
@@ -93,26 +121,25 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
 
 void scaleMesh(scene::IMesh *mesh, v3f scale)
 {
-       if(mesh == NULL)
+       if (mesh == NULL)
                return;
 
-       core::aabbox3d<f32> bbox;
-       bbox.reset(0,0,0);
+       aabb3f bbox;
+       bbox.reset(0, 0, 0);
 
-       u16 mc = mesh->getMeshBufferCount();
-       for(u16 j=0; j<mc; j++)
-       {
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 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;
-               }
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
+
                buf->recalculateBoundingBox();
 
                // calculate total bounding box
-               if(j == 0)
+               if (j == 0)
                        bbox = buf->getBoundingBox();
                else
                        bbox.addInternalBox(buf->getBoundingBox());
@@ -122,26 +149,25 @@ void scaleMesh(scene::IMesh *mesh, v3f scale)
 
 void translateMesh(scene::IMesh *mesh, v3f vec)
 {
-       if(mesh == NULL)
+       if (mesh == NULL)
                return;
 
-       core::aabbox3d<f32> bbox;
-       bbox.reset(0,0,0);
+       aabb3f bbox;
+       bbox.reset(0, 0, 0);
 
-       u16 mc = mesh->getMeshBufferCount();
-       for(u16 j=0; j<mc; j++)
-       {
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 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 += vec;
-               }
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
+
                buf->recalculateBoundingBox();
 
                // calculate total bounding box
-               if(j == 0)
+               if (j == 0)
                        bbox = buf->getBoundingBox();
                else
                        bbox.addInternalBox(buf->getBoundingBox());
@@ -149,21 +175,35 @@ void translateMesh(scene::IMesh *mesh, v3f vec)
        mesh->setBoundingBox(bbox);
 }
 
+
 void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
 {
-       if(mesh == NULL)
+       if (mesh == NULL)
                return;
-       
-       u16 mc = mesh->getMeshBufferCount();
-       for(u16 j=0; j<mc; j++)
-       {
+
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 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;
-               }
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Color = color;
+       }
+}
+
+void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
+{
+       const u32 stride = getVertexPitchFromType(buf->getVertexType());
+       u32 vertex_count = buf->getVertexCount();
+       u8 *vertices = (u8 *) buf->getVertices();
+       for (u32 i = 0; i < vertex_count; i++) {
+               video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride);
+               video::SColor *vc = &(vertex->Color);
+               // Reset color
+               *vc = *buffercolor;
+               // Apply shading
+               applyFacesShading(*vc, vertex->Normal);
        }
 }
 
@@ -172,101 +212,159 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh,
                const video::SColor &colorY,
                const video::SColor &colorZ)
 {
-       if(mesh == NULL)
+       if (mesh == NULL)
                return;
-       
+
        u16 mc = mesh->getMeshBufferCount();
-       for(u16 j=0; j<mc; j++)
-       {
+       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;
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       f32 x = fabs(vertex->Normal.X);
+                       f32 y = fabs(vertex->Normal.Y);
+                       f32 z = fabs(vertex->Normal.Z);
+                       if (x >= y && x >= z)
+                               vertex->Color = colorX;
+                       else if (y >= z)
+                               vertex->Color = colorY;
                        else
-                               vertices[i].Color = colorZ;
+                               vertex->Color = colorZ;
+               }
+       }
+}
+
+void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
+               const video::SColor &color)
+{
+       if (!mesh)
+               return;
 
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       if (normal == vertex->Normal) {
+                               vertex->Color = color;
+                       }
                }
        }
 }
 
+void rotateMeshXYby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees);
+       }
+}
+
+void rotateMeshXZby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees);
+       }
+}
+
+void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees);
+       }
+}
+
 void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
-{              
-       int axisdir = facedir>>2;
+{
+       int axisdir = facedir >> 2;
        facedir &= 0x03;
 
        u16 mc = mesh->getMeshBufferCount();
-       for(u16 j = 0; j < mc; j++)
-       {
+       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++)
-               {
-                       switch (axisdir)
-                       {
-                       case 0:
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateXZBy(-90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateXZBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateXZBy(90);
-                               break;
-                       case 1: // z+
-                               vertices[i].Pos.rotateYZBy(90);
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateXYBy(90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateXYBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateXYBy(-90);
-                               break;
-                       case 2: //z-
-                               vertices[i].Pos.rotateYZBy(-90);
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateXYBy(-90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateXYBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateXYBy(90);
-                               break;
-                       case 3:  //x+
-                               vertices[i].Pos.rotateXYBy(-90);
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateYZBy(90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateYZBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateYZBy(-90);
-                               break;
-                       case 4:  //x-
-                               vertices[i].Pos.rotateXYBy(90);
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateYZBy(-90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateYZBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateYZBy(90);
-                               break;
-                       case 5:
-                               vertices[i].Pos.rotateXYBy(-180);
-                               if(facedir == 1)
-                                       vertices[i].Pos.rotateXZBy(90);
-                               else if(facedir == 2)
-                                       vertices[i].Pos.rotateXZBy(180);
-                               else if(facedir == 3)
-                                       vertices[i].Pos.rotateXZBy(-90);
-                               break;
-                       default:
-                               break;
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       switch (axisdir) {
+                               case 0:
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXZBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXZBy(90);
+                                       break;
+                               case 1: // z+
+                                       vertex->Pos.rotateYZBy(90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXYBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXYBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXYBy(-90);
+                                       break;
+                               case 2: //z-
+                                       vertex->Pos.rotateYZBy(-90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXYBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXYBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXYBy(90);
+                                       break;
+                               case 3:  //x+
+                                       vertex->Pos.rotateXYBy(-90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateYZBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateYZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateYZBy(-90);
+                                       break;
+                               case 4:  //x-
+                                       vertex->Pos.rotateXYBy(90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateYZBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateYZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateYZBy(90);
+                                       break;
+                               case 5:
+                                       vertex->Pos.rotateXYBy(-180);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXZBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXZBy(-90);
+                                       break;
+                               default:
+                                       break;
                        }
                }
        }
@@ -274,13 +372,12 @@ void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
 
 void recalculateBoundingBox(scene::IMesh *src_mesh)
 {
-       core::aabbox3d<f32> bbox;
+       aabb3f bbox;
        bbox.reset(0,0,0);
-       for(u16 j = 0; j < src_mesh->getMeshBufferCount(); j++)
-       {
+       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
                scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j);
                buf->recalculateBoundingBox();
-               if(j == 0)
+               if (j == 0)
                        bbox = buf->getBoundingBox();
                else
                        bbox.addInternalBox(buf->getBoundingBox());
@@ -288,26 +385,61 @@ void recalculateBoundingBox(scene::IMesh *src_mesh)
        src_mesh->setBoundingBox(bbox);
 }
 
-scene::IMesh* cloneMesh(scene::IMesh *src_mesh)
+scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
+{
+       switch (mesh_buffer->getVertexType()) {
+       case video::EVT_STANDARD: {
+               video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       case video::EVT_2TCOORDS: {
+               video::S3DVertex2TCoords *v =
+                       (video::S3DVertex2TCoords *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBufferTangents *cloned_buffer =
+                       new scene::SMeshBufferTangents();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       case video::EVT_TANGENTS: {
+               video::S3DVertexTangents *v =
+                       (video::S3DVertexTangents *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBufferTangents *cloned_buffer =
+                       new scene::SMeshBufferTangents();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       }
+       // This should not happen.
+       sanity_check(false);
+       return NULL;
+}
+
+scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
 {
        scene::SMesh* dst_mesh = new scene::SMesh();
-       for(u16 j = 0; j < src_mesh->getMeshBufferCount(); j++)
-       {
-               scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j);
-               video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices();
-               u16 *indices = (u16*)buf->getIndices();
-               scene::SMeshBuffer *temp_buf = new scene::SMeshBuffer();
-               temp_buf->append(vertices, buf->getVertexCount(),
-                       indices, buf->getIndexCount());
+       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
+               scene::IMeshBuffer *temp_buf = cloneMeshBuffer(
+                       src_mesh->getMeshBuffer(j));
                dst_mesh->addMeshBuffer(temp_buf);
                temp_buf->drop();
+
        }
-       return dst_mesh;                                        
+       return dst_mesh;
 }
 
-scene::IMesh* convertNodeboxNodeToMesh(ContentFeatures *f)
+scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
+               const f32 *uv_coords, float expand)
 {
        scene::SMesh* dst_mesh = new scene::SMesh();
+
        for (u16 j = 0; j < 6; j++)
        {
                scene::IMeshBuffer *buf = new scene::SMeshBuffer();
@@ -316,57 +448,49 @@ scene::IMesh* convertNodeboxNodeToMesh(ContentFeatures *f)
                dst_mesh->addMeshBuffer(buf);
                buf->drop();
        }
-       
-       video::SColor c(255,255,255,255);       
 
-       std::vector<aabb3f> boxes = f->node_box.fixed;
-               
-       for(std::vector<aabb3f>::iterator
+       video::SColor c(255,255,255,255);
+
+       for (std::vector<aabb3f>::const_iterator
                        i = boxes.begin();
-                       i != boxes.end(); i++)
+                       i != boxes.end(); ++i)
        {
                aabb3f box = *i;
+               box.repair();
 
-               f32 temp;
-               if (box.MinEdge.X > box.MaxEdge.X)
-                       {
-                               temp=box.MinEdge.X;
-                               box.MinEdge.X=box.MaxEdge.X;
-                               box.MaxEdge.X=temp;
-                       }
-               if (box.MinEdge.Y > box.MaxEdge.Y)
-                       {
-                               temp=box.MinEdge.Y;
-                               box.MinEdge.Y=box.MaxEdge.Y;
-                               box.MaxEdge.Y=temp;
-                       }
-               if (box.MinEdge.Z > box.MaxEdge.Z)
-                       {
-                               temp=box.MinEdge.Z;
-                               box.MinEdge.Z=box.MaxEdge.Z;
-                               box.MaxEdge.Z=temp;
-                       }
-               // Compute texture coords
-               f32 tx1 = (box.MinEdge.X/BS)+0.5;
-               f32 ty1 = (box.MinEdge.Y/BS)+0.5;
-               f32 tz1 = (box.MinEdge.Z/BS)+0.5;
-               f32 tx2 = (box.MaxEdge.X/BS)+0.5;
-               f32 ty2 = (box.MaxEdge.Y/BS)+0.5;
-               f32 tz2 = (box.MaxEdge.Z/BS)+0.5;
-               f32 txc[24] = {
+               box.MinEdge.X -= expand;
+               box.MinEdge.Y -= expand;
+               box.MinEdge.Z -= expand;
+               box.MaxEdge.X += expand;
+               box.MaxEdge.Y += expand;
+               box.MaxEdge.Z += expand;
+
+               // Compute texture UV coords
+               f32 tx1 = (box.MinEdge.X / BS) + 0.5;
+               f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
+               f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
+               f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
+               f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
+               f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
+
+               f32 txc_default[24] = {
                        // up
-                       tx1, 1-tz2, tx2, 1-tz1,
+                       tx1, 1 - tz2, tx2, 1 - tz1,
                        // down
                        tx1, tz1, tx2, tz2,
                        // right
-                       tz1, 1-ty2, tz2, 1-ty1,
+                       tz1, 1 - ty2, tz2, 1 - ty1,
                        // left
-                       1-tz2, 1-ty2, 1-tz1, 1-ty1,
+                       1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1,
                        // back
-                       1-tx2, 1-ty2, 1-tx1, 1-ty1,
+                       1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1,
                        // front
-                       tx1, 1-ty2, tx2, 1-ty1,
+                       tx1, 1 - ty2, tx2, 1 - ty1,
                };
+
+               // use default texture UV mapping if not provided
+               const f32 *txc = uv_coords ? uv_coords : txc_default;
+
                v3f min = box.MinEdge;
                v3f max = box.MaxEdge;
 
@@ -412,5 +536,599 @@ scene::IMesh* convertNodeboxNodeToMesh(ContentFeatures *f)
                        buf->append(vertices + j, 4, indices, 6);
                }
        }
-       return dst_mesh;                                        
+       return dst_mesh;
+}
+
+struct vcache
+{
+       core::array<u32> tris;
+       float score;
+       s16 cachepos;
+       u16 NumActiveTris;
+};
+
+struct tcache
+{
+       u16 ind[3];
+       float score;
+       bool drawn;
+};
+
+const u16 cachesize = 32;
+
+float FindVertexScore(vcache *v)
+{
+       const float CacheDecayPower = 1.5f;
+       const float LastTriScore = 0.75f;
+       const float ValenceBoostScale = 2.0f;
+       const float ValenceBoostPower = 0.5f;
+       const float MaxSizeVertexCache = 32.0f;
+
+       if (v->NumActiveTris == 0)
+       {
+               // No tri needs this vertex!
+               return -1.0f;
+       }
+
+       float Score = 0.0f;
+       int CachePosition = v->cachepos;
+       if (CachePosition < 0)
+       {
+               // Vertex is not in FIFO cache - no score.
+       }
+       else
+       {
+               if (CachePosition < 3)
+               {
+                       // This vertex was used in the last triangle,
+                       // so it has a fixed score.
+                       Score = LastTriScore;
+               }
+               else
+               {
+                       // Points for being high in the cache.
+                       const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
+                       Score = 1.0f - (CachePosition - 3) * Scaler;
+                       Score = powf(Score, CacheDecayPower);
+               }
+       }
+
+       // Bonus points for having a low number of tris still to
+       // use the vert, so we get rid of lone verts quickly.
+       float ValenceBoost = powf(v->NumActiveTris,
+                               -ValenceBoostPower);
+       Score += ValenceBoostScale * ValenceBoost;
+
+       return Score;
+}
+
+/*
+       A specialized LRU cache for the Forsyth algorithm.
+*/
+
+class f_lru
+{
+
+public:
+       f_lru(vcache *v, tcache *t): vc(v), tc(t)
+       {
+               for (u16 i = 0; i < cachesize; i++)
+               {
+                       cache[i] = -1;
+               }
+       }
+
+       // Adds this vertex index and returns the highest-scoring triangle index
+       u32 add(u16 vert, bool updatetris = false)
+       {
+               bool found = false;
+
+               // Mark existing pos as empty
+               for (u16 i = 0; i < cachesize; i++)
+               {
+                       if (cache[i] == vert)
+                       {
+                               // Move everything down
+                               for (u16 j = i; j; j--)
+                               {
+                                       cache[j] = cache[j - 1];
+                               }
+
+                               found = true;
+                               break;
+                       }
+               }
+
+               if (!found)
+               {
+                       if (cache[cachesize-1] != -1)
+                               vc[cache[cachesize-1]].cachepos = -1;
+
+                       // Move everything down
+                       for (u16 i = cachesize - 1; i; i--)
+                       {
+                               cache[i] = cache[i - 1];
+                       }
+               }
+
+               cache[0] = vert;
+
+               u32 highest = 0;
+               float hiscore = 0;
+
+               if (updatetris)
+               {
+                       // Update cache positions
+                       for (u16 i = 0; i < cachesize; i++)
+                       {
+                               if (cache[i] == -1)
+                                       break;
+
+                               vc[cache[i]].cachepos = i;
+                               vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
+                       }
+
+                       // Update triangle scores
+                       for (u16 i = 0; i < cachesize; i++)
+                       {
+                               if (cache[i] == -1)
+                                       break;
+
+                               const u16 trisize = vc[cache[i]].tris.size();
+                               for (u16 t = 0; t < trisize; t++)
+                               {
+                                       tcache *tri = &tc[vc[cache[i]].tris[t]];
+
+                                       tri->score =
+                                               vc[tri->ind[0]].score +
+                                               vc[tri->ind[1]].score +
+                                               vc[tri->ind[2]].score;
+
+                                       if (tri->score > hiscore)
+                                       {
+                                               hiscore = tri->score;
+                                               highest = vc[cache[i]].tris[t];
+                                       }
+                               }
+                       }
+               }
+
+               return highest;
+       }
+
+private:
+       s32 cache[cachesize];
+       vcache *vc;
+       tcache *tc;
+};
+
+/**
+Vertex cache optimization according to the Forsyth paper:
+http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+
+The function is thread-safe (read: you can optimize several meshes in different threads)
+
+\param mesh Source mesh for the operation.  */
+scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
+{
+       if (!mesh)
+               return 0;
+
+       scene::SMesh *newmesh = new scene::SMesh();
+       newmesh->BoundingBox = mesh->getBoundingBox();
+
+       const u32 mbcount = mesh->getMeshBufferCount();
+
+       for (u32 b = 0; b < mbcount; ++b)
+       {
+               const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
+
+               if (mb->getIndexType() != video::EIT_16BIT)
+               {
+                       //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
+                       newmesh->drop();
+                       return 0;
+               }
+
+               const u32 icount = mb->getIndexCount();
+               const u32 tcount = icount / 3;
+               const u32 vcount = mb->getVertexCount();
+               const u16 *ind = mb->getIndices();
+
+               vcache *vc = new vcache[vcount];
+               tcache *tc = new tcache[tcount];
+
+               f_lru lru(vc, tc);
+
+               // init
+               for (u16 i = 0; i < vcount; i++)
+               {
+                       vc[i].score = 0;
+                       vc[i].cachepos = -1;
+                       vc[i].NumActiveTris = 0;
+               }
+
+               // First pass: count how many times a vert is used
+               for (u32 i = 0; i < icount; i += 3)
+               {
+                       vc[ind[i]].NumActiveTris++;
+                       vc[ind[i + 1]].NumActiveTris++;
+                       vc[ind[i + 2]].NumActiveTris++;
+
+                       const u32 tri_ind = i/3;
+                       tc[tri_ind].ind[0] = ind[i];
+                       tc[tri_ind].ind[1] = ind[i + 1];
+                       tc[tri_ind].ind[2] = ind[i + 2];
+               }
+
+               // Second pass: list of each triangle
+               for (u32 i = 0; i < tcount; i++)
+               {
+                       vc[tc[i].ind[0]].tris.push_back(i);
+                       vc[tc[i].ind[1]].tris.push_back(i);
+                       vc[tc[i].ind[2]].tris.push_back(i);
+
+                       tc[i].drawn = false;
+               }
+
+               // Give initial scores
+               for (u16 i = 0; i < vcount; i++)
+               {
+                       vc[i].score = FindVertexScore(&vc[i]);
+               }
+               for (u32 i = 0; i < tcount; i++)
+               {
+                       tc[i].score =
+                                       vc[tc[i].ind[0]].score +
+                                       vc[tc[i].ind[1]].score +
+                                       vc[tc[i].ind[2]].score;
+               }
+
+               switch(mb->getVertexType())
+               {
+                       case video::EVT_STANDARD:
+                       {
+                               video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
+
+                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertex, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j = 0; j < 3; j++)
+                                       {
+                                               vcache *vert = &vc[tc[highest].ind[j]];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2], true);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+                       }
+                       break;
+                       case video::EVT_2TCOORDS:
+                       {
+                               video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
+
+                               scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j = 0; j < 3; j++)
+                                       {
+                                               vcache *vert = &vc[tc[highest].ind[j]];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2]);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+
+                       }
+                       break;
+                       case video::EVT_TANGENTS:
+                       {
+                               video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
+
+                               scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j = 0; j < 3; j++)
+                                       {
+                                               vcache *vert = &vc[tc[highest].ind[j]];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2]);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+                       }
+                       break;
+               }
+
+               delete [] vc;
+               delete [] tc;
+
+       } // for each meshbuffer
+
+       return newmesh;
 }