| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp 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 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 ASELoader.cpp |
| * @brief Implementation of the ASE importer class |
| */ |
| |
| #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER |
| |
| #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER |
| |
| // internal headers |
| #include "ASELoader.h" |
| #include "StringComparison.h" |
| #include "SkeletonMeshBuilder.h" |
| #include "TargetAnimation.h" |
| #include <assimp/Importer.hpp> |
| #include <assimp/IOSystem.hpp> |
| #include <assimp/DefaultLogger.hpp> |
| #include <assimp/scene.h> |
| #include <assimp/importerdesc.h> |
| |
| #include <memory> |
| |
| // utilities |
| #include "fast_atof.h" |
| |
| using namespace Assimp; |
| using namespace Assimp::ASE; |
| |
| static const aiImporterDesc desc = { |
| "ASE Importer", |
| "", |
| "", |
| "Similar to 3DS but text-encoded", |
| aiImporterFlags_SupportTextFlavour, |
| 0, |
| 0, |
| 0, |
| 0, |
| "ase ask" |
| }; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Constructor to be privately used by Importer |
| ASEImporter::ASEImporter() |
| : mParser(), |
| mBuffer(), |
| pcScene(), |
| configRecomputeNormals(), |
| noSkeletonMesh() |
| {} |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor, private as well |
| ASEImporter::~ASEImporter() |
| {} |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Returns whether the class can handle the format of the given file. |
| bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const |
| { |
| // check file extension |
| const std::string extension = GetExtension(pFile); |
| |
| if( extension == "ase" || extension == "ask") |
| return true; |
| |
| if ((!extension.length() || cs) && pIOHandler) { |
| const char* tokens[] = {"*3dsmax_asciiexport"}; |
| return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); |
| } |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Loader meta information |
| const aiImporterDesc* ASEImporter::GetInfo () const |
| { |
| return &desc; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup configuration options |
| void ASEImporter::SetupProperties(const Importer* pImp) |
| { |
| configRecomputeNormals = (pImp->GetPropertyInteger( |
| AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS,1) ? true : false); |
| |
| noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Imports the given file into the given scene structure. |
| void ASEImporter::InternReadFile( const std::string& pFile, |
| aiScene* pScene, IOSystem* pIOHandler) |
| { |
| std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb")); |
| |
| // Check whether we can read from the file |
| if( file.get() == NULL) { |
| throw DeadlyImportError( "Failed to open ASE file " + pFile + "."); |
| } |
| |
| // Allocate storage and copy the contents of the file to a memory buffer |
| std::vector<char> mBuffer2; |
| TextFileToBuffer(file.get(),mBuffer2); |
| |
| this->mBuffer = &mBuffer2[0]; |
| this->pcScene = pScene; |
| |
| // ------------------------------------------------------------------ |
| // Guess the file format by looking at the extension |
| // ASC is considered to be the older format 110, |
| // ASE is the actual version 200 (that is currently written by max) |
| // ------------------------------------------------------------------ |
| unsigned int defaultFormat; |
| std::string::size_type s = pFile.length()-1; |
| switch (pFile.c_str()[s]) { |
| |
| case 'C': |
| case 'c': |
| defaultFormat = AI_ASE_OLD_FILE_FORMAT; |
| break; |
| default: |
| defaultFormat = AI_ASE_NEW_FILE_FORMAT; |
| }; |
| |
| // Construct an ASE parser and parse the file |
| ASE::Parser parser(mBuffer,defaultFormat); |
| mParser = &parser; |
| mParser->Parse(); |
| |
| //------------------------------------------------------------------ |
| // Check whether we god at least one mesh. If we did - generate |
| // materials and copy meshes. |
| // ------------------------------------------------------------------ |
| if ( !mParser->m_vMeshes.empty()) { |
| |
| // If absolutely no material has been loaded from the file |
| // we need to generate a default material |
| GenerateDefaultMaterial(); |
| |
| // process all meshes |
| bool tookNormals = false; |
| std::vector<aiMesh*> avOutMeshes; |
| avOutMeshes.reserve(mParser->m_vMeshes.size()*2); |
| for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i) { |
| if ((*i).bSkip) { |
| continue; |
| } |
| BuildUniqueRepresentation(*i); |
| |
| // Need to generate proper vertex normals if necessary |
| if(GenerateNormals(*i)) { |
| tookNormals = true; |
| } |
| |
| // Convert all meshes to aiMesh objects |
| ConvertMeshes(*i,avOutMeshes); |
| } |
| if (tookNormals) { |
| DefaultLogger::get()->debug("ASE: Taking normals from the file. Use " |
| "the AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS setting if you " |
| "experience problems"); |
| } |
| |
| // Now build the output mesh list. Remove dummies |
| pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); |
| aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; |
| for (std::vector<aiMesh*>::const_iterator i = avOutMeshes.begin();i != avOutMeshes.end();++i) { |
| if (!(*i)->mNumFaces) { |
| continue; |
| } |
| *pp++ = *i; |
| } |
| pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); |
| |
| // Build final material indices (remove submaterials and setup |
| // the final list) |
| BuildMaterialIndices(); |
| } |
| |
| // ------------------------------------------------------------------ |
| // Copy all scene graph nodes - lights, cameras, dummies and meshes |
| // into one huge list. |
| //------------------------------------------------------------------ |
| std::vector<BaseNode*> nodes; |
| nodes.reserve(mParser->m_vMeshes.size() +mParser->m_vLights.size() |
| + mParser->m_vCameras.size() + mParser->m_vDummies.size()); |
| |
| // Lights |
| for (auto &light : mParser->m_vLights)nodes.push_back(&light); |
| // Cameras |
| for (auto &camera : mParser->m_vCameras)nodes.push_back(&camera); |
| // Meshes |
| for (auto &mesh : mParser->m_vMeshes)nodes.push_back(&mesh); |
| // Dummies |
| for (auto &dummy : mParser->m_vDummies)nodes.push_back(&dummy); |
| |
| // build the final node graph |
| BuildNodes(nodes); |
| |
| // build output animations |
| BuildAnimations(nodes); |
| |
| // build output cameras |
| BuildCameras(); |
| |
| // build output lights |
| BuildLights(); |
| |
| // ------------------------------------------------------------------ |
| // If we have no meshes use the SkeletonMeshBuilder helper class |
| // to build a mesh for the animation skeleton |
| // FIXME: very strange results |
| // ------------------------------------------------------------------ |
| if (!pScene->mNumMeshes) { |
| pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; |
| if (!noSkeletonMesh) { |
| SkeletonMeshBuilder skeleton(pScene); |
| } |
| } |
| } |
| // ------------------------------------------------------------------------------------------------ |
| void ASEImporter::GenerateDefaultMaterial() |
| { |
| ai_assert(NULL != mParser); |
| |
| bool bHas = false; |
| for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i) { |
| if ((*i).bSkip)continue; |
| if (ASE::Face::DEFAULT_MATINDEX == (*i).iMaterialIndex) { |
| (*i).iMaterialIndex = (unsigned int)mParser->m_vMaterials.size(); |
| bHas = true; |
| } |
| } |
| if (bHas || mParser->m_vMaterials.empty()) { |
| // add a simple material without submaterials to the parser's list |
| mParser->m_vMaterials.push_back ( ASE::Material() ); |
| ASE::Material& mat = mParser->m_vMaterials.back(); |
| |
| mat.mDiffuse = aiColor3D(0.6f,0.6f,0.6f); |
| mat.mSpecular = aiColor3D(1.0f,1.0f,1.0f); |
| mat.mAmbient = aiColor3D(0.05f,0.05f,0.05f); |
| mat.mShading = Discreet3DS::Gouraud; |
| mat.mName = AI_DEFAULT_MATERIAL_NAME; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void ASEImporter::BuildAnimations(const std::vector<BaseNode*>& nodes) |
| { |
| // check whether we have at least one mesh which has animations |
| std::vector<ASE::BaseNode*>::const_iterator i = nodes.begin(); |
| unsigned int iNum = 0; |
| for (;i != nodes.end();++i) { |
| |
| // TODO: Implement Bezier & TCB support |
| if ((*i)->mAnim.mPositionType != ASE::Animation::TRACK) { |
| DefaultLogger::get()->warn("ASE: Position controller uses Bezier/TCB keys. " |
| "This is not supported."); |
| } |
| if ((*i)->mAnim.mRotationType != ASE::Animation::TRACK) { |
| DefaultLogger::get()->warn("ASE: Rotation controller uses Bezier/TCB keys. " |
| "This is not supported."); |
| } |
| if ((*i)->mAnim.mScalingType != ASE::Animation::TRACK) { |
| DefaultLogger::get()->warn("ASE: Position controller uses Bezier/TCB keys. " |
| "This is not supported."); |
| } |
| |
| // We compare against 1 here - firstly one key is not |
| // really an animation and secondly MAX writes dummies |
| // that represent the node transformation. |
| if ((*i)->mAnim.akeyPositions.size()>1 || (*i)->mAnim.akeyRotations.size()>1 || (*i)->mAnim.akeyScaling.size()>1){ |
| ++iNum; |
| } |
| if ((*i)->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( (*i)->mTargetPosition.x )) { |
| ++iNum; |
| } |
| } |
| if (iNum) { |
| // Generate a new animation channel and setup everything for it |
| pcScene->mNumAnimations = 1; |
| pcScene->mAnimations = new aiAnimation*[1]; |
| aiAnimation* pcAnim = pcScene->mAnimations[0] = new aiAnimation(); |
| pcAnim->mNumChannels = iNum; |
| pcAnim->mChannels = new aiNodeAnim*[iNum]; |
| pcAnim->mTicksPerSecond = mParser->iFrameSpeed * mParser->iTicksPerFrame; |
| |
| iNum = 0; |
| |
| // Now iterate through all meshes and collect all data we can find |
| for (i = nodes.begin();i != nodes.end();++i) { |
| |
| ASE::BaseNode* me = *i; |
| if ( me->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( me->mTargetPosition.x )) { |
| // Generate an extra channel for the camera/light target. |
| // BuildNodes() does also generate an extra node, named |
| // <baseName>.Target. |
| aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); |
| nd->mNodeName.Set(me->mName + ".Target"); |
| |
| // If there is no input position channel we will need |
| // to supply the default position from the node's |
| // local transformation matrix. |
| /*TargetAnimationHelper helper; |
| if (me->mAnim.akeyPositions.empty()) |
| { |
| aiMatrix4x4& mat = (*i)->mTransform; |
| helper.SetFixedMainAnimationChannel(aiVector3D( |
| mat.a4, mat.b4, mat.c4)); |
| } |
| else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions); |
| helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions); |
| |
| helper.Process(&me->mTargetAnim.akeyPositions);*/ |
| |
| // Allocate the key array and fill it |
| nd->mNumPositionKeys = (unsigned int) me->mTargetAnim.akeyPositions.size(); |
| nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; |
| |
| ::memcpy(nd->mPositionKeys,&me->mTargetAnim.akeyPositions[0], |
| nd->mNumPositionKeys * sizeof(aiVectorKey)); |
| } |
| |
| if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1 || me->mAnim.akeyScaling.size() > 1) { |
| // Begin a new node animation channel for this node |
| aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); |
| nd->mNodeName.Set(me->mName); |
| |
| // copy position keys |
| if (me->mAnim.akeyPositions.size() > 1 ) |
| { |
| // Allocate the key array and fill it |
| nd->mNumPositionKeys = (unsigned int) me->mAnim.akeyPositions.size(); |
| nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; |
| |
| ::memcpy(nd->mPositionKeys,&me->mAnim.akeyPositions[0], |
| nd->mNumPositionKeys * sizeof(aiVectorKey)); |
| } |
| // copy rotation keys |
| if (me->mAnim.akeyRotations.size() > 1 ) { |
| // Allocate the key array and fill it |
| nd->mNumRotationKeys = (unsigned int) me->mAnim.akeyRotations.size(); |
| nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; |
| |
| // -------------------------------------------------------------------- |
| // Rotation keys are offsets to the previous keys. |
| // We have the quaternion representations of all |
| // of them, so we just need to concatenate all |
| // (unit-length) quaternions to get the absolute |
| // rotations. |
| // Rotation keys are ABSOLUTE for older files |
| // -------------------------------------------------------------------- |
| |
| aiQuaternion cur; |
| for (unsigned int a = 0; a < nd->mNumRotationKeys;++a) { |
| aiQuatKey q = me->mAnim.akeyRotations[a]; |
| |
| if (mParser->iFileFormat > 110) { |
| cur = (a ? cur*q.mValue : q.mValue); |
| q.mValue = cur.Normalize(); |
| } |
| nd->mRotationKeys[a] = q; |
| |
| // need this to get to Assimp quaternion conventions |
| nd->mRotationKeys[a].mValue.w *= -1.f; |
| } |
| } |
| // copy scaling keys |
| if (me->mAnim.akeyScaling.size() > 1 ) { |
| // Allocate the key array and fill it |
| nd->mNumScalingKeys = (unsigned int) me->mAnim.akeyScaling.size(); |
| nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; |
| |
| ::memcpy(nd->mScalingKeys,&me->mAnim.akeyScaling[0], |
| nd->mNumScalingKeys * sizeof(aiVectorKey)); |
| } |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Build output cameras |
| void ASEImporter::BuildCameras() |
| { |
| if (!mParser->m_vCameras.empty()) { |
| pcScene->mNumCameras = (unsigned int)mParser->m_vCameras.size(); |
| pcScene->mCameras = new aiCamera*[pcScene->mNumCameras]; |
| |
| for (unsigned int i = 0; i < pcScene->mNumCameras;++i) { |
| aiCamera* out = pcScene->mCameras[i] = new aiCamera(); |
| ASE::Camera& in = mParser->m_vCameras[i]; |
| |
| // copy members |
| out->mClipPlaneFar = in.mFar; |
| out->mClipPlaneNear = (in.mNear ? in.mNear : 0.1f); |
| out->mHorizontalFOV = in.mFOV; |
| |
| out->mName.Set(in.mName); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Build output lights |
| void ASEImporter::BuildLights() |
| { |
| if (!mParser->m_vLights.empty()) { |
| pcScene->mNumLights = (unsigned int)mParser->m_vLights.size(); |
| pcScene->mLights = new aiLight*[pcScene->mNumLights]; |
| |
| for (unsigned int i = 0; i < pcScene->mNumLights;++i) { |
| aiLight* out = pcScene->mLights[i] = new aiLight(); |
| ASE::Light& in = mParser->m_vLights[i]; |
| |
| // The direction is encoded in the transformation matrix of the node. |
| // In 3DS MAX the light source points into negative Z direction if |
| // the node transformation is the identity. |
| out->mDirection = aiVector3D(0.f,0.f,-1.f); |
| |
| out->mName.Set(in.mName); |
| switch (in.mLightType) |
| { |
| case ASE::Light::TARGET: |
| out->mType = aiLightSource_SPOT; |
| out->mAngleInnerCone = AI_DEG_TO_RAD(in.mAngle); |
| out->mAngleOuterCone = (in.mFalloff ? AI_DEG_TO_RAD(in.mFalloff) : out->mAngleInnerCone); |
| break; |
| |
| case ASE::Light::DIRECTIONAL: |
| out->mType = aiLightSource_DIRECTIONAL; |
| break; |
| |
| default: |
| //case ASE::Light::OMNI: |
| out->mType = aiLightSource_POINT; |
| break; |
| }; |
| out->mColorDiffuse = out->mColorSpecular = in.mColor * in.mIntensity; |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void ASEImporter::AddNodes(const std::vector<BaseNode*>& nodes, |
| aiNode* pcParent,const char* szName) |
| { |
| aiMatrix4x4 m; |
| AddNodes(nodes,pcParent,szName,m); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Add meshes to a given node |
| void ASEImporter::AddMeshes(const ASE::BaseNode* snode,aiNode* node) |
| { |
| for (unsigned int i = 0; i < pcScene->mNumMeshes;++i) { |
| // Get the name of the mesh (the mesh instance has been temporarily stored in the third vertex color) |
| const aiMesh* pcMesh = pcScene->mMeshes[i]; |
| const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2]; |
| |
| if (mesh == snode) { |
| ++node->mNumMeshes; |
| } |
| } |
| |
| if(node->mNumMeshes) { |
| node->mMeshes = new unsigned int[node->mNumMeshes]; |
| for (unsigned int i = 0, p = 0; i < pcScene->mNumMeshes;++i) { |
| |
| const aiMesh* pcMesh = pcScene->mMeshes[i]; |
| const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2]; |
| if (mesh == snode) { |
| node->mMeshes[p++] = i; |
| |
| // Transform all vertices of the mesh back into their local space -> |
| // at the moment they are pretransformed |
| aiMatrix4x4 m = mesh->mTransform; |
| m.Inverse(); |
| |
| aiVector3D* pvCurPtr = pcMesh->mVertices; |
| const aiVector3D* pvEndPtr = pvCurPtr + pcMesh->mNumVertices; |
| while (pvCurPtr != pvEndPtr) { |
| *pvCurPtr = m * (*pvCurPtr); |
| pvCurPtr++; |
| } |
| |
| // Do the same for the normal vectors, if we have them. |
| // As always, inverse transpose. |
| if (pcMesh->mNormals) { |
| aiMatrix3x3 m3 = aiMatrix3x3( mesh->mTransform ); |
| m3.Transpose(); |
| |
| pvCurPtr = pcMesh->mNormals; |
| pvEndPtr = pvCurPtr + pcMesh->mNumVertices; |
| while (pvCurPtr != pvEndPtr) { |
| *pvCurPtr = m3 * (*pvCurPtr); |
| pvCurPtr++; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Add child nodes to a given parent node |
| void ASEImporter::AddNodes (const std::vector<BaseNode*>& nodes, |
| aiNode* pcParent, const char* szName, |
| const aiMatrix4x4& mat) |
| { |
| const size_t len = szName ? ::strlen(szName) : 0; |
| ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); |
| |
| // Receives child nodes for the pcParent node |
| std::vector<aiNode*> apcNodes; |
| |
| // Now iterate through all nodes in the scene and search for one |
| // which has *us* as parent. |
| for (std::vector<BaseNode*>::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { |
| const BaseNode* snode = *it; |
| if (szName) { |
| if (len != snode->mParent.length() || ::strcmp(szName,snode->mParent.c_str())) |
| continue; |
| } |
| else if (snode->mParent.length()) |
| continue; |
| |
| (*it)->mProcessed = true; |
| |
| // Allocate a new node and add it to the output data structure |
| apcNodes.push_back(new aiNode()); |
| aiNode* node = apcNodes.back(); |
| |
| node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); |
| node->mParent = pcParent; |
| |
| // Setup the transformation matrix of the node |
| aiMatrix4x4 mParentAdjust = mat; |
| mParentAdjust.Inverse(); |
| node->mTransformation = mParentAdjust*snode->mTransform; |
| |
| // Add sub nodes - prevent stack overflow due to recursive parenting |
| if (node->mName != node->mParent->mName) { |
| AddNodes(nodes,node,node->mName.data,snode->mTransform); |
| } |
| |
| // Further processing depends on the type of the node |
| if (snode->mType == ASE::BaseNode::Mesh) { |
| // If the type of this node is "Mesh" we need to search |
| // the list of output meshes in the data structure for |
| // all those that belonged to this node once. This is |
| // slightly inconvinient here and a better solution should |
| // be used when this code is refactored next. |
| AddMeshes(snode,node); |
| } |
| else if (is_not_qnan( snode->mTargetPosition.x )) { |
| // If this is a target camera or light we generate a small |
| // child node which marks the position of the camera |
| // target (the direction information is contained in *this* |
| // node's animation track but the exact target position |
| // would be lost otherwise) |
| if (!node->mNumChildren) { |
| node->mChildren = new aiNode*[1]; |
| } |
| |
| aiNode* nd = new aiNode(); |
| |
| nd->mName.Set ( snode->mName + ".Target" ); |
| |
| nd->mTransformation.a4 = snode->mTargetPosition.x - snode->mTransform.a4; |
| nd->mTransformation.b4 = snode->mTargetPosition.y - snode->mTransform.b4; |
| nd->mTransformation.c4 = snode->mTargetPosition.z - snode->mTransform.c4; |
| |
| nd->mParent = node; |
| |
| // The .Target node is always the first child node |
| for (unsigned int m = 0; m < node->mNumChildren;++m) |
| node->mChildren[m+1] = node->mChildren[m]; |
| |
| node->mChildren[0] = nd; |
| node->mNumChildren++; |
| |
| // What we did is so great, it is at least worth a debug message |
| DefaultLogger::get()->debug("ASE: Generating separate target node ("+snode->mName+")"); |
| } |
| } |
| |
| // Allocate enough space for the child nodes |
| // We allocate one slot more in case this is a target camera/light |
| pcParent->mNumChildren = (unsigned int)apcNodes.size(); |
| if (pcParent->mNumChildren) { |
| pcParent->mChildren = new aiNode*[apcNodes.size()+1 /* PLUS ONE !!! */]; |
| |
| // now build all nodes for our nice new children |
| for (unsigned int p = 0; p < apcNodes.size();++p) |
| pcParent->mChildren[p] = apcNodes[p]; |
| } |
| return; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Build the output node graph |
| void ASEImporter::BuildNodes(std::vector<BaseNode*>& nodes) { |
| ai_assert(NULL != pcScene); |
| |
| // allocate the one and only root node |
| aiNode* root = pcScene->mRootNode = new aiNode(); |
| root->mName.Set("<ASERoot>"); |
| |
| // Setup the coordinate system transformation |
| pcScene->mRootNode->mNumChildren = 1; |
| pcScene->mRootNode->mChildren = new aiNode*[1]; |
| aiNode* ch = pcScene->mRootNode->mChildren[0] = new aiNode(); |
| ch->mParent = root; |
| |
| // Change the transformation matrix of all nodes |
| for (BaseNode *node : nodes) { |
| aiMatrix4x4& m = node->mTransform; |
| m.Transpose(); // row-order vs column-order |
| } |
| |
| // add all nodes |
| AddNodes(nodes,ch,NULL); |
| |
| // now iterate through al nodes and find those that have not yet |
| // been added to the nodegraph (= their parent could not be recognized) |
| std::vector<const BaseNode*> aiList; |
| for (std::vector<BaseNode*>::iterator it = nodes.begin(), end = nodes.end();it != end; ++it) { |
| if ((*it)->mProcessed) { |
| continue; |
| } |
| |
| // check whether our parent is known |
| bool bKnowParent = false; |
| |
| // search the list another time, starting *here* and try to find out whether |
| // there is a node that references *us* as a parent |
| for (std::vector<BaseNode*>::const_iterator it2 = nodes.begin();it2 != end; ++it2) { |
| if (it2 == it) { |
| continue; |
| } |
| |
| if ((*it2)->mName == (*it)->mParent) { |
| bKnowParent = true; |
| break; |
| } |
| } |
| if (!bKnowParent) { |
| aiList.push_back(*it); |
| } |
| } |
| |
| // Are there ane orphaned nodes? |
| if (!aiList.empty()) { |
| std::vector<aiNode*> apcNodes; |
| apcNodes.reserve(aiList.size() + pcScene->mRootNode->mNumChildren); |
| |
| for (unsigned int i = 0; i < pcScene->mRootNode->mNumChildren;++i) |
| apcNodes.push_back(pcScene->mRootNode->mChildren[i]); |
| |
| delete[] pcScene->mRootNode->mChildren; |
| for (std::vector<const BaseNode*>::/*const_*/iterator i = aiList.begin();i != aiList.end();++i) { |
| const ASE::BaseNode* src = *i; |
| |
| // The parent is not known, so we can assume that we must add |
| // this node to the root node of the whole scene |
| aiNode* pcNode = new aiNode(); |
| pcNode->mParent = pcScene->mRootNode; |
| pcNode->mName.Set(src->mName); |
| AddMeshes(src,pcNode); |
| AddNodes(nodes,pcNode,pcNode->mName.data); |
| apcNodes.push_back(pcNode); |
| } |
| |
| // Regenerate our output array |
| pcScene->mRootNode->mChildren = new aiNode*[apcNodes.size()]; |
| for (unsigned int i = 0; i < apcNodes.size();++i) |
| pcScene->mRootNode->mChildren[i] = apcNodes[i]; |
| |
| pcScene->mRootNode->mNumChildren = (unsigned int)apcNodes.size(); |
| } |
| |
| // Reset the third color set to NULL - we used this field to store a temporary pointer |
| for (unsigned int i = 0; i < pcScene->mNumMeshes;++i) |
| pcScene->mMeshes[i]->mColors[2] = NULL; |
| |
| // The root node should not have at least one child or the file is valid |
| if (!pcScene->mRootNode->mNumChildren) { |
| throw DeadlyImportError("ASE: No nodes loaded. The file is either empty or corrupt"); |
| } |
| |
| // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system |
| pcScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, |
| 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Convert the imported data to the internal verbose representation |
| void ASEImporter::BuildUniqueRepresentation(ASE::Mesh& mesh) { |
| // allocate output storage |
| std::vector<aiVector3D> mPositions; |
| std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; |
| std::vector<aiColor4D> mVertexColors; |
| std::vector<aiVector3D> mNormals; |
| std::vector<BoneVertex> mBoneVertices; |
| |
| unsigned int iSize = (unsigned int)mesh.mFaces.size() * 3; |
| mPositions.resize(iSize); |
| |
| // optional texture coordinates |
| for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i) { |
| if (!mesh.amTexCoords[i].empty()) { |
| amTexCoords[i].resize(iSize); |
| } |
| } |
| // optional vertex colors |
| if (!mesh.mVertexColors.empty()) { |
| mVertexColors.resize(iSize); |
| } |
| |
| // optional vertex normals (vertex normals can simply be copied) |
| if (!mesh.mNormals.empty()) { |
| mNormals.resize(iSize); |
| } |
| // bone vertices. There is no need to change the bone list |
| if (!mesh.mBoneVertices.empty()) { |
| mBoneVertices.resize(iSize); |
| } |
| |
| // iterate through all faces in the mesh |
| unsigned int iCurrent = 0, fi = 0; |
| for (std::vector<ASE::Face>::iterator i = mesh.mFaces.begin();i != mesh.mFaces.end();++i,++fi) { |
| for (unsigned int n = 0; n < 3;++n,++iCurrent) |
| { |
| mPositions[iCurrent] = mesh.mPositions[(*i).mIndices[n]]; |
| |
| // add texture coordinates |
| for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { |
| if (mesh.amTexCoords[c].empty())break; |
| amTexCoords[c][iCurrent] = mesh.amTexCoords[c][(*i).amUVIndices[c][n]]; |
| } |
| // add vertex colors |
| if (!mesh.mVertexColors.empty()) { |
| mVertexColors[iCurrent] = mesh.mVertexColors[(*i).mColorIndices[n]]; |
| } |
| // add normal vectors |
| if (!mesh.mNormals.empty()) { |
| mNormals[iCurrent] = mesh.mNormals[fi*3+n]; |
| mNormals[iCurrent].Normalize(); |
| } |
| |
| // handle bone vertices |
| if ((*i).mIndices[n] < mesh.mBoneVertices.size()) { |
| // (sometimes this will cause bone verts to be duplicated |
| // however, I' quite sure Schrompf' JoinVerticesStep |
| // will fix that again ...) |
| mBoneVertices[iCurrent] = mesh.mBoneVertices[(*i).mIndices[n]]; |
| } |
| (*i).mIndices[n] = iCurrent; |
| } |
| } |
| |
| // replace the old arrays |
| mesh.mNormals = mNormals; |
| mesh.mPositions = mPositions; |
| mesh.mVertexColors = mVertexColors; |
| |
| for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) |
| mesh.amTexCoords[c] = amTexCoords[c]; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Copy a texture from the ASE structs to the output material |
| void CopyASETexture(aiMaterial& mat, ASE::Texture& texture, aiTextureType type) |
| { |
| // Setup the texture name |
| aiString tex; |
| tex.Set( texture.mMapName); |
| mat.AddProperty( &tex, AI_MATKEY_TEXTURE(type,0)); |
| |
| // Setup the texture blend factor |
| if (is_not_qnan(texture.mTextureBlend)) |
| mat.AddProperty<ai_real>( &texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type,0)); |
| |
| // Setup texture UV transformations |
| mat.AddProperty<ai_real>(&texture.mOffsetU,5,AI_MATKEY_UVTRANSFORM(type,0)); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Convert from ASE material to output material |
| void ASEImporter::ConvertMaterial(ASE::Material& mat) |
| { |
| // LARGE TODO: Much code her is copied from 3DS ... join them maybe? |
| |
| // Allocate the output material |
| mat.pcInstance = new aiMaterial(); |
| |
| // At first add the base ambient color of the |
| // scene to the material |
| mat.mAmbient.r += mParser->m_clrAmbient.r; |
| mat.mAmbient.g += mParser->m_clrAmbient.g; |
| mat.mAmbient.b += mParser->m_clrAmbient.b; |
| |
| aiString name; |
| name.Set( mat.mName); |
| mat.pcInstance->AddProperty( &name, AI_MATKEY_NAME); |
| |
| // material colors |
| mat.pcInstance->AddProperty( &mat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); |
| mat.pcInstance->AddProperty( &mat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
| mat.pcInstance->AddProperty( &mat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); |
| mat.pcInstance->AddProperty( &mat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); |
| |
| // shininess |
| if (0.0f != mat.mSpecularExponent && 0.0f != mat.mShininessStrength) |
| { |
| mat.pcInstance->AddProperty( &mat.mSpecularExponent, 1, AI_MATKEY_SHININESS); |
| mat.pcInstance->AddProperty( &mat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); |
| } |
| // If there is no shininess, we can disable phong lighting |
| else if (D3DS::Discreet3DS::Metal == mat.mShading || |
| D3DS::Discreet3DS::Phong == mat.mShading || |
| D3DS::Discreet3DS::Blinn == mat.mShading) |
| { |
| mat.mShading = D3DS::Discreet3DS::Gouraud; |
| } |
| |
| // opacity |
| mat.pcInstance->AddProperty<ai_real>( &mat.mTransparency,1,AI_MATKEY_OPACITY); |
| |
| // Two sided rendering? |
| if (mat.mTwoSided) |
| { |
| int i = 1; |
| mat.pcInstance->AddProperty<int>(&i,1,AI_MATKEY_TWOSIDED); |
| } |
| |
| // shading mode |
| aiShadingMode eShading = aiShadingMode_NoShading; |
| switch (mat.mShading) |
| { |
| case D3DS::Discreet3DS::Flat: |
| eShading = aiShadingMode_Flat; break; |
| case D3DS::Discreet3DS::Phong : |
| eShading = aiShadingMode_Phong; break; |
| case D3DS::Discreet3DS::Blinn : |
| eShading = aiShadingMode_Blinn; break; |
| |
| // I don't know what "Wire" shading should be, |
| // assume it is simple lambertian diffuse (L dot N) shading |
| case D3DS::Discreet3DS::Wire: |
| { |
| // set the wireframe flag |
| unsigned int iWire = 1; |
| mat.pcInstance->AddProperty<int>( (int*)&iWire,1,AI_MATKEY_ENABLE_WIREFRAME); |
| } |
| case D3DS::Discreet3DS::Gouraud: |
| eShading = aiShadingMode_Gouraud; break; |
| case D3DS::Discreet3DS::Metal : |
| eShading = aiShadingMode_CookTorrance; break; |
| } |
| mat.pcInstance->AddProperty<int>( (int*)&eShading,1,AI_MATKEY_SHADING_MODEL); |
| |
| // DIFFUSE texture |
| if( mat.sTexDiffuse.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexDiffuse, aiTextureType_DIFFUSE); |
| |
| // SPECULAR texture |
| if( mat.sTexSpecular.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexSpecular, aiTextureType_SPECULAR); |
| |
| // AMBIENT texture |
| if( mat.sTexAmbient.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexAmbient, aiTextureType_AMBIENT); |
| |
| // OPACITY texture |
| if( mat.sTexOpacity.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexOpacity, aiTextureType_OPACITY); |
| |
| // EMISSIVE texture |
| if( mat.sTexEmissive.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexEmissive, aiTextureType_EMISSIVE); |
| |
| // BUMP texture |
| if( mat.sTexBump.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexBump, aiTextureType_HEIGHT); |
| |
| // SHININESS texture |
| if( mat.sTexShininess.mMapName.length() > 0) |
| CopyASETexture(*mat.pcInstance,mat.sTexShininess, aiTextureType_SHININESS); |
| |
| // store the name of the material itself, too |
| if( mat.mName.length() > 0) { |
| aiString tex;tex.Set( mat.mName); |
| mat.pcInstance->AddProperty( &tex, AI_MATKEY_NAME); |
| } |
| return; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Build output meshes |
| void ASEImporter::ConvertMeshes(ASE::Mesh& mesh, std::vector<aiMesh*>& avOutMeshes) |
| { |
| // validate the material index of the mesh |
| if (mesh.iMaterialIndex >= mParser->m_vMaterials.size()) { |
| mesh.iMaterialIndex = (unsigned int)mParser->m_vMaterials.size()-1; |
| DefaultLogger::get()->warn("Material index is out of range"); |
| } |
| |
| // If the material the mesh is assigned to is consisting of submeshes, split it |
| if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) { |
| std::vector<ASE::Material> vSubMaterials = mParser-> |
| m_vMaterials[mesh.iMaterialIndex].avSubMaterials; |
| |
| std::vector<unsigned int>* aiSplit = new std::vector<unsigned int>[vSubMaterials.size()]; |
| |
| // build a list of all faces per submaterial |
| for (unsigned int i = 0; i < mesh.mFaces.size();++i) { |
| // check range |
| if (mesh.mFaces[i].iMaterial >= vSubMaterials.size()) { |
| DefaultLogger::get()->warn("Submaterial index is out of range"); |
| |
| // use the last material instead |
| aiSplit[vSubMaterials.size()-1].push_back(i); |
| } |
| else aiSplit[mesh.mFaces[i].iMaterial].push_back(i); |
| } |
| |
| // now generate submeshes |
| for (unsigned int p = 0; p < vSubMaterials.size();++p) { |
| if (!aiSplit[p].empty()) { |
| |
| aiMesh* p_pcOut = new aiMesh(); |
| p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
| |
| // let the sub material index |
| p_pcOut->mMaterialIndex = p; |
| |
| // we will need this material |
| mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials[p].bNeed = true; |
| |
| // store the real index here ... color channel 3 |
| p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex; |
| |
| // store a pointer to the mesh in color channel 2 |
| p_pcOut->mColors[2] = (aiColor4D*) &mesh; |
| avOutMeshes.push_back(p_pcOut); |
| |
| // convert vertices |
| p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3; |
| p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size(); |
| |
| // receive output vertex weights |
| std::vector<std::pair<unsigned int, float> > *avOutputBones = NULL; |
| if (!mesh.mBones.empty()) { |
| avOutputBones = new std::vector<std::pair<unsigned int, float> >[mesh.mBones.size()]; |
| } |
| |
| // allocate enough storage for faces |
| p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; |
| |
| unsigned int iBase = 0,iIndex; |
| if (p_pcOut->mNumVertices) { |
| p_pcOut->mVertices = new aiVector3D[p_pcOut->mNumVertices]; |
| p_pcOut->mNormals = new aiVector3D[p_pcOut->mNumVertices]; |
| for (unsigned int q = 0; q < aiSplit[p].size();++q) { |
| |
| iIndex = aiSplit[p][q]; |
| |
| p_pcOut->mFaces[q].mIndices = new unsigned int[3]; |
| p_pcOut->mFaces[q].mNumIndices = 3; |
| |
| for (unsigned int t = 0; t < 3;++t, ++iBase) { |
| const uint32_t iIndex2 = mesh.mFaces[iIndex].mIndices[t]; |
| |
| p_pcOut->mVertices[iBase] = mesh.mPositions [iIndex2]; |
| p_pcOut->mNormals [iBase] = mesh.mNormals [iIndex2]; |
| |
| // convert bones, if existing |
| if (!mesh.mBones.empty()) { |
| ai_assert(avOutputBones); |
| // check whether there is a vertex weight for this vertex index |
| if (iIndex2 < mesh.mBoneVertices.size()) { |
| |
| for (std::vector<std::pair<int,float> >::const_iterator |
| blubb = mesh.mBoneVertices[iIndex2].mBoneWeights.begin(); |
| blubb != mesh.mBoneVertices[iIndex2].mBoneWeights.end();++blubb) { |
| |
| // NOTE: illegal cases have already been filtered out |
| avOutputBones[(*blubb).first].push_back(std::pair<unsigned int, float>( |
| iBase,(*blubb).second)); |
| } |
| } |
| } |
| p_pcOut->mFaces[q].mIndices[t] = iBase; |
| } |
| } |
| } |
| // convert texture coordinates (up to AI_MAX_NUMBER_OF_TEXTURECOORDS sets supported) |
| for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { |
| if (!mesh.amTexCoords[c].empty()) |
| { |
| p_pcOut->mTextureCoords[c] = new aiVector3D[p_pcOut->mNumVertices]; |
| iBase = 0; |
| for (unsigned int q = 0; q < aiSplit[p].size();++q) { |
| iIndex = aiSplit[p][q]; |
| for (unsigned int t = 0; t < 3;++t) { |
| p_pcOut->mTextureCoords[c][iBase++] = mesh.amTexCoords[c][mesh.mFaces[iIndex].mIndices[t]]; |
| } |
| } |
| // Setup the number of valid vertex components |
| p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; |
| } |
| } |
| |
| // Convert vertex colors (only one set supported) |
| if (!mesh.mVertexColors.empty()){ |
| p_pcOut->mColors[0] = new aiColor4D[p_pcOut->mNumVertices]; |
| iBase = 0; |
| for (unsigned int q = 0; q < aiSplit[p].size();++q) { |
| iIndex = aiSplit[p][q]; |
| for (unsigned int t = 0; t < 3;++t) { |
| p_pcOut->mColors[0][iBase++] = mesh.mVertexColors[mesh.mFaces[iIndex].mIndices[t]]; |
| } |
| } |
| } |
| // Copy bones |
| if (!mesh.mBones.empty()) { |
| p_pcOut->mNumBones = 0; |
| for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock) |
| if (!avOutputBones[mrspock].empty())p_pcOut->mNumBones++; |
| |
| p_pcOut->mBones = new aiBone* [ p_pcOut->mNumBones ]; |
| aiBone** pcBone = p_pcOut->mBones; |
| for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock) |
| { |
| if (!avOutputBones[mrspock].empty()) { |
| // we will need this bone. add it to the output mesh and |
| // add all per-vertex weights |
| aiBone* pc = *pcBone = new aiBone(); |
| pc->mName.Set(mesh.mBones[mrspock].mName); |
| |
| pc->mNumWeights = (unsigned int)avOutputBones[mrspock].size(); |
| pc->mWeights = new aiVertexWeight[pc->mNumWeights]; |
| |
| for (unsigned int captainkirk = 0; captainkirk < pc->mNumWeights;++captainkirk) |
| { |
| const std::pair<unsigned int,float>& ref = avOutputBones[mrspock][captainkirk]; |
| pc->mWeights[captainkirk].mVertexId = ref.first; |
| pc->mWeights[captainkirk].mWeight = ref.second; |
| } |
| ++pcBone; |
| } |
| } |
| // delete allocated storage |
| delete[] avOutputBones; |
| } |
| } |
| } |
| // delete storage |
| delete[] aiSplit; |
| } |
| else |
| { |
| // Otherwise we can simply copy the data to one output mesh |
| // This codepath needs less memory and uses fast memcpy()s |
| // to do the actual copying. So I think it is worth the |
| // effort here. |
| |
| aiMesh* p_pcOut = new aiMesh(); |
| p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
| |
| // set an empty sub material index |
| p_pcOut->mMaterialIndex = ASE::Face::DEFAULT_MATINDEX; |
| mParser->m_vMaterials[mesh.iMaterialIndex].bNeed = true; |
| |
| // store the real index here ... in color channel 3 |
| p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex; |
| |
| // store a pointer to the mesh in color channel 2 |
| p_pcOut->mColors[2] = (aiColor4D*) &mesh; |
| avOutMeshes.push_back(p_pcOut); |
| |
| // If the mesh hasn't faces or vertices, there are two cases |
| // possible: 1. the model is invalid. 2. This is a dummy |
| // helper object which we are going to remove later ... |
| if (mesh.mFaces.empty() || mesh.mPositions.empty()) { |
| return; |
| } |
| |
| // convert vertices |
| p_pcOut->mNumVertices = (unsigned int)mesh.mPositions.size(); |
| p_pcOut->mNumFaces = (unsigned int)mesh.mFaces.size(); |
| |
| // allocate enough storage for faces |
| p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; |
| |
| // copy vertices |
| p_pcOut->mVertices = new aiVector3D[mesh.mPositions.size()]; |
| memcpy(p_pcOut->mVertices,&mesh.mPositions[0], |
| mesh.mPositions.size() * sizeof(aiVector3D)); |
| |
| // copy normals |
| p_pcOut->mNormals = new aiVector3D[mesh.mNormals.size()]; |
| memcpy(p_pcOut->mNormals,&mesh.mNormals[0], |
| mesh.mNormals.size() * sizeof(aiVector3D)); |
| |
| // copy texture coordinates |
| for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { |
| if (!mesh.amTexCoords[c].empty()) { |
| p_pcOut->mTextureCoords[c] = new aiVector3D[mesh.amTexCoords[c].size()]; |
| memcpy(p_pcOut->mTextureCoords[c],&mesh.amTexCoords[c][0], |
| mesh.amTexCoords[c].size() * sizeof(aiVector3D)); |
| |
| // setup the number of valid vertex components |
| p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; |
| } |
| } |
| |
| // copy vertex colors |
| if (!mesh.mVertexColors.empty()) { |
| p_pcOut->mColors[0] = new aiColor4D[mesh.mVertexColors.size()]; |
| memcpy(p_pcOut->mColors[0],&mesh.mVertexColors[0], |
| mesh.mVertexColors.size() * sizeof(aiColor4D)); |
| } |
| |
| // copy faces |
| for (unsigned int iFace = 0; iFace < p_pcOut->mNumFaces;++iFace) { |
| p_pcOut->mFaces[iFace].mNumIndices = 3; |
| p_pcOut->mFaces[iFace].mIndices = new unsigned int[3]; |
| |
| // copy indices |
| p_pcOut->mFaces[iFace].mIndices[0] = mesh.mFaces[iFace].mIndices[0]; |
| p_pcOut->mFaces[iFace].mIndices[1] = mesh.mFaces[iFace].mIndices[1]; |
| p_pcOut->mFaces[iFace].mIndices[2] = mesh.mFaces[iFace].mIndices[2]; |
| } |
| |
| // copy vertex bones |
| if (!mesh.mBones.empty() && !mesh.mBoneVertices.empty()) { |
| std::vector<std::vector<aiVertexWeight> > avBonesOut( mesh.mBones.size() ); |
| |
| // find all vertex weights for this bone |
| unsigned int quak = 0; |
| for (std::vector<BoneVertex>::const_iterator harrypotter = mesh.mBoneVertices.begin(); |
| harrypotter != mesh.mBoneVertices.end();++harrypotter,++quak) { |
| |
| for (std::vector<std::pair<int,float> >::const_iterator |
| ronaldweasley = (*harrypotter).mBoneWeights.begin(); |
| ronaldweasley != (*harrypotter).mBoneWeights.end();++ronaldweasley) |
| { |
| aiVertexWeight weight; |
| weight.mVertexId = quak; |
| weight.mWeight = (*ronaldweasley).second; |
| avBonesOut[(*ronaldweasley).first].push_back(weight); |
| } |
| } |
| |
| // now build a final bone list |
| p_pcOut->mNumBones = 0; |
| for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy) |
| if (!avBonesOut[jfkennedy].empty())p_pcOut->mNumBones++; |
| |
| p_pcOut->mBones = new aiBone*[p_pcOut->mNumBones]; |
| aiBone** pcBone = p_pcOut->mBones; |
| for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy) { |
| if (!avBonesOut[jfkennedy].empty()) { |
| aiBone* pc = *pcBone = new aiBone(); |
| pc->mName.Set(mesh.mBones[jfkennedy].mName); |
| pc->mNumWeights = (unsigned int)avBonesOut[jfkennedy].size(); |
| pc->mWeights = new aiVertexWeight[pc->mNumWeights]; |
| ::memcpy(pc->mWeights,&avBonesOut[jfkennedy][0], |
| sizeof(aiVertexWeight) * pc->mNumWeights); |
| ++pcBone; |
| } |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup proper material indices and build output materials |
| void ASEImporter::BuildMaterialIndices() |
| { |
| ai_assert(NULL != pcScene); |
| |
| // iterate through all materials and check whether we need them |
| for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat) |
| { |
| ASE::Material& mat = mParser->m_vMaterials[iMat]; |
| if (mat.bNeed) { |
| // Convert it to the aiMaterial layout |
| ConvertMaterial(mat); |
| ++pcScene->mNumMaterials; |
| } |
| for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat) |
| { |
| ASE::Material& submat = mat.avSubMaterials[iSubMat]; |
| if (submat.bNeed) { |
| // Convert it to the aiMaterial layout |
| ConvertMaterial(submat); |
| ++pcScene->mNumMaterials; |
| } |
| } |
| } |
| |
| // allocate the output material array |
| pcScene->mMaterials = new aiMaterial*[pcScene->mNumMaterials]; |
| D3DS::Material** pcIntMaterials = new D3DS::Material*[pcScene->mNumMaterials]; |
| |
| unsigned int iNum = 0; |
| for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat) { |
| ASE::Material& mat = mParser->m_vMaterials[iMat]; |
| if (mat.bNeed) |
| { |
| ai_assert(NULL != mat.pcInstance); |
| pcScene->mMaterials[iNum] = mat.pcInstance; |
| |
| // Store the internal material, too |
| pcIntMaterials[iNum] = &mat; |
| |
| // Iterate through all meshes and search for one which is using |
| // this top-level material index |
| for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh) |
| { |
| aiMesh* mesh = pcScene->mMeshes[iMesh]; |
| if (ASE::Face::DEFAULT_MATINDEX == mesh->mMaterialIndex && |
| iMat == (uintptr_t)mesh->mColors[3]) |
| { |
| mesh->mMaterialIndex = iNum; |
| mesh->mColors[3] = NULL; |
| } |
| } |
| iNum++; |
| } |
| for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat) { |
| ASE::Material& submat = mat.avSubMaterials[iSubMat]; |
| if (submat.bNeed) { |
| ai_assert(NULL != submat.pcInstance); |
| pcScene->mMaterials[iNum] = submat.pcInstance; |
| |
| // Store the internal material, too |
| pcIntMaterials[iNum] = &submat; |
| |
| // Iterate through all meshes and search for one which is using |
| // this sub-level material index |
| for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh) { |
| aiMesh* mesh = pcScene->mMeshes[iMesh]; |
| |
| if (iSubMat == mesh->mMaterialIndex && iMat == (uintptr_t)mesh->mColors[3]) { |
| mesh->mMaterialIndex = iNum; |
| mesh->mColors[3] = NULL; |
| } |
| } |
| iNum++; |
| } |
| } |
| } |
| |
| // Dekete our temporary array |
| delete[] pcIntMaterials; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Generate normal vectors basing on smoothing groups |
| bool ASEImporter::GenerateNormals(ASE::Mesh& mesh) { |
| |
| if (!mesh.mNormals.empty() && !configRecomputeNormals) |
| { |
| // Check whether there are only uninitialized normals. If there are |
| // some, skip all normals from the file and compute them on our own |
| for (std::vector<aiVector3D>::const_iterator qq = mesh.mNormals.begin();qq != mesh.mNormals.end();++qq) { |
| if ((*qq).x || (*qq).y || (*qq).z) |
| { |
| return true; |
| } |
| } |
| } |
| // The array is reused. |
| ComputeNormalsWithSmoothingsGroups<ASE::Face>(mesh); |
| return false; |
| } |
| |
| #endif // ASSIMP_BUILD_NO_3DS_IMPORTER |
| |
| #endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER |