diff --git a/code/ACLoader.cpp b/code/ACLoader.cpp index c1cb3928b..e86a3ea7d 100644 --- a/code/ACLoader.cpp +++ b/code/ACLoader.cpp @@ -50,7 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ACLoader.h" #include "ParsingUtils.h" #include "fast_atof.h" - +//#include "Subdivision.h" using namespace Assimp; @@ -159,7 +159,7 @@ void AC3DImporter::LoadObjectSection(std::vector& objects) Object& obj = objects.back(); aiLight* light = NULL; - if (!ASSIMP_stricmp(buffer,"light")) + if (!ASSIMP_strincmp(buffer,"light",5)) { // This is a light source. Add it to the list mLights->push_back(light = new aiLight()); @@ -173,7 +173,22 @@ void AC3DImporter::LoadObjectSection(std::vector& objects) light->mName.length = ::sprintf(light->mName.data,"ACLight_%i",mLights->size()-1); obj.name = std::string( light->mName.data ); + DefaultLogger::get()->debug("AC3D: Light source encountered"); + obj.type = Object::Light; } + else if (!ASSIMP_strincmp(buffer,"group",5)) + { + obj.type = Object::Group; + } + else if (!ASSIMP_strincmp(buffer,"poly",4)) + { + obj.type = Object::Poly; + } + else if (!ASSIMP_strincmp(buffer,"world",5)) + { + obj.type = Object::World; + } + while (GetNextLine()) { @@ -213,6 +228,11 @@ void AC3DImporter::LoadObjectSection(std::vector& objects) SkipSpaces(&buffer); AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texRepeat); } + else if (TokenMatch(buffer,"texoff",6)) + { + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texOffset); + } else if (TokenMatch(buffer,"rot",3)) { SkipSpaces(&buffer); @@ -223,6 +243,11 @@ void AC3DImporter::LoadObjectSection(std::vector& objects) SkipSpaces(&buffer); AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,3,&obj.translation); } + else if (TokenMatch(buffer,"subdiv",6)) + { + SkipSpaces(&buffer); + obj.subDiv = strtol10(buffer,&buffer); + } else if (TokenMatch(buffer,"numvert",7)) { SkipSpaces(&buffer); @@ -352,6 +377,17 @@ void AC3DImporter::ConvertMaterial(const Object& object, { s.Set(object.texture); matDest.AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // UV transformation + if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y || + object.texOffset.x || object.texOffset.y) + { + aiUVTransform transform; + transform.mScaling = object.texRepeat; + transform.mTranslation = object.texOffset; + matDest.AddProperty((float*)&transform,sizeof(aiUVTransform), + AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } } matDest.AddProperty(&matSrc.rgb,1, AI_MATKEY_COLOR_DIFFUSE); @@ -377,9 +413,11 @@ void AC3DImporter::ConvertMaterial(const Object& object, aiNode* AC3DImporter::ConvertObjectSection(Object& object, std::vector& meshes, std::vector& outMaterials, - const std::vector& materials) + const std::vector& materials, + aiNode* parent) { aiNode* node = new aiNode(); + node->mParent = parent; if (object.vertices.size()) { if (!object.surfaces.size() || !object.numRefs) @@ -434,7 +472,7 @@ aiNode* AC3DImporter::ConvertObjectSection(Object& object, register unsigned int idx = (*it).mat; if (idx >= needMat.size()) { - DefaultLogger::get()->error("AC3D: material index os out of range"); + DefaultLogger::get()->error("AC3D: material index is out of range"); idx = 0; } if ((*it).entries.empty()) @@ -539,13 +577,14 @@ aiNode* AC3DImporter::ConvertObjectSection(Object& object, face.mIndices[i] = cur++; // copy vertex positions - *vertices = object.vertices[entry.first]; + *vertices = object.vertices[entry.first] + object.translation; - // copy texture coordinates (apply the UV offset) + + // copy texture coordinates if (uv) { - uv->x = entry.second.x * object.texRepeat.x; - uv->y = entry.second.y * object.texRepeat.y; + uv->x = entry.second.x; + uv->y = entry.second.y; ++uv; } } @@ -571,11 +610,11 @@ aiNode* AC3DImporter::ConvertObjectSection(Object& object, // copy vertex positions *vertices++ = object.vertices[(*it2).first]; - // copy texture coordinates (apply the UV offset) + // copy texture coordinates if (uv) { - uv->x = (*it2).second.x * object.texRepeat.x; - uv->y = (*it2).second.y * object.texRepeat.y; + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; ++uv; } @@ -591,18 +630,65 @@ aiNode* AC3DImporter::ConvertObjectSection(Object& object, if (uv) { - uv->x = (*it2).second.x * object.texRepeat.x; - uv->y = (*it2).second.y * object.texRepeat.y; + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; ++uv; } } } } } +#if 0 + // Now apply catmull clark subdivision if necessary. However, this is + // not *absolutely* correct: it might be we split a mesh up into + // multiple sub meshes, one for each material. AC3D doesn't do that + // in its subdivision implementation, so our output *could* look + // different in some cases. + + if (object.subDiv) + { + Subdivider* div = Subdivider::Create(Subdivider::CATMULL_CLARKE); + div->Subdivide(mesh,object.subDiv); + delete div; + } +#endif } } } + if (object.name.length()) + node->mName.Set(object.name); + else + { + // generate a name depending on the type of the node + switch (object.type) + { + case Object::Group: + node->mName.length = ::sprintf(node->mName.data,"ACGroup_%i",groups++); + break; + case Object::Poly: + node->mName.length = ::sprintf(node->mName.data,"ACPoly_%i",polys++); + break; + case Object::Light: + node->mName.length = ::sprintf(node->mName.data,"ACLight_%i",lights++); + break; + + // there shouldn't be more than one world, but we don't care + case Object::World: + node->mName.length = ::sprintf(node->mName.data,"ACWorld_%i",worlds++); + break; + } + } + + + // setup the local transformation matrix of the object + // compute the transformation offset to the parent node + node->mTransformation = aiMatrix4x4 ( object.rotation ); + + node->mTransformation.a4 = object.translation.x; + node->mTransformation.b4 = object.translation.y; + node->mTransformation.c4 = object.translation.z; + // add children to the object if (object.children.size()) { @@ -610,20 +696,10 @@ aiNode* AC3DImporter::ConvertObjectSection(Object& object, node->mChildren = new aiNode*[node->mNumChildren]; for (unsigned int i = 0; i < node->mNumChildren;++i) { - node->mChildren[i] = ConvertObjectSection(object.children[i],meshes,outMaterials,materials); - node->mChildren[i]->mParent = node; + node->mChildren[i] = ConvertObjectSection(object.children[i],meshes,outMaterials,materials,node); } } - node->mName.Set(object.name); - - // setup the local transformation matrix of the object - node->mTransformation = aiMatrix4x4 ( object.rotation ); - - node->mTransformation.a4 = object.translation.x; - node->mTransformation.b4 = -object.translation.y; - node->mTransformation.c4 = object.translation.z; - return node; } @@ -653,6 +729,8 @@ void AC3DImporter::InternReadFile( const std::string& pFile, buffer = &mBuffer2[0]; mNumMeshes = 0; + lights = polys = worlds = groups = 0; + if (::strncmp(buffer,"AC3D",4)) throw new ImportErrorException("AC3D: No valid AC3D file, magic sequence not found"); @@ -731,7 +809,7 @@ void AC3DImporter::InternReadFile( const std::string& pFile, if (!::strncmp( pScene->mRootNode->mName.data, "Node", 4)) pScene->mRootNode->mName.Set(""); - // build output arrays + // copy meshes if (meshes.empty()) { throw new ImportErrorException("An unknown error occured during converting"); @@ -740,11 +818,12 @@ void AC3DImporter::InternReadFile( const std::string& pFile, pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; ::memcpy(pScene->mMeshes,&meshes[0],pScene->mNumMeshes*sizeof(void*)); - + // copy materials pScene->mNumMaterials = (unsigned int)omaterials.size(); pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; ::memcpy(pScene->mMaterials,&omaterials[0],pScene->mNumMaterials*sizeof(void*)); + // copy lights pScene->mNumLights = (unsigned int)lights.size(); if (lights.size()) { diff --git a/code/ACLoader.h b/code/ACLoader.h index 64ad999f1..bc657c267 100644 --- a/code/ACLoader.h +++ b/code/ACLoader.h @@ -113,9 +113,20 @@ protected: // Represents an AC3D object struct Object { + enum Type + { + World = 0x0, + Poly = 0x1, + Group = 0x2, + Light = 0x4 + } type; + + Object() : texRepeat(1.f,1.f) , numRefs (0) + , subDiv (0) + , type (World) {} // name of the object @@ -128,7 +139,7 @@ protected: std::string texture; // texture repat factors (scaling for all coordinates) - aiVector2D texRepeat; + aiVector2D texRepeat, texOffset; // rotation matrix aiMatrix3x3 rotation; @@ -144,6 +155,10 @@ protected: // number of indices (= num verts in verbose format) unsigned int numRefs; + + // number of subdivisions to be performed on the + // imported data + unsigned int subDiv; }; @@ -208,7 +223,8 @@ private: aiNode* ConvertObjectSection(Object& object, std::vector& meshes, std::vector& outMaterials, - const std::vector& materials); + const std::vector& materials, + aiNode* parent = NULL); // ------------------------------------------------------------------- @@ -239,6 +255,9 @@ private: // current list of light sources std::vector* mLights; + + // name counters + unsigned int lights, groups, polys, worlds; }; } // end of namespace Assimp diff --git a/code/AssimpPCH.h b/code/AssimpPCH.h index 3caa36b12..a22cf340e 100644 --- a/code/AssimpPCH.h +++ b/code/AssimpPCH.h @@ -109,14 +109,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # include "../include/BoostWorkaround/boost/scoped_ptr.hpp" # include "../include/BoostWorkaround/boost/scoped_array.hpp" # include "../include/BoostWorkaround/boost/format.hpp" -# include "../include/BoostWorkaround/boost/common_factor_rt.hpp" +# include "../include/BoostWorkaround/boost/foreach.hpp" #else # include # include # include -# include # include #endif diff --git a/code/BaseImporter.cpp b/code/BaseImporter.cpp index 157c38fa3..3c94c48d9 100644 --- a/code/BaseImporter.cpp +++ b/code/BaseImporter.cpp @@ -175,6 +175,9 @@ struct BatchData // List of all imports std::list requests; + + // Base path + std::string pathBase; }; // ------------------------------------------------------------------------------------------------ @@ -202,21 +205,64 @@ BatchLoader::~BatchLoader() delete data; } +// ------------------------------------------------------------------------------------------------ +void BatchLoader::SetBasePath (const std::string& pBase) +{ + BatchData* data = ( BatchData* )pimpl; + data->pathBase = pBase; + + // file name? we just need the directory + std::string::size_type ss,ss2; + if (std::string::npos != (ss = data->pathBase.find_first_of('.'))) + { + if (std::string::npos != (ss2 = data->pathBase.find_last_of('\\')) || + std::string::npos != (ss2 = data->pathBase.find_last_of('/'))) + { + if (ss > ss2) + data->pathBase.erase(ss2,data->pathBase.length()-ss2); + } + else return; + } + + // make sure the directory is terminated properly + char s; + if ((s = *(data->pathBase.end()-1)) != '\\' && s != '/') + data->pathBase.append("\\"); +} + // ------------------------------------------------------------------------------------------------ void BatchLoader::AddLoadRequest (const std::string& file, unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/) { + ai_assert(!file.empty()); + // no threaded implementation for the moment BatchData* data = ( BatchData* )pimpl; - std::list::iterator it = std::find(data->requests.begin(), - data->requests.end(), file); - if (it != data->requests.end()) + std::string real; + + // build a full path if this is a relative path and + // we have a new base directory given + if (file.length() > 2 && file[1] != ':' && data->pathBase.length()) { - (*it).refCnt++; - return; + real = data->pathBase + file; } - data->requests.push_back(LoadRequest(file,steps,map)); + else real = file; + + // check whether we have this loading request already + std::list::iterator it; + for (it = data->requests.begin();it != data->requests.end(); ++it) + { + // Call IOSystem's path comparison function here + if (data->pIOSystem->ComparePaths((*it).file,real)) + { + (*it).refCnt++; + return; + } + } + + // no, we don't have it. So add it to the queue ... + data->requests.push_back(LoadRequest(real,steps,map)); } // ------------------------------------------------------------------------------------------------ @@ -224,16 +270,27 @@ aiScene* BatchLoader::GetImport (const std::string& file) { // no threaded implementation for the moment BatchData* data = ( BatchData* )pimpl; - std::list::iterator it = std::find(data->requests.begin(), - data->requests.end(), file); - if (it != data->requests.end() && (*it).loaded) + std::string real; + + // build a full path if this is a relative path and + // we have a new base directory given + if (file.length() > 2 && file[1] != ':' && data->pathBase.length()) { - aiScene* sc = (*it).scene; - if (!(--(*it).refCnt)) + real = data->pathBase + file; + } + else real = file; + for (std::list::iterator it = data->requests.begin();it != data->requests.end(); ++it) + { + // Call IOSystem's path comparison function here + if (data->pIOSystem->ComparePaths((*it).file,real) && (*it).loaded) { - data->requests.erase(it); + aiScene* sc = (*it).scene; + if (!(--(*it).refCnt)) + { + data->requests.erase(it); + } + return sc; } - return sc; } return NULL; } @@ -257,9 +314,16 @@ void BatchLoader::LoadAll() data->pImporter->mIntProperties = (*it).map.ints; data->pImporter->mStringProperties = (*it).map.strings; + if (!DefaultLogger::isNullLogger()) + { + DefaultLogger::get()->info("%%% BEGIN EXTERNAL FILE %%%"); + DefaultLogger::get()->info("File: " + (*it).file); + } data->pImporter->ReadFile((*it).file,pp); (*it).scene = const_cast(data->pImporter->GetOrphanedScene()); (*it).loaded = true; + + DefaultLogger::get()->info("%%% END EXTERNAL FILE %%%"); } } diff --git a/code/BaseImporter.h b/code/BaseImporter.h index 898ff7d73..0742a54d6 100644 --- a/code/BaseImporter.h +++ b/code/BaseImporter.h @@ -275,6 +275,17 @@ public: ~BatchLoader(); + /** Sets the base path to be used for all subsequent load + * calls. This is the working directory of Assimp. + * + * Every (inplicit) occurence of '.\' will be replaced with it. + * + * @param pBase Base path. This *may* also be the path to + * a file (the directory of the file is taken then, of course) + */ + void SetBasePath (const std::string& pBase); + + /** Add a new file to the list of files to be loaded. * * @param file File to be loaded diff --git a/code/ColladaParser.h b/code/ColladaParser.h index 6efffe5d0..51b9ad5ba 100644 --- a/code/ColladaParser.h +++ b/code/ColladaParser.h @@ -293,7 +293,7 @@ protected: template const Type& ColladaParser::ResolveLibraryReference( const std::map& pLibrary, const std::string& pURL) const { - std::map::const_iterator it = pLibrary.find( pURL); + typename std::map::const_iterator it = pLibrary.find( pURL); if( it == pLibrary.end()) ThrowException( boost::str( boost::format( "Unable to resolve library reference \"%s\".") % pURL)); return it->second; diff --git a/code/ComputeUVMappingProcess.cpp b/code/ComputeUVMappingProcess.cpp index 3ccc6ea5f..e179a85a5 100644 --- a/code/ComputeUVMappingProcess.cpp +++ b/code/ComputeUVMappingProcess.cpp @@ -70,31 +70,305 @@ bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const } // ------------------------------------------------------------------------------------------------ -unsigned int ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,aiAxis axis) +// Compute the AABB of a mesh +inline void FindAABB (aiMesh* mesh, aiVector3D& min, aiVector3D& max) { - DefaultLogger::get()->error("Mapping type currently not implemented"); - return 0; + min = aiVector3D (10e10f, 10e10f, 10e10f); + max = aiVector3D (-10e10f,-10e10f,-10e10f); + for (unsigned int i = 0;i < mesh->mNumVertices;++i) + { + const aiVector3D& v = mesh->mVertices[i]; + min.x = ::std::min(v.x,min.x); + min.y = ::std::min(v.y,min.y); + min.z = ::std::min(v.z,min.z); + max.x = ::std::max(v.x,max.x); + max.y = ::std::max(v.y,max.y); + max.z = ::std::max(v.z,max.z); + } } // ------------------------------------------------------------------------------------------------ -unsigned int ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,aiAxis axis) +// Helper function to determine the 'real' center of a mesh +inline void FindMeshCenter (aiMesh* mesh, aiVector3D& out, aiVector3D& min, aiVector3D& max) { - DefaultLogger::get()->error("Mapping type currently not implemented"); - return 0; + FindAABB(mesh,min,max); + out = min + (max-min)*0.5f; } // ------------------------------------------------------------------------------------------------ -unsigned int ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,aiAxis axis) +// Helper function to determine the 'real' center of a mesh +inline void FindMeshCenter (aiMesh* mesh, aiVector3D& out) { - DefaultLogger::get()->error("Mapping type currently not implemented"); - return 0; + aiVector3D min,max; + FindMeshCenter(mesh,out,min,max); } // ------------------------------------------------------------------------------------------------ -unsigned int ComputeUVMappingProcess::ComputeBoxMapping(aiMesh* mesh) +// Check whether a ray intersects a plane and find the intersection point +inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos, + const aiVector3D& planeNormal, aiVector3D& pos) +{ + const float b = planeNormal * (planePos - ray.pos); + float h = ray.dir * planeNormal; + if (h < 10e-5f && h > -10e-5f || (h = b/h) < 0) + return false; + + pos = ray.pos + (ray.dir * h); + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Find the first empty UV channel in a mesh +inline unsigned int FindEmptyUVChannel (aiMesh* mesh) +{ + for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m) + if (!mesh->mTextureCoords[m])return m; + + DefaultLogger::get()->error("Unable to compute UV coordinates, no free UV slot found"); + return 0xffffffff; +} + +// ------------------------------------------------------------------------------------------------ +// Try to remove UV seams +void RemoveUVSeams (aiMesh* mesh, aiVector3D* out) +{ + // TODO: just a very rough algorithm. I think it could be done + // much easier, but I don't know how and am currently too tired to + // to think about a better solution. + + const static float LOWER_LIMIT = 0.1f; + const static float UPPER_LIMIT = 0.9f; + + const static float LOWER_EPSILON = 1e-3f; + const static float UPPER_EPSILON = 1.f-1e-3f; + + for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx) + { + const aiFace& face = mesh->mFaces[fidx]; + if (face.mNumIndices < 3) continue; // triangles and polygons only, please + + unsigned int small = face.mNumIndices, large = small; + bool zero = false, one = false, round_to_zero = false; + + // Check whether this face lies on a UV seam. We can just guess, + // but the assumption that a face with at least one very small + // on the one side and one very large U coord on the other side + // lies on a UV seam should work for most cases. + for (unsigned int n = 0; n < face.mNumIndices;++n) + { + if (out[face.mIndices[n]].x < LOWER_LIMIT) + { + small = n; + + // If we have a U value very close to 0 we can't + // round the others to 0, too. + if (out[face.mIndices[n]].x <= LOWER_EPSILON) + zero = true; + else round_to_zero = true; + } + if (out[face.mIndices[n]].x > UPPER_LIMIT) + { + large = n; + + // If we have a U value very close to 1 we can't + // round the others to 1, too. + if (out[face.mIndices[n]].x >= UPPER_EPSILON) + one = true; + } + } + if (small != face.mNumIndices && large != face.mNumIndices) + { + for (unsigned int n = 0; n < face.mNumIndices;++n) + { + // If the u value is over the upper limit and no other u + // value of that face is 0, round it to 0 + if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero) + out[face.mIndices[n]].x = 0.f; + + // If the u value is below the lower limit and no other u + // value of that face is 1, round it to 1 + else if (out[face.mIndices[n]].x < LOWER_LIMIT && !one) + out[face.mIndices[n]].x = 1.f; + + // The face contains both 0 and 1 as UV coords. This can occur + // for faces which have an edge that lies directly on the seam. + // Due to numerical inaccuracies one U coord becomes 0, the + // other 1. But we do still have a third UV coord to determine + // to which side we must round to. + else if (one && zero) + { + if (round_to_zero && out[face.mIndices[n]].x >= UPPER_EPSILON) + out[face.mIndices[n]].x = 0.f; + else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON) + out[face.mIndices[n]].x = 1.f; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,aiAxis axis, aiVector3D* out) +{ + aiVector3D center; + FindMeshCenter (mesh, center); + + // For each point get a normalized projection vector in the sphere, + // get its longitude and latitude and map them to their respective + // UV axes. Problems occur around the poles ... unsolvable. + // + // The spherical coordinate system looks like this: + // x = cos(lon)*cos(lat) + // y = sin(lon)*cos(lat) + // z = sin(lat) + // + // Thus we can derive: + // lat = arcsin (z) + // lon = arctan (y/x) + + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) + { + const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize(); + float lat, lon; + + switch (axis) + { + case aiAxis_X: + lat = asin (diff.x); + lon = atan2 (diff.z, diff.y); + break; + case aiAxis_Y: + lat = asin (diff.y); + lon = atan2 (diff.x, diff.z); + break; + case aiAxis_Z: + lat = asin (diff.z); + lon = atan2 (diff.y, diff.x); + break; + } + out[pnt] = aiVector3D((lon + (float)AI_MATH_PI ) / (float)AI_MATH_TWO_PI, + (lat + (float)AI_MATH_HALF_PI) / (float)AI_MATH_PI, 0.f); + } + + // Now find and remove UV seams. A seam occurs if a face has a tcoord + // close to zero on the one side, and a tcoord close to one on the + // other side. + RemoveUVSeams(mesh,out); +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,aiAxis axis, aiVector3D* out) +{ + aiVector3D center, min, max; + FindMeshCenter(mesh, center, min, max); + + ai_assert(0 == aiAxis_X); + const float diff = max[axis] - min[axis]; + if (!diff) + { + DefaultLogger::get()->error("Can't compute cylindrical mapping, the mesh is " + "flat in the requested axis"); + + return; + } + + // If the main axis is 'z', the z coordinate of a point 'p' is mapped + // directly to the texture V axis. The other axis is derived from + // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where + // 'c' is the center point of the mesh. + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) + { + const aiVector3D& pos = mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + switch (axis) + { + case aiAxis_X: + uv.y = (pos.x - min.x) / diff; + uv.x = atan2 ( pos.z - center.z, pos.y - center.y); + break; + case aiAxis_Y: + uv.y = (pos.y - min.y) / diff; + uv.x = atan2 ( pos.x - center.x, pos.z - center.z); + break; + case aiAxis_Z: + uv.y = (pos.z - min.z) / diff; + uv.x = atan2 ( pos.y - center.y, pos.x - center.x); + break; + } + uv.x = (uv.x +(float)AI_MATH_PI ) / (float)AI_MATH_TWO_PI; + uv.z = 0.f; + } + + // Now find and remove UV seams. A seam occurs if a face has a tcoord + // close to zero on the one side, and a tcoord close to one on the + // other side. + RemoveUVSeams(mesh,out); +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,aiAxis axis, aiVector3D* out) +{ + aiVector3D center, min, max; + FindMeshCenter(mesh, center, min, max); + + float diffu,diffv; + + switch (axis) + { + case aiAxis_X: + diffu = max.z - min.z; + diffv = max.y - min.y; + break; + case aiAxis_Y: + diffu = max.x - min.x; + diffv = max.z - min.z; + break; + case aiAxis_Z: + diffu = max.y - min.y; + diffv = max.z - min.z; + break; + } + + if (!diffu || !diffv) + { + DefaultLogger::get()->error("Can't compute plane mapping, the mesh is " + "flat in the requested axis"); + + return; + } + + // That's rather simple. We just project the vertices onto a plane + // that lies on the two coordinate aces orthogonal to the main axis + for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) + { + const aiVector3D& pos = mesh->mVertices[pnt]; + aiVector3D& uv = out[pnt]; + + switch (axis) + { + case aiAxis_X: + uv.x = (pos.z - min.z) / diffu; + uv.y = (pos.y - min.y) / diffv; + break; + case aiAxis_Y: + uv.x = (pos.x - min.x) / diffu; + uv.y = (pos.z - min.z) / diffv; + break; + case aiAxis_Z: + uv.x = (pos.y - min.y) / diffu; + uv.y = (pos.x - min.x) / diffv; + break; + } + uv.z = 0.f; + } + +} + +// ------------------------------------------------------------------------------------------------ +void ComputeUVMappingProcess::ComputeBoxMapping(aiMesh* mesh, aiVector3D* out) { DefaultLogger::get()->error("Mapping type currently not implemented"); - return 0; } // ------------------------------------------------------------------------------------------------ @@ -103,17 +377,23 @@ void ComputeUVMappingProcess::Execute( aiScene* pScene) DefaultLogger::get()->debug("GenUVCoordsProcess begin"); char buffer[1024]; + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) + throw new ImportErrorException("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + + std::list mappingStack; + /* Iterate through all materials and search for non-UV mapped textures */ for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { + mappingStack.clear(); aiMaterial* mat = pScene->mMaterials[i]; for (unsigned int a = 0; a < mat->mNumProperties;++a) { aiMaterialProperty* prop = mat->mProperties[a]; if (!::strcmp( prop->mKey.data, "$tex.mapping")) { - aiTextureMapping mapping = *((aiTextureMapping*)prop->mData); + aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData); if (aiTextureMapping_UV != mapping) { if (!DefaultLogger::isNullLogger()) @@ -125,7 +405,10 @@ void ComputeUVMappingProcess::Execute( aiScene* pScene) DefaultLogger::get()->info(buffer); } - aiAxis axis; + if (aiTextureMapping_OTHER == mapping) + continue; + + MappingInfo info (mapping); // Get further properties - currently only the major axis for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2) @@ -136,40 +419,73 @@ void ComputeUVMappingProcess::Execute( aiScene* pScene) if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis")) { - axis = *((aiAxis*)prop2->mData); + info.axis = *((aiAxis*)prop2->mData); break; } } - /* We have found a non-UV mapped texture. Now - * we need to find all meshes using this material - * that we can compute UV channels for them. - */ - for (unsigned int m = 0; m < pScene->mNumMeshes;++m) + unsigned int idx; + + // Check whether we have this mapping mode already + std::list::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info); + if (mappingStack.end() != it) { - aiMesh* mesh = pScene->mMeshes[m]; - if (mesh->mMaterialIndex != i) continue; - - switch (mapping) - { - case aiTextureMapping_SPHERE: - ComputeSphereMapping(mesh,axis); - break; - case aiTextureMapping_CYLINDER: - ComputeCylinderMapping(mesh,axis); - break; - case aiTextureMapping_PLANE: - ComputePlaneMapping(mesh,axis); - break; - case aiTextureMapping_BOX: - ComputeBoxMapping(mesh); - break; - } + idx = (*it).uv; } + else + { + /* We have found a non-UV mapped texture. Now + * we need to find all meshes using this material + * that we can compute UV channels for them. + */ + for (unsigned int m = 0; m < pScene->mNumMeshes;++m) + { + aiMesh* mesh = pScene->mMeshes[m]; + unsigned int outIdx; + if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == 0xffffffff || + !mesh->mNumVertices) + { + continue; + } + + // Allocate output storage + aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices]; + + switch (mapping) + { + case aiTextureMapping_SPHERE: + ComputeSphereMapping(mesh,info.axis,p); + break; + case aiTextureMapping_CYLINDER: + ComputeCylinderMapping(mesh,info.axis,p); + break; + case aiTextureMapping_PLANE: + ComputePlaneMapping(mesh,info.axis,p); + break; + case aiTextureMapping_BOX: + ComputeBoxMapping(mesh,p); + break; + default: + ai_assert(false); + } + if (m && idx != outIdx) + { + DefaultLogger::get()->warn("UV index mismatch. Not all meshes assigned to " + "this material have equal numbers of UV channels. The UV index stored in " + "the material structure does therefore not apply for all meshes. "); + } + idx = outIdx; + } + info.uv = idx; + mappingStack.push_back(info); + } + + // Update the material property list + mapping = aiTextureMapping_UV; + ((MaterialHelper*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex)); } } } } - DefaultLogger::get()->debug("GenUVCoordsProcess finished"); } diff --git a/code/ComputeUVMappingProcess.h b/code/ComputeUVMappingProcess.h index 8290fad8f..bf5bf1c6e 100644 --- a/code/ComputeUVMappingProcess.h +++ b/code/ComputeUVMappingProcess.h @@ -89,35 +89,59 @@ protected: * * @param mesh Mesh to be processed * @param axis Main axis - * @return Index of the newly generated UV channel + * @param out Receives output UV coordinates */ - unsigned int ComputeSphereMapping(aiMesh* mesh,aiAxis axis); + void ComputeSphereMapping(aiMesh* mesh,aiAxis axis, + aiVector3D* out); // ------------------------------------------------------------------- /** Computes cylindrical UV coordinates for a mesh * * @param mesh Mesh to be processed * @param axis Main axis - * @return Index of the newly generated UV channel + * @param out Receives output UV coordinates */ - unsigned int ComputeCylinderMapping(aiMesh* mesh,aiAxis axis); + void ComputeCylinderMapping(aiMesh* mesh,aiAxis axis, + aiVector3D* out); // ------------------------------------------------------------------- /** Computes planar UV coordinates for a mesh * * @param mesh Mesh to be processed * @param axis Main axis - * @return Index of the newly generated UV channel + * @param out Receives output UV coordinates */ - unsigned int ComputePlaneMapping(aiMesh* mesh,aiAxis axis); + void ComputePlaneMapping(aiMesh* mesh,aiAxis axis, + aiVector3D* out); // ------------------------------------------------------------------- /** Computes cubic UV coordinates for a mesh * * @param mesh Mesh to be processed - * @return Index of the newly generated UV channel + * @param out Receives output UV coordinates */ - unsigned int ComputeBoxMapping(aiMesh* mesh); + void ComputeBoxMapping(aiMesh* mesh, aiVector3D* out); + +private: + + // temporary structure to describe a mapping + struct MappingInfo + { + MappingInfo(aiTextureMapping _type) + : type (_type) + , axis (aiAxis_X) + , uv (0u) + {} + + aiTextureMapping type; + aiAxis axis; + unsigned int uv; + + bool operator== (const MappingInfo& other) + { + return type == other.type && axis == other.axis; + } + }; }; } // end of namespace Assimp diff --git a/code/DefaultIOSystem.cpp b/code/DefaultIOSystem.cpp index 794e4871a..671c73c85 100644 --- a/code/DefaultIOSystem.cpp +++ b/code/DefaultIOSystem.cpp @@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; + // ------------------------------------------------------------------------------------------------ // Constructor. DefaultIOSystem::DefaultIOSystem() @@ -103,3 +104,42 @@ std::string DefaultIOSystem::getOsSeparator() const #endif return sep; } + +// ------------------------------------------------------------------------------------------------ +// IOSystem default implementation (ComparePaths isn't a pure virtual function) +bool IOSystem::ComparePaths (const std::string& one, + const std::string& second) +{ + return !ASSIMP_stricmp(one,second); +} + +// this should be sufficient for all platforms :D +#define PATHLIMIT 1024 + +// ------------------------------------------------------------------------------------------------ +// Convert a relative path into an absolute path +inline void MakeAbsolutePath (const std::string& in, char* _out) +{ + ::_fullpath(_out, in.c_str(),PATHLIMIT); +} + +// ------------------------------------------------------------------------------------------------ +// DefaultIOSystem's more specialized implementation +bool DefaultIOSystem::ComparePaths (const std::string& one, + const std::string& second) +{ + // chances are quite good both paths are formatted identically, + // so we can hopefully return here already + if( !ASSIMP_stricmp(one,second) ) + return true; + + char temp1[PATHLIMIT]; + char temp2[PATHLIMIT]; + + MakeAbsolutePath (one, temp1); + MakeAbsolutePath (second, temp2); + + return !ASSIMP_stricmp(temp1,temp2); +} + +#undef PATHLIMIT diff --git a/code/DefaultIOSystem.h b/code/DefaultIOSystem.h index 147fb2673..6e9fdfb1f 100644 --- a/code/DefaultIOSystem.h +++ b/code/DefaultIOSystem.h @@ -73,6 +73,10 @@ public: // ------------------------------------------------------------------- /** Closes the given file and releases all resources associated with it. */ void Close( IOStream* pFile); + + // ------------------------------------------------------------------- + /** Compare two paths */ + bool ComparePaths (const std::string& one, const std::string& second); }; } //!ns Assimp diff --git a/code/GenFaceNormalsProcess.cpp b/code/GenFaceNormalsProcess.cpp index 5ad484729..d00086358 100644 --- a/code/GenFaceNormalsProcess.cpp +++ b/code/GenFaceNormalsProcess.cpp @@ -76,6 +76,9 @@ void GenFaceNormalsProcess::Execute( aiScene* pScene) { DefaultLogger::get()->debug("GenFaceNormalsProcess begin"); + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) + throw new ImportErrorException("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + bool bHas = false; for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { diff --git a/code/GenVertexNormalsProcess.cpp b/code/GenVertexNormalsProcess.cpp index 861aacb25..ef0665bd3 100644 --- a/code/GenVertexNormalsProcess.cpp +++ b/code/GenVertexNormalsProcess.cpp @@ -88,6 +88,9 @@ void GenVertexNormalsProcess::Execute( aiScene* pScene) { DefaultLogger::get()->debug("GenVertexNormalsProcess begin"); + if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) + throw new ImportErrorException("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here"); + bool bHas = false; for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { diff --git a/code/IRRLoader.cpp b/code/IRRLoader.cpp index dc8b80d11..d312d5da0 100644 --- a/code/IRRLoader.cpp +++ b/code/IRRLoader.cpp @@ -51,8 +51,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SceneCombiner.h" #include "StandardShapes.h" + +// We need boost::common_factor to compute the lcm/gcd of a number +#ifdef ASSIMP_BUILD_BOOST_WORKAROUND +# include "../include/BoostWorkaround/boost/common_factor_rt.hpp" +#else +# include +#endif + using namespace Assimp; +// Transformation matrix to convert from Assimp to IRR space +static aiMatrix4x4 AI_TO_IRR_MATRIX = aiMatrix4x4 ( 1.0f, 0.0f, 0.0f, + 0.f, 0.0f, 0.0f, -1.0f, 0.f, 0.0f, 1.0f, 0.0f, 0.f, 0.f, 0.f, 0.f, 1.f); // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer @@ -163,7 +174,7 @@ aiMesh* IRRImporter::BuildSingleQuadMesh(const SkyboxVertex& v1, *vec = v4.normal; // copy texture coordinates - out->mTextureCoords[0] = new aiVector3D[4]; + vec = out->mTextureCoords[0] = new aiVector3D[4]; *vec++ = v1.uv; *vec++ = v2.uv; *vec++ = v3.uv; @@ -240,12 +251,12 @@ void IRRImporter::BuildSkybox(std::vector& meshes, std::vectormMaterialIndex = materials.size()-1u; } // ------------------------------------------------------------------------------------------------ -void IRRImporter::CopyMaterial(std::vector materials, +void IRRImporter::CopyMaterial(std::vector& materials, std::vector< std::pair >& inmaterials, unsigned int& defMatIdx, aiMesh* mesh) @@ -297,44 +308,71 @@ inline void FindSuitableMultiple(int& angle) } // ------------------------------------------------------------------------------------------------ -void IRRImporter::ComputeAnimations(Node* root, std::vector& anims, - const aiMatrix4x4& transform) +void IRRImporter::ComputeAnimations(Node* root, aiNode* real, std::vector& anims) { - ai_assert(NULL != root); + ai_assert(NULL != root && NULL != real); if (root->animators.empty())return; + const aiMatrix4x4& transform = real->mTransformation; - typedef std::pair< TemporaryAnim, Animator* > AnimPair; - const unsigned int resolution = 1; - - std::vector temp; - temp.reserve(root->animators.size()); - + unsigned int total = 0; for (std::list::iterator it = root->animators.begin(); it != root->animators.end(); ++it) { - if ((*it).type == Animator::UNKNOWN || - (*it).type == Animator::OTHER) + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) { DefaultLogger::get()->warn("IRR: Skipping unknown or unsupported animator"); continue; } - temp.push_back(AnimPair(TemporaryAnim(),&(*it))); + ++total; + } + if (!total)return; + else if (1 == total) + { + DefaultLogger::get()->warn("IRR: Generating dummy nodes to simulate multiple animators"); } - if (temp.empty())return; + // NOTE: 1 tick == i millisecond - // All animators are applied one after another. We generate a set of - // transformation matrices for each of it. Then we combine all - // transformation matrices, decompose them and build an output animation. - - for (std::vector::iterator it = temp.begin(); - it != temp.end(); ++it) + unsigned int cur = 0; + for (std::list::iterator it = root->animators.begin(); + it != root->animators.end(); ++it) { - TemporaryAnim& out = (*it).first; - Animator* in = (*it).second; + if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER)continue; - switch (in->type) + Animator& in = *it ; + aiNodeAnim* anim = new aiNodeAnim(); + + if (cur != total-1) + { + // Build a new name - a prefix instead of a suffix because it is + // easier to check against + anim->mNodeName.length = ::sprintf(anim->mNodeName.data, + "$INST_DUMMY_%i_%s",total-1, + (root->name.length() ? root->name.c_str() : "")); + + // we'll also need to insert a dummy in the node hierarchy. + aiNode* dummy = new aiNode(); + + for (unsigned int i = 0; i < real->mParent->mNumChildren;++i) + if (real->mParent->mChildren[i] == real) + real->mParent->mChildren[i] = dummy; + + dummy->mParent = real->mParent; + dummy->mName = anim->mNodeName; + + dummy->mNumChildren = 1; + dummy->mChildren = new aiNode*[dummy->mNumChildren]; + dummy->mChildren[0] = real; + + // the transformation matrix of the dummy node is the identity + + real->mParent = dummy; + } + else anim->mNodeName.Set(root->name); + ++cur; + + switch (in.type) { case Animator::ROTATION: { @@ -347,17 +385,20 @@ void IRRImporter::ComputeAnimations(Node* root, std::vector& anims, // here in order to get good results. // ----------------------------------------------------- int angles[3]; - angles[0] = (int)(in->direction.x*100); - angles[1] = (int)(in->direction.y*100); - angles[2] = (int)(in->direction.z*100); + angles[0] = (int)(in.direction.x*100); + angles[1] = (int)(in.direction.y*100); + angles[2] = (int)(in.direction.z*100); angles[0] %= 360; angles[1] %= 360; angles[2] %= 360; - FindSuitableMultiple(angles[0]); - FindSuitableMultiple(angles[1]); - FindSuitableMultiple(angles[2]); + if ((angles[0]*angles[1]) && (angles[1]*angles[2])) + { + FindSuitableMultiple(angles[0]); + FindSuitableMultiple(angles[1]); + FindSuitableMultiple(angles[2]); + } int lcm = 360; @@ -390,87 +431,136 @@ void IRRImporter::ComputeAnimations(Node* root, std::vector& anims, max = std::max(max, (float)lcm / angles[2]); - // Allocate transformation matrices - out.SetupMatrices((unsigned int)(max*fps)); + anim->mNumRotationKeys = (unsigned int)(max*fps); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + // begin with a zero angle aiVector3D angle; - for (unsigned int i = 0; i < out.last;++i) + for (unsigned int i = 0; i < anim->mNumRotationKeys;++i) { - // build the rotation matrix for the given euler angles - aiMatrix4x4& m = out.matrices[i]; + // build the quaternion for the given euler angles + aiQuatKey& q = anim->mRotationKeys[i]; - // we start with the node transformation - m = transform; - - aiMatrix4x4 m2; - - if (angle.x) - m *= aiMatrix4x4::RotationX(angle.x,m2); - - if (angle.y) - m *= aiMatrix4x4::RotationX(angle.y,m2); - - if (angle.z) - m *= aiMatrix4x4::RotationZ(angle.z,m2); + q.mValue = aiQuaternion(angle.x, angle.y, angle.z); + q.mTime = (double)i; // increase the angle - angle += in->direction; + angle += in.direction; } // This animation is repeated and repeated ... - out.post = aiAnimBehaviour_REPEAT; + anim->mPostState = aiAnimBehaviour_REPEAT; + anim->mPreState = aiAnimBehaviour_CONSTANT; } break; case Animator::FLY_CIRCLE: { - + anim->mPostState = aiAnimBehaviour_REPEAT; + anim->mPreState = aiAnimBehaviour_CONSTANT; + + // ----------------------------------------------------- + // Find out how much time we'll need to perform a + // full circle. + // ----------------------------------------------------- + const double seconds = (1. / in.speed) / 1000.; + const double tdelta = 1000. / fps; + + anim->mNumPositionKeys = (unsigned int) (fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + // from Irrlicht, what else should we do than copying it? + aiVector3D vecU,vecV; + if (in.direction.y) + { + vecV = aiVector3D(50,0,0) ^ in.direction; + } + else vecV = aiVector3D(0,50,00) ^ in.direction; + vecV.Normalize(); + vecU = (vecV ^ in.direction).Normalize(); + + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys;++i) + { + aiVectorKey& key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; + + const float t = (float) ( in.speed * key.mTime ); + key.mValue = in.circleCenter + in.circleRadius * ((vecU*::cos(t)) + (vecV*::sin(t))); + } } break; case Animator::FLY_STRAIGHT: { - + anim->mPostState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT); + anim->mPreState = aiAnimBehaviour_CONSTANT; + + const double seconds = in.timeForWay / 1000.; + const double tdelta = 1000. / fps; + + anim->mNumPositionKeys = (unsigned int) (fps * seconds); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + aiVector3D diff = in.direction - in.circleCenter; + const float lengthOfWay = diff.Length(); + diff.Normalize(); + + const double timeFactor = lengthOfWay / in.timeForWay; + + // build the output keys + for (unsigned int i = 0; i < anim->mNumPositionKeys;++i) + { + aiVectorKey& key = anim->mPositionKeys[i]; + key.mTime = i * tdelta; + key.mValue = in.circleCenter + diff * float(timeFactor * key.mTime); + } } break; case Animator::FOLLOW_SPLINE: { - out.post = aiAnimBehaviour_REPEAT; - const int size = (int)in->splineKeys.size(); + anim->mPostState = aiAnimBehaviour_REPEAT; + anim->mPreState = aiAnimBehaviour_CONSTANT; + + const int size = (int)in.splineKeys.size(); if (!size) { // We have no point in the spline. That's bad. Really bad. DefaultLogger::get()->warn("IRR: Spline animators with no points defined"); + + delete anim;anim = NULL; break; } else if (size == 1) { - // We have just one point in the spline - out.SetupMatrices(1); - out.matrices[0].a4 = in->splineKeys[0].mValue.x; - out.matrices[0].b4 = in->splineKeys[0].mValue.y; - out.matrices[0].c4 = in->splineKeys[0].mValue.z; + // We have just one point in the spline so we don't need the full calculation + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + + anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue; + anim->mPositionKeys[0].mTime = 0.f; break; } unsigned int ticksPerFull = 15; - out.SetupMatrices(ticksPerFull*fps); + anim->mNumPositionKeys = (unsigned int) ( ticksPerFull * fps ); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - for (unsigned int i = 0; i < out.last;++i) + for (unsigned int i = 0; i < anim->mNumPositionKeys;++i) { - aiMatrix4x4& m = out.matrices[i]; + aiVectorKey& key = anim->mPositionKeys[i]; - const float dt = (i * in->speed * 0.001f ); + const float dt = (i * in.speed * 0.001f ); const float u = dt - floor(dt); const int idx = (int)floor(dt) % size; // get the 4 current points to evaluate the spline - const aiVector3D& p0 = in->splineKeys[ ClampSpline( idx - 1, size ) ].mValue; - const aiVector3D& p1 = in->splineKeys[ ClampSpline( idx + 0, size ) ].mValue; - const aiVector3D& p2 = in->splineKeys[ ClampSpline( idx + 1, size ) ].mValue; - const aiVector3D& p3 = in->splineKeys[ ClampSpline( idx + 2, size ) ].mValue; + const aiVector3D& p0 = in.splineKeys[ ClampSpline( idx - 1, size ) ].mValue; + const aiVector3D& p1 = in.splineKeys[ ClampSpline( idx + 0, size ) ].mValue; + const aiVector3D& p2 = in.splineKeys[ ClampSpline( idx + 1, size ) ].mValue; + const aiVector3D& p3 = in.splineKeys[ ClampSpline( idx + 2, size ) ].mValue; // compute polynomials const float u2 = u*u; @@ -482,54 +572,90 @@ void IRRImporter::ComputeAnimations(Node* root, std::vector& anims, const float h4 = u3 - u2; // compute the spline tangents - const aiVector3D t1 = ( p2 - p0 ) * in->tightness; - aiVector3D t2 = ( p3 - p1 ) * in->tightness; + const aiVector3D t1 = ( p2 - p0 ) * in.tightness; + aiVector3D t2 = ( p3 - p1 ) * in.tightness; // and use them to get the interpolated point t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2); // build a simple translation matrix from it - m.a4 = t2.x; - m.b4 = t2.y; - m.c4 = t2.z; + key.mValue = t2.x; + key.mTime = (double) i; } } break; }; - } - - - aiNodeAnim* out = new aiNodeAnim(); - out->mNodeName.Set(root->name); - - if (temp.size() == 1) - { - // If there's just one animator to be processed our - // task is quite easy - TemporaryAnim& one = temp[0].first; - - out->mPostState = one.post; - out->mNumPositionKeys = one.last; - out->mNumScalingKeys = one.last; - out->mNumRotationKeys = one.last; - - out->mPositionKeys = new aiVectorKey[one.last]; - out->mScalingKeys = new aiVectorKey[one.last]; - out->mRotationKeys = new aiQuatKey[one.last]; - - for (unsigned int i = 0; i < one.last;++i) + if (anim) { - aiVectorKey& scaling = out->mScalingKeys[i]; - aiVectorKey& position = out->mPositionKeys[i]; - aiQuatKey& rotation = out->mRotationKeys[i]; - - scaling.mTime = position.mTime = rotation.mTime = (double)i; - one.matrices[i].Decompose(scaling.mValue, rotation.mValue, position.mValue); + anims.push_back(anim); + ++total; } } +} - // NOTE: It is possible that some of the tracks we're returning - // are dummy tracks, but the ScenePreprocessor will fix that, hopefully +// ------------------------------------------------------------------------------------------------ +// This function is maybe more generic than we'd need it here +void SetupMapping (MaterialHelper* mat, aiTextureMapping mode, aiAxis axis = aiAxis_Y) +{ + // Check whether there are texture properties defined - setup + // the desired texture mapping mode for all of them and ignore + // all UV settings we might encounter. WE HAVE NO UVS! + + std::vector p; + p.reserve(mat->mNumProperties+1); + + for (unsigned int i = 0; i < mat->mNumProperties;++i) + { + aiMaterialProperty* prop = mat->mProperties[i]; + if (!::strcmp( prop->mKey.data, "$tex.file")) + { + // Setup the mapping key + aiMaterialProperty* m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapping"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Integer; + + m->mDataLength = 4; + m->mData = new char[4]; + *((int*)m->mData) = mode; + + p.push_back(prop); + p.push_back(m); + + // Setup the mapping axis + if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || + mode == aiTextureMapping_SPHERE) + { + m = new aiMaterialProperty(); + m->mKey.Set("$tex.mapaxis"); + m->mIndex = prop->mIndex; + m->mSemantic = prop->mSemantic; + m->mType = aiPTI_Integer; + + m->mDataLength = 4; + m->mData = new char[4]; + *((int*)m->mData) = axis; + p.push_back(m); + } + } + else if (! ::strcmp( prop->mKey.data, "$tex.uvwsrc")) + { + delete mat->mProperties[i]; + } + else p.push_back(prop); + } + + if (p.empty())return; + + // rebuild the output array + if (p.size() > mat->mNumAllocated) + { + delete[] mat->mProperties; + mat->mProperties = new aiMaterialProperty*[p.size()]; + } + mat->mNumProperties = (unsigned int)p.size(); + ::memcpy(mat->mProperties,&p[0],sizeof(void*)*mat->mNumProperties); } // ------------------------------------------------------------------------------------------------ @@ -538,10 +664,11 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, std::vector& meshes, std::vector& anims, std::vector& attach, - std::vector materials, + std::vector& materials, unsigned int& defMatIdx) { unsigned int oldMeshSize = (unsigned int)meshes.size(); + unsigned int meshTrafoAssign = 0; // Now determine the type of the node switch (root->type) @@ -549,7 +676,10 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, case Node::ANIMMESH: case Node::MESH: { - // get the loaded mesh from the scene and add it to + if (!root->meshPath.length()) + break; + + // Get the loaded mesh from the scene and add it to // the list of all scenes to be attached to the // graph we're currently building aiScene* scene = batch.GetImport(root->meshPath); @@ -560,9 +690,45 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, break; } attach.push_back(AttachmentInfo(scene,rootOut)); + meshTrafoAssign = 1; - // now combine the material we've loaded for this mesh - // with the real meshes we got from the file. As we + // If the root node of the scene is animated - and *this* node + // is animated, too, we need to insert a dummy node into the + // hierarchy in order to avoid interferences with animations + for (unsigned int i = 0; i < scene->mNumAnimations;++i) + { + aiAnimation* anim = scene->mAnimations[i]; + for (unsigned int a = 0; a < anim->mNumChannels;++a) + { + if (scene->mRootNode->mName == anim->mChannels[a]->mNodeName) + { + if (root->animators.empty()) + { + meshTrafoAssign = 2; + } + else + { + meshTrafoAssign = 3; + aiNode* dummy = new aiNode(); + dummy->mName.Set("$CSpaceSeam$"); + dummy->mNumChildren = 1; + dummy->mChildren = new aiNode*[1]; + dummy->mChildren[0] = scene->mRootNode; + + scene->mRootNode->mParent = dummy; + scene->mRootNode = dummy; + scene->mRootNode->mTransformation = AI_TO_IRR_MATRIX; + } + break; + } + } + } + if (1 == meshTrafoAssign) + scene->mRootNode->mTransformation *= AI_TO_IRR_MATRIX; + + + // Now combine the material we've loaded for this mesh + // with the real materials we got from the file. As we // don't execute any pp-steps on the file, the numbers // should be equal. If they are not, we can impossibly // do this ... @@ -575,7 +741,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, } for (unsigned int i = 0; i < scene->mNumMaterials;++i) { - // delete the old material + // Delete the old material, we don't need it anymore delete scene->mMaterials[i]; std::pair& src = root->materials[i]; @@ -599,8 +765,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, mesh->mMaterialIndex]; MaterialHelper* mat = (MaterialHelper*)src.first; - if (mesh->HasVertexColors(0) && - src.second & AI_IRRMESH_MAT_trans_vertex_alpha) + if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) { bool bdo = true; for (unsigned int a = 1; a < mesh->mNumVertices;++a) @@ -624,14 +789,13 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, } // If we have a second texture coordinate set and a second texture - // (either lightmap, normalmap, 2layered material we need to - // setup the correct UV index for it). The texture can either + // (either lightmap, normalmap, 2layered material) we need to + // setup the correct UV index for it. The texture can either // be diffuse (lightmap & 2layer) or a normal map (normal & parallax) if (mesh->HasTextureCoords(1)) { int idx = 1; - if (src.second & (AI_IRRMESH_MAT_solid_2layer | - AI_IRRMESH_MAT_lightmap)) + if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) { mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_DIFFUSE(0)); } @@ -653,11 +817,11 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, case Node::SPHERE: { - // generate the sphere model. Our input parameter to + // Generate the sphere model. Our input parameter to // the sphere generation algorithm is the number of // subdivisions of each triangle - but here we have // the number of poylgons on a specific axis. Just - // use some limits ... + // use some hardcoded limits to approximate this ... unsigned int mul = root->spherePolyCountX*root->spherePolyCountY; if (mul < 100)mul = 2; else if (mul < 300)mul = 3; @@ -667,10 +831,14 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, &StandardShapes::MakeSphere)); // Adjust scaling - root->scaling *= root->sphereRadius; + root->scaling *= root->sphereRadius/2; // Copy one output material CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + + // Now adjust this output material - if there is a first texture + // set, setup spherical UV mapping around the Y axis. + SetupMapping ( (MaterialHelper*) materials.back(), aiTextureMapping_SPHERE, aiAxis_Y ); } break; @@ -685,6 +853,10 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, // Copy one output material CopyMaterial(materials, root->materials, defMatIdx, meshes.back()); + + // Now adjust this output material - if there is a first texture + // set, setup cubic UV mapping + SetupMapping ( (MaterialHelper*) materials.back(), aiTextureMapping_BOX ); } break; @@ -745,10 +917,7 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, // Now compute the final local transformation matrix of the // node from the given translation, rotation and scaling values. // (the rotation is given in Euler angles, XYZ order) - aiMatrix4x4 m; - rootOut->mTransformation = aiMatrix4x4::RotationX(AI_DEG_TO_RAD(root->rotation.x),m) - * aiMatrix4x4::RotationY(AI_DEG_TO_RAD(root->rotation.y),m) - * aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(root->rotation.z),m); + rootOut->mTransformation.FromEulerAngles(AI_DEG_TO_RAD(root->rotation) ); // apply scaling aiMatrix4x4& mat = rootOut->mTransformation; @@ -763,12 +932,15 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, mat.c3 *= root->scaling.z; // apply translation - mat.a4 = root->position.x; - mat.b4 = root->position.y; - mat.c4 = root->position.z; + mat.a4 += root->position.x; + mat.b4 += root->position.y; + mat.c4 += root->position.z; + + if (meshTrafoAssign == 2) + mat *= AI_TO_IRR_MATRIX; // now compute animations for the node - ComputeAnimations(root,anims,mat); + ComputeAnimations(root,rootOut, anims); // Add all children recursively. First allocate enough storage // for them, then call us again @@ -804,6 +976,7 @@ void IRRImporter::InternReadFile( const std::string& pFile, // The root node of the scene Node* root = new Node(Node::DUMMY); root->parent = NULL; + root->name = ""; // Current node parent Node* curParent = root; @@ -819,6 +992,7 @@ void IRRImporter::InternReadFile( const std::string& pFile, // Batch loader used to load external models BatchLoader batch(pIOHandler); + batch.SetBasePath(pFile); cameras.reserve(5); lights.reserve(5); @@ -848,6 +1022,8 @@ void IRRImporter::InternReadFile( const std::string& pFile, * and join its animation channels with ours. * "empty" - A dummy node * "camera" - A camera + * "terrain" - a terrain node (data comes from a heightmap) + * "billboard", "" * * Each of these nodes can be animated and all can have multiple * materials assigned (except lights, cameras and dummies, of course). @@ -855,8 +1031,9 @@ void IRRImporter::InternReadFile( const std::string& pFile, // *********************************************************************** const char* sz = reader->getAttributeValueSafe("type"); Node* nd; - if (!ASSIMP_stricmp(sz,"mesh")) + if (!ASSIMP_stricmp(sz,"mesh") || !ASSIMP_stricmp(sz,"octTree")) { + // OctTree's and meshes are treated equally nd = new Node(Node::MESH); } else if (!ASSIMP_stricmp(sz,"cube")) @@ -868,7 +1045,7 @@ void IRRImporter::InternReadFile( const std::string& pFile, else if (!ASSIMP_stricmp(sz,"skybox")) { nd = new Node(Node::SKYBOX); - ++guessedMeshCnt; + guessedMeshCnt += 6; } else if (!ASSIMP_stricmp(sz,"camera")) { @@ -901,6 +1078,16 @@ void IRRImporter::InternReadFile( const std::string& pFile, { nd = new Node(Node::DUMMY); } + else if (!ASSIMP_stricmp(sz,"terrain")) + { + nd = new Node(Node::TERRAIN); + } + else if (!ASSIMP_stricmp(sz,"billBoard")) + { + // We don't support billboards, so ignore them + DefaultLogger::get()->error("IRR: Billboards are not supported by Assimp"); + nd = new Node(Node::DUMMY); + } else { DefaultLogger::get()->warn("IRR: Found unknown node: " + std::string(sz)); @@ -929,19 +1116,21 @@ void IRRImporter::InternReadFile( const std::string& pFile, else if (!ASSIMP_stricmp(reader->getNodeName(),"attributes")) { /* We should have a valid node here + * FIX: no ... the scene root node is also contained in an attributes block */ if (!curNode) { +#if 0 DefaultLogger::get()->error("IRR: Encountered element, but " "there is no node active"); +#endif continue; } Animator* curAnim = NULL; - // FIX: Materials can occur for nearly any type of node - if (inMaterials /* && curNode->type == Node::ANIMMESH || - curNode->type == Node::MESH */) + // Materials can occur for nearly any type of node + if (inMaterials && curNode->type != Node::DUMMY) { /* This is a material description - parse it! */ @@ -976,10 +1165,6 @@ void IRRImporter::InternReadFile( const std::string& pFile, VectorProperty prop; ReadVectorProperty(prop); - // Convert to our coordinate system - std::swap( (float&)prop.value.z, (float&)prop.value.y ); - prop.value.y *= -1.f; - if (inAnimator) { if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") @@ -999,7 +1184,7 @@ void IRRImporter::InternReadFile( const std::string& pFile, // and parse its properties key.mValue = prop.value; - key.mTime = strtol10(&prop.name.c_str()[5]); + key.mTime = strtol10(&prop.name[5]); } } else if (curAnim->type == Animator::FLY_CIRCLE) @@ -1127,7 +1312,7 @@ void IRRImporter::InternReadFile( const std::string& pFile, else if (Node::LIGHT == curNode->type) { /* Additional light information - */ + */ if (prop.name == "Attenuation") { lights.back()->mAttenuationLinear = prop.value; @@ -1204,6 +1389,22 @@ void IRRImporter::InternReadFile( const std::string& pFile, } else if (Node::LIGHT == curNode->type && "LightType" == prop.name) { + if (prop.value == "Spot") + lights.back()->mType = aiLightSource_SPOT; + else if (prop.value == "Point") + lights.back()->mType = aiLightSource_POINT; + else if (prop.value == "Directional") + lights.back()->mType = aiLightSource_DIRECTIONAL; + else + { + // We won't pass the validation with aiLightSourceType_UNDEFINED, + // so we remove the light and replace it with a silly dummy node + delete lights.back(); + lights.pop_back(); + curNode->type = Node::DUMMY; + + DefaultLogger::get()->error("Ignoring light of unknown type: " + prop.value); + } } else if (prop.name == "Mesh" && Node::MESH == curNode->type || Node::ANIMMESH == curNode->type) @@ -1224,8 +1425,35 @@ void IRRImporter::InternReadFile( const std::string& pFile, aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS); } - batch.AddLoadRequest(prop.value,pp,&map); - curNode->meshPath = prop.value; + /* TODO: maybe implement the protection against recursive + * loading calls directly in BatchLoader? The current + * implementation is not absolutely safe. A LWS and an IRR + * file referencing each other *could* cause the system to + * recurse forever. + */ + std::string::size_type pos = prop.value.find_last_of('.'); + + // no file extension - can't read, so we don't need to try it + if( pos == std::string::npos) + { + DefaultLogger::get()->error("IRR: Can't load files without a file extension"); + } + else + { + std::string extension = prop.value.substr( pos); + for (std::string::iterator i = extension.begin(); i != extension.end();++i) + *i = ::tolower(*i); + + if (".irr" == prop.value) + { + DefaultLogger::get()->error("IRR: Can't load another IRR file recursively"); + } + else + { + batch.AddLoadRequest(prop.value,pp,&map); + curNode->meshPath = prop.value; + } + } } else if (inAnimator && prop.name == "Type") { @@ -1275,12 +1503,12 @@ void IRRImporter::InternReadFile( const std::string& pFile, { // currently is no node set. We need to go // back in the node hierarchy - curParent = curParent->parent; if (!curParent) { curParent = root; DefaultLogger::get()->error("IRR: Too many closing elements"); } + else curParent = curParent->parent; } else curNode = NULL; } @@ -1314,6 +1542,8 @@ void IRRImporter::InternReadFile( const std::string& pFile, else DefaultLogger::get()->warn("IRR: Camera aspect is not given, can't compute horizontal FOV"); } + batch.LoadAll(); + /* Allocate a tempoary scene data structure */ aiScene* tempScene = new aiScene(); @@ -1322,15 +1552,21 @@ void IRRImporter::InternReadFile( const std::string& pFile, /* Copy the cameras to the output array */ - tempScene->mNumCameras = (unsigned int)cameras.size(); - tempScene->mCameras = new aiCamera*[tempScene->mNumCameras]; - ::memcpy(tempScene->mCameras,&cameras[0],sizeof(void*)*tempScene->mNumCameras); + if (!cameras.empty()) + { + tempScene->mNumCameras = (unsigned int)cameras.size(); + tempScene->mCameras = new aiCamera*[tempScene->mNumCameras]; + ::memcpy(tempScene->mCameras,&cameras[0],sizeof(void*)*tempScene->mNumCameras); + } /* Copy the light sources to the output array */ - tempScene->mNumLights = (unsigned int)lights.size(); - tempScene->mLights = new aiLight*[tempScene->mNumLights]; - ::memcpy(tempScene->mLights,&lights[0],sizeof(void*)*tempScene->mNumLights); + if (!lights.empty()) + { + tempScene->mNumLights = (unsigned int)lights.size(); + tempScene->mLights = new aiLight*[tempScene->mNumLights]; + ::memcpy(tempScene->mLights,&lights[0],sizeof(void*)*tempScene->mNumLights); + } // temporary data std::vector< aiNodeAnim*> anims; @@ -1370,27 +1606,51 @@ void IRRImporter::InternReadFile( const std::string& pFile, an->mChannels = new aiNodeAnim*[an->mNumChannels]; ::memcpy(an->mChannels, & anims [0], sizeof(void*)*an->mNumChannels); } - if (meshes.empty()) - { - // There are no meshes in the scene - the scene is incomplete - pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - DefaultLogger::get()->info("IRR: No Meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE flag"); - } - else + if (!meshes.empty()) { // copy all meshes to the temporary scene tempScene->mNumMeshes = (unsigned int)meshes.size(); tempScene->mMeshes = new aiMesh*[tempScene->mNumMeshes]; - ::memcpy(tempScene->mMeshes,&meshes[0],tempScene->mNumMeshes); + ::memcpy(tempScene->mMeshes,&meshes[0],tempScene->mNumMeshes* + sizeof(void*)); + } + + /* Copy all materials to the output array + */ + if (!materials.empty()) + { + tempScene->mNumMaterials = (unsigned int)materials.size(); + tempScene->mMaterials = new aiMaterial*[tempScene->mNumMaterials]; + ::memcpy(tempScene->mMaterials,&materials[0],sizeof(void*)* + tempScene->mNumMaterials); } /* Now merge all sub scenes and attach them to the correct * attachment points in the scenegraph. */ - SceneCombiner::MergeScenes(pScene,tempScene,attach); + SceneCombiner::MergeScenes(&pScene,tempScene,attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES | + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES); + + + /* If we have no meshes | no materials now set the INCOMPLETE + * scene flag. This is necessary if we failed to load all + * models from external files + */ + if (!pScene->mNumMeshes || !pScene->mNumMaterials) + { + DefaultLogger::get()->warn("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE"); + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } + + + // transformation matrix to convert from IRRMESH to ASSIMP coordinates + pScene->mRootNode->mTransformation *= aiMatrix4x4(1.0f, 0.0f, 0.0f, 0.f, 0.0f, 0.0f, -1.0f, + 0.f, 0.0f, 1.0f, 0.0f, 0.f, 0.f, 0.f, 0.f, 1.f); /* Finished ... everything destructs automatically and all * temporary scenes have already been deleted by MergeScenes() */ + return; } diff --git a/code/IRRLoader.h b/code/IRRLoader.h index 5dc698434..d32d6f030 100644 --- a/code/IRRLoader.h +++ b/code/IRRLoader.h @@ -238,35 +238,6 @@ private: aiVector3D position, normal, uv; }; - /** Temporary data structure to describe an IRR animator - * - * Irrlicht animations always start at the beginning, so - * we don't need "first" and "pre" for the moment. - */ - struct TemporaryAnim - { - TemporaryAnim() - : last (0) - , post (aiAnimBehaviour_DEFAULT) - , matrices (NULL) - {} - - ~TemporaryAnim() - { - delete[] matrices; - } - - void SetupMatrices(unsigned int num) - { - last = num; - matrices = new aiMatrix4x4[num]; - } - - unsigned int last; - aiAnimBehaviour post; - - aiMatrix4x4* matrices; - }; // ------------------------------------------------------------------- /** Fill the scenegraph recursively @@ -276,7 +247,7 @@ private: std::vector& meshes, std::vector& anims, std::vector& attach, - std::vector materials, + std::vector& materials, unsigned int& defaultMatIdx); @@ -308,7 +279,7 @@ private: * @param defMatIdx Default material index - 0xffffffff if not there * @param mesh Mesh to work on */ - void CopyMaterial(std::vector materials, + void CopyMaterial(std::vector& materials, std::vector< std::pair >& inmaterials, unsigned int& defMatIdx, aiMesh* mesh); @@ -319,16 +290,14 @@ private: * * @param root Node to be processed * @param anims The list of output animations - * @param transform Transformation matrix of the current node - * (relative to the parent's coordinate space) */ - void ComputeAnimations(Node* root, std::vector& anims, - const aiMatrix4x4& transform); + void ComputeAnimations(Node* root, aiNode* real, + std::vector& anims); private: - unsigned int fps; + double fps; }; diff --git a/code/IRRMeshLoader.cpp b/code/IRRMeshLoader.cpp index ca7833522..c844ab3a9 100644 --- a/code/IRRMeshLoader.cpp +++ b/code/IRRMeshLoader.cpp @@ -238,11 +238,18 @@ aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) ColorFromARGBPacked(prop.value,clr); mat->AddProperty(&clr,1,AI_MATKEY_COLOR_SPECULAR); } + + // NOTE: The 'emissive' property causes problems. It is + // often != 0, even if there is obviously no light + // emitted by the described surface. In fact I think + // IRRLICHT ignores this property, too. +#if 0 else if (prop.name == "Emissive") { ColorFromARGBPacked(prop.value,clr); mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE); } +#endif } // Float properties else if (!ASSIMP_stricmp(reader->getNodeName(),"float")) @@ -746,24 +753,22 @@ void IRRMeshImporter::InternReadFile( const std::string& pFile, sz = fast_atof_move(sz,(float&)temp.x); SkipSpaces(&sz); - sz = fast_atof_move(sz,(float&)temp.z); - SkipSpaces(&sz); - sz = fast_atof_move(sz,(float&)temp.y); SkipSpaces(&sz); - temp.y *= -1.0f; + + sz = fast_atof_move(sz,(float&)temp.z); + SkipSpaces(&sz); curVertices.push_back(temp); // Read the vertex normals sz = fast_atof_move(sz,(float&)temp.x); SkipSpaces(&sz); - sz = fast_atof_move(sz,(float&)temp.z); - SkipSpaces(&sz); - sz = fast_atof_move(sz,(float&)temp.y); SkipSpaces(&sz); - temp.y *= -1.0f; + + sz = fast_atof_move(sz,(float&)temp.z); + SkipSpaces(&sz); curNormals.push_back(temp); // read the vertex colors @@ -953,6 +958,10 @@ void IRRMeshImporter::InternReadFile( const std::string& pFile, for (unsigned int i = 0; i < pScene->mNumMeshes;++i) pScene->mRootNode->mMeshes[i] = i; + // transformation matrix to convert from IRRMESH to ASSIMP coordinates + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 1.0f, 0.0f, 0.0f, 0.f, 0.0f, 0.0f, -1.0f, 0.f, 0.0f, 1.0f, 0.0f, 0.f, 0.f, 0.f, 0.f, 1.f); + delete reader; AI_DEBUG_INVALIDATE_PTR(reader); } \ No newline at end of file diff --git a/code/LWSLoader.cpp b/code/LWSLoader.cpp index de9d4178d..c0bb78c47 100644 --- a/code/LWSLoader.cpp +++ b/code/LWSLoader.cpp @@ -1,2 +1,97 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- -#include "AssimpPCH.h" \ No newline at end of file +Copyright (c) 2006-2008, ASSIMP Development Team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the LWS importer class */ + +#include "AssimpPCH.h" + +#include "LWSLoader.h" +#include "ParsingUtils.h" +#include "fast_atof.h" + +#include "SceneCombiner.h" + +using namespace Assimp; + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LWSImporter::LWSImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LWSImporter::~LWSImporter() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool LWSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const +{ + std::string::size_type pos = pFile.find_last_of('.'); + + // no file extension - can't read + if( pos == std::string::npos) + return false; + + std::string extension = pFile.substr( pos); + for (std::string::iterator i = extension.begin(); i != extension.end();++i) + *i = ::tolower(*i); + + return extension == ".lws"; +} + +// ------------------------------------------------------------------------------------------------ +void LWSImporter::GetExtensionList(std::string& append) +{ + append.append("*.lws"); +} + +// ------------------------------------------------------------------------------------------------ +void LWSImporter::InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) +{ + return; +} diff --git a/code/LWSLoader.h b/code/LWSLoader.h index c8ec2cefc..766a7c3b9 100644 --- a/code/LWSLoader.h +++ b/code/LWSLoader.h @@ -44,6 +44,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { + namespace LWS { + +// --------------------------------------------------------------------------- +/** Represents an element in a LWS file. + * + * This can either be a single data line - or it can + * be a data group - { name ... n } + */ +class Element +{ + std::string name, data; + std::list children; + + void Parse (const char* buffer); +}; + + +} // end namespace LWS // --------------------------------------------------------------------------- /** LWS (LightWave Scene Format) importer class. @@ -76,10 +94,7 @@ protected: /** Called by Importer::GetExtensionList() for each loaded importer. * See BaseImporter::GetExtensionList() for details */ - void GetExtensionList(std::string& append) - { - append.append("*.lws"); - } + void GetExtensionList(std::string& append); // ------------------------------------------------------------------- /** Imports the given file into the given scene structure. diff --git a/code/OptimizeGraphProcess.cpp b/code/OptimizeGraphProcess.cpp index 6ef8d4dd4..cb25e2581 100644 --- a/code/OptimizeGraphProcess.cpp +++ b/code/OptimizeGraphProcess.cpp @@ -102,6 +102,7 @@ void OptimizeGraphProcess::FindLockedNodes(aiNode* node) { ai_assert(NULL != node); + // process animations for (unsigned int i = 0; i < pScene->mNumAnimations;++i) { aiAnimation* pani = pScene->mAnimations[i]; @@ -110,11 +111,34 @@ void OptimizeGraphProcess::FindLockedNodes(aiNode* node) aiNodeAnim* pba = pani->mChannels[a]; if (pba->mNodeName == node->mName) { - // this node is locked + // this node is locked, it is referenced by an animation channel node->mNumChildren |= AI_OG_UINT_MSB; } } } + + // process cameras + for (unsigned int i = 0; i < pScene->mNumCameras;++i) + { + aiCamera* p = pScene->mCameras[i]; + if (p->mName == node->mName) + { + // this node is locked, it is referenced by a camera + node->mNumChildren |= AI_OG_UINT_MSB; + } + } + + // process lights + for (unsigned int i = 0; i < pScene->mNumLights;++i) + { + aiLight* p = pScene->mLights[i]; + if (p->mName == node->mName) + { + // this node is locked, it is referenced by a light + node->mNumChildren |= AI_OG_UINT_MSB; + } + } + // call all children for (unsigned int i = 0; i < node->mNumChildren;++i) FindLockedNodes(node->mChildren[i]); @@ -250,233 +274,6 @@ inline unsigned int OptimizeGraphProcess::BinarySearch(NodeIndexList& sortedArra return (unsigned int)sortedArray.size(); } -// ------------------------------------------------------------------------------------------------ -void OptimizeGraphProcess::BuildUniqueBoneList( - std::vector::const_iterator it, - std::vector::const_iterator end, - std::list& asBones) -{ - - unsigned int iOffset = 0; - for (; it != end;++it) - { - for (unsigned int l = 0; l < (*it)->mNumBones;++l) - { - aiBone* p = (*it)->mBones[l]; - uint32_t itml = SuperFastHash(p->mName.data,(unsigned int)p->mName.length); - - std::list::iterator it2 = asBones.begin(); - std::list::iterator end2 = asBones.end(); - - for (;it2 != end2;++it2) - { - if ((*it2).first == itml) - { - (*it2).pSrcBones.push_back(BoneSrcIndex(p,iOffset)); - break; - } - } - if (end2 == it2) - { - // need to begin a new bone entry - asBones.push_back(BoneWithHash()); - BoneWithHash& btz = asBones.back(); - - // setup members - btz.first = itml; - btz.second = &p->mName; - btz.pSrcBones.push_back(BoneSrcIndex(p,iOffset)); - } - } - iOffset += (*it)->mNumVertices; - } -} - -// ------------------------------------------------------------------------------------------------ -void OptimizeGraphProcess::JoinBones( - std::vector::const_iterator it, - std::vector::const_iterator end, - aiMesh* out) -{ - ai_assert(NULL != out); - - // find we need to build an unique list of all bones. - // we work with hashes to make the comparisons MUCH faster, - // at least if we have many bones. - std::list asBones; - BuildUniqueBoneList(it,end,asBones); - - // now create the output bones - out->mBones = new aiBone*[asBones.size()]; - - for (std::list::const_iterator it = asBones.begin(), - end = asBones.end(); it != end;++it) - { - aiBone* pc = out->mBones[out->mNumBones++] = new aiBone(); - pc->mName = aiString( *((*it).second )); - - // get an itrator to the end of the list - std::vector< BoneSrcIndex >::const_iterator wend = (*it).pSrcBones.end(); - - // loop through all bones to be joined for this bone - for (std::vector< BoneSrcIndex >::const_iterator - wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit) - { - pc->mNumWeights += (*wmit).first->mNumWeights; - - // NOTE: different offset matrices for bones with equal names - // are - at the moment - not handled correctly. - if (wmit != (*it).pSrcBones.begin() && - pc->mOffsetMatrix != (*wmit).first->mOffsetMatrix) - { - DefaultLogger::get()->warn("Bones with equal names but different " - "offset matrices can't be joined at the moment. If this causes " - "problems, deactivate the OptimizeGraph-Step"); - - continue; - } - pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix; - } - // allocate the vertex weight array - aiVertexWeight* avw = pc->mWeights = new aiVertexWeight[pc->mNumWeights]; - - // and copy the final weights - adjust the vertex IDs by the - // face index offset of the coresponding mesh. - for (std::vector< BoneSrcIndex >::const_iterator - wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit) - { - aiBone* pip = (*wmit).first; - for (unsigned int mp = 0; mp < pip->mNumWeights;++mp,++avw) - { - const aiVertexWeight& vfi = pip->mWeights[mp]; - avw->mWeight = vfi.mWeight; - avw->mVertexId = vfi.mVertexId + (*wmit).second; - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -void OptimizeGraphProcess::JoinMeshes(std::vector& meshList, - aiMesh*& out, unsigned int max) -{ - ai_assert(NULL != out && 0 != max); - - out->mMaterialIndex = meshList[0]->mMaterialIndex; - - // allocate the output mesh - out = new aiMesh(); - std::vector::const_iterator end = meshList.begin()+max; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - out->mNumVertices += (*it)->mNumVertices; - out->mNumFaces += (*it)->mNumFaces; - out->mNumBones += AI_OG_UNMASK((*it)->mNumBones); - - // combine primitive type flags - out->mPrimitiveTypes |= (*it)->mPrimitiveTypes; - } - - if (out->mNumVertices) // just for safety - { - aiVector3D* pv2; - - // copy vertex positions - if (meshList[0]->HasPositions()) - { - pv2 = out->mVertices = new aiVector3D[out->mNumVertices]; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - ::memcpy(pv2,(*it)->mVertices,(*it)->mNumVertices*sizeof(aiVector3D)); - pv2 += (*it)->mNumVertices; - } - } - // copy normals - if (meshList[0]->HasNormals()) - { - pv2 = out->mNormals = new aiVector3D[out->mNumVertices]; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - ::memcpy(pv2,(*it)->mNormals,(*it)->mNumVertices*sizeof(aiVector3D)); - pv2 += (*it)->mNumVertices; - } - } - // copy tangents and bitangents - if (meshList[0]->HasTangentsAndBitangents()) - { - pv2 = out->mTangents = new aiVector3D[out->mNumVertices]; - aiVector3D* pv2b = out->mBitangents = new aiVector3D[out->mNumVertices]; - - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - ::memcpy(pv2, (*it)->mTangents, (*it)->mNumVertices*sizeof(aiVector3D)); - ::memcpy(pv2b,(*it)->mBitangents,(*it)->mNumVertices*sizeof(aiVector3D)); - pv2 += (*it)->mNumVertices; - pv2b += (*it)->mNumVertices; - } - } - // copy texture coordinates - unsigned int n = 0; - while (meshList[0]->HasTextureCoords(n)) - { - out->mNumUVComponents[n] = meshList[0]->mNumUVComponents[n]; - - pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices]; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - ::memcpy(pv2,(*it)->mTextureCoords[n],(*it)->mNumVertices*sizeof(aiVector3D)); - pv2 += (*it)->mNumVertices; - } - ++n; - } - // copy vertex colors - n = 0; - while (meshList[0]->HasVertexColors(n)) - { - aiColor4D* pv2 = out->mColors[n] = new aiColor4D[out->mNumVertices]; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - ::memcpy(pv2,(*it)->mColors[n],(*it)->mNumVertices*sizeof(aiColor4D)); - pv2 += (*it)->mNumVertices; - } - ++n; - } - } - - if (out->mNumFaces) // just for safety - { - // copy faces - out->mFaces = new aiFace[out->mNumFaces]; - aiFace* pf2 = out->mFaces; - - unsigned int ofs = 0; - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - { - for (unsigned int m = 0; m < (*it)->mNumFaces;++m,++pf2) - { - aiFace& face = (*it)->mFaces[m]; - pf2->mNumIndices = face.mNumIndices; - pf2->mIndices = face.mIndices; - - if (ofs) - { - // add the offset to the vertex - for (unsigned int q = 0; q < face.mNumIndices; ++q) - face.mIndices[q] += ofs; - } - ofs += (*it)->mNumVertices; - face.mIndices = NULL; - } - } - } - - // bones - as this is quite lengthy, I moved the code to a separate function - if (out->mNumBones)JoinBones(meshList.begin(),end,out); - - // delete all source meshes - for (std::vector::const_iterator it = meshList.begin(); it != end;++it) - delete *it; -} // ------------------------------------------------------------------------------------------------ void OptimizeGraphProcess::ApplyNodeMeshesOptimization(aiNode* pNode) @@ -513,7 +310,7 @@ void OptimizeGraphProcess::ApplyNodeMeshesOptimization(aiNode* pNode) if (iNumMeshes > 0) { apcMeshes[iNumMeshes++] = pScene->mMeshes[nm]; - JoinMeshes(apcMeshes,out,iNumMeshes); +// JoinMeshes(apcMeshes,out,iNumMeshes); } else out = pScene->mMeshes[nm]; @@ -785,13 +582,13 @@ void OptimizeGraphProcess::Execute( aiScene* pScene) a) the term "mesh node" stands for a node with numMeshes > 0 b) the term "animation node" stands for a node with numMeshes == 0, - regardless whether the node is referenced by animation channels. + regardless whether the node is referenced by animation channels, + lights or cameras Algorithm: 1. Compute hashes for all meshes that we're able to check whether two meshes are compatible. - 2. Remove animation nodes if we have been configured to do so 3. Find out which nodes may not be moved, so to speak are "locked" - a locked node will never be joined with neighbors. - A node lock is indicated by a set MSB in the aiNode::mNumChildren member diff --git a/code/OptimizeGraphProcess.h b/code/OptimizeGraphProcess.h index e00af97e7..d39441dd1 100644 --- a/code/OptimizeGraphProcess.h +++ b/code/OptimizeGraphProcess.h @@ -87,14 +87,6 @@ struct NodeIndexEntry : public std::pair aiNode* pNode; }; typedef std::vector NodeIndexList; -typedef std::pair BoneSrcIndex; - -// --------------------------------------------------------------------------- -struct BoneWithHash : public std::pair -{ - std::vector pSrcBones; -}; - // --------------------------------------------------------------------------- /** This post processing step reformats the output node graph to be more @@ -230,38 +222,6 @@ protected: void ApplyNodeMeshesOptimization(aiNode* pNode); - // ------------------------------------------------------------------- - /** Join meshes. - * The output meshes are deleted afterwards. - * @param meshList List of meshes to be joined - * @param out Receives a pointer to the output mesh. - */ - void JoinMeshes(std::vector& meshList,aiMesh*& out, - unsigned int max); - - - // ------------------------------------------------------------------- - /** Join bones from a collection of meshes. - * - * @param it First mesh to be processed - * @param end Last mesh to be processed - * @param out Valid output mesh to receive the output bone list. - */ - void JoinBones(std::vector::const_iterator it, - std::vector::const_iterator end, - aiMesh* out); - - - // ------------------------------------------------------------------- - /** Build a list of unique bones from a collection of meshes. - * - * @param it First mesh to be processed - * @param end Last mesh to be processed - * @param asBones Receives a list of unique bones - */ - void BuildUniqueBoneList(std::vector::const_iterator it, - std::vector::const_iterator end, - std::list& asBones); // ------------------------------------------------------------------- /** Build the output mesh list. diff --git a/code/RemoveVCProcess.cpp b/code/RemoveVCProcess.cpp index 62d0897c6..eff2e2a4b 100644 --- a/code/RemoveVCProcess.cpp +++ b/code/RemoveVCProcess.cpp @@ -48,14 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// MSB for type unsigned int -#define AI_RC_UINT_MSB (1u<<((sizeof(unsigned int)<<3u)-1u)) -#define AI_RC_UINT_MSB_2 (AI_RC_UINT_MSB>>1u) - -// unmask the two upper bits of an unsigned int -#define AI_RC_UNMASK(p) (p & (~(AI_RC_UINT_MSB|AI_RC_UINT_MSB_2))) - - // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer RemoveVCProcess::RemoveVCProcess() @@ -79,89 +71,70 @@ bool RemoveVCProcess::IsActive( unsigned int pFlags) const // ------------------------------------------------------------------------------------------------ // Small helper function to delete all elements in a T** aray using delete template -inline void ArrayDelete(T**& in, unsigned int num) +inline void ArrayDelete(T**& in, unsigned int& num) { for (unsigned int i = 0; i < num; ++i) delete in[i]; delete[] in; in = NULL; + num = 0; } -// ------------------------------------------------------------------------------------------------ -// Small helper function to set a sepcific bit in the aiNode::mNumMeshes member of all nodes -// that are referenced by the elements of an array (T::mName must be there) -template -inline void MaskNodes(aiNode* node,T** in, unsigned int num, unsigned int bit) -{ - if (node->mName.length) - { - for (unsigned int i = 0; i < num;++i) - { - T* cur = in[i]; - if (cur->mName == node->mName) - { - node->mNumMeshes |= bit; - break; - } - } - } - for (unsigned int i = 0; i < node->mNumChildren;++i) - MaskNodes(node->mChildren[i],in,num,bit); -} - -// ------------------------------------------------------------------------------------------------ -// Updates the node graph - removes all nodes which have the "remove" flag set and the -// "don't remove" flag not set. Nodes with meshes are never deleted. -bool UpdateNodeGraph(aiNode* node,std::list& childsOfParent,bool root) -{ - register bool b = false; - - std::list mine; - for (unsigned int i = 0; i < node->mNumChildren;++i) - { - if(UpdateNodeGraph(node->mChildren[i],mine,false)) - b = true; - } - - if (!root && !node->mNumMeshes && AI_RC_UINT_MSB == (node->mNumMeshes & (AI_RC_UINT_MSB | AI_RC_UINT_MSB_2))) - { - // this node needs to be removed - if(node->mNumChildren) - { - childsOfParent.insert(childsOfParent.end(),mine.begin(),mine.end()); - - // set all children to NULL to make sure they are not deleted when we delete ourself - for (unsigned int i = 0; i < node->mNumChildren;++i) - node->mChildren[i] = NULL; - } - b = true; - delete node; - } - else - { - AI_RC_UNMASK(node->mNumMeshes); - childsOfParent.push_back(node); - - if (b) - { - // reallocate the array of our children here - node->mNumChildren = (unsigned int)mine.size(); - aiNode** const children = new aiNode*[mine.size()]; - aiNode** ptr = children; - - for (std::list::iterator it = mine.begin(), end = mine.end(); - it != end; ++it) - { - *ptr++ = *it; - } - delete[] node->mChildren; - node->mChildren = children; - return false; - } - } - return b; -} +//// ------------------------------------------------------------------------------------------------ +//// Updates the node graph - removes all nodes which have the "remove" flag set and the +//// "don't remove" flag not set. Nodes with meshes are never deleted. +//bool UpdateNodeGraph(aiNode* node,std::list& childsOfParent,bool root) +//{ +// register bool b = false; +// +// std::list mine; +// for (unsigned int i = 0; i < node->mNumChildren;++i) +// { +// if(UpdateNodeGraph(node->mChildren[i],mine,false)) +// b = true; +// } +// +// // somewhat tricky ... mNumMeshes must be originally 0 and MSB2 may not be set, +// // so we can do a simple comparison against MSB here +// if (!root && AI_RC_UINT_MSB == node->mNumMeshes ) +// { +// // this node needs to be removed +// if(node->mNumChildren) +// { +// childsOfParent.insert(childsOfParent.end(),mine.begin(),mine.end()); +// +// // set all children to NULL to make sure they are not deleted when we delete ourself +// for (unsigned int i = 0; i < node->mNumChildren;++i) +// node->mChildren[i] = NULL; +// } +// b = true; +// delete node; +// } +// else +// { +// AI_RC_UNMASK(node->mNumMeshes); +// childsOfParent.push_back(node); +// +// if (b) +// { +// // reallocate the array of our children here +// node->mNumChildren = (unsigned int)mine.size(); +// aiNode** const children = new aiNode*[mine.size()]; +// aiNode** ptr = children; +// +// for (std::list::iterator it = mine.begin(), end = mine.end(); +// it != end; ++it) +// { +// *ptr++ = *it; +// } +// delete[] node->mChildren; +// node->mChildren = children; +// return false; +// } +// } +// return b; +//} // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. @@ -175,6 +148,7 @@ void RemoveVCProcess::Execute( aiScene* pScene) // handle animations if ( configDeleteFlags & aiComponent_ANIMATIONS) { + bHas = true; ArrayDelete(pScene->mAnimations,pScene->mNumAnimations); } @@ -212,21 +186,13 @@ void RemoveVCProcess::Execute( aiScene* pScene) // handle light sources if ( configDeleteFlags & aiComponent_LIGHTS) { - // mask nodes for removal - MaskNodes(pScene->mRootNode,pScene->mLights,pScene->mNumLights, - AI_RC_UINT_MSB); - - bHas = bMasked = true; + bHas = true; ArrayDelete(pScene->mLights,pScene->mNumLights); } // handle camneras if ( configDeleteFlags & aiComponent_CAMERAS) { - // mask nodes for removal - MaskNodes(pScene->mRootNode,pScene->mLights,pScene->mNumLights, - AI_RC_UINT_MSB); - bHas = true; ArrayDelete(pScene->mCameras,pScene->mNumCameras); } @@ -241,46 +207,11 @@ void RemoveVCProcess::Execute( aiScene* pScene) { for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { - if( this->ProcessMesh( pScene->mMeshes[a])) + if( ProcessMesh( pScene->mMeshes[a])) bHas = true; } - if (configDeleteFlags & aiComponent_BONEWEIGHTS && bHas)bMasked = true; } - // now check which scenegraph nodes are unnecessary now - // we use the upper two bits of aiNode::mNumMeshes as - // temporary storage. - // MSB means: REMOVE ME! - // MSB>>1 means: NO, DON'T REMOVE ME (Veto) - if (bMasked) - { - if (pScene->mNumLights) - { - MaskNodes(pScene->mRootNode,pScene->mLights,pScene->mNumLights, - AI_RC_UINT_MSB_2); - } - if (pScene->mNumCameras) - { - MaskNodes(pScene->mRootNode,pScene->mCameras,pScene->mNumCameras, - AI_RC_UINT_MSB_2); - } - if (!(configDeleteFlags & aiComponent_BONEWEIGHTS)) - { - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - { - aiMesh* mesh = pScene->mMeshes[i]; - if (mesh->mNumBones) - { - MaskNodes(pScene->mRootNode,mesh->mBones,mesh->mNumBones, - AI_RC_UINT_MSB_2); - } - } - } - std::list dummy; - UpdateNodeGraph(pScene->mRootNode,dummy, true); - - // the root node will never be deleted - } // now check whether the result is still a full scene if (!pScene->mNumMeshes || !pScene->mNumMaterials) @@ -385,13 +316,8 @@ bool RemoveVCProcess::ProcessMesh(aiMesh* pMesh) // handle bones if (configDeleteFlags & aiComponent_BONEWEIGHTS && pMesh->mBones) { - // mask nodes for removal - MaskNodes(mScene->mRootNode,pMesh->mBones,pMesh->mNumBones, - AI_RC_UINT_MSB); - ArrayDelete(pMesh->mBones,pMesh->mNumBones); ret = true; } - return ret; } diff --git a/code/SceneCombiner.cpp b/code/SceneCombiner.cpp index c88ee2d71..389f1b1cc 100644 --- a/code/SceneCombiner.cpp +++ b/code/SceneCombiner.cpp @@ -38,22 +38,83 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ + +// ---------------------------------------------------------------------------- /** @file Implements Assimp::SceneCombiner. This is a smart utility * class that can be used to combine several scenes, meshes, ... - * in one. + * in one. Currently these utilities are used by the IRR and LWS + * loaders and by the OptimizeGraph step. */ - +// ---------------------------------------------------------------------------- #include "AssimpPCH.h" #include "SceneCombiner.h" #include "fast_atof.h" +#include "Hash.h" -namespace Assimp +// ---------------------------------------------------------------------------- +// We need boost::random here. The workaround uses rand() instead of a proper +// Mersenne twister, but I think it should still be 'random' enough for our +// purposes. +// ---------------------------------------------------------------------------- +#ifdef ASSIMP_BUILD_BOOST_WORKAROUND + +# include "../include/BoostWorkaround/boost/random/uniform_int.hpp" +# include "../include/BoostWorkaround/boost/random/variate_generator.hpp" +# include "../include/BoostWorkaround/boost/random/mersenne_twister.hpp" + +#else + +# include +# include +# include + +#endif + + +namespace Assimp { + +// ------------------------------------------------------------------------------------------------ +/** This is a small helper data structure simplifying our work + */ +struct SceneHelper { + SceneHelper () + : scene (NULL) + , idlen (0) + { + id[0] = 0; + } + + SceneHelper (aiScene* _scene) + : scene (_scene) + , idlen (0) + { + id[0] = 0; + } + + AI_FORCE_INLINE aiScene* operator-> () const + { + return scene; + } + + // scene we're working on + aiScene* scene; + + // prefix to be added to all identifiers in the scene ... + char id [32]; + + // and its strlen() + unsigned int idlen; +}; // ------------------------------------------------------------------------------------------------ // Add a prefix to a string inline void PrefixString(aiString& string,const char* prefix, unsigned int len) { + // If the string is already prefixed, we won't prefix it a second time + if (string.length >= 1 && string.data[0] == '$') + return; + // Add the prefix ::memmove(string.data+len,string.data,string.length+1); ::memcpy (string.data, prefix, len); @@ -75,73 +136,244 @@ void SceneCombiner::AddNodePrefixes(aiNode* node, const char* prefix, unsigned i AddNodePrefixes(node->mChildren[i],prefix,len); } +// ------------------------------------------------------------------------------------------------ +// Add an offset to all mesh indices in a node graph +void SceneCombiner::OffsetNodeMeshIndices (aiNode* node, unsigned int offset) +{ + for (unsigned int i = 0; i < node->mNumMeshes;++i) + node->mMeshes[i] += offset; + + for (unsigned int i = 0; i < node->mNumChildren;++i) + OffsetNodeMeshIndices(node->mChildren[i],offset); +} + // ------------------------------------------------------------------------------------------------ // Merges two scenes. Currently only used by the LWS loader. -void SceneCombiner::MergeScenes(aiScene* dest,std::vector& src, +void SceneCombiner::MergeScenes(aiScene** _dest,std::vector& src, unsigned int flags) { - ai_assert(NULL != dest); - ai_assert(0 == dest->mNumTextures); - ai_assert(0 == dest->mNumLights); - ai_assert(0 == dest->mNumCameras); - ai_assert(0 == dest->mNumMeshes); - ai_assert(0 == dest->mNumMaterials); + ai_assert(NULL != _dest); - if (src.empty())return; + // if _dest points to NULL allocate a new scene. Otherwise clear the old and reuse it + if (src.empty()) + { + if (*_dest) + { + (*_dest)->~aiScene(); + SceneCombiner::CopySceneFlat(_dest,src[0]); + } + else *_dest = src[0]; + return; + } + if (*_dest)(*_dest)->~aiScene(); + else *_dest = new aiScene(); - // some iterators will be needed multiple times - std::vector::iterator begin = src.begin(), - end = src.end(), cur; + aiScene* dest = *_dest; + + // Create a dummy scene to serve as master for the others + aiScene* master = new aiScene(); + master->mRootNode = new aiNode(); + master->mRootNode->mName.Set(""); + + std::vector srcList (src.size()); + for (unsigned int i = 0; i < srcList.size();++i) + { + srcList[i] = AttachmentInfo(src[i],master->mRootNode); + } + + // 'master' will be deleted afterwards + MergeScenes (_dest, master, srcList, flags); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::AttachToGraph (aiNode* attach, std::vector& srcList) +{ + unsigned int cnt; + + for (cnt = 0; cnt < attach->mNumChildren;++cnt) + AttachToGraph(attach->mChildren[cnt],srcList); + + cnt = 0; + for (std::vector::iterator it = srcList.begin(); + it != srcList.end(); ++it) + { + if ((*it).attachToNode == attach) + ++cnt; + } + + if (cnt) + { + aiNode** n = new aiNode*[cnt+attach->mNumChildren]; + if (attach->mNumChildren) + { + ::memcpy(n,attach->mChildren,sizeof(void*)*attach->mNumChildren); + delete[] attach->mChildren; + } + attach->mChildren = n; + + n += attach->mNumChildren; + attach->mNumChildren += cnt; + + for (unsigned int i = 0; i < srcList.size();++i) + { + NodeAttachmentInfo& att = srcList[i]; + if (att.attachToNode == attach) + { + *n = att.node; + (**n).mParent = attach; + ++n; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::AttachToGraph ( aiScene* master, + std::vector& src) +{ + ai_assert(NULL != master); + AttachToGraph(master->mRootNode,src); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, + std::vector& srcList, + unsigned int flags) +{ + ai_assert(NULL != _dest); + + // if _dest points to NULL allocate a new scene. Otherwise clear the old and reuse it + if (srcList.empty()) + { + if (*_dest) + { + (*_dest)->~aiScene(); + SceneCombiner::CopySceneFlat(_dest,master); + } + else *_dest = master; + return; + } + if (*_dest)(*_dest)->~aiScene(); + else *_dest = new aiScene(); + + aiScene* dest = *_dest; + + std::vector src (srcList.size()+1); + src[0].scene = master; + for (unsigned int i = 0; i < srcList.size();++i) + { + src[i+1] = SceneHelper( srcList[i].scene ); + } + + // this helper array specifies which scenes are duplicates of others + std::vector duplicates(src.size(),0xffffffff); // this helper array is used as lookup table several times std::vector offset(src.size()); - std::vector::iterator ofsbegin = offset.begin(), - offend = offset.end(), ofscur; + + + // Find duplicate scenes + for (unsigned int i = 0; i < src.size();++i) + { + if (duplicates[i] != i && duplicates[i] != 0xffffffff)continue; + duplicates[i] = i; + for ( unsigned int a = i+1; a < src.size(); ++a) + { + if (src[i].scene == src[a].scene) + duplicates[a] = i; + } + } + + // Generate unique names for all named stuff? + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) + { + // Construct a proper random number generator + boost::mt19937 rng( ::clock() ); + boost::uniform_int<> dist(1,1 << 24u); + boost::variate_generator > rndGen(rng, dist); + + for (unsigned int i = 1; i < src.size();++i) + { + //if (i != duplicates[i]) + //{ + // // duplicate scenes share the same UID + // ::strcpy( src[i].id, src[duplicates[i]].id ); + // src[i].idlen = src[duplicates[i]].idlen; + + // continue; + //} + + const unsigned int random = rndGen(); + src[i].idlen = ::sprintf(src[i].id,"$%.6X$_",random); + } + } unsigned int cnt; - bool bNeedPrefix = false; // First find out how large the respective output arrays must be - for ( cur = begin; cur != end; ++cur) + for ( unsigned int n = 0; n < src.size();++n ) { - dest->mNumTextures += (*cur)->mNumTextures; - dest->mNumMaterials += (*cur)->mNumMaterials; - dest->mNumMeshes += (*cur)->mNumMeshes; + SceneHelper* cur = &src[n]; + + if (n == duplicates[n] || flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + { + dest->mNumTextures += (*cur)->mNumTextures; + dest->mNumMaterials += (*cur)->mNumMaterials; + dest->mNumMeshes += (*cur)->mNumMeshes; + } + dest->mNumLights += (*cur)->mNumLights; dest->mNumCameras += (*cur)->mNumCameras; dest->mNumAnimations += (*cur)->mNumAnimations; - if ((*cur)->mNumAnimations > 0 || - (*cur)->mNumCameras > 0 || - (*cur)->mNumLights > 0) - { - bNeedPrefix = true; - } + // Combine the flags of all scenes + dest->mFlags |= (*cur)->mFlags; } - // generate the output texture list + an offset table + // generate the output texture list + an offset table for all texture indices if (dest->mNumTextures) { aiTexture** pip = dest->mTextures = new aiTexture*[dest->mNumMaterials]; - for ( cur = begin, ofscur = ofsbegin,cnt = 0; cur != end; ++cur,++ofscur) + cnt = 0; + for ( unsigned int n = 0; n < src.size();++n ) { - for (unsigned int i = 0; i < (*cur)->mNumTextures;++i,++pip) - *pip = (*cur)->mTextures[i]; + SceneHelper* cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumTextures;++i) + { + if (n != duplicates[n]) + { + if ( flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip,(*cur)->mTextures[i]); - *ofscur = cnt; - cnt += (*cur)->mNumTextures; + else continue; + } + else *pip = (*cur)->mTextures[i]; + ++pip; + } + + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mTextures); } } - // generate the output material list + an offset table + // generate the output material list + an offset table for all material indices if (dest->mNumMaterials) { aiMaterial** pip = dest->mMaterials = new aiMaterial*[dest->mNumMaterials]; - for ( cur = begin, ofscur = ofsbegin,cnt = 0; cur != end; ++cur,++ofscur) + cnt = 0; + for ( unsigned int n = 0; n < src.size();++n ) { - for (unsigned int i = 0; i < (*cur)->mNumMaterials;++i,++pip) + SceneHelper* cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumMaterials;++i) { - *pip = (*cur)->mMaterials[i]; + if (n != duplicates[n]) + { + if ( flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip,(*cur)->mMaterials[i]); + + else continue; + } + else *pip = (*cur)->mMaterials[i]; if ((*cur)->mNumTextures != dest->mNumTextures) { @@ -163,98 +395,178 @@ void SceneCombiner::MergeScenes(aiScene* dest,std::vector& src, if ('*' == s.data[0]) { // Offset the index and write it back .. - const unsigned int idx = strtol10(&s.data[1]) + *ofscur; + const unsigned int idx = strtol10(&s.data[1]) + offset[n]; itoa10(&s.data[1],sizeof(s.data)-1,idx); } } + + // Need to generate new, unique material names? + else if (!::strcmp( prop->mKey.data,"$mat.name" ) && + flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) + { + aiString* pcSrc = (aiString*) prop->mData; + PrefixString(*pcSrc, (*cur).id, (*cur).idlen); + } } } + ++pip; } - *ofscur = cnt; - cnt += (*cur)->mNumMaterials; + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mMaterials); } } - // generate the output mesh list + again an offset table + // generate the output mesh list + again an offset table for all mesh indices if (dest->mNumMeshes) { aiMesh** pip = dest->mMeshes = new aiMesh*[dest->mNumMeshes]; - for ( cur = begin, ofscur = ofsbegin,cnt = 0; cur != end; ++cur,++ofscur) + cnt = 0; + for ( unsigned int n = 0; n < src.size();++n ) { - for (unsigned int i = 0; i < (*cur)->mNumMeshes;++i,++pip) + SceneHelper* cur = &src[n]; + for (unsigned int i = 0; i < (*cur)->mNumMeshes;++i) { - *pip = (*cur)->mMeshes[i]; + if (n != duplicates[n]) + { + if ( flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) + Copy(pip, (*cur)->mMeshes[i]); + + else continue; + } + else *pip = (*cur)->mMeshes[i]; // update the material index of the mesh - (*pip)->mMaterialIndex += *ofscur; + (*pip)->mMaterialIndex += offset[n]; + + ++pip; } // reuse the offset array - store now the mesh offset in it - *ofscur = cnt; - cnt += (*cur)->mNumMeshes; + offset[n] = cnt; + cnt = (unsigned int)(pip - dest->mMeshes); } } + std::vector nodes; + nodes.reserve(srcList.size()); + + // ---------------------------------------------------------------------------- // Now generate the output node graph. We need to make those // names in the graph that are referenced by anims or lights - // or cameras unique. So we add a prefix to them ... $SC1_ - // First step for now: find out which nodes are referenced by - // anim bones or cameras and add the prefix to their names. + // or cameras unique. So we add a prefix to them ... $_ + // We could also use a counter, but using a random value allows us to + // use just one prefix if we are joining multiple scene hierarchies recursively. + // Chances are quite good we don't collide, so we try that ... + // ---------------------------------------------------------------------------- - if (bNeedPrefix) + // Allocate space for light sources, cameras and animations + aiLight** ppLights = dest->mLights = (dest->mNumLights + ? new aiLight*[dest->mNumLights] : NULL); + + aiCamera** ppCameras = dest->mCameras = (dest->mNumCameras + ? new aiCamera*[dest->mNumCameras] : NULL); + + aiAnimation** ppAnims = dest->mAnimations = (dest->mNumAnimations + ? new aiAnimation*[dest->mNumAnimations] : NULL); + + for ( unsigned int n = 0; n < src.size();++n ) { - // Allocate space for light sources, cameras and animations - aiLight** ppLights = dest->mLights = (dest->mNumLights - ? new aiLight*[dest->mNumLights] : NULL); + SceneHelper* cur = &src[n]; + aiNode* node; - aiCamera** ppCameras = dest->mCameras = (dest->mNumCameras - ? new aiCamera*[dest->mNumCameras] : NULL); - - aiAnimation** ppAnims = dest->mAnimations = (dest->mNumAnimations - ? new aiAnimation*[dest->mNumAnimations] : NULL); - - - for (cur = begin, cnt = 0; cur != end; ++cur) + // To offset or not to offset, this is the question + if (n != duplicates[n]) { - char buffer[10]; - buffer[0] = '$'; - buffer[1] = 'S'; - buffer[2] = 'C'; + Copy( &node, (*cur)->mRootNode ); - char* sz = &buffer[itoa10(&buffer[3],sizeof(buffer)-3, cnt++)+2]; - *sz++ = '_'; - *sz++ = '\0'; - - const unsigned int len = (unsigned int)::strlen(buffer); - AddNodePrefixes((*cur)->mRootNode,buffer,len); - - // Copy light sources, add the prefix to them, too - for (unsigned int i = 0; i < (*cur)->mNumLights;++i,++ppLights) + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { - *ppLights = (*cur)->mLights[i]; - PrefixString((*ppLights)->mName,buffer,len); - } - // Copy cameras, add the prefix to them, too - for (unsigned int i = 0; i < (*cur)->mNumCameras;++i,++ppCameras) - { - *ppCameras = (*cur)->mCameras[i]; - PrefixString((*ppCameras)->mName,buffer,len); + // (note:) they are already 'offseted' by offset[duplicates[n]] + OffsetNodeMeshIndices(node,offset[n] - offset[duplicates[n]]); } + } + else // if (n == duplicates[n]) + { + node = (*cur)->mRootNode; + OffsetNodeMeshIndices(node,offset[n]); + } + if (n) // src[0] is the master node + nodes.push_back(NodeAttachmentInfo( node,srcList[n-1].attachToNode )); - // Copy animations, add the prefix to them, too - for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i,++ppAnims) + // -------------------------------------------------------------------- + // Copy light sources + for (unsigned int i = 0; i < (*cur)->mNumLights;++i,++ppLights) + { + if (n != duplicates[n]) // duplicate scene? { - *ppAnims = (*cur)->mAnimations[i]; - PrefixString((*ppAnims)->mName,buffer,len); + Copy(ppLights, (*cur)->mLights[i]); } + else *ppLights = (*cur)->mLights[i]; + } + + // -------------------------------------------------------------------- + // Copy cameras + for (unsigned int i = 0; i < (*cur)->mNumCameras;++i,++ppCameras) + { + if (n != duplicates[n]) // duplicate scene? + { + Copy(ppCameras, (*cur)->mCameras[i]); + } + else *ppCameras = (*cur)->mCameras[i]; + } + + // -------------------------------------------------------------------- + // Copy animations + for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i,++ppAnims) + { + if (n != duplicates[n]) // duplicate scene? + { + Copy(ppAnims, (*cur)->mAnimations[i]); + } + else *ppAnims = (*cur)->mAnimations[i]; } } - // now delete all input scenes - for (cur = begin; cur != end; ++cur) + for ( unsigned int n = 1; n < src.size();++n ) { - aiScene* deleteMe = *cur; + SceneHelper* cur = &src[n]; + // -------------------------------------------------------------------- + // Add prefixes + if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) + { + for (unsigned int i = 0; i < (*cur)->mNumLights;++i) + PrefixString(dest->mLights[i]->mName,(*cur).id,(*cur).idlen); + + for (unsigned int i = 0; i < (*cur)->mNumCameras;++i) + PrefixString(dest->mCameras[i]->mName,(*cur).id,(*cur).idlen); + + for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i) + { + aiAnimation* anim = dest->mAnimations[i]; + PrefixString(anim->mName,(*cur).id,(*cur).idlen); + + // don't forget to update all node animation channels + for (unsigned int a = 0; a < anim->mNumChannels;++a) + PrefixString(anim->mChannels[a]->mNodeName,(*cur).id,(*cur).idlen); + } + + AddNodePrefixes(nodes[n-1].node,(*cur).id,(*cur).idlen); + } + } + + // Now build the output graph + AttachToGraph ( master, nodes); + dest->mRootNode = master->mRootNode; + + // now delete all input scenes. Make sure duplicate scenes aren't + // deleted more than one time + for ( unsigned int n = 0; n < src.size();++n ) + { + if (n != duplicates[n]) // duplicate scene? + continue; + + aiScene* deleteMe = src[n].scene; // We need to delete the arrays before the destructor is called - // we are reusing the array members @@ -264,28 +576,522 @@ void SceneCombiner::MergeScenes(aiScene* dest,std::vector& src, delete[] deleteMe->mMaterials; deleteMe->mMaterials = NULL; delete[] deleteMe->mAnimations; deleteMe->mAnimations = NULL; - delete[] deleteMe->mRootNode->mChildren; - deleteMe->mRootNode->mChildren = NULL; + deleteMe->mRootNode = NULL; // Now we can safely delete the scene - delete[] deleteMe; - AI_DEBUG_INVALIDATE_PTR(*cur); + delete deleteMe; + } + + // We're finished +} + +// ------------------------------------------------------------------------------------------------ +// Build a list of unique bones +void SceneCombiner::BuildUniqueBoneList(std::list& asBones, + std::vector::const_iterator it, + std::vector::const_iterator end) +{ + unsigned int iOffset = 0; + for (; it != end;++it) + { + for (unsigned int l = 0; l < (*it)->mNumBones;++l) + { + aiBone* p = (*it)->mBones[l]; + uint32_t itml = SuperFastHash(p->mName.data,(unsigned int)p->mName.length); + + std::list::iterator it2 = asBones.begin(); + std::list::iterator end2 = asBones.end(); + + for (;it2 != end2;++it2) + { + if ((*it2).first == itml) + { + (*it2).pSrcBones.push_back(BoneSrcIndex(p,iOffset)); + break; + } + } + if (end2 == it2) + { + // need to begin a new bone entry + asBones.push_back(BoneWithHash()); + BoneWithHash& btz = asBones.back(); + + // setup members + btz.first = itml; + btz.second = &p->mName; + btz.pSrcBones.push_back(BoneSrcIndex(p,iOffset)); + } + } + iOffset += (*it)->mNumVertices; } } - // ------------------------------------------------------------------------------------------------ -void SceneCombiner::MergeScenes(aiScene* dest, const aiScene* master, - std::vector& src, - unsigned int flags) +// Merge a list of bones +void SceneCombiner::MergeBones(aiMesh* out,std::vector::const_iterator it, + std::vector::const_iterator end) { + ai_assert(NULL != out && !out->mNumBones); + + // find we need to build an unique list of all bones. + // we work with hashes to make the comparisons MUCH faster, + // at least if we have many bones. + std::list asBones; + BuildUniqueBoneList(asBones, it,end); + + // now create the output bones + out->mBones = new aiBone*[asBones.size()]; + + for (std::list::const_iterator it = asBones.begin(),end = asBones.end(); + it != end;++it) + { + // Allocate a bone and setup it's name + aiBone* pc = out->mBones[out->mNumBones++] = new aiBone(); + pc->mName = aiString( *((*it).second )); + + // Get an itrator to the end of the list + std::vector< BoneSrcIndex >::const_iterator wend = (*it).pSrcBones.end(); + + // Loop through all bones to be joined for this bone + for (std::vector< BoneSrcIndex >::const_iterator + wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit) + { + pc->mNumWeights += (*wmit).first->mNumWeights; + + // NOTE: different offset matrices for bones with equal names + // are - at the moment - not handled correctly. + if (wmit != (*it).pSrcBones.begin() && + pc->mOffsetMatrix != (*wmit).first->mOffsetMatrix) + { + DefaultLogger::get()->warn("Bones with equal names but different " + "offset matrices can't be joined at the moment. If this causes " + "problems, deactivate the OptimizeGraph-Step"); + + continue; + } + pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix; + } + + // Allocate the vertex weight array + aiVertexWeight* avw = pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + + // And copy the final weights - adjust the vertex IDs by the + // face index offset of the coresponding mesh. + for (std::vector< BoneSrcIndex >::const_iterator + wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit) + { + aiBone* pip = (*wmit).first; + for (unsigned int mp = 0; mp < pip->mNumWeights;++mp,++avw) + { + const aiVertexWeight& vfi = pip->mWeights[mp]; + avw->mWeight = vfi.mWeight; + avw->mVertexId = vfi.mVertexId + (*wmit).second; + } + } + } } +// ------------------------------------------------------------------------------------------------ +// Merge a list of meshes +void SceneCombiner::MergeMeshes(aiMesh** _out,unsigned int flags, + std::vector::const_iterator begin, + std::vector::const_iterator end) +{ + ai_assert(NULL != _out); + + if (begin == end) + { + *_out = NULL; // no meshes ... + return; + } + + // Allocate the output mesh + aiMesh* out = *_out = new aiMesh(); + out->mMaterialIndex = (*begin)->mMaterialIndex; + + // Find out how much output storage we'll need + for (std::vector::const_iterator it = begin; it != end;++it) + { + out->mNumVertices += (*it)->mNumVertices; + out->mNumFaces += (*it)->mNumFaces; + out->mNumBones += (*it)->mNumBones; + + // combine primitive type flags + out->mPrimitiveTypes |= (*it)->mPrimitiveTypes; + } + + if (out->mNumVertices) // just for safety + { + aiVector3D* pv2; + + // copy vertex positions + if ((**begin).HasPositions()) + { + pv2 = out->mVertices = new aiVector3D[out->mNumVertices]; + for (std::vector::const_iterator it = begin; it != end;++it) + { + if ((*it)->mNormals) + { + ::memcpy(pv2,(*it)->mVertices,(*it)->mNumVertices*sizeof(aiVector3D)); + } + else DefaultLogger::get()->warn("JoinMeshes: Positions expected, but mesh contains no positions"); + pv2 += (*it)->mNumVertices; + } + } + // copy normals + if ((**begin).HasNormals()) + { + pv2 = out->mNormals = new aiVector3D[out->mNumVertices]; + for (std::vector::const_iterator it = begin; it != end;++it) + { + if ((*it)->mNormals) + { + ::memcpy(pv2,(*it)->mNormals,(*it)->mNumVertices*sizeof(aiVector3D)); + } + else DefaultLogger::get()->warn("JoinMeshes: Normals expected, but mesh contains no normals"); + pv2 += (*it)->mNumVertices; + } + } + // copy tangents and bitangents + if ((**begin).HasTangentsAndBitangents()) + { + pv2 = out->mTangents = new aiVector3D[out->mNumVertices]; + aiVector3D* pv2b = out->mBitangents = new aiVector3D[out->mNumVertices]; + + for (std::vector::const_iterator it = begin; it != end;++it) + { + if ((*it)->mTangents) + { + ::memcpy(pv2, (*it)->mTangents, (*it)->mNumVertices*sizeof(aiVector3D)); + ::memcpy(pv2b,(*it)->mBitangents,(*it)->mNumVertices*sizeof(aiVector3D)); + } + else DefaultLogger::get()->warn("JoinMeshes: Tangents expected, but mesh contains no tangents"); + pv2 += (*it)->mNumVertices; + pv2b += (*it)->mNumVertices; + } + } + // copy texture coordinates + unsigned int n = 0; + while ((**begin).HasTextureCoords(n)) + { + out->mNumUVComponents[n] = (*begin)->mNumUVComponents[n]; + + pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices]; + for (std::vector::const_iterator it = begin; it != end;++it) + { + if ((*it)->mTextureCoords[n]) + { + ::memcpy(pv2,(*it)->mTextureCoords[n],(*it)->mNumVertices*sizeof(aiVector3D)); + } + else DefaultLogger::get()->warn("JoinMeshes: UVs expected, but mesh contains no UVs"); + pv2 += (*it)->mNumVertices; + } + ++n; + } + // copy vertex colors + n = 0; + while ((**begin).HasVertexColors(n)) + { + aiColor4D* pv2 = out->mColors[n] = new aiColor4D[out->mNumVertices]; + for (std::vector::const_iterator it = begin; it != end;++it) + { + if ((*it)->mColors[n]) + { + ::memcpy(pv2,(*it)->mColors[n],(*it)->mNumVertices*sizeof(aiColor4D)); + } + else DefaultLogger::get()->warn("JoinMeshes: VCs expected, but mesh contains no VCs"); + pv2 += (*it)->mNumVertices; + } + ++n; + } + } + + if (out->mNumFaces) // just for safety + { + // copy faces + out->mFaces = new aiFace[out->mNumFaces]; + aiFace* pf2 = out->mFaces; + + unsigned int ofs = 0; + for (std::vector::const_iterator it = begin; it != end;++it) + { + for (unsigned int m = 0; m < (*it)->mNumFaces;++m,++pf2) + { + aiFace& face = (*it)->mFaces[m]; + pf2->mNumIndices = face.mNumIndices; + pf2->mIndices = face.mIndices; + + if (ofs) + { + // add the offset to the vertex + for (unsigned int q = 0; q < face.mNumIndices; ++q) + face.mIndices[q] += ofs; + } + ofs += (*it)->mNumVertices; + face.mIndices = NULL; + } + } + } + + // bones - as this is quite lengthy, I moved the code to a separate function + if (out->mNumBones) + MergeBones(out,begin,end); + + // delete all source meshes + for (std::vector::const_iterator it = begin; it != end;++it) + delete *it; +} // ------------------------------------------------------------------------------------------------ -void SceneCombiner::MergeMeshes(aiMesh* dest,std::vector& src, - unsigned int flags) +template +inline void CopyPtrArray (Type**& dest, Type** src, unsigned int num) { + if (!num) + { + dest = NULL; + return; + } + dest = new Type*[num]; + for (unsigned int i = 0; i < num;++i) + SceneCombiner::Copy(&dest[i],src[i]); } +// ------------------------------------------------------------------------------------------------ +template +inline void GetArrayCopy (Type*& dest, unsigned int num ) +{ + if (!dest)return; + Type* old = dest; + + dest = new Type[num]; + ::memcpy(dest, old, sizeof(Type) * num); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::CopySceneFlat(aiScene** _dest,aiScene* src) +{ + // reuse the old scene or allocate a new? + if (*_dest)(*_dest)->~aiScene(); + else *_dest = new aiScene(); + + ::memcpy(*_dest,src,sizeof(aiScene)); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::CopyScene(aiScene** _dest,aiScene* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiScene* dest = *_dest = new aiScene(); + + // copy animations + dest->mNumAnimations = src->mNumAnimations; + CopyPtrArray(dest->mAnimations,src->mAnimations, + dest->mNumAnimations); + + // copy textures + dest->mNumTextures = src->mNumTextures; + CopyPtrArray(dest->mTextures,src->mTextures, + dest->mNumTextures); + + // copy materials + dest->mNumMaterials = src->mNumMaterials; + CopyPtrArray(dest->mMaterials,src->mMaterials, + dest->mNumMaterials); + + // copy lights + dest->mNumLights = src->mNumLights; + CopyPtrArray(dest->mLights,src->mLights, + dest->mNumLights); + + // copy cameras + dest->mNumCameras = src->mNumCameras; + CopyPtrArray(dest->mCameras,src->mCameras, + dest->mNumCameras); + + // copy meshes + dest->mNumMeshes = src->mNumMeshes; + CopyPtrArray(dest->mMeshes,src->mMeshes, + dest->mNumMeshes); + + // now - copy the root node of the scene (deep copy, too) + Copy( &dest->mRootNode, src->mRootNode); + + // and keep the flags ... + dest->mFlags = src->mFlags; +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiMesh** _dest, const aiMesh* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiMesh* dest = *_dest = new aiMesh(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiMesh)); + + // and reallocate all arrays + GetArrayCopy( dest->mVertices, dest->mNumVertices ); + GetArrayCopy( dest->mNormals , dest->mNumVertices ); + GetArrayCopy( dest->mTangents, dest->mNumVertices ); + GetArrayCopy( dest->mBitangents, dest->mNumVertices ); + + unsigned int n = 0; + while (dest->HasTextureCoords(n)) + GetArrayCopy( dest->mTextureCoords[n++], dest->mNumVertices ); + + n = 0; + while (dest->HasVertexColors(n)) + GetArrayCopy( dest->mColors[n++], dest->mNumVertices ); + + // make a deep copy of all bones + CopyPtrArray(dest->mBones,dest->mBones,dest->mNumBones); + + // make a deep copy of all faces + GetArrayCopy(dest->mFaces,dest->mNumFaces); + for (unsigned int i = 0; i < dest->mNumFaces;++i) + { + aiFace& f = dest->mFaces[i]; + GetArrayCopy(f.mIndices,f.mNumIndices); + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiMaterial** _dest, const aiMaterial* src) +{ + ai_assert(NULL != _dest && NULL != src); + + MaterialHelper* dest = (MaterialHelper*) ( *_dest = new MaterialHelper() ); + dest->mNumAllocated = src->mNumAllocated; + dest->mNumProperties = src->mNumProperties; + dest->mProperties = new aiMaterialProperty* [dest->mNumAllocated]; + + for (unsigned int i = 0; i < dest->mNumProperties;++i) + { + aiMaterialProperty* prop = dest->mProperties[i] = new aiMaterialProperty(); + aiMaterialProperty* sprop = src->mProperties[i]; + + prop->mDataLength = sprop->mDataLength; + prop->mData = new char[prop->mDataLength]; + ::memcpy(prop->mData,sprop->mData,prop->mDataLength); + + prop->mIndex = sprop->mIndex; + prop->mSemantic = sprop->mSemantic; + prop->mKey = sprop->mKey; + prop->mType = sprop->mType; + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiTexture** _dest, const aiTexture* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiTexture* dest = *_dest = new aiTexture(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiTexture)); + + // and reallocate all arrays. We must do it manually here + const char* old = (const char*)dest->pcData; + if (old) + { + unsigned int cpy; + if (!dest->mHeight)cpy = dest->mWidth; + else cpy = dest->mHeight * dest->mWidth * sizeof(aiTexel); + + if (!cpy) + { + dest->pcData = NULL; + return; + } + // the cast is legal, the aiTexel c'tor does nothing important + dest->pcData = (aiTexel*) new char[cpy]; + ::memcpy(dest->pcData, old, cpy); + } +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiAnimation** _dest, const aiAnimation* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiAnimation* dest = *_dest = new aiAnimation(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiAnimation)); + + // and reallocate all arrays + GetArrayCopy( dest->mChannels, dest->mNumChannels ); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiNodeAnim** _dest, const aiNodeAnim* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiNodeAnim* dest = *_dest = new aiNodeAnim(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiNodeAnim)); + + // and reallocate all arrays + GetArrayCopy( dest->mPositionKeys, dest->mNumPositionKeys ); + GetArrayCopy( dest->mScalingKeys, dest->mNumScalingKeys ); + GetArrayCopy( dest->mRotationKeys, dest->mNumRotationKeys ); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiCamera** _dest,const aiCamera* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiCamera* dest = *_dest = new aiCamera(); + + // get a flat copy, that's already OK + ::memcpy(dest,src,sizeof(aiCamera)); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiLight** _dest, const aiLight* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiLight* dest = *_dest = new aiLight(); + + // get a flat copy, that's already OK + ::memcpy(dest,src,sizeof(aiLight)); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiBone** _dest, const aiBone* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiBone* dest = *_dest = new aiBone(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiBone)); + + // and reallocate all arrays + GetArrayCopy( dest->mWeights, dest->mNumWeights ); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy (aiNode** _dest, const aiNode* src) +{ + ai_assert(NULL != _dest && NULL != src); + + aiNode* dest = *_dest = new aiNode(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiNode)); + + // and reallocate all arrays + GetArrayCopy( dest->mMeshes, dest->mNumMeshes ); + CopyPtrArray( dest->mChildren, src->mChildren,dest->mNumChildren); +} + + } \ No newline at end of file diff --git a/code/SceneCombiner.h b/code/SceneCombiner.h index 77d55afbc..de4ae77bf 100644 --- a/code/SceneCombiner.h +++ b/code/SceneCombiner.h @@ -69,6 +69,43 @@ struct AttachmentInfo aiNode* attachToNode; }; +// --------------------------------------------------------------------------- +struct NodeAttachmentInfo +{ + NodeAttachmentInfo() + : node (NULL) + , attachToNode (NULL) + {} + + NodeAttachmentInfo(aiNode* _scene, aiNode* _attachToNode) + : node (_scene) + , attachToNode (_attachToNode) + {} + + aiNode* node; + aiNode* attachToNode; +}; + +// generate unique names for all named scene items +#define AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES 0x1 +// generate unique names for materials, too +#define AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES 0x2 +// use deep copies of duplicate scenes +#define AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY 0x4 + + +typedef std::pair BoneSrcIndex; + +// --------------------------------------------------------------------------- +/** \brief Helper data structure for SceneCombiner::MergeBones. + * + */ +struct BoneWithHash : public std::pair +{ + std::vector pSrcBones; +}; + + // --------------------------------------------------------------------------- /** \brief Static helper class providing various utilities to merge two * scenes. It is intended as internal utility and NOT for use by @@ -87,12 +124,14 @@ public: // ------------------------------------------------------------------- /** Merges two or more scenes. * - * @param dest Destination scene. Must be empty. + * @param dest Receives a pointer to the destination scene. If the + * pointer doesn't point to NULL when the function is called, the + * existing scene is cleared and refilled. * @param src Non-empty list of scenes to be merged. The function - * deletes the input scenes afterwards. + * deletes the input scenes afterwards. There may be duplicate scenes. * @param flags Combination of the AI_INT_MERGE_SCENE flags defined above */ - static void MergeScenes(aiScene* dest,std::vector& src, + static void MergeScenes(aiScene** dest,std::vector& src, unsigned int flags = 0); @@ -100,31 +139,63 @@ public: /** Merges two or more scenes and attaches all sceenes to a specific * position in the node graph of the masteer scene. * - * @param dest Destination scene. Must be empty. + * @param dest Receives a pointer to the destination scene. If the + * pointer doesn't point to NULL when the function is called, the + * existing scene is cleared and refilled. * @param master Master scene. It will be deleted afterwards. All * other scenes will be inserted in its node graph. * @param src Non-empty list of scenes to be merged along with their * corresponding attachment points in the master scene. The function - * deletes the input scenes afterwards. + * deletes the input scenes afterwards. There may be duplicate scenes. * @param flags Combination of the AI_INT_MERGE_SCENE flags defined above */ - static void MergeScenes(aiScene* dest, const aiScene* master, + static void MergeScenes(aiScene** dest, aiScene* master, std::vector& src, unsigned int flags = 0); // ------------------------------------------------------------------- /** Merges two or more meshes + * + * The meshes should have equal vertex formats. Only components + * that are provided by ALL meshes will be present in the output mesh. + * An exception is made for VColors - they are set to black. The + * meshes should have the same material indices, too. The output + * material index is always the material index of the first mesh. * * @param dest Destination mesh. Must be empty. - * @param src Non-empty list of meshes to be merged. The function - * deletes the input meshes afterwards. * @param flags Currently no parameters + * @param begin First mesh to be processed + * @param end Points to the mesh after the last mesh to be processed */ - static void MergeMeshes(aiMesh* dest,std::vector& src, - unsigned int flags); + static void MergeMeshes(aiMesh** dest,unsigned int flags, + std::vector::const_iterator begin, + std::vector::const_iterator end); + // ------------------------------------------------------------------- + /** Merges two or more bones + * + * @param out Mesh to receive the output bone list + * @param flags Currently no parameters + * @param begin First mesh to be processed + * @param end Points to the mesh after the last mesh to be processed + */ + static void MergeBones(aiMesh* out,std::vector::const_iterator it, + std::vector::const_iterator end); + + + // ------------------------------------------------------------------- + /** Builds a list of uniquely named bones in a mesh list + * + * @param asBones Receives the output list + * @param it First mesh to be processed + * @param end Last mesh to be processed + */ + static void BuildUniqueBoneList(std::list& asBones, + std::vector::const_iterator it, + std::vector::const_iterator end); + // ------------------------------------------------------------------- /** Add a name prefix to all nodes in a scene. * @@ -134,6 +205,75 @@ public: */ static void AddNodePrefixes(aiNode* node, const char* prefix, unsigned int len); + + // ------------------------------------------------------------------- + /** Add an offset to all mesh indices in a node graph + * + * @param Current node. This function is called recursively. + * @param offset Offset to be added to all mesh indices + */ + static void OffsetNodeMeshIndices (aiNode* node, unsigned int offset); + + // ------------------------------------------------------------------- + /** Attach a list of node graphs to well-defined nodes in a master + * graph. This is a helper for MergeScenes() + * + * @param master Master scene + * @param srcList List of source scenes along with their attachment + * points. If an attachment point is NULL (or does not exist in + * the master graph), a scene is attached to the root of the master + * graph (as an additional child node) + * @duplicates List of duplicates. If elem[n] == n the scene is not + * a duplicate. Otherwise elem[n] links scene n to its first occurence. + */ + static void AttachToGraph ( aiScene* master, + std::vector& srcList); + + static void AttachToGraph (aiNode* attach, + std::vector& srcList); + + + // ------------------------------------------------------------------- + /** Get a deep copy of a scene + * + * @param dest Receives a pointer to the destination scene + * @param src Source scene - remains unmodified. + */ + static void CopyScene(aiScene** dest,aiScene* source); + + + // ------------------------------------------------------------------- + /** Get a flat copy of a scene + * + * Only the first hierarchy layer is copied. All pointer members of + * aiScene are shared by source and destination scene. If the + * pointer doesn't point to NULL when the function is called, the + * existing scene is cleared and refilled. + * @param dest Receives a pointer to the destination scene + * @param src Source scene - remains unmodified. + */ + static void CopySceneFlat(aiScene** dest,aiScene* source); + + + // ------------------------------------------------------------------- + /** Get a deep copy of a mesh + * + * @param dest Receives a pointer to the destination mesh + * @param src Source mesh - remains unmodified. + */ + static void Copy (aiMesh** dest, const aiMesh* src); + + // similar to Copy(): + static void Copy (aiMaterial** dest, const aiMaterial* src); + static void Copy (aiTexture** dest, const aiTexture* src); + static void Copy (aiAnimation** dest, const aiAnimation* src); + static void Copy (aiCamera** dest, const aiCamera* src); + static void Copy (aiBone** dest, const aiBone* src); + static void Copy (aiLight** dest, const aiLight* src); + static void Copy (aiNodeAnim** dest, const aiNodeAnim* src); + + // recursive, of course + static void Copy (aiNode** dest, const aiNode* src); }; } diff --git a/code/ScenePreprocessor.cpp b/code/ScenePreprocessor.cpp index fc540d076..64a8e45f6 100644 --- a/code/ScenePreprocessor.cpp +++ b/code/ScenePreprocessor.cpp @@ -194,6 +194,6 @@ void ScenePreprocessor::ProcessAnimation (aiAnimation* anim) if (anim->mDuration == -1.) { DefaultLogger::get()->debug("Setting animation duration"); - anim->mDuration = last - first; + anim->mDuration = last - std::min( first, 0. ); } } \ No newline at end of file diff --git a/code/TargetAnimation.cpp b/code/TargetAnimation.cpp index 96f175bac..940834a14 100644 --- a/code/TargetAnimation.cpp +++ b/code/TargetAnimation.cpp @@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ KeyIterator::KeyIterator(const std::vector* _objPos, const std::vector* _targetObjPos, const aiVector3D* defaultObjectPos /*= NULL*/, @@ -80,7 +80,14 @@ KeyIterator::KeyIterator(const std::vector* _objPos, } } -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ +template +T Interpolate(const T& one, const T& two, float val) +{ + return one + (two-one)*val; +} + +// ------------------------------------------------------------------------------------------------ void KeyIterator::operator ++() { // If we are already at the end of all keyframes, return @@ -129,8 +136,8 @@ void KeyIterator::operator ++() const aiVectorKey& last = targetObjPos->at(nextTargetObjPos); const aiVectorKey& first = targetObjPos->at(nextTargetObjPos-1); - /* curTargetPosition = Interpolate(first.mValue, last.mValue, - (curTime-first.mTime) / (last.mTime-first.mTime));*/ + curTargetPosition = Interpolate(first.mValue, last.mValue, (float) ( + (curTime-first.mTime) / (last.mTime-first.mTime) )); } // increment counters @@ -155,8 +162,8 @@ void KeyIterator::operator ++() const aiVectorKey& last = objPos->at(nextObjPos); const aiVectorKey& first = objPos->at(nextObjPos-1); - /*curPosition = Interpolate(first.mValue, last.mValue, - (curTime-first.mTime) / (last.mTime-first.mTime));*/ + curPosition = Interpolate(first.mValue, last.mValue, (float) ( + (curTime-first.mTime) / (last.mTime-first.mTime))); } } @@ -167,7 +174,7 @@ void KeyIterator::operator ++() } } -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ void TargetAnimationHelper::SetTargetAnimationChannel ( const std::vector* _targetPositions) { @@ -175,7 +182,7 @@ void TargetAnimationHelper::SetTargetAnimationChannel ( targetPositions = _targetPositions; } -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ void TargetAnimationHelper::SetMainAnimationChannel ( const std::vector* _objectPositions) { @@ -183,7 +190,7 @@ void TargetAnimationHelper::SetMainAnimationChannel ( objectPositions = _objectPositions; } -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ void TargetAnimationHelper::SetFixedMainAnimationChannel( const aiVector3D& fixed) { @@ -191,23 +198,53 @@ void TargetAnimationHelper::SetFixedMainAnimationChannel( fixedMain = fixed; } -// --------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ void TargetAnimationHelper::Process(std::vector* distanceTrack) { ai_assert(NULL != targetPositions); + // TODO: in most cases we won't need the extra array + std::vector* fill = NULL; + std::vector real; + if (distanceTrack) + { + fill = (distanceTrack == objectPositions ? &real : distanceTrack); + } + fill->reserve(std::max( objectPositions->size(), targetPositions->size() )); + // Iterate through all object keys and interpolate their values if necessary. // Then get the corresponding target position, compute the difference // vector between object and target position. Then compute a rotation matrix // that rotates the base vector of the object coordinate system at that time // to match the diff vector. + KeyIterator iter(objectPositions,targetPositions,&fixedMain); - unsigned int curTarget; for (;!iter.Finished();++iter) { const aiVector3D& position = iter.GetCurPosition(); const aiVector3D& tposition = iter.GetCurTargetPosition(); - + // diff vector + aiVector3D diff = tposition - position; + float f = diff.SquareLength(); + if (!f) + { + DefaultLogger::get()->error("Target position equals object position"); + continue; + } + f = ::sqrt(f); + + // output distance vector + if (fill) + { + fill->push_back(aiVectorKey()); + aiVectorKey& v = fill->back(); + v.mTime = iter.GetCurTime(); + v.mValue = aiVector3D (0.f,0.f,f); + } + diff /= f; } + + if (real.size()) + *distanceTrack = real; } \ No newline at end of file diff --git a/code/TargetAnimation.h b/code/TargetAnimation.h index bd947559c..c06d432a5 100644 --- a/code/TargetAnimation.h +++ b/code/TargetAnimation.h @@ -98,6 +98,9 @@ public: inline const aiVector3D& GetCurTargetPosition() const {return curTargetPosition;} + inline double GetCurTime() const + {return curTime;} + private: //! Did we reach the end? diff --git a/code/TextureTransform.cpp b/code/TextureTransform.cpp index 114f481f0..0eaf045d3 100644 --- a/code/TextureTransform.cpp +++ b/code/TextureTransform.cpp @@ -114,22 +114,20 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) return; } - float absTranslationX = info.mScaling.x * info.mTranslation.x; - float absTranslationY = info.mScaling.y * info.mTranslation.y; /* Optimize UV translation in the U direction. To determine whether * or not we can optimize we need to look at the requested mapping * type (e.g. if mirroring is active there IS a difference between * offset 2 and 3) */ - if ((rounded = (int)absTranslationX)) + if ((rounded = (int)info.mTranslation.x)) { float out; szTemp[0] = 0; if (aiTextureMapMode_Wrap == info.mapU) { // Wrap - simple take the fraction of the field - out = (absTranslationX-(float)rounded) / info.mScaling.x; + out = info.mTranslation.x-(float)rounded; sprintf(szTemp,"[w] UV U offset %f can " "be simplified to %f",info.mTranslation.x,out); } @@ -137,7 +135,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) { // Mirror if (rounded % 2)rounded--; - out = (absTranslationX-(float)rounded) / info.mScaling.x; + out = info.mTranslation.x-(float)rounded; sprintf(szTemp,"[m/d] UV U offset %f can " "be simplified to %f",info.mTranslation.x,out); @@ -162,14 +160,14 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) * type (e.g. if mirroring is active there IS a difference between * offset 2 and 3) */ - if ((rounded = (int)absTranslationY)) + if ((rounded = (int)info.mTranslation.y)) { float out; szTemp[0] = 0; if (aiTextureMapMode_Wrap == info.mapV) { // Wrap - simple take the fraction of the field - out = (absTranslationY-(float)rounded) / info.mScaling.y; + out = info.mTranslation.y-(float)rounded; sprintf(szTemp,"[w] UV V offset %f can " "be simplified to %f",info.mTranslation.y,out); } @@ -177,7 +175,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) { // Mirror if (rounded % 2)rounded--; - out = (absTranslationY-(float)rounded) / info.mScaling.y; + out = info.mTranslation.x-(float)rounded; sprintf(szTemp,"[m/d] UV V offset %f can " "be simplified to %f",info.mTranslation.y,out); @@ -272,7 +270,7 @@ void TextureTransformStep::Execute( aiScene* pScene) info.uvIndex = *((int*)prop2->mData); // Store a direct pointer for later use - update.directShortcut = (unsigned int*) &prop2->mData; + update.directShortcut = (unsigned int*) prop2->mData; } else if ( !::strcmp( prop2->mKey.data, "$tex.mapmodeu")) @@ -286,8 +284,7 @@ void TextureTransformStep::Execute( aiScene* pScene) // ValidateDS should check this ai_assert(prop2->mDataLength >= 20); - ::memcpy(&info.mTranslation.x,prop2->mData, - sizeof(float)*5); + ::memcpy(&info.mTranslation.x,prop2->mData,sizeof(float)*5); delete[] prop2->mData; diff --git a/code/TextureTransform.h b/code/TextureTransform.h index e6926929a..d519ff791 100644 --- a/code/TextureTransform.h +++ b/code/TextureTransform.h @@ -114,7 +114,7 @@ struct STransformVecInfo : public aiUVTransform inline bool operator== (const STransformVecInfo& other) const { // We use a small epsilon here - const float epsilon = 0.05f; + const static float epsilon = 0.05f; if (fabs( mTranslation.x - other.mTranslation.x ) > epsilon || fabs( mTranslation.y - other.mTranslation.y ) > epsilon) diff --git a/code/ValidateDataStructure.cpp b/code/ValidateDataStructure.cpp index 596338e85..2738292ed 100644 --- a/code/ValidateDataStructure.cpp +++ b/code/ValidateDataStructure.cpp @@ -637,8 +637,6 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, szType,iIndex,iNumIndices,szType); } if (!iNumIndices)return; - - // TODO: check whether the mappings are correctly std::vector mappings(iNumIndices); // Now check whether all UV indices are valid ... @@ -711,7 +709,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, for (unsigned int a = 0; a < mScene->mNumMeshes;++a) { aiMesh* mesh = mScene->mMeshes[a]; - if(mesh->mMaterialIndex == (unsigned int)iIndex) + if(mesh->mMaterialIndex == (unsigned int)iIndex && mappings[0] == aiTextureMapping_UV) { if (!mesh->mTextureCoords[0]) { diff --git a/code/makefile b/code/makefile index 14f1a6d98..452c17c1d 100644 --- a/code/makefile +++ b/code/makefile @@ -77,7 +77,7 @@ SOURCES = AssimpPCH.cpp \ TargetAnimation.cpp \ ComputeUVMappingProcess.cpp \ ColladaLoader.cpp \ - ColladaParser.cpp + ColladaParser.cpp OBJECTS = $(SOURCES:.cpp=.o) diff --git a/code/makefile.mingw b/code/makefile.mingw index 949597d60..f369039b6 100644 --- a/code/makefile.mingw +++ b/code/makefile.mingw @@ -77,7 +77,7 @@ SOURCES = AssimpPCH.cpp \ TargetAnimation.cpp \ ComputeUVMappingProcess.cpp \ ColladaLoader.cpp \ - ColladaParser.cpp + ColladaParser.cpp OBJECTS = $(SOURCES:.cpp=.o) diff --git a/include/BoostWorkaround/boost/common_factor_rt.hpp b/include/BoostWorkaround/boost/common_factor_rt.hpp new file mode 100644 index 000000000..04af64ab6 --- /dev/null +++ b/include/BoostWorkaround/boost/common_factor_rt.hpp @@ -0,0 +1,36 @@ + + +#ifndef BOOST_MATH_COMMON_FACTOR_RT_HPP +#define BOOST_MATH_COMMON_FACTOR_RT_HPP + + +namespace boost { +namespace math { + +// TODO: use binary GCD for unsigned integers .... +template < typename IntegerType > +IntegerType gcd( IntegerType const &a, IntegerType const &b ) +{ + while ( true ) + { + if ( a == zero ) + return b; + b %= a; + + if ( b == zero ) + return a; + a %= b; + } +} + +template < typename IntegerType > +IntegerType lcm( IntegerType const &a, IntegerType const &b ) +{ + IntegerType t = gcd (a,b); + if (!t)return t; + return a / t * b; +} + +}} + +#endif \ No newline at end of file diff --git a/include/BoostWorkaround/boost/multi_array.hpp b/include/BoostWorkaround/boost/random/mersenne_twister.hpp similarity index 100% rename from include/BoostWorkaround/boost/multi_array.hpp rename to include/BoostWorkaround/boost/random/mersenne_twister.hpp diff --git a/include/BoostWorkaround/boost/random/uniform_int.hpp b/include/BoostWorkaround/boost/random/uniform_int.hpp new file mode 100644 index 000000000..e69de29bb diff --git a/include/BoostWorkaround/boost/random/variate_generator.hpp b/include/BoostWorkaround/boost/random/variate_generator.hpp new file mode 100644 index 000000000..e69de29bb diff --git a/include/IOSystem.h b/include/IOSystem.h index 7fa0937c8..15c6050cd 100644 --- a/include/IOSystem.h +++ b/include/IOSystem.h @@ -51,8 +51,8 @@ public: virtual std::string getOsSeparator() const = 0; // ------------------------------------------------------------------- - /** Open a new file with a given path. When the access to the file is finished, - * call Close() to release all associated resources. + /** Open a new file with a given path. When the access to the file + * is finished, call Close() to release all associated resources. * * @param pFile Path to the file * @param pMode Desired file I/O mode. Required are: "wb", "w", "wt", @@ -60,8 +60,8 @@ public: * * @return New IOStream interface allowing the lib to access * the underlying file. - * @note When implementing this class to provide custom IO handling, you propably - * have to supply an own implementation of IOStream as well. + * @note When implementing this class to provide custom IO handling, + * you propably have to supply an own implementation of IOStream as well. */ virtual IOStream* Open( const std::string& pFile, @@ -72,6 +72,20 @@ public: * @param pFile The file instance previously created by Open(). */ virtual void Close( IOStream* pFile) = 0; + + + // ------------------------------------------------------------------- + /** Compares two paths and check whether the point to identical files. + * + * The dummy implementation of this virtual performs a + * case-insensitive comparison of the path strings. + * @param one First file + * @param second Second file + * @return true if the paths point to the same file. The file needn't + * be existing, however. + */ + virtual bool ComparePaths (const std::string& one, + const std::string& second); }; // ---------------------------------------------------------------------------- @@ -87,6 +101,7 @@ inline IOSystem::~IOSystem() } // ---------------------------------------------------------------------------- + } //!ns Assimp #endif //AI_IOSYSTEM_H_INC diff --git a/include/aiDefines.h b/include/aiDefines.h index 7ee0ac167..541b56cba 100644 --- a/include/aiDefines.h +++ b/include/aiDefines.h @@ -191,6 +191,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Use our own definition of PI here #define AI_MATH_PI (3.1415926538) #define AI_MATH_TWO_PI (AI_MATH_PI * 2.0) +#define AI_MATH_HALF_PI (AI_MATH_PI * 0.5) // macrod to convert from radians to degrees and the reverse #define AI_DEG_TO_RAD(x) (x*0.0174532925f) diff --git a/include/aiMatrix4x4.h b/include/aiMatrix4x4.h index 5ba02468d..df762d307 100644 --- a/include/aiMatrix4x4.h +++ b/include/aiMatrix4x4.h @@ -119,6 +119,7 @@ struct aiMatrix4x4 * \param z Rotation angle for the z-axis, in radians */ inline void FromEulerAngles(float x, float y, float z); + inline void FromEulerAngles(const aiVector3D& blubb); /** \brief Returns a rotation matrix for a rotation around the x axis diff --git a/include/aiMatrix4x4.inl b/include/aiMatrix4x4.inl index 6fcd0dce0..c77d1707a 100644 --- a/include/aiMatrix4x4.inl +++ b/include/aiMatrix4x4.inl @@ -87,15 +87,12 @@ inline aiMatrix4x4& aiMatrix4x4::Inverse() float det = Determinant(); if(det == 0.0f) { + const float nan = std::numeric_limits::quiet_NaN(); *this = aiMatrix4x4( - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN()); + nan,nan,nan,nan, + nan,nan,nan,nan, + nan,nan,nan,nan, + nan,nan,nan,nan); return *this; } @@ -129,7 +126,6 @@ inline float* aiMatrix4x4::operator[](unsigned int p_iIndex) return &this->a1 + p_iIndex * 4; } - // --------------------------------------------------------------------------- inline const float* aiMatrix4x4::operator[](unsigned int p_iIndex) const { @@ -208,6 +204,11 @@ inline void aiMatrix4x4::DecomposeNoScaling (aiQuaternion& rotation, rotation = aiQuaternion((aiMatrix3x3)_this); } // --------------------------------------------------------------------------- +inline void aiMatrix4x4::FromEulerAngles(const aiVector3D& blubb) +{ + FromEulerAngles(blubb.x,blubb.y,blubb.z); +} +// --------------------------------------------------------------------------- inline void aiMatrix4x4::FromEulerAngles(float x, float y, float z) { aiMatrix4x4& _this = *this; diff --git a/include/aiTypes.h b/include/aiTypes.h index 2bd80cff9..a00afacc2 100644 --- a/include/aiTypes.h +++ b/include/aiTypes.h @@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // include math helper classes #include "aiVector3D.h" +#include "aiVector2D.h" #include "aiMatrix3x3.h" #include "aiMatrix4x4.h" @@ -66,22 +67,6 @@ const size_t MAXLEN = 1024; #include "./Compiler/pushpack1.h" -// --------------------------------------------------------------------------- -/** Represents a two-dimensional vector. -*/ -struct aiVector2D -{ -#ifdef __cplusplus - aiVector2D () : x(0.0f), y(0.0f) {} - aiVector2D (float _x, float _y) : x(_x), y(_y) {} - aiVector2D (const aiVector2D& o) : x(o.x), y(o.y) {} - -#endif // !__cplusplus - - //! X and y coordinates - float x, y; -} PACK_STRUCT; - // --------------------------------------------------------------------------- /** Represents a plane in a three-dimensional, euclidean space @@ -94,13 +79,32 @@ struct aiPlane : a(_a), b(_b), c(_c), d(_d) {} aiPlane (const aiPlane& o) : a(o.a), b(o.b), c(o.c), d(o.d) {} - + #endif // !__cplusplus //! Plane equation float a,b,c,d; } PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** Represents a ray +*/ +struct aiRay +{ +#ifdef __cplusplus + aiRay () {} + aiRay (const aiVector3D& _pos, const aiVector3D& _dir) + : pos(_pos), dir(_dir) {} + + aiRay (const aiRay& o) : pos (o.pos), dir (o.dir) {} + +#endif // !__cplusplus + + //! Position and direction of the ray + aiVector3D pos, dir; +} PACK_STRUCT; + // aiVector3D type moved to separate header due to size of operators // aiQuaternion type moved to separate header due to size of operators // aiMatrix4x4 type moved to separate header due to size of operators diff --git a/include/aiVector2D.h b/include/aiVector2D.h new file mode 100644 index 000000000..71c7312ad --- /dev/null +++ b/include/aiVector2D.h @@ -0,0 +1,148 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ +/** @file 2D vector structure, including operators when compiling in C++ */ +#ifndef AI_VECTOR2D_H_INC +#define AI_VECTOR2D_H_INC + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./Compiler/pushpack1.h" + +// --------------------------------------------------------------------------- +/** Represents a two-dimensional vector. */ +struct aiVector2D +{ +#ifdef __cplusplus + aiVector2D () : x(0.0f), y(0.0f) {} + aiVector2D (float _x, float _y) : x(_x), y(_y) {} + aiVector2D (float _xyz) : x(_xyz), y(_xyz) {} + aiVector2D (const aiVector2D& o) : x(o.x), y(o.y) {} + + void Set( float pX, float pY) { x = pX; y = pY;} + float SquareLength() const { return x*x + y*y; } + float Length() const { return sqrt( SquareLength()); } + aiVector2D& Normalize() { *this /= Length(); return *this; } + const aiVector2D& operator += (const aiVector2D& o) { x += o.x; y += o.y; return *this; } + const aiVector2D& operator -= (const aiVector2D& o) { x -= o.x; y -= o.y; return *this; } + const aiVector2D& operator *= (float f) { x *= f; y *= f; return *this; } + const aiVector2D& operator /= (float f) { x /= f; y /= f; return *this; } + + inline float operator[](unsigned int i) const {return *(&x + i);} + inline float& operator[](unsigned int i) {return *(&x + i);} + + inline bool operator== (const aiVector2D& other) const + {return x == other.x && y == other.y;} + + inline bool operator!= (const aiVector2D& other) const + {return x != other.x || y != other.y;} + + inline aiVector2D& operator= (float f) + {x = y = f;return *this;} + + const aiVector2D SymMul(const aiVector2D& o) + {return aiVector2D(x*o.x,y*o.y);} + +#endif // __cplusplus + + float x, y; +} PACK_STRUCT; + +#include "./Compiler/poppack1.h" + +#ifdef __cplusplus +} // end extern "C" + +// symmetric addition +inline aiVector2D operator + (const aiVector2D& v1, const aiVector2D& v2) +{ + return aiVector2D( v1.x + v2.x, v1.y + v2.y); +} + +// symmetric subtraction +inline aiVector2D operator - (const aiVector2D& v1, const aiVector2D& v2) +{ + return aiVector2D( v1.x - v2.x, v1.y - v2.y); +} + +// scalar product +inline float operator * (const aiVector2D& v1, const aiVector2D& v2) +{ + return v1.x*v2.x + v1.y*v2.y; +} + +// scalar multiplication +inline aiVector2D operator * ( float f, const aiVector2D& v) +{ + return aiVector2D( f*v.x, f*v.y); +} + +// and the other way around +inline aiVector2D operator * ( const aiVector2D& v, float f) +{ + return aiVector2D( f*v.x, f*v.y); +} + +// scalar division +inline aiVector2D operator / ( const aiVector2D& v, float f) +{ + + return v * (1/f); +} + +// vector division +inline aiVector2D operator / ( const aiVector2D& v, const aiVector2D& v2) +{ + return aiVector2D(v.x / v2.x,v.y / v2.y); +} + +// vector inversion +inline aiVector2D operator - ( const aiVector2D& v) +{ + return aiVector2D( -v.x, -v.y); +} + +#endif // __cplusplus + +#endif // AI_VECTOR2D_H_INC diff --git a/test/ACFiles/SphereWithLight.ac b/test/ACFiles/SphereWithLight.ac index 2bc960fa7..5fd2cc43e 100644 --- a/test/ACFiles/SphereWithLight.ac +++ b/test/ACFiles/SphereWithLight.ac @@ -9,7 +9,7 @@ kids 0 OBJECT poly name "sphere" loc -0.0624103 -0.012381 0.0558408 -texture "./../LWOFiles/LWO2/MappingModes/concrete.jpg" +texture "./../LWOFiles/LWO2/MappingModes/earthSpherical.jpg" crease 45.000000 numvert 134 -0.00202139 0.0563461 0 diff --git a/test/ACFiles/SphereWithLightUvScaling4X.ac b/test/ACFiles/SphereWithLightUvScaling4X.ac index f8ac53b0c..5e5f85f33 100644 --- a/test/ACFiles/SphereWithLightUvScaling4X.ac +++ b/test/ACFiles/SphereWithLightUvScaling4X.ac @@ -9,7 +9,7 @@ kids 0 OBJECT poly name "sphere" loc -0.0624103 -0.012381 0.0558408 -texture "./../LWOFiles/LWO2/MappingModes/concrete.jpg" +texture "./../LWOFiles/LWO2/MappingModes/earthSpherical.jpg" texrep 1.9 2.5 texoff 0.019 0.5 crease 45.000000 diff --git a/test/LWOFiles/LWO2/MappingModes/concretePlaneMapped.lwo b/test/LWOFiles/LWO2/MappingModes/concretePlaneMapped.lwo index f4e72846d..a46c8ff92 100644 Binary files a/test/LWOFiles/LWO2/MappingModes/concretePlaneMapped.lwo and b/test/LWOFiles/LWO2/MappingModes/concretePlaneMapped.lwo differ diff --git a/test/LWOFiles/LWO2/MappingModes/concreteUVMapped.lwo b/test/LWOFiles/LWO2/MappingModes/concreteUVMapped.lwo index 18e90c6c1..dd876f1bf 100644 Binary files a/test/LWOFiles/LWO2/MappingModes/concreteUVMapped.lwo and b/test/LWOFiles/LWO2/MappingModes/concreteUVMapped.lwo differ diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index ccf30c422..406cda939 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -663,6 +663,10 @@ RelativePath="..\..\include\aiTypes.h" > + + @@ -722,6 +726,10 @@ + +