nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file Implementation of the Collada loader */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: #ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER nuclear@0: nuclear@0: #include "assimp/anim.h" nuclear@0: #include "ColladaLoader.h" nuclear@0: #include "ColladaParser.h" nuclear@0: nuclear@0: #include "fast_atof.h" nuclear@0: #include "ParsingUtils.h" nuclear@0: #include "SkeletonMeshBuilder.h" nuclear@0: nuclear@0: #include "time.h" nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: nuclear@0: static const aiImporterDesc desc = { nuclear@0: "Collada Importer", nuclear@0: "", nuclear@0: "", nuclear@0: "http://collada.org", nuclear@0: aiImporterFlags_SupportTextFlavour, nuclear@0: 1, nuclear@0: 3, nuclear@0: 1, nuclear@0: 5, nuclear@0: "dae" nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: ColladaLoader::ColladaLoader() nuclear@0: : noSkeletonMesh() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: ColladaLoader::~ColladaLoader() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the class can handle the format of the given file. nuclear@0: bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const nuclear@0: { nuclear@0: // check file extension nuclear@0: std::string extension = GetExtension(pFile); nuclear@0: nuclear@0: if( extension == "dae") nuclear@0: return true; nuclear@0: nuclear@0: // XML - too generic, we need to open the file and search for typical keywords nuclear@0: if( extension == "xml" || !extension.length() || checkSig) { nuclear@0: /* If CanRead() is called in order to check whether we nuclear@0: * support a specific file extension in general pIOHandler nuclear@0: * might be NULL and it's our duty to return true here. nuclear@0: */ nuclear@0: if (!pIOHandler)return true; nuclear@0: const char* tokens[] = {"collada"}; nuclear@0: return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ColladaLoader::SetupProperties(const Importer* pImp) nuclear@0: { nuclear@0: noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Get file extension list nuclear@0: const aiImporterDesc* ColladaLoader::GetInfo () const nuclear@0: { nuclear@0: return &desc; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file into the given scene structure. nuclear@0: void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) nuclear@0: { nuclear@0: mFileName = pFile; nuclear@0: nuclear@0: // clean all member arrays - just for safety, it should work even if we did not nuclear@0: mMeshIndexByID.clear(); nuclear@0: mMaterialIndexByName.clear(); nuclear@0: mMeshes.clear(); nuclear@0: newMats.clear(); nuclear@0: mLights.clear(); nuclear@0: mCameras.clear(); nuclear@0: mTextures.clear(); nuclear@0: nuclear@0: // parse the input file nuclear@0: ColladaParser parser( pIOHandler, pFile); nuclear@0: nuclear@0: if( !parser.mRootNode) nuclear@0: throw DeadlyImportError( "Collada: File came out empty. Something is wrong here."); nuclear@0: nuclear@0: // reserve some storage to avoid unnecessary reallocs nuclear@0: newMats.reserve(parser.mMaterialLibrary.size()*2); nuclear@0: mMeshes.reserve(parser.mMeshLibrary.size()*2); nuclear@0: nuclear@0: mCameras.reserve(parser.mCameraLibrary.size()); nuclear@0: mLights.reserve(parser.mLightLibrary.size()); nuclear@0: nuclear@0: // create the materials first, for the meshes to find nuclear@0: BuildMaterials( parser, pScene); nuclear@0: nuclear@0: // build the node hierarchy from it nuclear@0: pScene->mRootNode = BuildHierarchy( parser, parser.mRootNode); nuclear@0: nuclear@0: // ... then fill the materials with the now adjusted settings nuclear@0: FillMaterials(parser, pScene); nuclear@0: nuclear@0: // Apply unitsize scale calculation nuclear@0: pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0, nuclear@0: 0, parser.mUnitSize, 0, 0, nuclear@0: 0, 0, parser.mUnitSize, 0, nuclear@0: 0, 0, 0, 1); nuclear@0: nuclear@0: // Convert to Y_UP, if different orientation nuclear@0: if( parser.mUpDirection == ColladaParser::UP_X) nuclear@0: pScene->mRootNode->mTransformation *= aiMatrix4x4( nuclear@0: 0, -1, 0, 0, nuclear@0: 1, 0, 0, 0, nuclear@0: 0, 0, 1, 0, nuclear@0: 0, 0, 0, 1); nuclear@0: else if( parser.mUpDirection == ColladaParser::UP_Z) nuclear@0: pScene->mRootNode->mTransformation *= aiMatrix4x4( nuclear@0: 1, 0, 0, 0, nuclear@0: 0, 0, 1, 0, nuclear@0: 0, -1, 0, 0, nuclear@0: 0, 0, 0, 1); nuclear@0: nuclear@0: // store all meshes nuclear@0: StoreSceneMeshes( pScene); nuclear@0: nuclear@0: // store all materials nuclear@0: StoreSceneMaterials( pScene); nuclear@0: nuclear@0: // store all lights nuclear@0: StoreSceneLights( pScene); nuclear@0: nuclear@0: // store all cameras nuclear@0: StoreSceneCameras( pScene); nuclear@0: nuclear@0: // store all animations nuclear@0: StoreAnimations( pScene, parser); nuclear@0: nuclear@0: nuclear@0: // If no meshes have been loaded, it's probably just an animated skeleton. nuclear@0: if (!pScene->mNumMeshes) { nuclear@0: nuclear@0: if (!noSkeletonMesh) { nuclear@0: SkeletonMeshBuilder hero(pScene); nuclear@0: } nuclear@0: pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Recursively constructs a scene node for the given parser node and returns it. nuclear@0: aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode) nuclear@0: { nuclear@0: // create a node for it nuclear@0: aiNode* node = new aiNode(); nuclear@0: nuclear@0: // find a name for the new node. It's more complicated than you might think nuclear@0: node->mName.Set( FindNameForNode( pNode)); nuclear@0: nuclear@0: // calculate the transformation matrix for it nuclear@0: node->mTransformation = pParser.CalculateResultTransform( pNode->mTransforms); nuclear@0: nuclear@0: // now resolve node instances nuclear@0: std::vector instances; nuclear@0: ResolveNodeInstances(pParser,pNode,instances); nuclear@0: nuclear@0: // add children. first the *real* ones nuclear@0: node->mNumChildren = pNode->mChildren.size()+instances.size(); nuclear@0: node->mChildren = new aiNode*[node->mNumChildren]; nuclear@0: nuclear@0: for( size_t a = 0; a < pNode->mChildren.size(); a++) nuclear@0: { nuclear@0: node->mChildren[a] = BuildHierarchy( pParser, pNode->mChildren[a]); nuclear@0: node->mChildren[a]->mParent = node; nuclear@0: } nuclear@0: nuclear@0: // ... and finally the resolved node instances nuclear@0: for( size_t a = 0; a < instances.size(); a++) nuclear@0: { nuclear@0: node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy( pParser, instances[a]); nuclear@0: node->mChildren[pNode->mChildren.size() + a]->mParent = node; nuclear@0: } nuclear@0: nuclear@0: // construct meshes nuclear@0: BuildMeshesForNode( pParser, pNode, node); nuclear@0: nuclear@0: // construct cameras nuclear@0: BuildCamerasForNode(pParser, pNode, node); nuclear@0: nuclear@0: // construct lights nuclear@0: BuildLightsForNode(pParser, pNode, node); nuclear@0: return node; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Resolve node instances nuclear@0: void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Collada::Node* pNode, nuclear@0: std::vector& resolved) nuclear@0: { nuclear@0: // reserve enough storage nuclear@0: resolved.reserve(pNode->mNodeInstances.size()); nuclear@0: nuclear@0: // ... and iterate through all nodes to be instanced as children of pNode nuclear@0: for (std::vector::const_iterator it = pNode->mNodeInstances.begin(), nuclear@0: end = pNode->mNodeInstances.end(); it != end; ++it) nuclear@0: { nuclear@0: // find the corresponding node in the library nuclear@0: const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find((*it).mNode); nuclear@0: const Collada::Node* nd = itt == pParser.mNodeLibrary.end() ? NULL : (*itt).second; nuclear@0: nuclear@0: // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632 nuclear@0: // need to check for both name and ID to catch all. To avoid breaking valid files, nuclear@0: // the workaround is only enabled when the first attempt to resolve the node has failed. nuclear@0: if (!nd) { nuclear@0: nd = FindNode(pParser.mRootNode,(*it).mNode); nuclear@0: } nuclear@0: if (!nd) nuclear@0: DefaultLogger::get()->error("Collada: Unable to resolve reference to instanced node " + (*it).mNode); nuclear@0: nuclear@0: else { nuclear@0: // attach this node to the list of children nuclear@0: resolved.push_back(nd); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Resolve UV channels nuclear@0: void ColladaLoader::ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler, nuclear@0: const Collada::SemanticMappingTable& table) nuclear@0: { nuclear@0: std::map::const_iterator it = table.mMap.find(sampler.mUVChannel); nuclear@0: if (it != table.mMap.end()) { nuclear@0: if (it->second.mType != Collada::IT_Texcoord) nuclear@0: DefaultLogger::get()->error("Collada: Unexpected effect input mapping"); nuclear@0: nuclear@0: sampler.mUVId = it->second.mSet; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Builds lights for the given node and references them nuclear@0: void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) nuclear@0: { nuclear@0: BOOST_FOREACH( const Collada::LightInstance& lid, pNode->mLights) nuclear@0: { nuclear@0: // find the referred light nuclear@0: ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find( lid.mLight); nuclear@0: if( srcLightIt == pParser.mLightLibrary.end()) nuclear@0: { nuclear@0: DefaultLogger::get()->warn("Collada: Unable to find light for ID \"" + lid.mLight + "\". Skipping."); nuclear@0: continue; nuclear@0: } nuclear@0: const Collada::Light* srcLight = &srcLightIt->second; nuclear@0: if (srcLight->mType == aiLightSource_AMBIENT) { nuclear@0: DefaultLogger::get()->error("Collada: Skipping ambient light for the moment"); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // now fill our ai data structure nuclear@0: aiLight* out = new aiLight(); nuclear@0: out->mName = pTarget->mName; nuclear@0: out->mType = (aiLightSourceType)srcLight->mType; nuclear@0: nuclear@0: // collada lights point in -Z by default, rest is specified in node transform nuclear@0: out->mDirection = aiVector3D(0.f,0.f,-1.f); nuclear@0: nuclear@0: out->mAttenuationConstant = srcLight->mAttConstant; nuclear@0: out->mAttenuationLinear = srcLight->mAttLinear; nuclear@0: out->mAttenuationQuadratic = srcLight->mAttQuadratic; nuclear@0: nuclear@0: // collada doesn't differenciate between these color types nuclear@0: out->mColorDiffuse = out->mColorSpecular = out->mColorAmbient = srcLight->mColor*srcLight->mIntensity; nuclear@0: nuclear@0: // convert falloff angle and falloff exponent in our representation, if given nuclear@0: if (out->mType == aiLightSource_SPOT) { nuclear@0: nuclear@0: out->mAngleInnerCone = AI_DEG_TO_RAD( srcLight->mFalloffAngle ); nuclear@0: nuclear@0: // ... some extension magic. nuclear@0: if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) nuclear@0: { nuclear@0: // ... some deprecation magic. nuclear@0: if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) nuclear@0: { nuclear@0: // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess .... nuclear@0: // epsilon chosen to be 0.1 nuclear@0: out->mAngleOuterCone = AI_DEG_TO_RAD (acos(pow(0.1f,1.f/srcLight->mFalloffExponent))+ nuclear@0: srcLight->mFalloffAngle); nuclear@0: } nuclear@0: else { nuclear@0: out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD( srcLight->mPenumbraAngle ); nuclear@0: if (out->mAngleOuterCone < out->mAngleInnerCone) nuclear@0: std::swap(out->mAngleInnerCone,out->mAngleOuterCone); nuclear@0: } nuclear@0: } nuclear@0: else out->mAngleOuterCone = AI_DEG_TO_RAD( srcLight->mOuterAngle ); nuclear@0: } nuclear@0: nuclear@0: // add to light list nuclear@0: mLights.push_back(out); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Builds cameras for the given node and references them nuclear@0: void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) nuclear@0: { nuclear@0: BOOST_FOREACH( const Collada::CameraInstance& cid, pNode->mCameras) nuclear@0: { nuclear@0: // find the referred light nuclear@0: ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find( cid.mCamera); nuclear@0: if( srcCameraIt == pParser.mCameraLibrary.end()) nuclear@0: { nuclear@0: DefaultLogger::get()->warn("Collada: Unable to find camera for ID \"" + cid.mCamera + "\". Skipping."); nuclear@0: continue; nuclear@0: } nuclear@0: const Collada::Camera* srcCamera = &srcCameraIt->second; nuclear@0: nuclear@0: // orthographic cameras not yet supported in Assimp nuclear@0: if (srcCamera->mOrtho) { nuclear@0: DefaultLogger::get()->warn("Collada: Orthographic cameras are not supported."); nuclear@0: } nuclear@0: nuclear@0: // now fill our ai data structure nuclear@0: aiCamera* out = new aiCamera(); nuclear@0: out->mName = pTarget->mName; nuclear@0: nuclear@0: // collada cameras point in -Z by default, rest is specified in node transform nuclear@0: out->mLookAt = aiVector3D(0.f,0.f,-1.f); nuclear@0: nuclear@0: // near/far z is already ok nuclear@0: out->mClipPlaneFar = srcCamera->mZFar; nuclear@0: out->mClipPlaneNear = srcCamera->mZNear; nuclear@0: nuclear@0: // ... but for the rest some values are optional nuclear@0: // and we need to compute the others in any combination. nuclear@0: if (srcCamera->mAspect != 10e10f) nuclear@0: out->mAspect = srcCamera->mAspect; nuclear@0: nuclear@0: if (srcCamera->mHorFov != 10e10f) { nuclear@0: out->mHorizontalFOV = srcCamera->mHorFov; nuclear@0: nuclear@0: if (srcCamera->mVerFov != 10e10f && srcCamera->mAspect == 10e10f) { nuclear@0: out->mAspect = tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) / nuclear@0: tan(AI_DEG_TO_RAD(srcCamera->mVerFov)); nuclear@0: } nuclear@0: } nuclear@0: else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) { nuclear@0: out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(atan(srcCamera->mAspect * nuclear@0: tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f))); nuclear@0: } nuclear@0: nuclear@0: // Collada uses degrees, we use radians nuclear@0: out->mHorizontalFOV = AI_DEG_TO_RAD(out->mHorizontalFOV); nuclear@0: nuclear@0: // add to camera list nuclear@0: mCameras.push_back(out); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Builds meshes for the given node and references them nuclear@0: void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) nuclear@0: { nuclear@0: // accumulated mesh references by this node nuclear@0: std::vector newMeshRefs; nuclear@0: newMeshRefs.reserve(pNode->mMeshes.size()); nuclear@0: nuclear@0: // add a mesh for each subgroup in each collada mesh nuclear@0: BOOST_FOREACH( const Collada::MeshInstance& mid, pNode->mMeshes) nuclear@0: { nuclear@0: const Collada::Mesh* srcMesh = NULL; nuclear@0: const Collada::Controller* srcController = NULL; nuclear@0: nuclear@0: // find the referred mesh nuclear@0: ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find( mid.mMeshOrController); nuclear@0: if( srcMeshIt == pParser.mMeshLibrary.end()) nuclear@0: { nuclear@0: // if not found in the mesh-library, it might also be a controller referring to a mesh nuclear@0: ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find( mid.mMeshOrController); nuclear@0: if( srcContrIt != pParser.mControllerLibrary.end()) nuclear@0: { nuclear@0: srcController = &srcContrIt->second; nuclear@0: srcMeshIt = pParser.mMeshLibrary.find( srcController->mMeshId); nuclear@0: if( srcMeshIt != pParser.mMeshLibrary.end()) nuclear@0: srcMesh = srcMeshIt->second; nuclear@0: } nuclear@0: nuclear@0: if( !srcMesh) nuclear@0: { nuclear@0: DefaultLogger::get()->warn( boost::str( boost::format( "Collada: Unable to find geometry for ID \"%s\". Skipping.") % mid.mMeshOrController)); nuclear@0: continue; nuclear@0: } nuclear@0: } else nuclear@0: { nuclear@0: // ID found in the mesh library -> direct reference to an unskinned mesh nuclear@0: srcMesh = srcMeshIt->second; nuclear@0: } nuclear@0: nuclear@0: // build a mesh for each of its subgroups nuclear@0: size_t vertexStart = 0, faceStart = 0; nuclear@0: for( size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) nuclear@0: { nuclear@0: const Collada::SubMesh& submesh = srcMesh->mSubMeshes[sm]; nuclear@0: if( submesh.mNumFaces == 0) nuclear@0: continue; nuclear@0: nuclear@0: // find material assigned to this submesh nuclear@0: std::string meshMaterial; nuclear@0: std::map::const_iterator meshMatIt = mid.mMaterials.find( submesh.mMaterial); nuclear@0: nuclear@0: const Collada::SemanticMappingTable* table = NULL; nuclear@0: if( meshMatIt != mid.mMaterials.end()) nuclear@0: { nuclear@0: table = &meshMatIt->second; nuclear@0: meshMaterial = table->mMatName; nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: DefaultLogger::get()->warn( boost::str( boost::format( "Collada: No material specified for subgroup <%s> in geometry <%s>.") % submesh.mMaterial % mid.mMeshOrController)); nuclear@0: if( !mid.mMaterials.empty() ) nuclear@0: meshMaterial = mid.mMaterials.begin()->second.mMatName; nuclear@0: } nuclear@0: nuclear@0: // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table nuclear@0: // given. The only mapping stuff which we do actually support is the UV channel. nuclear@0: std::map::const_iterator matIt = mMaterialIndexByName.find( meshMaterial); nuclear@0: unsigned int matIdx; nuclear@0: if( matIt != mMaterialIndexByName.end()) nuclear@0: matIdx = matIt->second; nuclear@0: else nuclear@0: matIdx = 0; nuclear@0: nuclear@0: if (table && !table->mMap.empty() ) { nuclear@0: std::pair& mat = newMats[matIdx]; nuclear@0: nuclear@0: // Iterate through all texture channels assigned to the effect and nuclear@0: // check whether we have mapping information for it. nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table); nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table); nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table); nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table); nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent,*table); nuclear@0: ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table); nuclear@0: } nuclear@0: nuclear@0: // built lookup index of the Mesh-Submesh-Material combination nuclear@0: ColladaMeshIndex index( mid.mMeshOrController, sm, meshMaterial); nuclear@0: nuclear@0: // if we already have the mesh at the library, just add its index to the node's array nuclear@0: std::map::const_iterator dstMeshIt = mMeshIndexByID.find( index); nuclear@0: if( dstMeshIt != mMeshIndexByID.end()) { nuclear@0: newMeshRefs.push_back( dstMeshIt->second); nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: // else we have to add the mesh to the collection and store its newly assigned index at the node nuclear@0: aiMesh* dstMesh = CreateMesh( pParser, srcMesh, submesh, srcController, vertexStart, faceStart); nuclear@0: nuclear@0: // store the mesh, and store its new index in the node nuclear@0: newMeshRefs.push_back( mMeshes.size()); nuclear@0: mMeshIndexByID[index] = mMeshes.size(); nuclear@0: mMeshes.push_back( dstMesh); nuclear@0: vertexStart += dstMesh->mNumVertices; faceStart += submesh.mNumFaces; nuclear@0: nuclear@0: // assign the material index nuclear@0: dstMesh->mMaterialIndex = matIdx; nuclear@0: dstMesh->mName = mid.mMeshOrController; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // now place all mesh references we gathered in the target node nuclear@0: pTarget->mNumMeshes = newMeshRefs.size(); nuclear@0: if( newMeshRefs.size()) nuclear@0: { nuclear@0: pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes]; nuclear@0: std::copy( newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh nuclear@0: aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, nuclear@0: const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace) nuclear@0: { nuclear@0: aiMesh* dstMesh = new aiMesh; nuclear@0: nuclear@0: // count the vertices addressed by its faces nuclear@0: const size_t numVertices = std::accumulate( pSrcMesh->mFaceSize.begin() + pStartFace, nuclear@0: pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, 0); nuclear@0: nuclear@0: // copy positions nuclear@0: dstMesh->mNumVertices = numVertices; nuclear@0: dstMesh->mVertices = new aiVector3D[numVertices]; nuclear@0: std::copy( pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + nuclear@0: pStartVertex + numVertices, dstMesh->mVertices); nuclear@0: nuclear@0: // normals, if given. HACK: (thom) Due to the glorious Collada spec we never nuclear@0: // know if we have the same number of normals as there are positions. So we nuclear@0: // also ignore any vertex attribute if it has a different count nuclear@0: if( pSrcMesh->mNormals.size() >= pStartVertex + numVertices) nuclear@0: { nuclear@0: dstMesh->mNormals = new aiVector3D[numVertices]; nuclear@0: std::copy( pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + nuclear@0: pStartVertex + numVertices, dstMesh->mNormals); nuclear@0: } nuclear@0: nuclear@0: // tangents, if given. nuclear@0: if( pSrcMesh->mTangents.size() >= pStartVertex + numVertices) nuclear@0: { nuclear@0: dstMesh->mTangents = new aiVector3D[numVertices]; nuclear@0: std::copy( pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + nuclear@0: pStartVertex + numVertices, dstMesh->mTangents); nuclear@0: } nuclear@0: nuclear@0: // bitangents, if given. nuclear@0: if( pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) nuclear@0: { nuclear@0: dstMesh->mBitangents = new aiVector3D[numVertices]; nuclear@0: std::copy( pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + nuclear@0: pStartVertex + numVertices, dstMesh->mBitangents); nuclear@0: } nuclear@0: nuclear@0: // same for texturecoords, as many as we have nuclear@0: // empty slots are not allowed, need to pack and adjust UV indexes accordingly nuclear@0: for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++) nuclear@0: { nuclear@0: if( pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) nuclear@0: { nuclear@0: dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; nuclear@0: for( size_t b = 0; b < numVertices; ++b) nuclear@0: dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex+b]; nuclear@0: nuclear@0: dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; nuclear@0: ++real; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // same for vertex colors, as many as we have. again the same packing to avoid empty slots nuclear@0: for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++) nuclear@0: { nuclear@0: if( pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) nuclear@0: { nuclear@0: dstMesh->mColors[real] = new aiColor4D[numVertices]; nuclear@0: std::copy( pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices,dstMesh->mColors[real]); nuclear@0: ++real; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // create faces. Due to the fact that each face uses unique vertices, we can simply count up on each vertex nuclear@0: size_t vertex = 0; nuclear@0: dstMesh->mNumFaces = pSubMesh.mNumFaces; nuclear@0: dstMesh->mFaces = new aiFace[dstMesh->mNumFaces]; nuclear@0: for( size_t a = 0; a < dstMesh->mNumFaces; ++a) nuclear@0: { nuclear@0: size_t s = pSrcMesh->mFaceSize[ pStartFace + a]; nuclear@0: aiFace& face = dstMesh->mFaces[a]; nuclear@0: face.mNumIndices = s; nuclear@0: face.mIndices = new unsigned int[s]; nuclear@0: for( size_t b = 0; b < s; ++b) nuclear@0: face.mIndices[b] = vertex++; nuclear@0: } nuclear@0: nuclear@0: // create bones if given nuclear@0: if( pSrcController) nuclear@0: { nuclear@0: // refuse if the vertex count does not match nuclear@0: // if( pSrcController->mWeightCounts.size() != dstMesh->mNumVertices) nuclear@0: // throw DeadlyImportError( "Joint Controller vertex count does not match mesh vertex count"); nuclear@0: nuclear@0: // resolve references - joint names nuclear@0: const Collada::Accessor& jointNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointNameSource); nuclear@0: const Collada::Data& jointNames = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointNamesAcc.mSource); nuclear@0: // joint offset matrices nuclear@0: const Collada::Accessor& jointMatrixAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource); nuclear@0: const Collada::Data& jointMatrices = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointMatrixAcc.mSource); nuclear@0: // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider nuclear@0: const Collada::Accessor& weightNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor); nuclear@0: if( &weightNamesAcc != &jointNamesAcc) nuclear@0: throw DeadlyImportError( "Temporary implementational lazyness. If you read this, please report to the author."); nuclear@0: // vertex weights nuclear@0: const Collada::Accessor& weightsAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor); nuclear@0: const Collada::Data& weights = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightsAcc.mSource); nuclear@0: nuclear@0: if( !jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) nuclear@0: throw DeadlyImportError( "Data type mismatch while resolving mesh joints"); nuclear@0: // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex nuclear@0: if( pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) nuclear@0: throw DeadlyImportError( "Unsupported vertex_weight addressing scheme. "); nuclear@0: nuclear@0: // create containers to collect the weights for each bone nuclear@0: size_t numBones = jointNames.mStrings.size(); nuclear@0: std::vector > dstBones( numBones); nuclear@0: nuclear@0: // build a temporary array of pointers to the start of each vertex's weights nuclear@0: typedef std::vector< std::pair > IndexPairVector; nuclear@0: std::vector weightStartPerVertex; nuclear@0: weightStartPerVertex.resize(pSrcController->mWeightCounts.size(),pSrcController->mWeights.end()); nuclear@0: nuclear@0: IndexPairVector::const_iterator pit = pSrcController->mWeights.begin(); nuclear@0: for( size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) nuclear@0: { nuclear@0: weightStartPerVertex[a] = pit; nuclear@0: pit += pSrcController->mWeightCounts[a]; nuclear@0: } nuclear@0: nuclear@0: // now for each vertex put the corresponding vertex weights into each bone's weight collection nuclear@0: for( size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) nuclear@0: { nuclear@0: // which position index was responsible for this vertex? that's also the index by which nuclear@0: // the controller assigns the vertex weights nuclear@0: size_t orgIndex = pSrcMesh->mFacePosIndices[a]; nuclear@0: // find the vertex weights for this vertex nuclear@0: IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex]; nuclear@0: size_t pairCount = pSrcController->mWeightCounts[orgIndex]; nuclear@0: nuclear@0: for( size_t b = 0; b < pairCount; ++b, ++iit) nuclear@0: { nuclear@0: size_t jointIndex = iit->first; nuclear@0: size_t vertexIndex = iit->second; nuclear@0: nuclear@0: float weight = ReadFloat( weightsAcc, weights, vertexIndex, 0); nuclear@0: nuclear@0: // one day I gonna kill that XSI Collada exporter nuclear@0: if( weight > 0.0f) nuclear@0: { nuclear@0: aiVertexWeight w; nuclear@0: w.mVertexId = a - pStartVertex; nuclear@0: w.mWeight = weight; nuclear@0: dstBones[jointIndex].push_back( w); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // count the number of bones which influence vertices of the current submesh nuclear@0: size_t numRemainingBones = 0; nuclear@0: for( std::vector >::const_iterator it = dstBones.begin(); it != dstBones.end(); ++it) nuclear@0: if( it->size() > 0) nuclear@0: numRemainingBones++; nuclear@0: nuclear@0: // create bone array and copy bone weights one by one nuclear@0: dstMesh->mNumBones = numRemainingBones; nuclear@0: dstMesh->mBones = new aiBone*[numRemainingBones]; nuclear@0: size_t boneCount = 0; nuclear@0: for( size_t a = 0; a < numBones; ++a) nuclear@0: { nuclear@0: // omit bones without weights nuclear@0: if( dstBones[a].size() == 0) nuclear@0: continue; nuclear@0: nuclear@0: // create bone with its weights nuclear@0: aiBone* bone = new aiBone; nuclear@0: bone->mName = ReadString( jointNamesAcc, jointNames, a); nuclear@0: bone->mOffsetMatrix.a1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 0); nuclear@0: bone->mOffsetMatrix.a2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 1); nuclear@0: bone->mOffsetMatrix.a3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 2); nuclear@0: bone->mOffsetMatrix.a4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 3); nuclear@0: bone->mOffsetMatrix.b1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 4); nuclear@0: bone->mOffsetMatrix.b2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 5); nuclear@0: bone->mOffsetMatrix.b3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 6); nuclear@0: bone->mOffsetMatrix.b4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 7); nuclear@0: bone->mOffsetMatrix.c1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 8); nuclear@0: bone->mOffsetMatrix.c2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 9); nuclear@0: bone->mOffsetMatrix.c3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 10); nuclear@0: bone->mOffsetMatrix.c4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 11); nuclear@0: bone->mNumWeights = dstBones[a].size(); nuclear@0: bone->mWeights = new aiVertexWeight[bone->mNumWeights]; nuclear@0: std::copy( dstBones[a].begin(), dstBones[a].end(), bone->mWeights); nuclear@0: nuclear@0: // apply bind shape matrix to offset matrix nuclear@0: aiMatrix4x4 bindShapeMatrix; nuclear@0: bindShapeMatrix.a1 = pSrcController->mBindShapeMatrix[0]; nuclear@0: bindShapeMatrix.a2 = pSrcController->mBindShapeMatrix[1]; nuclear@0: bindShapeMatrix.a3 = pSrcController->mBindShapeMatrix[2]; nuclear@0: bindShapeMatrix.a4 = pSrcController->mBindShapeMatrix[3]; nuclear@0: bindShapeMatrix.b1 = pSrcController->mBindShapeMatrix[4]; nuclear@0: bindShapeMatrix.b2 = pSrcController->mBindShapeMatrix[5]; nuclear@0: bindShapeMatrix.b3 = pSrcController->mBindShapeMatrix[6]; nuclear@0: bindShapeMatrix.b4 = pSrcController->mBindShapeMatrix[7]; nuclear@0: bindShapeMatrix.c1 = pSrcController->mBindShapeMatrix[8]; nuclear@0: bindShapeMatrix.c2 = pSrcController->mBindShapeMatrix[9]; nuclear@0: bindShapeMatrix.c3 = pSrcController->mBindShapeMatrix[10]; nuclear@0: bindShapeMatrix.c4 = pSrcController->mBindShapeMatrix[11]; nuclear@0: bindShapeMatrix.d1 = pSrcController->mBindShapeMatrix[12]; nuclear@0: bindShapeMatrix.d2 = pSrcController->mBindShapeMatrix[13]; nuclear@0: bindShapeMatrix.d3 = pSrcController->mBindShapeMatrix[14]; nuclear@0: bindShapeMatrix.d4 = pSrcController->mBindShapeMatrix[15]; nuclear@0: bone->mOffsetMatrix *= bindShapeMatrix; nuclear@0: nuclear@0: // HACK: (thom) Some exporters address the bone nodes by SID, others address them by ID or even name. nuclear@0: // Therefore I added a little name replacement here: I search for the bone's node by either name, ID or SID, nuclear@0: // and replace the bone's name by the node's name so that the user can use the standard nuclear@0: // find-by-name method to associate nodes with bones. nuclear@0: const Collada::Node* bnode = FindNode( pParser.mRootNode, bone->mName.data); nuclear@0: if( !bnode) nuclear@0: bnode = FindNodeBySID( pParser.mRootNode, bone->mName.data); nuclear@0: nuclear@0: // assign the name that we would have assigned for the source node nuclear@0: if( bnode) nuclear@0: bone->mName.Set( FindNameForNode( bnode)); nuclear@0: else nuclear@0: DefaultLogger::get()->warn( boost::str( boost::format( "ColladaLoader::CreateMesh(): could not find corresponding node for joint \"%s\".") % bone->mName.data)); nuclear@0: nuclear@0: // and insert bone nuclear@0: dstMesh->mBones[boneCount++] = bone; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: return dstMesh; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all meshes in the given scene nuclear@0: void ColladaLoader::StoreSceneMeshes( aiScene* pScene) nuclear@0: { nuclear@0: pScene->mNumMeshes = mMeshes.size(); nuclear@0: if( mMeshes.size() > 0) nuclear@0: { nuclear@0: pScene->mMeshes = new aiMesh*[mMeshes.size()]; nuclear@0: std::copy( mMeshes.begin(), mMeshes.end(), pScene->mMeshes); nuclear@0: mMeshes.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all cameras in the given scene nuclear@0: void ColladaLoader::StoreSceneCameras( aiScene* pScene) nuclear@0: { nuclear@0: pScene->mNumCameras = mCameras.size(); nuclear@0: if( mCameras.size() > 0) nuclear@0: { nuclear@0: pScene->mCameras = new aiCamera*[mCameras.size()]; nuclear@0: std::copy( mCameras.begin(), mCameras.end(), pScene->mCameras); nuclear@0: mCameras.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all lights in the given scene nuclear@0: void ColladaLoader::StoreSceneLights( aiScene* pScene) nuclear@0: { nuclear@0: pScene->mNumLights = mLights.size(); nuclear@0: if( mLights.size() > 0) nuclear@0: { nuclear@0: pScene->mLights = new aiLight*[mLights.size()]; nuclear@0: std::copy( mLights.begin(), mLights.end(), pScene->mLights); nuclear@0: mLights.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all textures in the given scene nuclear@0: void ColladaLoader::StoreSceneTextures( aiScene* pScene) nuclear@0: { nuclear@0: pScene->mNumTextures = mTextures.size(); nuclear@0: if( mTextures.size() > 0) nuclear@0: { nuclear@0: pScene->mTextures = new aiTexture*[mTextures.size()]; nuclear@0: std::copy( mTextures.begin(), mTextures.end(), pScene->mTextures); nuclear@0: mTextures.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all materials in the given scene nuclear@0: void ColladaLoader::StoreSceneMaterials( aiScene* pScene) nuclear@0: { nuclear@0: pScene->mNumMaterials = newMats.size(); nuclear@0: nuclear@0: if (newMats.size() > 0) { nuclear@0: pScene->mMaterials = new aiMaterial*[newMats.size()]; nuclear@0: for (unsigned int i = 0; i < newMats.size();++i) nuclear@0: pScene->mMaterials[i] = newMats[i].second; nuclear@0: nuclear@0: newMats.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Stores all animations nuclear@0: void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser) nuclear@0: { nuclear@0: // recursivly collect all animations from the collada scene nuclear@0: StoreAnimations( pScene, pParser, &pParser.mAnims, ""); nuclear@0: nuclear@0: // catch special case: many animations with the same length, each affecting only a single node. nuclear@0: // we need to unite all those single-node-anims to a proper combined animation nuclear@0: for( size_t a = 0; a < mAnims.size(); ++a) nuclear@0: { nuclear@0: aiAnimation* templateAnim = mAnims[a]; nuclear@0: if( templateAnim->mNumChannels == 1) nuclear@0: { nuclear@0: // search for other single-channel-anims with the same duration nuclear@0: std::vector collectedAnimIndices; nuclear@0: for( size_t b = a+1; b < mAnims.size(); ++b) nuclear@0: { nuclear@0: aiAnimation* other = mAnims[b]; nuclear@0: if( other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && other->mTicksPerSecond == templateAnim->mTicksPerSecond ) nuclear@0: collectedAnimIndices.push_back( b); nuclear@0: } nuclear@0: nuclear@0: // if there are other animations which fit the template anim, combine all channels into a single anim nuclear@0: if( !collectedAnimIndices.empty() ) nuclear@0: { nuclear@0: aiAnimation* combinedAnim = new aiAnimation(); nuclear@0: combinedAnim->mName = aiString( std::string( "combinedAnim_") + char( '0' + a)); nuclear@0: combinedAnim->mDuration = templateAnim->mDuration; nuclear@0: combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond; nuclear@0: combinedAnim->mNumChannels = collectedAnimIndices.size() + 1; nuclear@0: combinedAnim->mChannels = new aiNodeAnim*[combinedAnim->mNumChannels]; nuclear@0: // add the template anim as first channel by moving its aiNodeAnim to the combined animation nuclear@0: combinedAnim->mChannels[0] = templateAnim->mChannels[0]; nuclear@0: templateAnim->mChannels[0] = NULL; nuclear@0: delete templateAnim; nuclear@0: // combined animation replaces template animation in the anim array nuclear@0: mAnims[a] = combinedAnim; nuclear@0: nuclear@0: // move the memory of all other anims to the combined anim and erase them from the source anims nuclear@0: for( size_t b = 0; b < collectedAnimIndices.size(); ++b) nuclear@0: { nuclear@0: aiAnimation* srcAnimation = mAnims[collectedAnimIndices[b]]; nuclear@0: combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0]; nuclear@0: srcAnimation->mChannels[0] = NULL; nuclear@0: delete srcAnimation; nuclear@0: } nuclear@0: nuclear@0: // in a second go, delete all the single-channel-anims that we've stripped from their channels nuclear@0: // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one nuclear@0: while( !collectedAnimIndices.empty() ) nuclear@0: { nuclear@0: mAnims.erase( mAnims.begin() + collectedAnimIndices.back()); nuclear@0: collectedAnimIndices.pop_back(); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // now store all anims in the scene nuclear@0: if( !mAnims.empty()) nuclear@0: { nuclear@0: pScene->mNumAnimations = mAnims.size(); nuclear@0: pScene->mAnimations = new aiAnimation*[mAnims.size()]; nuclear@0: std::copy( mAnims.begin(), mAnims.end(), pScene->mAnimations); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructs the animations for the given source anim nuclear@0: void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string pPrefix) nuclear@0: { nuclear@0: std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName; nuclear@0: nuclear@0: // create nested animations, if given nuclear@0: for( std::vector::const_iterator it = pSrcAnim->mSubAnims.begin(); it != pSrcAnim->mSubAnims.end(); ++it) nuclear@0: StoreAnimations( pScene, pParser, *it, animName); nuclear@0: nuclear@0: // create animation channels, if any nuclear@0: if( !pSrcAnim->mChannels.empty()) nuclear@0: CreateAnimation( pScene, pParser, pSrcAnim, animName); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructs the animation for the given source anim nuclear@0: void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName) nuclear@0: { nuclear@0: // collect a list of animatable nodes nuclear@0: std::vector nodes; nuclear@0: CollectNodes( pScene->mRootNode, nodes); nuclear@0: nuclear@0: std::vector anims; nuclear@0: for( std::vector::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) nuclear@0: { nuclear@0: // find all the collada anim channels which refer to the current node nuclear@0: std::vector entries; nuclear@0: std::string nodeName = (*nit)->mName.data; nuclear@0: nuclear@0: // find the collada node corresponding to the aiNode nuclear@0: const Collada::Node* srcNode = FindNode( pParser.mRootNode, nodeName); nuclear@0: // ai_assert( srcNode != NULL); nuclear@0: if( !srcNode) nuclear@0: continue; nuclear@0: nuclear@0: // now check all channels if they affect the current node nuclear@0: for( std::vector::const_iterator cit = pSrcAnim->mChannels.begin(); nuclear@0: cit != pSrcAnim->mChannels.end(); ++cit) nuclear@0: { nuclear@0: const Collada::AnimationChannel& srcChannel = *cit; nuclear@0: Collada::ChannelEntry entry; nuclear@0: nuclear@0: // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others nuclear@0: // find the slash that separates the node name - there should be only one nuclear@0: std::string::size_type slashPos = srcChannel.mTarget.find( '/'); nuclear@0: if( slashPos == std::string::npos) nuclear@0: continue; nuclear@0: if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos) nuclear@0: continue; nuclear@0: std::string targetID = srcChannel.mTarget.substr( 0, slashPos); nuclear@0: if( targetID != srcNode->mID) nuclear@0: continue; nuclear@0: nuclear@0: // find the dot that separates the transformID - there should be only one or zero nuclear@0: std::string::size_type dotPos = srcChannel.mTarget.find( '.'); nuclear@0: if( dotPos != std::string::npos) nuclear@0: { nuclear@0: if( srcChannel.mTarget.find( '.', dotPos+1) != std::string::npos) nuclear@0: continue; nuclear@0: nuclear@0: entry.mTransformId = srcChannel.mTarget.substr( slashPos+1, dotPos - slashPos - 1); nuclear@0: nuclear@0: std::string subElement = srcChannel.mTarget.substr( dotPos+1); nuclear@0: if( subElement == "ANGLE") nuclear@0: entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle nuclear@0: else if( subElement == "X") nuclear@0: entry.mSubElement = 0; nuclear@0: else if( subElement == "Y") nuclear@0: entry.mSubElement = 1; nuclear@0: else if( subElement == "Z") nuclear@0: entry.mSubElement = 2; nuclear@0: else nuclear@0: DefaultLogger::get()->warn( boost::str( boost::format( "Unknown anim subelement <%s>. Ignoring") % subElement)); nuclear@0: } else nuclear@0: { nuclear@0: // no subelement following, transformId is remaining string nuclear@0: entry.mTransformId = srcChannel.mTarget.substr( slashPos+1); nuclear@0: } nuclear@0: nuclear@0: // determine which transform step is affected by this channel nuclear@0: entry.mTransformIndex = SIZE_MAX; nuclear@0: for( size_t a = 0; a < srcNode->mTransforms.size(); ++a) nuclear@0: if( srcNode->mTransforms[a].mID == entry.mTransformId) nuclear@0: entry.mTransformIndex = a; nuclear@0: nuclear@0: if( entry.mTransformIndex == SIZE_MAX) { nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: entry.mChannel = &(*cit); nuclear@0: entries.push_back( entry); nuclear@0: } nuclear@0: nuclear@0: // if there's no channel affecting the current node, we skip it nuclear@0: if( entries.empty()) nuclear@0: continue; nuclear@0: nuclear@0: // resolve the data pointers for all anim channels. Find the minimum time while we're at it nuclear@0: float startTime = 1e20f, endTime = -1e20f; nuclear@0: for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) nuclear@0: { nuclear@0: Collada::ChannelEntry& e = *it; nuclear@0: e.mTimeAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceTimes); nuclear@0: e.mTimeData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mTimeAccessor->mSource); nuclear@0: e.mValueAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceValues); nuclear@0: e.mValueData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mValueAccessor->mSource); nuclear@0: nuclear@0: // time count and value count must match nuclear@0: if( e.mTimeAccessor->mCount != e.mValueAccessor->mCount) nuclear@0: throw DeadlyImportError( boost::str( boost::format( "Time count / value count mismatch in animation channel \"%s\".") % e.mChannel->mTarget)); nuclear@0: nuclear@0: if( e.mTimeAccessor->mCount > 0 ) nuclear@0: { nuclear@0: // find bounding times nuclear@0: startTime = std::min( startTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, 0, 0)); nuclear@0: endTime = std::max( endTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount-1, 0)); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: std::vector resultTrafos; nuclear@0: if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) nuclear@0: { nuclear@0: // create a local transformation chain of the node's transforms nuclear@0: std::vector transforms = srcNode->mTransforms; nuclear@0: nuclear@0: // now for every unique point in time, find or interpolate the key values for that time nuclear@0: // and apply them to the transform chain. Then the node's present transformation can be calculated. nuclear@0: float time = startTime; nuclear@0: while( 1) nuclear@0: { nuclear@0: for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) nuclear@0: { nuclear@0: Collada::ChannelEntry& e = *it; nuclear@0: nuclear@0: // find the keyframe behind the current point in time nuclear@0: size_t pos = 0; nuclear@0: float postTime = 0.f; nuclear@0: while( 1) nuclear@0: { nuclear@0: if( pos >= e.mTimeAccessor->mCount) nuclear@0: break; nuclear@0: postTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos, 0); nuclear@0: if( postTime >= time) nuclear@0: break; nuclear@0: ++pos; nuclear@0: } nuclear@0: nuclear@0: pos = std::min( pos, e.mTimeAccessor->mCount-1); nuclear@0: nuclear@0: // read values from there nuclear@0: float temp[16]; nuclear@0: for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) nuclear@0: temp[c] = ReadFloat( *e.mValueAccessor, *e.mValueData, pos, c); nuclear@0: nuclear@0: // if not exactly at the key time, interpolate with previous value set nuclear@0: if( postTime > time && pos > 0) nuclear@0: { nuclear@0: float preTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos-1, 0); nuclear@0: float factor = (time - postTime) / (preTime - postTime); nuclear@0: nuclear@0: for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) nuclear@0: { nuclear@0: float v = ReadFloat( *e.mValueAccessor, *e.mValueData, pos-1, c); nuclear@0: temp[c] += (v - temp[c]) * factor; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // Apply values to current transformation nuclear@0: std::copy( temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement); nuclear@0: } nuclear@0: nuclear@0: // Calculate resulting transformation nuclear@0: aiMatrix4x4 mat = pParser.CalculateResultTransform( transforms); nuclear@0: nuclear@0: // out of lazyness: we store the time in matrix.d4 nuclear@0: mat.d4 = time; nuclear@0: resultTrafos.push_back( mat); nuclear@0: nuclear@0: // find next point in time to evaluate. That's the closest frame larger than the current in any channel nuclear@0: float nextTime = 1e20f; nuclear@0: for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) nuclear@0: { nuclear@0: Collada::ChannelEntry& e = *it; nuclear@0: nuclear@0: // find the next time value larger than the current nuclear@0: size_t pos = 0; nuclear@0: while( pos < e.mTimeAccessor->mCount) nuclear@0: { nuclear@0: float t = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos, 0); nuclear@0: if( t > time) nuclear@0: { nuclear@0: nextTime = std::min( nextTime, t); nuclear@0: break; nuclear@0: } nuclear@0: ++pos; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // no more keys on any channel after the current time -> we're done nuclear@0: if( nextTime > 1e19) nuclear@0: break; nuclear@0: nuclear@0: // else construct next keyframe at this following time point nuclear@0: time = nextTime; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // there should be some keyframes, but we aren't that fixated on valid input data nuclear@0: // ai_assert( resultTrafos.size() > 0); nuclear@0: nuclear@0: // build an animation channel for the given node out of these trafo keys nuclear@0: if( !resultTrafos.empty() ) nuclear@0: { nuclear@0: aiNodeAnim* dstAnim = new aiNodeAnim; nuclear@0: dstAnim->mNodeName = nodeName; nuclear@0: dstAnim->mNumPositionKeys = resultTrafos.size(); nuclear@0: dstAnim->mNumRotationKeys= resultTrafos.size(); nuclear@0: dstAnim->mNumScalingKeys = resultTrafos.size(); nuclear@0: dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; nuclear@0: dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; nuclear@0: dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; nuclear@0: nuclear@0: for( size_t a = 0; a < resultTrafos.size(); ++a) nuclear@0: { nuclear@0: aiMatrix4x4 mat = resultTrafos[a]; nuclear@0: double time = double( mat.d4); // remember? time is stored in mat.d4 nuclear@0: mat.d4 = 1.0f; nuclear@0: nuclear@0: dstAnim->mPositionKeys[a].mTime = time; nuclear@0: dstAnim->mRotationKeys[a].mTime = time; nuclear@0: dstAnim->mScalingKeys[a].mTime = time; nuclear@0: mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); nuclear@0: } nuclear@0: nuclear@0: anims.push_back( dstAnim); nuclear@0: } else nuclear@0: { nuclear@0: DefaultLogger::get()->warn( "Collada loader: found empty animation channel, ignored. Please check your exporter."); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if( !anims.empty()) nuclear@0: { nuclear@0: aiAnimation* anim = new aiAnimation; nuclear@0: anim->mName.Set( pName); nuclear@0: anim->mNumChannels = anims.size(); nuclear@0: anim->mChannels = new aiNodeAnim*[anims.size()]; nuclear@0: std::copy( anims.begin(), anims.end(), anim->mChannels); nuclear@0: anim->mDuration = 0.0f; nuclear@0: for( size_t a = 0; a < anims.size(); ++a) nuclear@0: { nuclear@0: anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime); nuclear@0: anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime); nuclear@0: anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime); nuclear@0: } nuclear@0: anim->mTicksPerSecond = 1; nuclear@0: mAnims.push_back( anim); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Add a texture to a material structure nuclear@0: void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, nuclear@0: const Collada::Effect& effect, nuclear@0: const Collada::Sampler& sampler, nuclear@0: aiTextureType type, unsigned int idx) nuclear@0: { nuclear@0: // first of all, basic file name nuclear@0: const aiString name = FindFilenameForEffectTexture( pParser, effect, sampler.mName ); nuclear@0: mat.AddProperty( &name, _AI_MATKEY_TEXTURE_BASE, type, idx ); nuclear@0: nuclear@0: // mapping mode nuclear@0: int map = aiTextureMapMode_Clamp; nuclear@0: if (sampler.mWrapU) nuclear@0: map = aiTextureMapMode_Wrap; nuclear@0: if (sampler.mWrapU && sampler.mMirrorU) nuclear@0: map = aiTextureMapMode_Mirror; nuclear@0: nuclear@0: mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx); nuclear@0: nuclear@0: map = aiTextureMapMode_Clamp; nuclear@0: if (sampler.mWrapV) nuclear@0: map = aiTextureMapMode_Wrap; nuclear@0: if (sampler.mWrapV && sampler.mMirrorV) nuclear@0: map = aiTextureMapMode_Mirror; nuclear@0: nuclear@0: mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx); nuclear@0: nuclear@0: // UV transformation nuclear@0: mat.AddProperty(&sampler.mTransform, 1, nuclear@0: _AI_MATKEY_UVTRANSFORM_BASE, type, idx); nuclear@0: nuclear@0: // Blend mode nuclear@0: mat.AddProperty((int*)&sampler.mOp , 1, nuclear@0: _AI_MATKEY_TEXBLEND_BASE, type, idx); nuclear@0: nuclear@0: // Blend factor nuclear@0: mat.AddProperty((float*)&sampler.mWeighting , 1, nuclear@0: _AI_MATKEY_TEXBLEND_BASE, type, idx); nuclear@0: nuclear@0: // UV source index ... if we didn't resolve the mapping, it is actually just nuclear@0: // a guess but it works in most cases. We search for the frst occurence of a nuclear@0: // number in the channel name. We assume it is the zero-based index into the nuclear@0: // UV channel array of all corresponding meshes. It could also be one-based nuclear@0: // for some exporters, but we won't care of it unless someone complains about. nuclear@0: if (sampler.mUVId != UINT_MAX) nuclear@0: map = sampler.mUVId; nuclear@0: else { nuclear@0: map = -1; nuclear@0: for (std::string::const_iterator it = sampler.mUVChannel.begin();it != sampler.mUVChannel.end(); ++it){ nuclear@0: if (IsNumeric(*it)) { nuclear@0: map = strtoul10(&(*it)); nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: if (-1 == map) { nuclear@0: DefaultLogger::get()->warn("Collada: unable to determine UV channel for texture"); nuclear@0: map = 0; nuclear@0: } nuclear@0: } nuclear@0: mat.AddProperty(&map,1,_AI_MATKEY_UVWSRC_BASE,type,idx); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Fills materials from the collada material definitions nuclear@0: void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pScene*/) nuclear@0: { nuclear@0: for (std::vector >::iterator it = newMats.begin(), nuclear@0: end = newMats.end(); it != end; ++it) nuclear@0: { nuclear@0: aiMaterial& mat = (aiMaterial&)*it->second; nuclear@0: Collada::Effect& effect = *it->first; nuclear@0: nuclear@0: // resolve shading mode nuclear@0: int shadeMode; nuclear@0: if (effect.mFaceted) /* fixme */ nuclear@0: shadeMode = aiShadingMode_Flat; nuclear@0: else { nuclear@0: switch( effect.mShadeType) nuclear@0: { nuclear@0: case Collada::Shade_Constant: nuclear@0: shadeMode = aiShadingMode_NoShading; nuclear@0: break; nuclear@0: case Collada::Shade_Lambert: nuclear@0: shadeMode = aiShadingMode_Gouraud; nuclear@0: break; nuclear@0: case Collada::Shade_Blinn: nuclear@0: shadeMode = aiShadingMode_Blinn; nuclear@0: break; nuclear@0: case Collada::Shade_Phong: nuclear@0: shadeMode = aiShadingMode_Phong; nuclear@0: break; nuclear@0: nuclear@0: default: nuclear@0: DefaultLogger::get()->warn("Collada: Unrecognized shading mode, using gouraud shading"); nuclear@0: shadeMode = aiShadingMode_Gouraud; nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: mat.AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); nuclear@0: nuclear@0: // double-sided? nuclear@0: shadeMode = effect.mDoubleSided; nuclear@0: mat.AddProperty( &shadeMode, 1, AI_MATKEY_TWOSIDED); nuclear@0: nuclear@0: // wireframe? nuclear@0: shadeMode = effect.mWireframe; nuclear@0: mat.AddProperty( &shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME); nuclear@0: nuclear@0: // add material colors nuclear@0: mat.AddProperty( &effect.mAmbient, 1,AI_MATKEY_COLOR_AMBIENT); nuclear@0: mat.AddProperty( &effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); nuclear@0: mat.AddProperty( &effect.mSpecular, 1,AI_MATKEY_COLOR_SPECULAR); nuclear@0: mat.AddProperty( &effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); nuclear@0: mat.AddProperty( &effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT); nuclear@0: mat.AddProperty( &effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE); nuclear@0: nuclear@0: // scalar properties nuclear@0: mat.AddProperty( &effect.mShininess, 1, AI_MATKEY_SHININESS); nuclear@0: mat.AddProperty( &effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY); nuclear@0: mat.AddProperty( &effect.mRefractIndex, 1, AI_MATKEY_REFRACTI); nuclear@0: nuclear@0: // transparency, a very hard one. seemingly not all files are following the nuclear@0: // specification here .. but we can trick. nuclear@0: if (effect.mTransparency >= 0.f && effect.mTransparency < 1.f) { nuclear@0: effect.mTransparency = 1.f- effect.mTransparency; nuclear@0: mat.AddProperty( &effect.mTransparency, 1, AI_MATKEY_OPACITY ); nuclear@0: mat.AddProperty( &effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT ); nuclear@0: } nuclear@0: nuclear@0: // add textures, if given nuclear@0: if( !effect.mTexAmbient.mName.empty()) nuclear@0: /* It is merely a lightmap */ nuclear@0: AddTexture( mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP); nuclear@0: nuclear@0: if( !effect.mTexEmissive.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE); nuclear@0: nuclear@0: if( !effect.mTexSpecular.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR); nuclear@0: nuclear@0: if( !effect.mTexDiffuse.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE); nuclear@0: nuclear@0: if( !effect.mTexBump.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS); nuclear@0: nuclear@0: if( !effect.mTexTransparent.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY); nuclear@0: nuclear@0: if( !effect.mTexReflective.mName.empty()) nuclear@0: AddTexture( mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructs materials from the collada material definitions nuclear@0: void ColladaLoader::BuildMaterials( ColladaParser& pParser, aiScene* /*pScene*/) nuclear@0: { nuclear@0: newMats.reserve(pParser.mMaterialLibrary.size()); nuclear@0: nuclear@0: for( ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); matIt != pParser.mMaterialLibrary.end(); ++matIt) nuclear@0: { nuclear@0: const Collada::Material& material = matIt->second; nuclear@0: // a material is only a reference to an effect nuclear@0: ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find( material.mEffect); nuclear@0: if( effIt == pParser.mEffectLibrary.end()) nuclear@0: continue; nuclear@0: Collada::Effect& effect = effIt->second; nuclear@0: nuclear@0: // create material nuclear@0: aiMaterial* mat = new aiMaterial; nuclear@0: aiString name( matIt->first); nuclear@0: mat->AddProperty(&name,AI_MATKEY_NAME); nuclear@0: nuclear@0: // store the material nuclear@0: mMaterialIndexByName[matIt->first] = newMats.size(); nuclear@0: newMats.push_back( std::pair( &effect,mat) ); nuclear@0: } nuclear@0: // ScenePreprocessor generates a default material automatically if none is there. nuclear@0: // All further code here in this loader works well without a valid material so nuclear@0: // we can safely let it to ScenePreprocessor. nuclear@0: #if 0 nuclear@0: if( newMats.size() == 0) nuclear@0: { nuclear@0: aiMaterial* mat = new aiMaterial; nuclear@0: aiString name( AI_DEFAULT_MATERIAL_NAME ); nuclear@0: mat->AddProperty( &name, AI_MATKEY_NAME); nuclear@0: nuclear@0: const int shadeMode = aiShadingMode_Phong; nuclear@0: mat->AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); nuclear@0: aiColor4D colAmbient( 0.2f, 0.2f, 0.2f, 1.0f), colDiffuse( 0.8f, 0.8f, 0.8f, 1.0f), colSpecular( 0.5f, 0.5f, 0.5f, 0.5f); nuclear@0: mat->AddProperty( &colAmbient, 1, AI_MATKEY_COLOR_AMBIENT); nuclear@0: mat->AddProperty( &colDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); nuclear@0: mat->AddProperty( &colSpecular, 1, AI_MATKEY_COLOR_SPECULAR); nuclear@0: const float specExp = 5.0f; nuclear@0: mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); nuclear@0: } nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Resolves the texture name for the given effect texture entry nuclear@0: aiString ColladaLoader::FindFilenameForEffectTexture( const ColladaParser& pParser, nuclear@0: const Collada::Effect& pEffect, const std::string& pName) nuclear@0: { nuclear@0: // recurse through the param references until we end up at an image nuclear@0: std::string name = pName; nuclear@0: while( 1) nuclear@0: { nuclear@0: // the given string is a param entry. Find it nuclear@0: Collada::Effect::ParamLibrary::const_iterator it = pEffect.mParams.find( name); nuclear@0: // if not found, we're at the end of the recursion. The resulting string should be the image ID nuclear@0: if( it == pEffect.mParams.end()) nuclear@0: break; nuclear@0: nuclear@0: // else recurse on nuclear@0: name = it->second.mReference; nuclear@0: } nuclear@0: nuclear@0: // find the image referred by this name in the image library of the scene nuclear@0: ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find( name); nuclear@0: if( imIt == pParser.mImageLibrary.end()) nuclear@0: { nuclear@0: throw DeadlyImportError( boost::str( boost::format( nuclear@0: "Collada: Unable to resolve effect texture entry \"%s\", ended up at ID \"%s\".") % pName % name)); nuclear@0: } nuclear@0: nuclear@0: aiString result; nuclear@0: nuclear@0: // if this is an embedded texture image setup an aiTexture for it nuclear@0: if (imIt->second.mFileName.empty()) nuclear@0: { nuclear@0: if (imIt->second.mImageData.empty()) { nuclear@0: throw DeadlyImportError("Collada: Invalid texture, no data or file reference given"); nuclear@0: } nuclear@0: nuclear@0: aiTexture* tex = new aiTexture(); nuclear@0: nuclear@0: // setup format hint nuclear@0: if (imIt->second.mEmbeddedFormat.length() > 3) { nuclear@0: DefaultLogger::get()->warn("Collada: texture format hint is too long, truncating to 3 characters"); nuclear@0: } nuclear@0: strncpy(tex->achFormatHint,imIt->second.mEmbeddedFormat.c_str(),3); nuclear@0: nuclear@0: // and copy texture data nuclear@0: tex->mHeight = 0; nuclear@0: tex->mWidth = imIt->second.mImageData.size(); nuclear@0: tex->pcData = (aiTexel*)new char[tex->mWidth]; nuclear@0: memcpy(tex->pcData,&imIt->second.mImageData[0],tex->mWidth); nuclear@0: nuclear@0: // setup texture reference string nuclear@0: result.data[0] = '*'; nuclear@0: result.length = 1 + ASSIMP_itoa10(result.data+1,MAXLEN-1,mTextures.size()); nuclear@0: nuclear@0: // and add this texture to the list nuclear@0: mTextures.push_back(tex); nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: result.Set( imIt->second.mFileName ); nuclear@0: ConvertPath(result); nuclear@0: } nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Convert a path read from a collada file to the usual representation nuclear@0: void ColladaLoader::ConvertPath (aiString& ss) nuclear@0: { nuclear@0: // TODO: collada spec, p 22. Handle URI correctly. nuclear@0: // For the moment we're just stripping the file:// away to make it work. nuclear@0: // Windoes doesn't seem to be able to find stuff like nuclear@0: // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' nuclear@0: if (0 == strncmp(ss.data,"file://",7)) nuclear@0: { nuclear@0: ss.length -= 7; nuclear@0: memmove(ss.data,ss.data+7,ss.length); nuclear@0: ss.data[ss.length] = '\0'; nuclear@0: } nuclear@0: nuclear@0: // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... nuclear@0: // I need to filter it without destroying linux paths starting with "/somewhere" nuclear@0: if( ss.data[0] == '/' && isalpha( ss.data[1]) && ss.data[2] == ':' ) nuclear@0: { nuclear@0: ss.length--; nuclear@0: memmove( ss.data, ss.data+1, ss.length); nuclear@0: ss.data[ss.length] = 0; nuclear@0: } nuclear@0: nuclear@0: // find and convert all %xy special chars nuclear@0: char* out = ss.data; nuclear@0: for( const char* it = ss.data; it != ss.data + ss.length; /**/ ) nuclear@0: { nuclear@0: if( *it == '%' && (it + 3) < ss.data + ss.length ) nuclear@0: { nuclear@0: // separate the number to avoid dragging in chars from behind into the parsing nuclear@0: char mychar[3] = { it[1], it[2], 0 }; nuclear@0: size_t nbr = strtoul16( mychar); nuclear@0: it += 3; nuclear@0: *out++ = (char)(nbr & 0xFF); nuclear@0: } else nuclear@0: { nuclear@0: *out++ = *it++; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // adjust length and terminator of the shortened string nuclear@0: *out = 0; nuclear@0: ss.length = (ptrdiff_t) (out - ss.data); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads a float value from an accessor and its data array. nuclear@0: float ColladaLoader::ReadFloat( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const nuclear@0: { nuclear@0: // FIXME: (thom) Test for data type here in every access? For the moment, I leave this to the caller nuclear@0: size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset; nuclear@0: ai_assert( pos < pData.mValues.size()); nuclear@0: return pData.mValues[pos]; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads a string value from an accessor and its data array. nuclear@0: const std::string& ColladaLoader::ReadString( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const nuclear@0: { nuclear@0: size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset; nuclear@0: ai_assert( pos < pData.mStrings.size()); nuclear@0: return pData.mStrings[pos]; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Collects all nodes into the given array nuclear@0: void ColladaLoader::CollectNodes( const aiNode* pNode, std::vector& poNodes) const nuclear@0: { nuclear@0: poNodes.push_back( pNode); nuclear@0: nuclear@0: for( size_t a = 0; a < pNode->mNumChildren; ++a) nuclear@0: CollectNodes( pNode->mChildren[a], poNodes); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Finds a node in the collada scene by the given name nuclear@0: const Collada::Node* ColladaLoader::FindNode( const Collada::Node* pNode, const std::string& pName) const nuclear@0: { nuclear@0: if( pNode->mName == pName || pNode->mID == pName) nuclear@0: return pNode; nuclear@0: nuclear@0: for( size_t a = 0; a < pNode->mChildren.size(); ++a) nuclear@0: { nuclear@0: const Collada::Node* node = FindNode( pNode->mChildren[a], pName); nuclear@0: if( node) nuclear@0: return node; nuclear@0: } nuclear@0: nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Finds a node in the collada scene by the given SID nuclear@0: const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const nuclear@0: { nuclear@0: if( pNode->mSID == pSID) nuclear@0: return pNode; nuclear@0: nuclear@0: for( size_t a = 0; a < pNode->mChildren.size(); ++a) nuclear@0: { nuclear@0: const Collada::Node* node = FindNodeBySID( pNode->mChildren[a], pSID); nuclear@0: if( node) nuclear@0: return node; nuclear@0: } nuclear@0: nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Finds a proper name for a node derived from the collada-node's properties nuclear@0: std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode) const nuclear@0: { nuclear@0: // now setup the name of the node. We take the name if not empty, otherwise the collada ID nuclear@0: // FIX: Workaround for XSI calling the instanced visual scene 'untitled' by default. nuclear@0: if (!pNode->mName.empty() && pNode->mName != "untitled") nuclear@0: return pNode->mName; nuclear@0: else if (!pNode->mID.empty()) nuclear@0: return pNode->mID; nuclear@0: else if (!pNode->mSID.empty()) nuclear@0: return pNode->mSID; nuclear@0: else nuclear@0: { nuclear@0: // No need to worry. Unnamed nodes are no problem at all, except nuclear@0: // if cameras or lights need to be assigned to them. nuclear@0: return boost::str( boost::format( "$ColladaAutoName$_%d") % clock()); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER