/*! Scans all meshes in the assimp scene and populates nameToBone and jointGroups */ void SLAssimpImporter::findJoints(const aiScene* scene) { for (SLuint i = 0; i < scene->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[i]; if(!mesh->HasBones()) continue; logMessage(LV_normal, " Mesh '%s' contains %d joints.\n", mesh->mName.C_Str(), mesh->mNumBones); for (SLuint j = 0; j < mesh->mNumBones; j++) { SLstring name = mesh->mBones[j]->mName.C_Str(); std::map<SLstring, SLMat4f>::iterator it = _jointOffsets.find(name); if(it != _jointOffsets.end()) continue; // add the offset matrix to our offset matrix map SLMat4f offsetMat; memcpy(&offsetMat, &mesh->mBones[j]->mOffsetMatrix, sizeof(SLMat4f)); offsetMat.transpose(); _jointOffsets[name] = offsetMat; logMessage(LV_detailed, " Bone '%s' found.\n", name.c_str()); } } }
//----------------------------------------------------------------------------- //! SLGLShader::createAndCompile creates & compiles the OpenGL shader object SLbool SLGLShader::createAndCompile() { // delete if object already exits if (_objectGL) glDeleteShader(_objectGL); if (_code!="") { switch (_type) { case VertexShader: _objectGL = glCreateShader(GL_VERTEX_SHADER); break; case FragmentShader: _objectGL = glCreateShader(GL_FRAGMENT_SHADER); break; default: SL_EXIT_MSG("SLGLShader::load: Unknown shader type."); } //SLstring verGLSL = SLGLState::getInstance()->glSLVersionNO(); //SLstring srcVersion = "#version " + verGLSL + "\n"; //if (verGLSL > "120") //{ if (_type == VertexShader) // { SLUtils::replaceString(_code, "attribute", "in"); // SLUtils::replaceString(_code, "varying", "out"); // } // if (_type == FragmentShader) // { SLUtils::replaceString(_code, "varying", "in"); // } //} //SLstring scrComplete = srcVersion + _code; SLstring scrComplete = _code; const char* src = scrComplete.c_str(); glShaderSource(_objectGL, 1, &src, 0); glCompileShader(_objectGL); // Check compiler log SLint compileSuccess = 0; glGetShaderiv(_objectGL, GL_COMPILE_STATUS, &compileSuccess); if (compileSuccess == GL_FALSE) { GLchar log[256]; glGetShaderInfoLog(_objectGL, sizeof(log), 0, &log[0]); SL_LOG("*** COMPILER ERROR ***\n"); SL_LOG("Source file: %s\n", _file.c_str()); SL_LOG("%s\n\n", log); return false; } return true; } else SL_WARN_MSG("SLGLShader::createAndCompile: Nothing to compile!"); return false; }
/*! SLFileSystem::fileExists returns true if the file exists. This code works only on windows because the file check is done case insensitive. */ SLbool SLFileSystem::fileExists(SLstring& pathfilename) { struct stat stFileInfo; if (stat(pathfilename.c_str(), &stFileInfo) == 0) return true; return false; }
//----------------------------------------------------------------------------- //! Scans the assimp scene graph structure and populates nameToNode void SLAssimpImporter::findNodes(aiNode* node, SLstring padding, SLbool lastChild) { SLstring name = node->mName.C_Str(); /* /// @todo we can't allow for duplicate node names, ever at the moment. The 'solution' below /// only hides the problem and moves it to a different part. // rename duplicate node names SLstring renamedString; if (_nodeMap.find(name) != _nodeMap.end()) { SLint index = 0; std::ostringstream ss; SLstring lastMatch = name; while (_nodeMap.find(lastMatch) != _nodeMap.end()) { ss.str(SLstring()); ss.clear(); ss << name << "_" << std::setw( 2 ) << std::setfill( '0' ) << index; lastMatch = ss.str(); index++; } ss.str(SLstring()); ss.clear(); ss << "(renamed from '" << name << "')"; renamedString = ss.str(); name = lastMatch; }*/ // this should not happen assert(_nodeMap.find(name) == _nodeMap.end() && "Duplicated node name found!"); _nodeMap[name] = node; //logMessage(LV_Detailed, "%s |\n", padding.c_str()); logMessage(LV_detailed, "%s |-[%s] (%d children, %d meshes)\n", padding.c_str(), name.c_str(), node->mNumChildren, node->mNumMeshes); if (lastChild) padding += " "; else padding += " |"; for (SLuint i = 0; i < node->mNumChildren; i++) { findNodes(node->mChildren[i], padding, (i == node->mNumChildren-1)); } }
//----------------------------------------------------------------------------- //! SLGLShader::load loads a shader file into string _shaderSource void SLGLShader::load(SLstring filename) { fstream shaderFile(filename.c_str(), ios::in); if (!shaderFile.is_open()) { SL_LOG("File open failed: %s\n", filename.c_str()); exit(1); } std::stringstream buffer; buffer << shaderFile.rdbuf(); // remove comments because some stupid ARM compiler can't handle GLSL comments #ifdef SL_OS_MACIOS _code = buffer.str(); #else _code = SLUtils::removeComments(buffer.str()); #endif }
/*! SLAssimpImporter::checkFilePath tries to build the full absolut texture file path. Some file formats have absolute path stored, some have relative paths. 1st attempt: modelPath + aiTexFile 2nd attempt: aiTexFile 3rd attempt: modelPath + getFileName(aiTexFile) If a model contains absolute path it is best to put all texture files beside the model file in the same folder. */ SLstring SLAssimpImporter::checkFilePath(SLstring modelPath, SLstring aiTexFile) { // Check path & file combination SLstring pathFile = modelPath + aiTexFile; if (SLFileSystem::fileExists(pathFile)) return pathFile; // Check file alone if (SLFileSystem::fileExists(aiTexFile)) return aiTexFile; // Check path & file combination pathFile = modelPath + SLUtils::getFileName(aiTexFile); if (SLFileSystem::fileExists(pathFile)) return pathFile; SLstring msg = "SLAssimpImporter: Texture file not found: \n" + aiTexFile + "\non model path: " + modelPath + "\n"; SL_WARN_MSG(msg.c_str()); // Return path for texture not found image; return SLGLTexture::defaultPath + "TexNotFound.png"; }
/*! SLAssimpImporter::loadAnimation loads the scene graph node tree recursively. */ SLAnimation* SLAssimpImporter::loadAnimation(aiAnimation* anim) { int animCount = 0; if (_skeleton) animCount = _skeleton->numAnimations(); ostringstream oss; oss << "unnamed_anim_" << animCount; SLstring animName = oss.str(); SLfloat animTicksPerSec = (anim->mTicksPerSecond == 0) ? 30.0f : (SLfloat)anim->mTicksPerSecond; SLfloat animDuration = (SLfloat)anim->mDuration / animTicksPerSec; if (anim->mName.length > 0) animName = anim->mName.C_Str(); // log logMessage(LV_minimal, "\nLoading animation %s\n", animName.c_str()); logMessage(LV_normal, " Duration(seconds): %f \n", animDuration); logMessage(LV_normal, " Duration(ticks): %f \n", anim->mDuration); logMessage(LV_normal, " Ticks per second: %f \n", animTicksPerSec); logMessage(LV_normal, " Num channels: %d\n", anim->mNumChannels); // exit if we didn't load a skeleton but have animations for one if (_skinnedMeshes.size() > 0) assert(_skeleton != nullptr && "The skeleton wasn't impoted correctly."); // create the animation SLAnimation* result; if (_skeleton) result = _skeleton->createAnimation(animName, animDuration); else { result = SLScene::current->animManager().createNodeAnimation(animName, animDuration); _nodeAnimations.push_back(result); } SLbool isSkeletonAnim = false; for (SLuint i = 0; i < anim->mNumChannels; i++) { aiNodeAnim* channel = anim->mChannels[i]; // find the node that is animated by this channel SLstring nodeName = channel->mNodeName.C_Str(); SLNode* affectedNode = _sceneRoot->find<SLNode>(nodeName); SLuint id = 0; SLbool isJointNode = (affectedNode == nullptr); // @todo: this is currently a work around but it can happen that we receive normal node animationtracks and joint animationtracks // we don't allow node animation tracks in a skeleton animation, so we should split an animation in two seperate // animations if this happens. for now we just ignore node animation tracks if we already have joint tracks // ofc this will crash if the first track is a node anim but its just temporary if (!isJointNode && isSkeletonAnim) continue; // is there a skeleton and is this animation channel not affecting a normal node? if (_skeletonRoot && !affectedNode) { isSkeletonAnim = true; SLJoint* affectedJoint = _skeleton->getJoint(nodeName); if (affectedJoint == nullptr) break; id = affectedJoint->id(); // @todo warn if we find an animation with some node channels and some joint channels // this shouldn't happen! /// @todo [high priority!] Address the problem of some bones not containing an animation channel /// when importing. Current workaround is to set their reset position to their bind pose. /// This will however fail if we have multiple animations affecting a single model and fading /// some of them out or in. This will require us to provide animations that have a channel /// for all bones even if they're just positional. // What does this next line do? // // The testimportfile we used (Astroboy.dae) has the following properties: // > It has joints in the skeleton that aren't animated by any channel. // > The joints need a reset position of (0, 0, 0) to work properly // because the joint position is contained in a single keyframe for every joint // // Since some of the joints don't have a channel that animates them, they also lack // the joint position that the other joints get from their animation channel. // So we need to set the initial state for all joints that have a channel // to identity. // All joints that arent in a channel will receive their local joint bind pose as // reset position. // // The problem stems from the design desicion to reset a whole skeleton before applying // animations to it. If we were to reset each joint just before applying a channel to it // we wouldn't have this problem. But we coulnd't blend animations as easily. // SLMat4f prevOM = affectedJoint->om(); affectedJoint->om(SLMat4f()); affectedJoint->setInitialState(); affectedJoint->om(prevOM); } // log logMessage(LV_normal, "\n Channel %d %s", i, (isJointNode) ? "(joint animation)\n" : "\n"); logMessage(LV_normal, " Affected node: %s\n", channel->mNodeName.C_Str()); logMessage(LV_detailed, " Num position keys: %d\n", channel->mNumPositionKeys); logMessage(LV_detailed, " Num rotation keys: %d\n", channel->mNumRotationKeys); logMessage(LV_detailed, " Num scaling keys: %d\n", channel->mNumScalingKeys); // joint animation channels should receive the correct node id, normal node animations just get 0 SLNodeAnimTrack* track = result->createNodeAnimationTrack(id); // this is a node animation only, so we add a reference to the affected node to the track if (affectedNode && !isSkeletonAnim) { track->animatedNode(affectedNode); } KeyframeMap keyframes; // add position keys for (SLuint i = 0; i < channel->mNumPositionKeys; i++) { SLfloat time = (SLfloat)channel->mPositionKeys[i].mTime; keyframes[time] = SLImportKeyframe(&channel->mPositionKeys[i], nullptr, nullptr); } // add rotation keys for (SLuint i = 0; i < channel->mNumRotationKeys; i++) { SLfloat time = (SLfloat)channel->mRotationKeys[i].mTime; if (keyframes.find(time) == keyframes.end()) keyframes[time] = SLImportKeyframe(nullptr, &channel->mRotationKeys[i], nullptr); else { // @todo this shouldn't abort but just throw an exception assert(keyframes[time].rotation == nullptr && "There were two rotation keys assigned to the same timestamp."); keyframes[time].rotation = &channel->mRotationKeys[i]; } } // add scaleing keys for (SLuint i = 0; i < channel->mNumScalingKeys; i++) { SLfloat time = (SLfloat)channel->mScalingKeys[i].mTime; if (keyframes.find(time) == keyframes.end()) keyframes[time] = SLImportKeyframe(nullptr, nullptr, &channel->mScalingKeys[i]); else { // @todo this shouldn't abort but just throw an exception assert(keyframes[time].scaling == nullptr && "There were two scaling keys assigned to the same timestamp."); keyframes[time].scaling = &channel->mScalingKeys[i]; } } logMessage(LV_normal, " Found %d distinct keyframe timestamp(s).\n", keyframes.size()); for (auto it : keyframes) { SLTransformKeyframe* kf = track->createNodeKeyframe(it.first); kf->translation(getTranslation(it.first, keyframes)); kf->rotation(getRotation(it.first, keyframes)); kf->scale(getScaling(it.first, keyframes)); // log logMessage(LV_detailed, "\n Generating keyframe at time '%.2f'\n", it.first); logMessage(LV_detailed, " Translation: (%.2f, %.2f, %.2f) %s\n", kf->translation().x, kf->translation().y, kf->translation().z, (it.second.translation != nullptr) ? "imported" : "generated"); logMessage(LV_detailed, " Rotation: (%.2f, %.2f, %.2f, %.2f) %s\n", kf->rotation().x(), kf->rotation().y(), kf->rotation().z(), kf->rotation().w(), (it.second.rotation != nullptr) ? "imported" : "generated"); logMessage(LV_detailed, " Scale: (%.2f, %.2f, %.2f) %s\n", kf->scale().x, kf->scale().y, kf->scale().z, (it.second.scaling != nullptr) ? "imported" : "generated"); } } return result; }
/*! SLAssimpImporter::loadMesh creates a new SLMesh an copies the meshs vertex data and triangle face indices. Normals & tangents are not loaded. They are calculated in SLMesh. */ SLMesh* SLAssimpImporter::loadMesh(aiMesh *mesh) { // Count first the NO. of triangles in the mesh SLuint numTriangles = 0; for(unsigned int i = 0; i < mesh->mNumFaces; ++i) if(mesh->mFaces[i].mNumIndices == 3) numTriangles++; // We only load meshes that contain triangles if (numTriangles==0 || mesh->mNumVertices==0) return nullptr; // create a new mesh. // The mesh pointer is added automatically to the SLScene::meshes vector. SLstring name = mesh->mName.data; SLMesh *m = new SLMesh(name.empty() ? "Imported Mesh" : name); // create position & normal vector m->P.clear(); m->P.resize(mesh->mNumVertices); // create normal vector if (mesh->HasNormals()) { m->N.clear(); m->N.resize(m->P.size()); } // allocate texCoord vector if needed if (mesh->HasTextureCoords(0)) { m->Tc.clear(); m->Tc.resize(m->P.size()); } // copy vertex positions & texCoord for(SLuint i = 0; i < m->P.size(); ++i) { m->P[i].set(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z); if (m->N.size()) m->N[i].set(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z); if (m->Tc.size()) m->Tc[i].set(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y); } // create face index vector if (m->P.size() < 65536) { m->I16.clear(); m->I16.resize(mesh->mNumFaces * 3); // load face triangle indices only SLuint j = 0; for(SLuint i = 0; i < mesh->mNumFaces; ++i) { if(mesh->mFaces[i].mNumIndices == 3) { m->I16[j++] = mesh->mFaces[i].mIndices[0]; m->I16[j++] = mesh->mFaces[i].mIndices[1]; m->I16[j++] = mesh->mFaces[i].mIndices[2]; } } } else { m->I32.clear(); m->I32.resize(mesh->mNumFaces * 3); // load face triangle indices only SLuint j = 0; for(SLuint i = 0; i < mesh->mNumFaces; ++i) { if(mesh->mFaces[i].mNumIndices == 3) { m->I32[j++] = mesh->mFaces[i].mIndices[0]; m->I32[j++] = mesh->mFaces[i].mIndices[1]; m->I32[j++] = mesh->mFaces[i].mIndices[2]; } } } if (!m->N.size()) m->calcNormals(); // load joints if (mesh->HasBones()) { _skinnedMeshes.push_back(m); m->skeleton(_skeleton); m->Ji.resize(m->P.size()); m->Jw.resize(m->P.size()); // make sure to initialize the weights with 0 vectors std::fill(m->Ji.begin(), m->Ji.end(), SLVec4f(0, 0, 0, 0)); std::fill(m->Jw.begin(), m->Jw.end(), SLVec4f(0, 0, 0, 0)); for (SLuint i = 0; i < mesh->mNumBones; i++) { aiBone* joint = mesh->mBones[i]; SLJoint* slJoint = _skeleton->getJoint(joint->mName.C_Str()); // @todo On OSX it happens from time to time that slJoint is nullptr if (slJoint) { SLuint jointId = slJoint->id(); for (SLuint j = 0; j < joint->mNumWeights; j++) { // add the weight SLuint vertId = joint->mWeights[j].mVertexId; SLfloat weight = joint->mWeights[j].mWeight; m->addWeight(vertId, jointId, weight); // check if the bones max radius changed // @todo this is very specific to this loaded mesh, // when we add a skeleton instances class this radius // calculation has to be done on the instance! slJoint->calcMaxRadius(SLVec3f(mesh->mVertices[vertId].x, mesh->mVertices[vertId].y, mesh->mVertices[vertId].z)); } } else { SL_LOG("Failed to load joint of skeleton in SLAssimpImporter::loadMesh: %s\n", joint->mName.C_Str()); return nullptr; } } } return m; }
/*! SLAssimpImporter::loadMaterial loads the AssImp material an returns the SLMaterial. The materials and textures are added to the SLScene material and texture vectors. */ SLMaterial* SLAssimpImporter::loadMaterial(SLint index, aiMaterial *material, SLstring modelPath) { // Get the materials name aiString matName; material->Get(AI_MATKEY_NAME, matName); SLstring name = matName.data; if (name.empty()) name = "Import Material"; // Create SLMaterial instance. It is also added to the SLScene::_materials vector SLMaterial* mat = new SLMaterial(name.c_str()); // set the texture types to import into our material const SLint textureCount = 4; aiTextureType textureTypes[textureCount]; textureTypes[0] = aiTextureType_DIFFUSE; textureTypes[1] = aiTextureType_NORMALS; textureTypes[2] = aiTextureType_SPECULAR; textureTypes[3] = aiTextureType_HEIGHT; // load all the textures for this material and add it to the material vector for(SLint i = 0; i < textureCount; ++i) { if(material->GetTextureCount(textureTypes[i]) > 0) { aiString aipath; material->GetTexture(textureTypes[i], 0, &aipath, nullptr, nullptr, nullptr, nullptr, nullptr); SLTextureType texType = textureTypes[i]==aiTextureType_DIFFUSE ? TT_color : textureTypes[i]==aiTextureType_NORMALS ? TT_normal : textureTypes[i]==aiTextureType_SPECULAR ? TT_gloss : textureTypes[i]==aiTextureType_HEIGHT ? TT_height : TT_unknown; SLstring texFile = checkFilePath(modelPath, aipath.data); SLGLTexture* tex = loadTexture(texFile, texType); mat->textures().push_back(tex); } } // get color data aiColor3D ambient, diffuse, specular, emissive; SLfloat shininess, refracti, reflectivity, opacity; material->Get(AI_MATKEY_COLOR_AMBIENT, ambient); material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse); material->Get(AI_MATKEY_COLOR_SPECULAR, specular); material->Get(AI_MATKEY_COLOR_EMISSIVE, emissive); material->Get(AI_MATKEY_SHININESS, shininess); material->Get(AI_MATKEY_REFRACTI, refracti); material->Get(AI_MATKEY_REFLECTIVITY, reflectivity); material->Get(AI_MATKEY_OPACITY, opacity); // increase shininess if specular color is not low. // The material will otherwise be to bright if (specular.r > 0.5f && specular.g > 0.5f && specular.b > 0.5f && shininess < 0.01f) shininess = 10.0f; // set color data mat->ambient(SLCol4f(ambient.r, ambient.g, ambient.b)); mat->diffuse(SLCol4f(diffuse.r, diffuse.g, diffuse.b)); mat->specular(SLCol4f(specular.r, specular.g, specular.b)); mat->emission(SLCol4f(emissive.r, emissive.g, emissive.b)); mat->shininess(shininess); //mat->kr(reflectivity); //mat->kt(1.0f-opacity); //mat->kn(refracti); return mat; }
/*! Loads the scene from a file and creates materials with textures, the meshes and the nodes for the scene graph. Materials, textures and meshes are added to the according vectors of SLScene for later deallocation. */ SLNode* SLAssimpImporter::load(SLstring file, //!< File with path or on default path SLbool loadMeshesOnly, //!< Only load nodes with meshes SLuint flags) //!< Import flags (see assimp/postprocess.h) { // clear the intermediate data clear(); // Check existance if (!SLFileSystem::fileExists(file)) { file = defaultPath + file; if (!SLFileSystem::fileExists(file)) { SLstring msg = "SLAssimpImporter: File not found: " + file + "\n"; SL_WARN_MSG(msg.c_str()); return nullptr; } } // Import file with assimp importer Assimp::Importer ai; const aiScene* scene = ai.ReadFile(file.c_str(), (SLuint)flags); if (!scene) { SLstring msg = "Failed to load file: " + file + "\n" + ai.GetErrorString() + "\n"; SL_WARN_MSG(msg.c_str()); return nullptr; } // initial scan of the scene performInitialScan(scene); // load skeleton loadSkeleton(nullptr, _skeletonRoot); // load materials SLstring modelPath = SLUtils::getPath(file); SLVMaterial materials; for(SLint i = 0; i < (SLint)scene->mNumMaterials; i++) materials.push_back(loadMaterial(i, scene->mMaterials[i], modelPath)); // load meshes & set their material std::map<int, SLMesh*> meshMap; // map from the ai index to our mesh for(SLint i = 0; i < (SLint)scene->mNumMeshes; i++) { SLMesh* mesh = loadMesh(scene->mMeshes[i]); if (mesh != 0) { mesh->mat = materials[scene->mMeshes[i]->mMaterialIndex]; _meshes.push_back(mesh); meshMap[i] = mesh; } else SL_LOG("SLAsssimpImporter::load failed: %s\nin path: %s\n", file.c_str(), modelPath.c_str()); } // load the scene nodes recursively _sceneRoot = loadNodesRec(nullptr, scene->mRootNode, meshMap, loadMeshesOnly); // load animations vector<SLAnimation*> animations; for (SLint i = 0; i < (SLint)scene->mNumAnimations; i++) animations.push_back(loadAnimation(scene->mAnimations[i])); logMessage(LV_minimal, "\n---------------------------\n\n"); return _sceneRoot; }