diff --git a/code/nel/tools/3d/shape2obj/main.cpp b/code/nel/tools/3d/shape2obj/main.cpp
new file mode 100644
index 000000000..91945bdb6
--- /dev/null
+++ b/code/nel/tools/3d/shape2obj/main.cpp
@@ -0,0 +1,668 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace NLMISC;
+using namespace NL3D;
+using namespace std;
+
+struct CVertex
+{
+ CVector vertex;
+ CVector normal;
+ CUV uv;
+};
+
+bool operator == (const CVertex &v1, const CVertex &v2)
+{
+ return (v1.vertex == v2.vertex) && (v1.normal == v2.normal) && (v1.uv == v2.uv);
+}
+
+bool operator < (const CVertex &v1, const CVertex &v2)
+{
+ return (v1.vertex < v2.vertex);
+}
+
+const CIndexBuffer *getRdrPassPrimitiveBlock(const CMeshMRMGeom *mesh, uint lodId, uint renderPass)
+{
+ return &(mesh->getRdrPassPrimitiveBlock(lodId, renderPass));
+}
+
+// ***************************************************************************
+
+const CIndexBuffer *getRdrPassPrimitiveBlock(const CMeshMRMSkinnedGeom *mesh, uint lodId, uint renderPass)
+{
+ static CIndexBuffer block;
+ mesh->getRdrPassPrimitiveBlock(lodId, renderPass, block);
+ return █
+}
+
+// ***************************************************************************
+
+bool ProcessMeshMRMSkinned(const std::string &filename, IShape *shapeMesh);
+bool ProcessMeshMRM(const std::string &filename, IShape *shapeMesh);
+bool ProcessMesh(const std::string &filename, IShape *shapeMesh);
+
+int main(int argc, char* argv[])
+{
+ if (argc < 2)
+ {
+ cout << "Syntax : shape2obj " << endl;
+
+ return 1;
+ }
+
+ if (!NLMISC::INelContext::isContextInitialised()) new NLMISC::CApplicationContext();
+
+ registerSerial3d();
+ CScene::registerBasics();
+
+ IShape *shapeMesh = NULL;
+
+ CIFile ifile;
+
+ // Sream a shape
+ CShapeStream streamShape;
+
+ string filename = argv[1];
+
+ if (!ifile.open(filename)) return 1;
+
+ try
+ {
+ // Stream it
+ streamShape.serial(ifile);
+
+ // Add the shape
+ shapeMesh = streamShape.getShapePointer();
+ }
+ catch (Exception& e)
+ {
+ cout << "Error : " << e.what() << endl;
+
+ return 1;
+ }
+
+ if (ProcessMeshMRMSkinned(filename, shapeMesh)) return 0;
+ if (ProcessMeshMRM(filename, shapeMesh)) return 0;
+ if (ProcessMesh(filename, shapeMesh)) return 0;
+
+ return 0;
+}
+
+bool ProcessMeshMRMSkinned(const std::string &filename, IShape *shapeMesh)
+{
+ CMeshMRMSkinned *mesh = dynamic_cast(shapeMesh);
+
+ if (!mesh) return false;
+
+ COFile ofile;
+
+ CMeshMRMSkinnedGeom* meshIn = (CMeshMRMSkinnedGeom*)&mesh->getMeshGeom();
+
+ std::vector skinWeights;
+ meshIn->getSkinWeights(skinWeights);
+ CVertexBuffer vertexBuffer;
+ meshIn->getVertexBuffer(vertexBuffer);
+
+ CVertexBufferRead vba;
+ vertexBuffer.lock (vba);
+ uint i, j;
+
+ // **** Select the Lod.
+ uint numLods= meshIn->getNbLod();
+
+ // get the max tris displayed
+ float numMeshFacesMin= (float)meshIn->getLevelDetail().MinFaceUsed;
+ float numMeshFacesMax= (float)meshIn->getLevelDetail().MaxFaceUsed;
+ // find the lod
+ sint lodId = numLods-1;
+
+ // **** First, for the best lod indicate what vertex is used or not. Also index geomorphs to know what real vertex is used
+ vector vertexUsed;
+ // -1 means "not used"
+ vertexUsed.resize(skinWeights.size(), -1);
+ // Parse all triangles.
+ for(i=0;igetNbRdrPass(lodId); ++i)
+ {
+ const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i);
+ CIndexBufferRead iba;
+ pb->lock (iba);
+ if (iba.getFormat() == CIndexBuffer::Indices32)
+ {
+ const uint32 *triPtr= (const uint32 *) iba.getPtr();
+ for(j=0;jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Flag the vertex with its own index => used.
+ vertexUsed[idx]= idx;
+ triPtr++;
+ }
+ }
+ else
+ {
+ const uint16 *triPtr= (const uint16 *) iba.getPtr();
+ for(j=0;jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Flag the vertex with its own index => used.
+ vertexUsed[idx]= idx;
+ triPtr++;
+ }
+ }
+ }
+ // Special for Geomorphs: must take The End target vertex.
+ const std::vector &geomorphs= meshIn->getGeomorphs(lodId);
+ for(i=0;i shadowVertices;
+ vector vertexToVSkin;
+ vertexToVSkin.resize(vertexUsed.size());
+ shadowVertices.reserve(vertexUsed.size());
+ // use a map to remove duplicates (because of UV/normal discontinuities before!!)
+ map shadowVertexMap;
+ uint numMerged= 0;
+ // Skip Geomorphs.
+ for(i=geomorphs.size();imaxW)
+ {
+ matId= sw.MatrixId[j];
+ maxW= sw.Weights[j];
+ }
+ }
+// shadowVert.MatrixId= matId;
+*/
+ // If dont find the shadowVertex in the map.
+ map::iterator it= shadowVertexMap.find(shadowVert);
+ if(it==shadowVertexMap.end())
+ {
+ // Append
+ uint index= shadowVertices.size();
+ vertexToVSkin[i]= index;
+ shadowVertices.push_back(shadowVert);
+ shadowVertexMap.insert(make_pair(shadowVert, index));
+ }
+ else
+ {
+ // Ok, map.
+ vertexToVSkin[i]= it->second;
+ numMerged++;
+ }
+
+ }
+ }
+
+ ofstream ofs(string(filename + ".obj").c_str());
+
+ for(size_t y = 0; y < shadowVertices.size(); ++y)
+ {
+ CVector v = shadowVertices[y].vertex;
+ CVector vn = shadowVertices[y].normal;
+ CUV vt = shadowVertices[y].uv;
+
+ ofs << "v " << v.x << " " << v.y << " " << v.z << endl;
+ ofs << "vn " << vn.x << " " << vn.y << " " << vn.z << endl;
+ ofs << "vt " << vt.U << " " << vt.V << endl;
+ }
+
+ // **** Get All Faces
+ // Final List Of Triangles that match the bone.
+ vector shadowTriangles;
+ shadowTriangles.reserve(1000);
+ // Parse all input tri of the mesh.
+ for(i=0; igetNbRdrPass(lodId); ++i)
+ {
+ ofs << "g pass" << i << endl;
+
+ const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i);
+ CIndexBufferRead iba;
+ pb->lock (iba);
+ if (iba.getFormat() == CIndexBuffer::Indices32)
+ {
+ const uint32 *triPtr= (const uint32 *) iba.getPtr();
+
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+ else
+ {
+ const uint16 *triPtr= (const uint16 *) iba.getPtr();
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+
+ for(size_t pass = 0; pass(shapeMesh);
+
+ if (!mesh) return false;
+
+ COFile ofile;
+
+ CMeshMRMGeom* meshIn = (CMeshMRMGeom*)&mesh->getMeshGeom();
+
+ std::vector skinWeights = meshIn->getSkinWeights();
+ CVertexBuffer vertexBuffer = meshIn->getVertexBuffer();
+
+ CVertexBufferRead vba;
+ vertexBuffer.lock (vba);
+ uint i, j;
+
+ // **** Select the Lod.
+ uint numLods= meshIn->getNbLod();
+
+ // get the max tris displayed
+ float numMeshFacesMin= (float)meshIn->getLevelDetail().MinFaceUsed;
+ float numMeshFacesMax= (float)meshIn->getLevelDetail().MaxFaceUsed;
+ // find the lod
+ sint lodId = numLods-1;
+
+ // **** First, for the best lod indicate what vertex is used or not. Also index geomorphs to know what real vertex is used
+ vector vertexUsed;
+ // -1 means "not used"
+ vertexUsed.resize(skinWeights.size(), -1);
+ // Parse all triangles.
+ for(i=0;igetNbRdrPass(lodId); ++i)
+ {
+ const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i);
+ CIndexBufferRead iba;
+ pb->lock (iba);
+ if (iba.getFormat() == CIndexBuffer::Indices32)
+ {
+ const uint32 *triPtr= (const uint32 *) iba.getPtr();
+ for(j=0;jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Flag the vertex with its own index => used.
+ vertexUsed[idx]= idx;
+ triPtr++;
+ }
+ }
+ else
+ {
+ const uint16 *triPtr= (const uint16 *) iba.getPtr();
+ for(j=0;jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Flag the vertex with its own index => used.
+ vertexUsed[idx]= idx;
+ triPtr++;
+ }
+ }
+ }
+ // Special for Geomorphs: must take The End target vertex.
+ const std::vector &geomorphs= meshIn->getGeomorphs(lodId);
+ for(i=0;i shadowVertices;
+ vector vertexToVSkin;
+ vertexToVSkin.resize(vertexUsed.size());
+ shadowVertices.reserve(vertexUsed.size());
+ // use a map to remove duplicates (because of UV/normal discontinuities before!!)
+ map shadowVertexMap;
+ uint numMerged= 0;
+ // Skip Geomorphs.
+ for(i=geomorphs.size();imaxW)
+ {
+ matId= sw.MatrixId[j];
+ maxW= sw.Weights[j];
+ }
+ }
+// shadowVert.MatrixId= matId;
+*/
+ // If dont find the shadowVertex in the map.
+ map::iterator it= shadowVertexMap.find(shadowVert);
+ if(it==shadowVertexMap.end())
+ {
+ // Append
+ uint index= shadowVertices.size();
+ vertexToVSkin[i]= index;
+ shadowVertices.push_back(shadowVert);
+ shadowVertexMap.insert(make_pair(shadowVert, index));
+ }
+ else
+ {
+ // Ok, map.
+ vertexToVSkin[i]= it->second;
+ numMerged++;
+ }
+
+ }
+ }
+
+ ofstream ofs(string(filename + ".obj").c_str());
+
+ for(size_t y = 0; y < shadowVertices.size(); ++y)
+ {
+ CVector v = shadowVertices[y].vertex;
+ CVector vn = shadowVertices[y].normal;
+ CUV vt = shadowVertices[y].uv;
+
+ ofs << "v " << v.x << " " << v.y << " " << v.z << endl;
+ ofs << "vn " << vn.x << " " << vn.y << " " << vn.z << endl;
+ ofs << "vt " << vt.U << " " << vt.V << endl;
+ }
+
+ // **** Get All Faces
+ // Final List Of Triangles that match the bone.
+ vector shadowTriangles;
+ shadowTriangles.reserve(1000);
+ // Parse all input tri of the mesh.
+ for(i=0; igetNbRdrPass(lodId); ++i)
+ {
+ ofs << "g pass" << i << endl;
+
+ const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i);
+ CIndexBufferRead iba;
+ pb->lock (iba);
+ if (iba.getFormat() == CIndexBuffer::Indices32)
+ {
+ const uint32 *triPtr= (const uint32 *) iba.getPtr();
+
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+ else
+ {
+ const uint16 *triPtr= (const uint16 *) iba.getPtr();
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+
+ for(size_t pass = 0; pass(shapeMesh);
+
+ if (!mesh) return false;
+
+ COFile ofile;
+
+ CMeshGeom* meshIn = (CMeshGeom*)&mesh->getMeshGeom();
+
+ CVertexBuffer vertexBuffer = meshIn->getVertexBuffer();
+
+// CVertexBufferRead vba;
+// vertexBuffer.lock (vba);
+
+ uint i = vertexBuffer.getNumVertices();
+
+ std::vector vertices;
+ meshIn->retrieveVertices(vertices);
+
+ std::vector indices;
+ meshIn->retrieveTriangles(indices);
+
+
+ // **** For all vertices used (not geomorphs), compute vertex Skins.
+ vector shadowVertices;
+ vector vertexToVSkin;
+ vertexToVSkin.resize(indices.size());
+ shadowVertices.reserve(indices.size());
+ // use a map to remove duplicates (because of UV/normal discontinuities before!!)
+ map shadowVertexMap;
+ uint numMerged= 0;
+ // Skip Geomorphs.
+ for(i=0;imaxW)
+ {
+ matId= sw.MatrixId[j];
+ maxW= sw.Weights[j];
+ }
+ }
+
+ // If dont find the shadowVertex in the map.
+ map::iterator it= shadowVertexMap.find(shadowVert);
+ if(it==shadowVertexMap.end())
+ {
+ // Append
+ uint index= shadowVertices.size();
+ vertexToVSkin[i]= index;
+ shadowVertices.push_back(shadowVert);
+ shadowVertexMap.insert(make_pair(shadowVert, index));
+ }
+ else
+ {
+ // Ok, map.
+ vertexToVSkin[i]= it->second;
+ numMerged++;
+ }
+ }
+
+ ofstream ofs(string(filename + ".obj").c_str());
+
+ for(size_t y = 0; y < shadowVertices.size(); ++y)
+ {
+ CVector v = shadowVertices[y].vertex;
+ CVector vn = shadowVertices[y].normal;
+ CUV vt = shadowVertices[y].uv;
+
+ ofs << "v " << v.x << " " << v.y << " " << v.z << endl;
+ ofs << "vn " << vn.x << " " << vn.y << " " << vn.z << endl;
+ ofs << "vt " << vt.U << " " << vt.V << endl;
+ }
+
+ // **** Get All Faces
+ // Final List Of Triangles that match the bone.
+ vector shadowTriangles;
+ shadowTriangles.reserve(1000);
+ // Parse all input tri of the mesh.
+ for(i=0; igetNbRdrPass(lodId); ++i)
+ {
+ ofs << "g pass" << i << endl;
+
+ const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i);
+ CIndexBufferRead iba;
+ pb->lock (iba);
+ if (iba.getFormat() == CIndexBuffer::Indices32)
+ {
+ const uint32 *triPtr= (const uint32 *) iba.getPtr();
+
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+ else
+ {
+ const uint16 *triPtr= (const uint16 *) iba.getPtr();
+ for(j=0; jgetNumIndexes(); ++j)
+ {
+ uint idx= *triPtr;
+ // Get the real Vertex (ie not the geomporhed one).
+ idx= vertexUsed[idx];
+ // Get the ShadowVertex associated
+ idx= vertexToVSkin[idx];
+
+ shadowTriangles.push_back(idx);
+ triPtr++;
+ }
+ }
+
+ for(size_t pass = 0; pass