// Copyright (C) 2002-2007 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CXAnimationPlayer.h" #include "ISceneNode.h" #include "IVideoDriver.h" #include "os.h" #include "SMeshBuffer.h" #include "IMeshManipulator.h" namespace irr { namespace scene { //! constructor CXAnimationPlayer::CXAnimationPlayer(CXFileReader* reader, video::IVideoDriver* driver, IMeshManipulator* manip, const c8* filename) : Reader(reader), Driver(driver), AnimatedMesh(0), FileName(filename), Manipulator(manip), IsAnimatedSkinnedMesh(false), CurrentAnimationTime(-1.0f), LastAnimationTime(1.0f), CurrentAnimationSet(0), DebugSkeletonCrossSize(1.0f) { OriginalMesh = new scene::SMesh(); if (!Reader) return; if (Driver) Driver->grab(); if (Manipulator) Manipulator->grab(); Reader->grab(); createAnimationData(); } //! destructor CXAnimationPlayer::~CXAnimationPlayer() { OriginalMesh->drop(); if (Reader) Reader->drop(); if (Driver) Driver->drop(); if (AnimatedMesh) AnimatedMesh->drop(); if (Manipulator) Manipulator->drop(); } //! Gets the frame count of the animated mesh. s32 CXAnimationPlayer::getFrameCount() { return IsAnimatedSkinnedMesh ? (s32)LastAnimationTime : 1; } //! Returns the IMesh interface for a frame. IMesh* CXAnimationPlayer::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop) { if (!IsAnimatedSkinnedMesh) return OriginalMesh; if (CurrentAnimationTime != (f32)frame) { CurrentAnimationTime = (f32)frame; animateSkeleton(); modifySkin(); updateBoundingBoxFromAnimation(); } return AnimatedMesh; } //! Returns an axis aligned bounding box of the mesh. const core::aabbox3d& CXAnimationPlayer::getBoundingBox() const { return Box; } //! Returns the type of the animated mesh. E_ANIMATED_MESH_TYPE CXAnimationPlayer::getMeshType() const { return EAMT_X; } void CXAnimationPlayer::createAnimationData() { int i, iCnt; // get joints from x-file core::array& pgRootFrames = Reader->getRootFrames(); iCnt = pgRootFrames.size(); for( i = 0; i < iCnt; i++ ) { createJointData(pgRootFrames[i], -1); createMeshData(); if (IsAnimatedSkinnedMesh && AnimatedMesh) { animateSkeleton(); modifySkin(); updateBoundingBoxFromAnimation(); DebugSkeletonCrossSize = AnimatedMesh->getBoundingBox().getExtent().X / 20.0f; } else DebugSkeletonCrossSize = OriginalMesh->getBoundingBox().getExtent().X / 20.0f; } } void CXAnimationPlayer::createMeshData() { int i, iCnt; core::array& pgRootFrames = Reader->getRootFrames(); iCnt = pgRootFrames.size(); for( i = 0; i < iCnt; i++ ) { // create mesh addFrameToMesh(pgRootFrames[i]); // recalculate box OriginalMesh->recalculateBoundingBox(); // store box (fix by jox, thnx) Box = OriginalMesh->getBoundingBox(); // sort weights in joints for (s32 j=0; j<(s32)Joints.size(); ++j) Joints[j].Weights.sort(); // copy mesh AnimatedMesh = Manipulator->createMeshCopy(OriginalMesh); // create and link animation data prepareAnimationData(); // first animation animateSkeleton(); modifySkin(); } } void CXAnimationPlayer::addFrameToMesh(CXFileReader::SXFrame& frame) { // go through all meshes for (u32 m=0; maddMeshBuffer(buf); buf->drop(); // new weights buffer Weights.push_back( core::array() ); // create material buf->Material = getMaterialFromXMaterial( frame.Meshes[m].MaterialList.Materials[mt]); // add all faces of this material addFacesToBuffer(OriginalMesh->MeshBuffers.size()-1, frame.Meshes[m], mt, frame); buf->recalculateBoundingBox(); } } // add child frames for (u32 c=0; cgetTexture(getTextureFileName(xmat.TextureFileName).c_str()); if (mat.Textures[0] == 0) mat.Textures[0] = Driver->getTexture(xmat.TextureFileName.c_str()); } return mat; } void CXAnimationPlayer::addFacesToBuffer(s32 meshbuffernr, CXFileReader::SXMesh& mesh, s32 matnr, const CXFileReader::SXFrame& frame) { scene::SMeshBuffer* buf = (SMeshBuffer*)OriginalMesh->MeshBuffers[meshbuffernr]; u32 tcnt = mesh.TextureCoords.size(); u32 ncnt = mesh.Normals.size(); u32 ccnt = mesh.VertexColors.size(); // precompute which joint belongs to which weight array core::array< s32 > jointNumberWeightNumberMap; for (u32 w=0; wMaterial.DiffuseColor; s32 nidx = buf->Vertices.linear_reverse_search(v); bool alreadyIn = (nidx != -1); if (!alreadyIn) { nidx = buf->Vertices.size(); buf->Indices.push_back(nidx); buf->Vertices.push_back(v); Weights[meshbuffernr].push_back(SVertexWeight()); } else buf->Indices.push_back(nidx); bool isWeighted = alreadyIn; // add weight data of this vertex to all joints if (!alreadyIn) for (s32 w=0; w<(s32)mesh.SkinWeights.size(); ++w) { s32 vertexinweights = mesh.SkinWeights[w].Weights.binary_search(CXFileReader::SXWeight(idx)); if (vertexinweights != -1) { s32 jnr = jointNumberWeightNumberMap[w]; if (jnr != -1) { // weight per joint Joints[jnr].Weights.push_back(SWeightData( meshbuffernr, nidx, mesh.SkinWeights[w].Weights[vertexinweights].Weight)); // weight per vertex SVertexWeight& weight = Weights[meshbuffernr].pointer()[nidx]; weight.add(mesh.SkinWeights[w].Weights[vertexinweights].Weight, jnr); isWeighted = true; } } } // end for all weights // if this vertex does not have a weight, create a virtual new // joint and attach it to this one if (!isWeighted) addVirtualWeight(meshbuffernr, nidx, mesh, frame); } // end for all faces } // end for all materials } // generate missing normals if (!ncnt) { // Using Mesh manipulator Manipulator->recalculateNormals ( buf, true ); } /*// transform vertices and normals core::matrix4 mat = frame.LocalMatrix; s32 vcnt = buf->Vertices.size(); for (s32 u=0; uVertices[u].Pos); mat.rotateVect(buf->Vertices[u].Normal); }*/ } void CXAnimationPlayer::addVirtualWeight(s32 meshbuffernr, s32 vtxidx, CXFileReader::SXMesh& mesh, const CXFileReader::SXFrame& frame) { // find original joint of vertex s32 jnr = getJointNumberFromName(frame.Name); if (jnr == -1) return; // weight per joint Joints[jnr].Weights.push_back(SWeightData( meshbuffernr, vtxidx, 1.0f)); // weight per vertex SVertexWeight& weight = Weights[meshbuffernr].pointer()[vtxidx]; weight.add(1.0f, jnr); } s32 CXAnimationPlayer::getJointNumberFromName(const core::stringc& name) const { for (s32 i=0; i<(s32)Joints.size(); ++i) if (Joints[i].Name == name) return i; return -1; } core::stringc CXAnimationPlayer::getTextureFileName(const core::stringc& texture) { s32 idx = -1; idx = FileName.findLast('/'); if (idx == -1) idx = FileName.findLast('\\'); if (idx == -1) return core::stringc(); core::stringc p = FileName.subString(0, idx+1); p.append(texture); return p; } void CXAnimationPlayer::createJointData(const CXFileReader::SXFrame& f, s32 JointParent) { // add joint s32 index = Joints.size(); Joints.push_back(SJoint()); SJoint& j = Joints.getLast(); j.Parent = JointParent; j.GlobalMatrix = f.GlobalMatrix; j.LocalMatrix = f.LocalMatrix; j.AnimatedMatrix = j.GlobalMatrix; j.LocalAnimatedMatrix = j.LocalMatrix; j.CombinedAnimationMatrix = j.AnimatedMatrix * j.MatrixOffset; j.IsVirtualJoint = false; j.Name = f.Name; // add all children for (s32 i=0; i<(s32)f.ChildFrames.size(); ++i) createJointData(f.ChildFrames[i], index); } //! Returns a pointer to a transformation matrix core::matrix4* CXAnimationPlayer::getMatrixOfJoint(s32 jointNumber, s32 frame) { if (jointNumber < 0 || jointNumber >= (s32)Joints.size()) return 0; return &Joints[jointNumber].AnimatedMatrix; } //! Gets joint count. s32 CXAnimationPlayer::getJointCount() const { return Joints.size(); } //! Gets the name of a joint. const c8* CXAnimationPlayer::getJointName(s32 number) const { if (number<0 || number>=(s32)Joints.size()) return 0; return Joints[number].Name.c_str(); } //! Gets a joint number from its name s32 CXAnimationPlayer::getJointNumber(const c8* name) const { for (s32 i=0; i<(s32)Joints.size(); ++i) if (Joints[i].Name == name) return i; return -1; } //! Returns a pointer to list of points containing the skeleton. const core::array* CXAnimationPlayer::getDrawableSkeleton(s32 frame) { DebugSkeleton.clear(); f32 k = DebugSkeletonCrossSize; f32 p = 0.0f; for (s32 i=0; i<(s32)Joints.size(); ++i) { core::vector3df start(p,p,p); core::vector3df end(p,p,p); Joints[i].AnimatedMatrix.transformVect(start); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(0,k,0)); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(0,-k,0)); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(k,0,0)); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(-k,0,0)); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(0,0,k)); DebugSkeleton.push_back(start); DebugSkeleton.push_back(start + core::vector3df(0,0,-k)); if (Joints[i].Parent != -1) { Joints[Joints[i].Parent].AnimatedMatrix.transformVect(end); DebugSkeleton.push_back(end); DebugSkeleton.push_back(start); } } return &DebugSkeleton; } //! animates the skeleton based on the animation data void CXAnimationPlayer::animateSkeleton() { if (!AnimationSets.empty()) { // reset joints for (u32 jii=0; jii= CurrentAnimationTime) { idx1 = t; idx2 = (t+1) % currentSet.Animations[i].Times.size(); break; } } if (idx1 == -1) continue; // calculate interpolation factor f32 factor = (CurrentAnimationTime - currentSet.Animations[i].Times[idx1]) / (currentSet.Animations[i].Times[idx2] - currentSet.Animations[i].Times[idx1]); // animate it switch(currentSet.Animations[i].keyType) { case 0: // rotation { // with this code, rotations are not 100% ok, they are // mirrored. core::quaternion q; q.slerp(currentSet.Animations[i].Quaternions[idx1], currentSet.Animations[i].Quaternions[idx2], factor); joint.LocalAnimatedMatrix *= q.getMatrix(); joint.WasAnimatedThisFrame = true; } break; case 1: // scale { core::matrix4 mat1, mat2; mat1.setScale(currentSet.Animations[i].Vectors[idx1]); mat2.setScale(currentSet.Animations[i].Vectors[idx2]); joint.LocalAnimatedMatrix *= mat1.interpolate(mat2, factor); joint.WasAnimatedThisFrame = true; } break; case 2: // position { core::matrix4 mat1, mat2; mat1.setTranslation(currentSet.Animations[i].Vectors[idx1]); mat2.setTranslation(currentSet.Animations[i].Vectors[idx2]); joint.LocalAnimatedMatrix *= mat1.interpolate(mat2, factor); joint.WasAnimatedThisFrame = true; } break; case 4: case 3: // matrix { joint.LocalAnimatedMatrix = currentSet.Animations[i].Matrices[idx1].interpolate( currentSet.Animations[i].Matrices[idx2], factor); joint.WasAnimatedThisFrame = true; } break; } } } // update all joints for (s32 ji=0; ji<(s32)Joints.size(); ++ji) { if (!Joints[ji].WasAnimatedThisFrame) Joints[ji].LocalAnimatedMatrix = Joints[ji].LocalMatrix; Joints[ji].AnimatedMatrix = Joints[ji].LocalAnimatedMatrix; if (Joints[ji].Parent != -1) Joints[ji].AnimatedMatrix = Joints[Joints[ji].Parent].AnimatedMatrix * Joints[ji].AnimatedMatrix; Joints[ji].CombinedAnimationMatrix = Joints[ji].AnimatedMatrix * Joints[ji].MatrixOffset; } } //! modifies the skin based on the animated skeleton void CXAnimationPlayer::modifySkin() { // set animated vertices to zero for (s32 k=0; k<(s32)Joints.size(); ++k) { for (s32 w=0; w<(s32)Joints[k].Weights.size(); ++w) { SWeightData& wd = Joints[k].Weights[w]; #ifdef _XREADER_DEBUG if (wd.buffer >= AnimatedMesh->getMeshBufferCount() || wd.vertex >= AnimatedMesh->getMeshBuffer(wd.buffer)->getVertexCount()) os::Printer::log("CXAnimationPlayer: Invalid Weights"); #endif video::S3DVertex* nv = (video::S3DVertex*)AnimatedMesh->getMeshBuffer(wd.buffer)->getVertices(); nv[wd.vertex].Pos.set(0,0,0); } } #ifdef _XREADER_DEBUG bool somethingIsWrong = false; // check if all vertices are set to zero for (u32 mb=0; mbgetMeshBufferCount(); ++mb) { video::S3DVertex* v = (video::S3DVertex*)AnimatedMesh->getMeshBuffer(mb)->getVertices(); s32 c = AnimatedMesh->getMeshBuffer(mb)->getVertexCount(); for (s32 vt=0; vtgetMeshBufferCount()); os::Printer::log(tmp); for (u32 mb=0; mbgetMeshBufferCount(); ++mb) { sprintf(tmp, "CXAnimationPlayer: Meshbuffer #%d: %d vertices", mb, AnimatedMesh->getMeshBuffer(mb)->getVertexCount()); os::Printer::log(tmp); } } #endif // transform vertices for (u32 mb=0; mbgetMeshBufferCount(); ++mb) { video::S3DVertex* av = (video::S3DVertex*)AnimatedMesh->getMeshBuffer(mb)->getVertices(); video::S3DVertex* ov = (video::S3DVertex*)OriginalMesh->getMeshBuffer(mb)->getVertices(); const u32 c = AnimatedMesh->getMeshBuffer(mb)->getVertexCount(); for (u32 vt=0; vtgetAnimationSetCount(); for (s32 i=0; igetAnimationSet(i); mySet.AnimationName = readerSet.AnimationName; // through all animations for (s32 a=0; a<(s32)readerSet.Animations.size(); ++a) { // through all keys for (s32 k=0; k<(s32)readerSet.Animations[a].Keys.size(); ++k) { // link with joint s32 jntnr = getJointNumberFromName(readerSet.Animations[a].FrameName); if (jntnr == -1) { os::Printer::log( "CXAnimationPlayer: Animationtrack without corresponding joint found", readerSet.Animations[a].FrameName.c_str()); continue; } // copy track s32 keyCount = (s32)readerSet.Animations[a].Keys[k].numberOfKeys; if (!keyCount) { os::Printer::log( "CXAnimationPlayer: Skipping Animationtrack with zero key frames", readerSet.Animations[a].FrameName.c_str()); continue; } // add new track mySet.Animations.push_back(SXAnimationTrack()); SXAnimationTrack& myTrack = mySet.Animations.getLast(); myTrack.jointNr = jntnr; IsAnimatedSkinnedMesh = true; s32 type = readerSet.Animations[a].Keys[k].keyType; s32 l; myTrack.keyType = type; #ifdef _XREADER_DEBUG char tmp[255]; sprintf(tmp, "adding track %s with %d keys, type:%d", readerSet.Animations[a].FrameName.c_str(), keyCount, type); os::Printer::log(tmp); #endif switch(type) { case 0: // quaternion for (l=0; l LastAnimationTime) LastAnimationTime = myTrack.Times.getLast(); } } } // sort animation tracks mySet.Animations.sort(); } } void CXAnimationPlayer::updateBoundingBoxFromAnimation() { if (!Joints.size()) return; bool first = true; for (u32 i=0; iBoundingBox = Box; } //! Returns amount of animations in .X-file. s32 CXAnimationPlayer::getAnimationCount() const { return AnimationSets.size(); } //! Returns the name of an animation. const c8* CXAnimationPlayer::getAnimationName(s32 idx) const { if (idx < 0 || idx >= (s32)AnimationSets.size()) return 0; return AnimationSets[idx].AnimationName.c_str(); } //! Sets an animation as animation to play back. void CXAnimationPlayer::setCurrentAnimation(s32 idx) { if (idx < 0 || idx >= (s32)AnimationSets.size()) return; CurrentAnimationSet = idx; } //! Sets an animation as animation to play back. bool CXAnimationPlayer::setCurrentAnimation(const c8* name) { for (s32 i=0; i<(s32)AnimationSets.size(); ++i) if (AnimationSets[i].AnimationName == name) { CurrentAnimationSet = i; return true; } return false; } } // end namespace scene } // end namespace irr