nuclear@0: #include nuclear@0: #include "assload.h" nuclear@0: #include "logger.h" nuclear@0: #include "datapath.h" nuclear@0: nuclear@0: #ifdef USE_ASSIMP nuclear@0: nuclear@0: #include nuclear@0: #include nuclear@0: #include "assimp/cimport.h" nuclear@0: #include "assimp/scene.h" nuclear@0: #include "assimp/postprocess.h" nuclear@0: #include "texman.h" nuclear@0: #include "material.h" nuclear@15: #include "scene.h" nuclear@0: nuclear@0: using namespace std; nuclear@15: using namespace goatgfx; nuclear@0: nuclear@0: static bool load_material(Material *mat, const aiMaterial *aimat); nuclear@0: static Object *load_node(const aiScene *aiscn, const aiNode *ainode); nuclear@0: static Mesh *load_mesh(const aiScene *aiscn, const aiMesh *aimesh); nuclear@0: static Curve *load_curve(const aiScene *aiscn, const aiMesh *aimesh); nuclear@0: static bool load_bones(Mesh *mesh, const aiMesh *aimesh); nuclear@0: nuclear@0: static Vector3 assimp_vector(const aiVector3D &v); nuclear@0: static Quaternion assimp_quat(const aiQuaternion &q); nuclear@0: static Matrix4x4 assimp_matrix(const aiMatrix4x4 &aim); nuclear@0: static long assimp_time(const aiAnimation *anim, double aitime); nuclear@0: static void print_hierarchy(const aiNode *node); nuclear@0: nuclear@0: static map obj_by_name; nuclear@0: static map mesh_by_aimesh; nuclear@0: nuclear@0: bool load_ass(Scene *scn, const char *fname) nuclear@0: { nuclear@0: static bool init_done; nuclear@0: nuclear@0: if(!init_done) { nuclear@0: static aiLogStream log_stream = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, 0); nuclear@0: aiAttachLogStream(&log_stream); nuclear@0: //aiEnableVerboseLogging(1); nuclear@0: init_done = true; nuclear@0: } nuclear@0: nuclear@0: unsigned int proc_flags = aiProcess_JoinIdenticalVertices | nuclear@0: aiProcess_CalcTangentSpace | nuclear@0: aiProcess_Triangulate | nuclear@0: aiProcess_SortByPType | nuclear@0: aiProcess_FlipUVs; nuclear@0: nuclear@0: const aiScene *aiscn = aiImportFile(datafile_path(fname).c_str(), proc_flags); nuclear@0: if(!aiscn) { nuclear@0: error_log("failed to load file: %s\n", fname); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: info_log("NODE HIERARCHY:\n"); nuclear@0: print_hierarchy(aiscn->mRootNode); nuclear@0: info_log("-------------------\n"); nuclear@0: nuclear@0: Vector3 root_pos, root_scaling(1.0, 1.0, 1.0); nuclear@0: Quaternion root_rot; nuclear@0: nuclear@0: if(aiscn->mRootNode) { nuclear@0: Matrix4x4 root_matrix = assimp_matrix(aiscn->mRootNode->mTransformation); nuclear@0: root_pos = root_matrix.get_translation(); nuclear@0: root_rot = root_matrix.get_rotation_quat(); nuclear@0: root_scaling = root_matrix.get_scaling(); nuclear@0: } nuclear@0: nuclear@0: // load all meshes nuclear@0: for(unsigned int i=0; imNumMeshes; i++) { nuclear@0: aiMesh *aimesh = aiscn->mMeshes[i]; nuclear@0: Mesh *mesh; nuclear@0: Curve *curve; nuclear@0: nuclear@0: switch(aimesh->mPrimitiveTypes) { nuclear@0: case aiPrimitiveType_TRIANGLE: nuclear@0: if((mesh = load_mesh(aiscn, aimesh))) { nuclear@0: mesh_by_aimesh[aimesh] = mesh; nuclear@0: scn->meshes.push_back(mesh); nuclear@0: } nuclear@0: break; nuclear@0: nuclear@0: case aiPrimitiveType_LINE: nuclear@0: if((curve = load_curve(aiscn, aimesh))) { nuclear@0: scn->curves.push_back(curve); nuclear@0: } nuclear@0: break; nuclear@0: nuclear@0: default: nuclear@0: error_log("unsupported primitive type: %u\n", aimesh->mPrimitiveTypes); nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // load all the nodes recursively nuclear@0: for(unsigned int i=0; imRootNode->mNumChildren; i++) { nuclear@0: Object *obj = load_node(aiscn, aiscn->mRootNode->mChildren[i]); nuclear@0: if(obj) { nuclear@0: Object *dummy = new Object; nuclear@0: dummy->set_name((string("dummyroot_") + string(obj->get_name())).c_str()); nuclear@0: dummy->set_position(root_pos); nuclear@0: dummy->set_rotation(root_rot); nuclear@0: dummy->set_scaling(root_scaling); nuclear@0: dummy->add_child(obj); nuclear@0: nuclear@0: obj = dummy; nuclear@0: scn->objects.push_back(obj); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // load and attach the bones to the meshes nuclear@0: for(unsigned int i=0; imNumMeshes; i++) { nuclear@0: aiMesh *aimesh = aiscn->mMeshes[i]; nuclear@0: nuclear@0: Mesh *mesh = mesh_by_aimesh[aimesh]; nuclear@0: load_bones(mesh, aimesh); nuclear@0: } nuclear@0: nuclear@0: obj_by_name.clear(); nuclear@0: mesh_by_aimesh.clear(); nuclear@0: nuclear@0: aiReleaseImport(aiscn); nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: static bool load_material(Material *mat, const aiMaterial *aimat) nuclear@0: { nuclear@0: aiColor4D aicol; nuclear@0: float shin, shin_str; nuclear@0: nuclear@0: if(aiGetMaterialColor(aimat, AI_MATKEY_COLOR_DIFFUSE, &aicol) == 0) { nuclear@0: mat->diffuse = Vector3(aicol[0], aicol[1], aicol[2]); nuclear@0: } nuclear@0: if(aiGetMaterialColor(aimat, AI_MATKEY_COLOR_SPECULAR, &aicol) == 0) { nuclear@0: mat->specular = Vector3(aicol[0], aicol[1], aicol[2]); nuclear@0: } nuclear@0: nuclear@0: unsigned int count = 1; nuclear@0: if(aiGetMaterialFloatArray(aimat, AI_MATKEY_SHININESS_STRENGTH, &shin_str, &count) != 0) { nuclear@0: shin_str = 1.0; nuclear@0: } nuclear@0: if(aiGetMaterialFloatArray(aimat, AI_MATKEY_SHININESS, &shin, &count) == 0) { nuclear@0: // XXX can't remember how I came up with this... nuclear@0: mat->shininess = shin * shin_str * 0.0001 * 128.0; nuclear@0: } nuclear@0: nuclear@0: // load textures nuclear@0: struct { int type; aiTextureType aitype; } textypes[] = { nuclear@0: {TEX_DIFFUSE, aiTextureType_DIFFUSE}, nuclear@0: {TEX_NORMAL, aiTextureType_NORMALS}, nuclear@0: {TEX_SPECULAR, aiTextureType_SPECULAR} nuclear@0: }; nuclear@0: nuclear@0: for(int i=0; itex[textypes[i].type] = texset.get(fname); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: static Object *load_node(const aiScene *aiscn, const aiNode *ainode) nuclear@0: { nuclear@0: Object *obj = new Object; nuclear@0: obj->set_name(ainode->mName.data); nuclear@0: nuclear@0: if(ainode->mNumMeshes) { nuclear@0: if(ainode->mNumMeshes > 1) { nuclear@0: info_log("%s warning: node %s has more than one meshes (%u)\n", __FUNCTION__, nuclear@0: ainode->mName.data, ainode->mNumMeshes); nuclear@0: } nuclear@0: nuclear@0: aiMesh *aimesh = aiscn->mMeshes[ainode->mMeshes[0]]; nuclear@0: obj->set_mesh(mesh_by_aimesh[aimesh]); nuclear@0: nuclear@0: // also grab the material of this mesh nuclear@0: load_material(&obj->material, aiscn->mMaterials[aimesh->mMaterialIndex]); nuclear@0: } nuclear@0: nuclear@0: // if there are animations, grab the first and try to use it nuclear@0: if(aiscn->mNumAnimations) { nuclear@0: aiAnimation *aianim = aiscn->mAnimations[0]; nuclear@0: aiNodeAnim *ainodeanim = 0; nuclear@0: for(unsigned int i=0; imNumChannels; i++) { nuclear@0: if(strcmp(aianim->mChannels[i]->mNodeName.data, ainode->mName.data) == 0) { nuclear@0: ainodeanim = aianim->mChannels[i]; nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(ainodeanim) { nuclear@0: // load all position (translation) keyframes nuclear@0: for(unsigned int i=0; imNumPositionKeys; i++) { nuclear@0: Vector3 pos = assimp_vector(ainodeanim->mPositionKeys[i].mValue); nuclear@0: long msec = assimp_time(aianim, ainodeanim->mPositionKeys[i].mTime); nuclear@0: obj->set_position(pos, msec); nuclear@0: } nuclear@0: nuclear@0: // load all rotation keyframes nuclear@0: for(unsigned int i=0; imNumRotationKeys; i++) { nuclear@0: Quaternion rot = assimp_quat(ainodeanim->mRotationKeys[i].mValue); nuclear@0: if(rot.length_sq() < SMALL_NUMBER) { nuclear@0: continue; nuclear@0: } nuclear@0: rot.normalize(); nuclear@0: long msec = assimp_time(aianim, ainodeanim->mRotationKeys[i].mTime); nuclear@0: obj->set_rotation(rot, msec); nuclear@0: } nuclear@0: nuclear@0: // load all scaling keyframes nuclear@0: for(unsigned int i=0; imNumScalingKeys; i++) { nuclear@0: Vector3 scale = assimp_vector(ainodeanim->mScalingKeys[i].mValue); nuclear@0: long msec = assimp_time(aianim, ainodeanim->mScalingKeys[i].mTime); nuclear@0: obj->set_scaling(scale, msec); nuclear@0: } nuclear@0: nuclear@0: obj->set_extrapolator(EXTRAP_REPEAT); // loop animation nuclear@0: } else { nuclear@0: Matrix4x4 local_matrix = assimp_matrix(ainode->mTransformation); nuclear@0: obj->set_local_matrix(local_matrix); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: /* recurse to all children */ nuclear@0: for(unsigned int i=0; imNumChildren; i++) { nuclear@0: Object *child = load_node(aiscn, ainode->mChildren[i]); nuclear@0: if(child) { nuclear@0: obj->add_child(child); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: obj_by_name[obj->get_name()] = obj; nuclear@0: return obj; nuclear@0: } nuclear@0: nuclear@0: static Mesh *load_mesh(const aiScene *aiscn, const aiMesh *aimesh) nuclear@0: { nuclear@0: Mesh *mesh = new Mesh; nuclear@0: nuclear@0: int num_verts = aimesh->mNumVertices; nuclear@0: int num_faces = aimesh->mNumFaces; nuclear@0: nuclear@0: mesh->set_attrib_data(MESH_ATTR_VERTEX, 3, num_verts, (float*)aimesh->mVertices); nuclear@0: nuclear@0: if(aimesh->mNormals) { nuclear@0: mesh->set_attrib_data(MESH_ATTR_NORMAL, 3, num_verts, (float*)aimesh->mNormals); nuclear@0: } nuclear@0: if(aimesh->mTangents) { nuclear@0: mesh->set_attrib_data(MESH_ATTR_TANGENT, 3, num_verts, (float*)aimesh->mTangents); nuclear@0: } nuclear@0: if(aimesh->mTextureCoords[0]) { nuclear@0: mesh->set_attrib_data(MESH_ATTR_TEXCOORD, 3, num_verts, (float*)aimesh->mTextureCoords[0]); nuclear@0: } nuclear@0: nuclear@0: if(aimesh->mBones) { nuclear@0: float *weights = mesh->set_attrib_data(MESH_ATTR_BONEWEIGHTS, 4, num_verts, 0); nuclear@0: float *boneidx = mesh->set_attrib_data(MESH_ATTR_BONEIDX, 4, num_verts, 0); nuclear@0: nuclear@0: memset(weights, 0, num_verts * 4 * sizeof *weights); nuclear@0: memset(boneidx, 0, num_verts * 4 * sizeof *boneidx); nuclear@0: nuclear@0: int *vertex_bone_count = new int[num_verts]; nuclear@0: memset(vertex_bone_count, 0, num_verts * sizeof *vertex_bone_count); nuclear@0: nuclear@0: for(unsigned int i=0; imNumBones; i++) { nuclear@0: aiBone *aibone = aimesh->mBones[i]; nuclear@0: nuclear@0: // for every vertex affected by this bone: nuclear@0: for(unsigned int j=0; jmNumWeights; j++) { nuclear@0: aiVertexWeight *aiweight = aibone->mWeights + j; nuclear@0: int vidx = aiweight->mVertexId; nuclear@0: int vert_boneidx = vertex_bone_count[vidx]; nuclear@0: if(vert_boneidx >= 4) { nuclear@0: error_log("WARNING vertex with more than 4 bones found\n"); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: weights[vidx * 4 + vert_boneidx] = aiweight->mWeight; nuclear@0: boneidx[vidx * 4 + vert_boneidx] = (float)i; nuclear@0: vertex_bone_count[vidx]++; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: delete [] vertex_bone_count; nuclear@0: nuclear@0: // normalize weights nuclear@0: for(int i=0; i 1e-4) { nuclear@0: error_log("WARNING vertex with weights < 1 (%f), normalizing...\n", wsum); nuclear@0: nuclear@0: if(wsum < 1e-6) { nuclear@0: // this is clearly broken, let's use the first bone in full nuclear@0: weights[i * 4] = 1.0; nuclear@0: } else { nuclear@0: weights[i * 4] /= wsum; nuclear@0: weights[i * 4 + 1] /= wsum; nuclear@0: weights[i * 4 + 2] /= wsum; nuclear@0: weights[i * 4 + 3] /= wsum; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: unsigned int *iptr = mesh->set_index_data(num_faces * 3); nuclear@0: for(int i=0; imFaces[i].mIndices[j]; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: return mesh; nuclear@0: } nuclear@0: nuclear@0: static Curve *load_curve(const aiScene *aiscn, const aiMesh *aimesh) nuclear@0: { nuclear@0: Curve *curve = new Curve; nuclear@0: nuclear@0: for(unsigned int i=0; imNumVertices; i++) { nuclear@0: Vector3 pt = assimp_vector(aimesh->mVertices[i]); nuclear@0: curve->add_point(pt); nuclear@0: } nuclear@0: info_log("loaded curve with %d points\n", aimesh->mNumVertices); nuclear@0: nuclear@0: return curve; nuclear@0: } nuclear@0: nuclear@0: static bool load_bones(Mesh *mesh, const aiMesh *aimesh) nuclear@0: { nuclear@0: if(!aimesh->mNumBones) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: for(unsigned int i=0; imNumBones; i++) { nuclear@0: aiBone *aibone = aimesh->mBones[i]; nuclear@0: Object *obj = obj_by_name[aibone->mName.data]; nuclear@0: if(!obj) { nuclear@0: error_log("bone %s not found\n", aibone->mName.data); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: obj->set_bone_matrix(assimp_matrix(aibone->mOffsetMatrix)); nuclear@0: mesh->add_bone(obj); nuclear@0: nuclear@0: info_log("adding bone: %s\n", obj->get_name()); nuclear@0: } nuclear@0: nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: static Vector3 assimp_vector(const aiVector3D &v) nuclear@0: { nuclear@0: return Vector3(v[0], v[1], v[2]); nuclear@0: } nuclear@0: nuclear@0: static Quaternion assimp_quat(const aiQuaternion &q) nuclear@0: { nuclear@0: return Quaternion(q.w, Vector3(q.x, q.y, q.z)); nuclear@0: } nuclear@0: nuclear@0: static Matrix4x4 assimp_matrix(const aiMatrix4x4 &aim) nuclear@0: { nuclear@0: Matrix4x4 m; nuclear@0: memcpy(m[0], &aim, 16 * sizeof(float)); nuclear@0: return m; nuclear@0: } nuclear@0: nuclear@0: /* convert an assimp keyframe time (ticks) into milliseconds */ nuclear@0: static long assimp_time(const aiAnimation *anim, double aitime) nuclear@0: { nuclear@0: double sec; nuclear@0: if(anim->mTicksPerSecond < 1e-6) { nuclear@0: // assume time is in frames? nuclear@0: sec = aitime / 30.0; nuclear@0: } else { nuclear@0: sec = aitime / anim->mTicksPerSecond; nuclear@0: } nuclear@0: return (long)(sec * 1000.0); nuclear@0: } nuclear@0: nuclear@0: static void print_hierarchy(const aiNode *node) nuclear@0: { nuclear@0: static int lvl; nuclear@0: static int lvlopen[256]; nuclear@0: nuclear@0: for(int i=0; i= lvl - 1 ? '+' : '|'); nuclear@0: } else { nuclear@0: putchar(i >= lvl - 1 ? '+' : ' '); nuclear@0: } nuclear@0: } nuclear@0: info_log("- \"%s\"\n", node->mName.data); nuclear@0: nuclear@0: lvlopen[lvl] = 1; nuclear@0: nuclear@0: lvl++; nuclear@0: for(unsigned int i=0; imNumChildren; i++) { nuclear@0: if(i == node->mNumChildren - 1) { nuclear@0: lvlopen[lvl - 1] = 0; nuclear@0: } nuclear@0: print_hierarchy(node->mChildren[i]); nuclear@0: } nuclear@0: lvl--; nuclear@0: } nuclear@0: nuclear@0: #else // !defined USE_ASSIMP nuclear@0: nuclear@15: using namespace goatgfx; nuclear@15: nuclear@0: bool load_ass(Scene *scn, const char *fname) nuclear@0: { nuclear@0: error_log("load_ass: assimp support not compiled in\n"); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: #endif