// Copyright (C) 2002-2012 Nikolaus Gebhardt // 2019 additional alignment and big_endian fixes by Corto and Salas00 // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_MS3D_LOADER_ #include "IReadFile.h" #include "os.h" #include "CMS3DMeshFileLoader.h" #include "CSkinnedMesh.h" namespace irr { namespace scene { #ifdef _DEBUG #define _IRR_DEBUG_MS3D_LOADER_ #endif // byte-align structures #include "irrpack.h" namespace { // File header struct MS3DHeader { char ID[10]; int Version; } PACK_STRUCT; // Vertex information struct MS3DVertex { u8 pad1[3]; u8 Flags; float Vertex[3]; char BoneID; u8 RefCount; u8 pad2[2]; } PACK_STRUCT; #define MS3DVERTEX_NUM_PAD_BYTES 5 // Triangle information struct MS3DTriangle { u16 Flags; u16 VertexIndices[3]; float VertexNormals[3][3]; float S[3], T[3]; u8 SmoothingGroup; u8 GroupIndex; u8 pad1[2]; } PACK_STRUCT; #define MS3DTRIANGLE_NUM_PAD_BYTES 2 // Material information struct MS3DMaterial { char Name[32]; float Ambient[4]; float Diffuse[4]; float Specular[4]; float Emissive[4]; float Shininess; // 0.0f - 128.0f float Transparency; // 0.0f - 1.0f u8 Mode; // 0, 1, 2 is unused now char Texture[128]; char Alphamap[128]; u8 pad1[3]; } PACK_STRUCT; #define MS3DMATERIAL_NUM_PAD_BYTES 3 // Joint information struct MS3DJoint { u8 pad[3]; u8 Flags; char Name[32]; char ParentName[32]; float Rotation[3]; float Translation[3]; u16 NumRotationKeyframes; u16 NumTranslationKeyframes; } PACK_STRUCT; #define MS3DJOINT_NUM_PAD_BYTES 3 // Keyframe data struct MS3DKeyframe { float Time; float Parameter[3]; } PACK_STRUCT; // vertex weights in 1.8.x struct MS3DVertexWeights { char boneIds[3]; u8 weights[3]; } PACK_STRUCT; } // end namespace // Default alignment #include "irrunpack.h" // Get float encoded in little endian in way not causing troubles when floats have to be memory aligned. static inline float get_unaligned_le_float(const u8 *ptr) { union { u8 u[4]; float f; } tmp; #ifdef __BIG_ENDIAN__ tmp.u[0] = ptr[3]; tmp.u[1] = ptr[2]; tmp.u[2] = ptr[1]; tmp.u[3] = ptr[0]; #else tmp.f = *(float*)ptr; #endif return tmp.f; } struct SGroup { core::stringc Name; core::array VertexIds; u16 MaterialIdx; }; //! Constructor CMS3DMeshFileLoader::CMS3DMeshFileLoader(video::IVideoDriver *driver) : Driver(driver), AnimatedMesh(0) { #ifdef _DEBUG setDebugName("CMS3DMeshFileLoader"); #endif } //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".bsp") bool CMS3DMeshFileLoader::isALoadableFileExtension(const io::path& filename) const { return core::hasFileExtension ( filename, "ms3d" ); } //! creates/loads an animated mesh from the file. //! \return Pointer to the created mesh. Returns 0 if loading failed. //! If you no longer need the mesh, you should call IAnimatedMesh::drop(). //! See IReferenceCounted::drop() for more information. IAnimatedMesh* CMS3DMeshFileLoader::createMesh(io::IReadFile* file) { if (!file) return 0; AnimatedMesh = new CSkinnedMesh(); if ( load(file) ) { AnimatedMesh->finalize(); } else { AnimatedMesh->drop(); AnimatedMesh = 0; } return AnimatedMesh; } //! loads a milkshape file bool CMS3DMeshFileLoader::load(io::IReadFile* file) { if (!file) return false; // find file size const long fileSize = file->getSize(); // read whole file u8* buffer = new u8[fileSize]; size_t read = file->read(buffer, fileSize); if (read != fileSize) { delete [] buffer; os::Printer::log("Could not read full file. Loading failed", file->getFileName(), ELL_ERROR); return false; } // read header const u8 *pPtr = (u8*)((void*)buffer); MS3DHeader *pHeader = (MS3DHeader*)pPtr; pPtr += sizeof(MS3DHeader); if ( strncmp( pHeader->ID, "MS3D000000", 10 ) != 0 ) { delete [] buffer; os::Printer::log("Not a valid Milkshape3D Model File. Loading failed", file->getFileName(), ELL_ERROR); return false; } #ifdef __BIG_ENDIAN__ pHeader->Version = os::Byteswap::byteswap(pHeader->Version); #endif if ( pHeader->Version < 3 || pHeader->Version > 4 ) { delete [] buffer; os::Printer::log("Only Milkshape3D version 3 and 4 (1.3 to 1.8) is supported. Loading failed", file->getFileName(), ELL_ERROR); return false; } #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Loaded header version", core::stringc(pHeader->Version).c_str()); #endif // get pointers to data // vertices u16 numVertices = *(u16*)pPtr; #ifdef __BIG_ENDIAN__ numVertices = os::Byteswap::byteswap(numVertices); #endif #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Load vertices", core::stringc(numVertices).c_str()); #endif pPtr += sizeof(u16); MS3DVertex *vertices = new MS3DVertex[numVertices]; if (pPtr + ((sizeof(MS3DVertex) - MS3DVERTEX_NUM_PAD_BYTES) * numVertices) > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } for (u16 tmp=0; tmp buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } for (u16 tmp=0; tmp groups; groups.reallocate(numGroups); //store groups u32 i; for (i=0; i buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } } // load materials u16 numMaterials = *(u16*)pPtr; #ifdef __BIG_ENDIAN__ numMaterials = os::Byteswap::byteswap(numMaterials); #endif #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Load Materials", core::stringc(numMaterials).c_str()); #endif pPtr += sizeof(u16); if(numMaterials == 0) { // if there are no materials, add at least one buffer AnimatedMesh->addMeshBuffer(); } MS3DMaterial *material = new MS3DMaterial; for (i=0; iAmbient[j] = os::Byteswap::byteswap(material->Ambient[j]); for (u16 j=0; j<4; ++j) material->Diffuse[j] = os::Byteswap::byteswap(material->Diffuse[j]); for (u16 j=0; j<4; ++j) material->Specular[j] = os::Byteswap::byteswap(material->Specular[j]); for (u16 j=0; j<4; ++j) material->Emissive[j] = os::Byteswap::byteswap(material->Emissive[j]); material->Shininess = os::Byteswap::byteswap(material->Shininess); material->Transparency = os::Byteswap::byteswap(material->Transparency); #endif pPtr += (sizeof(MS3DMaterial) - MS3DMATERIAL_NUM_PAD_BYTES); if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } scene::SSkinMeshBuffer *tmpBuffer = AnimatedMesh->addMeshBuffer(); tmpBuffer->Material.MaterialType = video::EMT_SOLID; tmpBuffer->Material.AmbientColor = video::SColorf(material->Ambient[0], material->Ambient[1], material->Ambient[2], material->Ambient[3]).toSColor (); tmpBuffer->Material.DiffuseColor = video::SColorf(material->Diffuse[0], material->Diffuse[1], material->Diffuse[2], material->Diffuse[3]).toSColor (); tmpBuffer->Material.EmissiveColor = video::SColorf(material->Emissive[0], material->Emissive[1], material->Emissive[2], material->Emissive[3]).toSColor (); tmpBuffer->Material.SpecularColor = video::SColorf(material->Specular[0], material->Specular[1], material->Specular[2], material->Specular[3]).toSColor (); tmpBuffer->Material.Shininess = material->Shininess; core::stringc TexturePath(material->Texture); if (TexturePath.trim()!="") { TexturePath=stripPathFromString(file->getFileName(),true) + stripPathFromString(TexturePath,false); tmpBuffer->Material.setTexture(0, Driver->getTexture(TexturePath)); } core::stringc AlphamapPath=(const c8*)material->Alphamap; if (AlphamapPath.trim()!="") { AlphamapPath=stripPathFromString(file->getFileName(),true) + stripPathFromString(AlphamapPath,false); tmpBuffer->Material.setTexture(2, Driver->getTexture(AlphamapPath)); } } delete material; // animation time f32 framesPerSecond = get_unaligned_le_float(pPtr); #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("FPS", core::stringc(framesPerSecond).c_str()); #endif pPtr += sizeof(float) * 2; // fps and current time if (framesPerSecond<1.f) framesPerSecond=1.f; AnimatedMesh->setAnimationSpeed(framesPerSecond); // ignore, calculated inside SkinnedMesh // s32 frameCount = *(int*)pPtr; #ifdef __BIG_ENDIAN__ // frameCount = os::Byteswap::byteswap(frameCount); #endif pPtr += sizeof(int); u16 jointCount = *(u16*)pPtr; #ifdef __BIG_ENDIAN__ jointCount = os::Byteswap::byteswap(jointCount); #endif #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Joints", core::stringc(jointCount).c_str()); #endif pPtr += sizeof(u16); if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } core::array parentNames; parentNames.reallocate(jointCount); // load joints for (i=0; iRotation[0] = %p (%d)\n", &pJoint->Rotation[0], (int)((long long)(&pJoint->Rotation[0]) % 4)); memcpy(&pJoint->Flags, pPtr, sizeof(MS3DJoint) - MS3DJOINT_NUM_PAD_BYTES); #ifdef __BIG_ENDIAN__ for (j=0; j<3; ++j) pJoint->Rotation[j] = os::Byteswap::byteswap(pJoint->Rotation[j]); for (j=0; j<3; ++j) pJoint->Translation[j] = os::Byteswap::byteswap(pJoint->Translation[j]); pJoint->NumRotationKeyframes= os::Byteswap::byteswap(pJoint->NumRotationKeyframes); pJoint->NumTranslationKeyframes = os::Byteswap::byteswap(pJoint->NumTranslationKeyframes); #endif pPtr = pPtr + sizeof(MS3DJoint) - MS3DJOINT_NUM_PAD_BYTES; if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } ISkinnedMesh::SJoint *jnt = AnimatedMesh->addJoint(); jnt->Name = pJoint->Name; #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Joint", jnt->Name.c_str()); os::Printer::log("Rotation keyframes", core::stringc(pJoint->NumRotationKeyframes).c_str()); os::Printer::log("Translation keyframes", core::stringc(pJoint->NumTranslationKeyframes).c_str()); #endif jnt->LocalMatrix.makeIdentity(); jnt->LocalMatrix.setRotationRadians( core::vector3df(pJoint->Rotation[0], pJoint->Rotation[1], pJoint->Rotation[2]) ); // convert right-handed to left-handed jnt->LocalMatrix[2]=-jnt->LocalMatrix[2]; jnt->LocalMatrix[6]=-jnt->LocalMatrix[6]; jnt->LocalMatrix[8]=-jnt->LocalMatrix[8]; jnt->LocalMatrix[9]=-jnt->LocalMatrix[9]; jnt->LocalMatrix.setTranslation( core::vector3df(pJoint->Translation[0], pJoint->Translation[1], -pJoint->Translation[2]) ); jnt->Animatedposition.set(jnt->LocalMatrix.getTranslation()); jnt->Animatedrotation.set(jnt->LocalMatrix.getRotationDegrees()); parentNames.push_back( (c8*)pJoint->ParentName ); /*if (pJoint->NumRotationKeyframes || pJoint->NumTranslationKeyframes) HasAnimation = true; */ MS3DKeyframe* kf = new MS3DKeyframe; // get rotation keyframes const u16 numRotationKeyframes = pJoint->NumRotationKeyframes; for (j=0; j < numRotationKeyframes; ++j) { memcpy(kf, pPtr, sizeof(MS3DKeyframe)); //printf("rotation kf = %p (%d)\n", kf, (int)((long long)kf % 4)); #ifdef __BIG_ENDIAN__ kf->Time = os::Byteswap::byteswap(kf->Time); for (u32 l=0; l<3; ++l) kf->Parameter[l] = os::Byteswap::byteswap(kf->Parameter[l]); #endif pPtr += sizeof(MS3DKeyframe); if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } ISkinnedMesh::SRotationKey *k=AnimatedMesh->addRotationKey(jnt); k->frame = kf->Time * framesPerSecond-1; core::matrix4 tmpMatrix; tmpMatrix.setRotationRadians( core::vector3df(kf->Parameter[0], kf->Parameter[1], kf->Parameter[2]) ); // convert right-handed to left-handed tmpMatrix[2]=-tmpMatrix[2]; tmpMatrix[6]=-tmpMatrix[6]; tmpMatrix[8]=-tmpMatrix[8]; tmpMatrix[9]=-tmpMatrix[9]; tmpMatrix=jnt->LocalMatrix*tmpMatrix; // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched from tmpMatrix to tmpMatrix.getTransposed() for downward compatibility. // Not tested so far if this was correct or wrong before quaternion fix! k->rotation = core::quaternion(tmpMatrix.getTransposed()); } // get translation keyframes const u16 numTranslationKeyframes = pJoint->NumTranslationKeyframes; for (j=0; jTime = os::Byteswap::byteswap(kf->Time); for (u32 l=0; l<3; ++l) kf->Parameter[l] = os::Byteswap::byteswap(kf->Parameter[l]); #endif pPtr += sizeof(MS3DKeyframe); if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } ISkinnedMesh::SPositionKey *k=AnimatedMesh->addPositionKey(jnt); k->frame = kf->Time * framesPerSecond-1; k->position = core::vector3df (kf->Parameter[0]+pJoint->Translation[0], kf->Parameter[1]+pJoint->Translation[1], -kf->Parameter[2]-pJoint->Translation[2]); } delete kf; delete pJoint; } core::array vertexWeights; f32 weightFactor=0; if (jointCount && (pHeader->Version == 4) && (pPtr < buffer+fileSize)) { s32 subVersion = *(s32*)pPtr; // comment subVersion, always 1 #ifdef __BIG_ENDIAN__ subVersion = os::Byteswap::byteswap(subVersion); #endif pPtr += sizeof(s32); for (u32 j=0; j<4; ++j) // four comment groups { #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Skipping comment group", core::stringc(j+1).c_str()); #endif u32 numComments = *(u32*)pPtr; #ifdef __BIG_ENDIAN__ numComments = os::Byteswap::byteswap(numComments); #endif pPtr += sizeof(u32); for (i=0; i buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } } if (pPtr < buffer+fileSize) { subVersion = *(s32*)pPtr; // vertex subVersion, 1 or 2 #ifdef __BIG_ENDIAN__ subVersion = os::Byteswap::byteswap(subVersion); #endif if (subVersion==1) weightFactor=1.f/255.f; else weightFactor=1.f/100.f; pPtr += sizeof(s32); #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Reading vertex weights"); #endif // read vertex weights, ignoring data 'extra' from 1.8.2 vertexWeights.reallocate(numVertices); const char offset = (subVersion==1)?6:10; for (i=0; i buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found.", file->getFileName(), ELL_ERROR); return false; } } if (pPtr < buffer+fileSize) { subVersion = *(s32*)pPtr; // joint subVersion, 1 or 2 #ifdef __BIG_ENDIAN__ subVersion = os::Byteswap::byteswap(subVersion); #endif pPtr += sizeof(s32); // skip joint colors #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Skip joint color"); #endif pPtr += 3*sizeof(float)*jointCount; if (pPtr > buffer+fileSize) { delete [] buffer; os::Printer::log("Loading failed. Corrupted data found", file->getFileName(), ELL_ERROR); return false; } } if (pPtr < buffer+fileSize) { subVersion = *(s32*)pPtr; // model subVersion, 1 or 2 #ifdef __BIG_ENDIAN__ subVersion = os::Byteswap::byteswap(subVersion); #endif pPtr += sizeof(s32); #ifdef _IRR_DEBUG_MS3D_LOADER_ os::Printer::log("Skip model extra information"); #endif // now the model extra information would follow // we also skip this for now } } //find parent of every joint for (u32 jointnum=0; jointnumgetAllJoints().size(); ++jointnum) { for (u32 j2=0; j2getAllJoints().size(); ++j2) { if (jointnum != j2 && parentNames[jointnum] == AnimatedMesh->getAllJoints()[j2]->Name ) { AnimatedMesh->getAllJoints()[j2]->Children.push_back(AnimatedMesh->getAllJoints()[jointnum]); break; } } } // create vertices and indices, attach them to the joints. video::S3DVertex v; core::array *Vertices; core::array Indices; for (i=0; igetMeshBuffers()[tmp]->Vertices_Standard; for (s32 j = 2; j!=-1; --j) { const u32 vertidx = triangles[i].VertexIndices[j]; v.TCoords.X = triangles[i].S[j]; v.TCoords.Y = triangles[i].T[j]; v.Normal.X = triangles[i].VertexNormals[j][0]; v.Normal.Y = triangles[i].VertexNormals[j][1]; v.Normal.Z = triangles[i].VertexNormals[j][2]; if(triangles[i].GroupIndex < groups.size() && groups[triangles[i].GroupIndex].MaterialIdx < AnimatedMesh->getMeshBuffers().size()) v.Color = AnimatedMesh->getMeshBuffers()[groups[triangles[i].GroupIndex].MaterialIdx]->Material.DiffuseColor; else v.Color.set(255,255,255,255); v.Pos.X = vertices[vertidx].Vertex[0]; v.Pos.Y = vertices[vertidx].Vertex[1]; v.Pos.Z = vertices[vertidx].Vertex[2]; // check if we already have this vertex in our vertex array s32 index = -1; for (u32 iV = 0; iV < Vertices->size(); ++iV) { if (v == (*Vertices)[iV]) { index = (s32)iV; break; } } if (index == -1) { index = Vertices->size(); const u32 matidx = groups[triangles[i].GroupIndex].MaterialIdx; if (vertexWeights.size()==0) { const s32 boneid = vertices[vertidx].BoneID; if ((u32)boneid < AnimatedMesh->getAllJoints().size()) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; w->strength = 1.0f; w->vertex_id = index; } } else if (jointCount) // new weights from 1.8.x { f32 sum = 1.0f; s32 boneid = vertices[vertidx].BoneID; if (((u32)boneid < AnimatedMesh->getAllJoints().size()) && (vertexWeights[vertidx].weights[0] != 0)) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; sum -= (w->strength = vertexWeights[vertidx].weights[0]*weightFactor); w->vertex_id = index; } boneid = vertexWeights[vertidx].boneIds[0]; if (((u32)boneid < AnimatedMesh->getAllJoints().size()) && (vertexWeights[vertidx].weights[1] != 0)) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; sum -= (w->strength = vertexWeights[vertidx].weights[1]*weightFactor); w->vertex_id = index; } boneid = vertexWeights[vertidx].boneIds[1]; if (((u32)boneid < AnimatedMesh->getAllJoints().size()) && (vertexWeights[vertidx].weights[2] != 0)) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; sum -= (w->strength = vertexWeights[vertidx].weights[2]*weightFactor); w->vertex_id = index; } boneid = vertexWeights[vertidx].boneIds[2]; if (((u32)boneid < AnimatedMesh->getAllJoints().size()) && (sum > 0.f)) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; w->strength = sum; w->vertex_id = index; } // fallback, if no bone chosen. Seems to be an error in the specs boneid = vertices[vertidx].BoneID; if ((sum == 1.f) && ((u32)boneid < AnimatedMesh->getAllJoints().size())) { ISkinnedMesh::SWeight *w=AnimatedMesh->addWeight(AnimatedMesh->getAllJoints()[boneid]); w->buffer_id = matidx; w->strength = 1.f; w->vertex_id = index; } } Vertices->push_back(v); } Indices.push_back(index); } } //create groups s32 iIndex = -1; for (i=0; i= AnimatedMesh->getMeshBuffers().size()) grp.MaterialIdx = 0; core::array& indices = AnimatedMesh->getMeshBuffers()[grp.MaterialIdx]->Indices; for (u32 k=0; k < grp.VertexIds.size(); ++k) for (u32 l=0; l<3; ++l) indices.push_back(Indices[++iIndex]); } delete [] buffer; delete [] triangles; delete [] vertices; return true; } core::stringc CMS3DMeshFileLoader::stripPathFromString(const core::stringc& inString, bool returnPath) const { s32 slashIndex=inString.findLast('/'); // forward slash s32 backSlash=inString.findLast('\\'); // back slash if (backSlash>slashIndex) slashIndex=backSlash; if (slashIndex==-1)//no slashes found { if (returnPath) return core::stringc(); //no path to return else return inString; } if (returnPath) return inString.subString(0, slashIndex + 1); else return inString.subString(slashIndex+1, inString.size() - (slashIndex+1)); } } // end namespace scene } // end namespace irr #endif