// Copyright (C) 2002-2009 Nikolaus Gebhardt // 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_SKINNED_MESH_SUPPORT_ #include "CSkinnedMesh.h" #include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "os.h" namespace irr { namespace scene { //! constructor CSkinnedMesh::CSkinnedMesh() : SkinningBuffers(0), AnimationFrames(0.f), LastAnimatedFrame(0.f), LastSkinnedFrame(0.f), InterpolationMode(EIM_LINEAR), HasAnimation(false), PreparedForSkinning(false), BoneControlUsed(false), AnimateNormals(true), HardwareSkinning(false) { #ifdef _DEBUG setDebugName("CSkinnedMesh"); #endif SkinningBuffers=&LocalBuffers; } //! destructor CSkinnedMesh::~CSkinnedMesh() { for (u32 i=0; idrop(); } } //! returns the amount of frames in milliseconds. //! If the amount is 1, it is a static (=non animated) mesh. u32 CSkinnedMesh::getFrameCount() const { return core::floor32(AnimationFrames); } //! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level. IMesh* CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop) { //animate(frame,startFrameLoop, endFrameLoop); if (frame==-1) return this; animateMesh((f32)frame, 1.0f); skinMesh(); return this; } //-------------------------------------------------------------------------- // Keyframe Animation //-------------------------------------------------------------------------- //! Animates this mesh's joints based on frame input //! blend: {0-old position, 1-New position} void CSkinnedMesh::animateMesh(f32 frame, f32 blend) { if ( !HasAnimation || LastAnimatedFrame==frame) return; LastAnimatedFrame=frame; if (blend<=0.f) return; //No need to animate for (u32 i=0; iAnimatedposition; const core::vector3df oldScale = joint->Animatedscale; const core::quaternion oldRotation = joint->Animatedrotation; core::vector3df position = oldPosition; core::vector3df scale = oldScale; core::quaternion rotation = oldRotation; getFrameData(frame, joint, position, joint->positionHint, scale, joint->scaleHint, rotation, joint->rotationHint); if (blend==1.0f) { //No blending needed joint->Animatedposition = position; joint->Animatedscale = scale; joint->Animatedrotation = rotation; } else { //Blend animation joint->Animatedposition = core::lerp(oldPosition, position, blend); joint->Animatedscale = core::lerp(oldScale, scale, blend); joint->Animatedrotation.slerp(oldRotation, rotation, blend); } } //Note: //_LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for //one render (to play two animations at the same time) _LocalAnimatedMatrix only needs to be built once. //a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move //---------------- // Temp! buildAll_LocalAnimatedMatrices(); //----------------- updateBoundingBox(); } void CSkinnedMesh::buildAll_LocalAnimatedMatrices() { for (u32 i=0; iUseAnimationFrom && (joint->UseAnimationFrom->PositionKeys.size() || joint->UseAnimationFrom->ScaleKeys.size() || joint->UseAnimationFrom->RotationKeys.size() )) { joint->LocalAnimatedMatrix=joint->Animatedrotation.getMatrix(); // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() --- f32 *m1 = joint->LocalAnimatedMatrix.pointer(); core::vector3df &Pos = joint->Animatedposition; m1[0] += Pos.X*m1[3]; m1[1] += Pos.Y*m1[3]; m1[2] += Pos.Z*m1[3]; m1[4] += Pos.X*m1[7]; m1[5] += Pos.Y*m1[7]; m1[6] += Pos.Z*m1[7]; m1[8] += Pos.X*m1[11]; m1[9] += Pos.Y*m1[11]; m1[10] += Pos.Z*m1[11]; m1[12] += Pos.X*m1[15]; m1[13] += Pos.Y*m1[15]; m1[14] += Pos.Z*m1[15]; // ----------------------------------- joint->GlobalSkinningSpace=false; if (joint->ScaleKeys.size()) { /* core::matrix4 scaleMatrix; scaleMatrix.setScale(joint->Animatedscale); joint->LocalAnimatedMatrix *= scaleMatrix; */ // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- core::matrix4& mat = joint->LocalAnimatedMatrix; mat[0] *= joint->Animatedscale.X; mat[1] *= joint->Animatedscale.X; mat[2] *= joint->Animatedscale.X; mat[3] *= joint->Animatedscale.X; mat[4] *= joint->Animatedscale.Y; mat[5] *= joint->Animatedscale.Y; mat[6] *= joint->Animatedscale.Y; mat[7] *= joint->Animatedscale.Y; mat[8] *= joint->Animatedscale.Z; mat[9] *= joint->Animatedscale.Z; mat[10] *= joint->Animatedscale.Z; mat[11] *= joint->Animatedscale.Z; // ----------------------------------- } } else { joint->LocalAnimatedMatrix=joint->LocalMatrix; } } } void CSkinnedMesh::buildAll_GlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) { if (!joint) { for (u32 i=0; iGlobalSkinningSpace) joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix; else joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix; } for (u32 j=0; jChildren.size(); ++j) buildAll_GlobalAnimatedMatrices(joint->Children[j], joint); } void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint, core::vector3df &position, s32 &positionHint, core::vector3df &scale, s32 &scaleHint, core::quaternion &rotation, s32 &rotationHint) { s32 foundPositionIndex = -1; s32 foundScaleIndex = -1; s32 foundRotationIndex = -1; if (joint->UseAnimationFrom) { const core::array &PositionKeys=joint->UseAnimationFrom->PositionKeys; const core::array &ScaleKeys=joint->UseAnimationFrom->ScaleKeys; const core::array &RotationKeys=joint->UseAnimationFrom->RotationKeys; if (PositionKeys.size()) { foundPositionIndex = -1; //Test the Hints... if (positionHint>=0 && (u32)positionHint < PositionKeys.size()) { //check this hint if (positionHint>0 && PositionKeys[positionHint].frame>=frame && PositionKeys[positionHint-1].frame=frame && PositionKeys[positionHint+0].frame= frame) //Keys should to be sorted by frame { foundPositionIndex=i; positionHint=i; break; } } } //Do interpolation... if (foundPositionIndex!=-1) { if (InterpolationMode==EIM_CONSTANT || foundPositionIndex==0) { position = PositionKeys[foundPositionIndex].position; } else if (InterpolationMode==EIM_LINEAR) { const SPositionKey& KeyA = PositionKeys[foundPositionIndex]; const SPositionKey& KeyB = PositionKeys[foundPositionIndex-1]; const f32 fd1 = frame - KeyA.frame; const f32 fd2 = KeyB.frame - frame; position = ((KeyB.position-KeyA.position)/(fd1+fd2))*fd1 + KeyA.position; } } } //------------------------------------------------------------ if (ScaleKeys.size()) { foundScaleIndex = -1; //Test the Hints... if (scaleHint>=0 && (u32)scaleHint < ScaleKeys.size()) { //check this hint if (scaleHint>0 && ScaleKeys[scaleHint].frame>=frame && ScaleKeys[scaleHint-1].frame=frame && ScaleKeys[scaleHint+0].frame= frame) //Keys should to be sorted by frame { foundScaleIndex=i; scaleHint=i; break; } } } //Do interpolation... if (foundScaleIndex!=-1) { if (InterpolationMode==EIM_CONSTANT || foundScaleIndex==0) { scale = ScaleKeys[foundScaleIndex].scale; } else if (InterpolationMode==EIM_LINEAR) { const SScaleKey& KeyA = ScaleKeys[foundScaleIndex]; const SScaleKey& KeyB = ScaleKeys[foundScaleIndex-1]; const f32 fd1 = frame - KeyA.frame; const f32 fd2 = KeyB.frame - frame; scale = ((KeyB.scale-KeyA.scale)/(fd1+fd2))*fd1 + KeyA.scale; } } } //------------------------------------------------------------- if (RotationKeys.size()) { foundRotationIndex = -1; //Test the Hints... if (rotationHint>=0 && (u32)rotationHint < RotationKeys.size()) { //check this hint if (rotationHint>0 && RotationKeys[rotationHint].frame>=frame && RotationKeys[rotationHint-1].frame=frame && RotationKeys[rotationHint+0].frame= frame) //Keys should be sorted by frame { foundRotationIndex=i; rotationHint=i; break; } } } //Do interpolation... if (foundRotationIndex!=-1) { if (InterpolationMode==EIM_CONSTANT || foundRotationIndex==0) { rotation = RotationKeys[foundRotationIndex].rotation; } else if (InterpolationMode==EIM_LINEAR) { const SRotationKey& KeyA = RotationKeys[foundRotationIndex]; const SRotationKey& KeyB = RotationKeys[foundRotationIndex-1]; const f32 fd1 = frame - KeyA.frame; const f32 fd2 = KeyB.frame - frame; const f32 t = fd1/(fd1+fd2); /* f32 t = 0; if (KeyA.frame!=KeyB.frame) t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame); */ rotation.slerp(KeyA.rotation, KeyB.rotation, t); } } } } } //-------------------------------------------------------------------------- // Software Skinning //-------------------------------------------------------------------------- //! Preforms a software skin on this mesh based of joint positions void CSkinnedMesh::skinMesh() { if ( !HasAnimation) return; //---------------- // This is marked as "Temp!". A shiny dubloon to whomever can tell me why. buildAll_GlobalAnimatedMatrices(); //----------------- if (!HardwareSkinning) { //Software skin.... u32 i; //rigid animation for (i=0; iAttachedMeshes.size(); ++j) { SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ]; Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix; } } //clear skinning helper array for (i=0; isize(); ++i) (*SkinningBuffers)[i]->setDirty(EBT_VERTEX); } updateBoundingBox(); } void CSkinnedMesh::SkinJoint(SJoint *joint, SJoint *parentJoint) { if (joint->Weights.size()) { //Find this joints pull on vertices... core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING); jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix); core::vector3df thisVertexMove, thisNormalMove; core::array &buffersUsed=*SkinningBuffers; //Skin Vertices Positions and Normals... for (u32 i=0; iWeights.size(); ++i) { SWeight& weight = joint->Weights[i]; // Pull this vertex... jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); if (AnimateNormals) jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal); if (! (*(weight.Moved)) ) { *(weight.Moved) = true; buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength; if (AnimateNormals) buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength; //*(weight._Pos) = thisVertexMove * weight.strength; } else { buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength; if (AnimateNormals) buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength; //*(weight._Pos) += thisVertexMove * weight.strength; } buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated(); } } //Skin all children for (u32 j=0; jChildren.size(); ++j) SkinJoint(joint->Children[j], joint); } E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const { return EAMT_SKINNED; } //! Gets joint count. u32 CSkinnedMesh::getJointCount() const { return AllJoints.size(); } //! Gets the name of a joint. const c8* CSkinnedMesh::getJointName(u32 number) const { if (number >= AllJoints.size()) return 0; return AllJoints[number]->Name.c_str(); } //! Gets a joint number from its name s32 CSkinnedMesh::getJointNumber(const c8* name) const { for (u32 i=0; iName == name) return i; } return -1; } //! returns amount of mesh buffers. u32 CSkinnedMesh::getMeshBufferCount() const { return LocalBuffers.size(); } //! returns pointer to a mesh buffer IMeshBuffer* CSkinnedMesh::getMeshBuffer(u32 nr) const { if (nr < LocalBuffers.size()) return LocalBuffers[nr]; else return 0; } //! Returns pointer to a mesh buffer which fits a material IMeshBuffer* CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const { for (u32 i=0; igetMaterial() == material) return LocalBuffers[i]; } return 0; } //! returns an axis aligned bounding box const core::aabbox3d& CSkinnedMesh::getBoundingBox() const { return BoundingBox; } //! set user axis aligned bounding box void CSkinnedMesh::setBoundingBox( const core::aabbox3df& box) { BoundingBox = box; } //! sets a flag of all contained materials to a new value void CSkinnedMesh::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue) { for (u32 i=0; iMaterial.setFlag(flag,newvalue); } //! set the hardware mapping hint, for driver void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer) { for (u32 i=0; isetHardwareMappingHint(newMappingHint, buffer); } //! flags the meshbuffer as changed, reloads hardware buffers void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer) { for (u32 i=0; isetDirty(buffer); } //! uses animation from another mesh bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh) { bool unmatched=false; for(u32 i=0;iUseAnimationFrom=0; if (joint->Name=="") unmatched=true; else { for(u32 j=0;jgetAllJoints().size();++j) { SJoint *otherJoint=mesh->getAllJoints()[j]; if (joint->Name==otherJoint->Name) { joint->UseAnimationFrom=otherJoint; } } if (!joint->UseAnimationFrom) unmatched=true; } } checkForAnimation(); return !unmatched; } //!Update Normals when Animating //!False= Don't animate them, faster //!True= Update normals (default) void CSkinnedMesh::updateNormalsWhenAnimating(bool on) { AnimateNormals = on; } //!Sets Interpolation Mode void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode) { InterpolationMode = mode; } core::array &CSkinnedMesh::getMeshBuffers() { return LocalBuffers; } core::array &CSkinnedMesh::getAllJoints() { return AllJoints; } const core::array &CSkinnedMesh::getAllJoints() const { return AllJoints; } //! (This feature is not implementated in irrlicht yet) bool CSkinnedMesh::setHardwareSkinning(bool on) { if (HardwareSkinning!=on) { if (on) { //set mesh to static pose... for (u32 i=0; iWeights.size(); ++j) { const u16 buffer_id=joint->Weights[j].buffer_id; const u32 vertex_id=joint->Weights[j].vertex_id; LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos; LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal; LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated(); } } } HardwareSkinning=on; } return HardwareSkinning; } void CSkinnedMesh::CalculateGlobalMatrices(SJoint *joint,SJoint *parentJoint) { if (!joint && parentJoint) // bit of protection from endless loops return; //Go through the root bones if (!joint) { for (u32 i=0; iGlobalMatrix = joint->LocalMatrix; else joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix; joint->LocalAnimatedMatrix=joint->LocalMatrix; joint->GlobalAnimatedMatrix=joint->GlobalMatrix; if (joint->GlobalInversedMatrix.isIdentity())//might be pre calculated { joint->GlobalInversedMatrix = joint->GlobalMatrix; joint->GlobalInversedMatrix.makeInverse(); // slow } for (u32 j=0; jChildren.size(); ++j) CalculateGlobalMatrices(joint->Children[j],joint); } void CSkinnedMesh::checkForAnimation() { u32 i,j; //Check for animation... HasAnimation = false; for(i=0;iUseAnimationFrom) { if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() || AllJoints[i]->UseAnimationFrom->ScaleKeys.size() || AllJoints[i]->UseAnimationFrom->RotationKeys.size() ) { HasAnimation = true; } } } //meshes with weights, are still counted as animated for ragdolls, etc if (!HasAnimation) { for(i=0;iWeights.size()) HasAnimation = true; } } if (HasAnimation) { //--- Find the length of the animation --- AnimationFrames=0; for(i=0;iUseAnimationFrom) { if (AllJoints[i]->UseAnimationFrom->PositionKeys.size()) if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > AnimationFrames) AnimationFrames=AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame; if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size()) if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > AnimationFrames) AnimationFrames=AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame; if (AllJoints[i]->UseAnimationFrom->RotationKeys.size()) if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > AnimationFrames) AnimationFrames=AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame; } } } if (HasAnimation && !PreparedForSkinning) { PreparedForSkinning=true; //check for bugs: for(i=0; i < AllJoints.size(); ++i) { SJoint *joint = AllJoints[i]; for (j=0; jWeights.size(); ++j) { const u16 buffer_id=joint->Weights[j].buffer_id; const u32 vertex_id=joint->Weights[j].vertex_id; //check for invalid ids if (buffer_id>=LocalBuffers.size()) { os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0; } else if (vertex_id>=LocalBuffers[buffer_id]->getVertexCount()) { os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0; } } } //An array used in skinning for (i=0; iWeights.size(); ++j) { const u16 buffer_id=joint->Weights[j].buffer_id; const u32 vertex_id=joint->Weights[j].vertex_id; joint->Weights[j].Moved = &Vertices_Moved[buffer_id] [vertex_id]; joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; //joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos; } } // normalize weights normalizeWeights(); } } //! called by loader after populating with mesh and bone data void CSkinnedMesh::finalize() { u32 i; LastAnimatedFrame=-1; LastSkinnedFrame=-1; //calculate bounding box for (i=0; irecalculateBoundingBox(); } if (AllJoints.size() || RootJoints.size()) { // populate AllJoints or RootJoints, depending on which is empty if (!RootJoints.size()) { for(u32 CheckingIdx=0; CheckingIdx < AllJoints.size(); ++CheckingIdx) { bool foundParent=false; for(i=0; i < AllJoints.size(); ++i) { for(u32 n=0; n < AllJoints[i]->Children.size(); ++n) { if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx]) foundParent=true; } } if (!foundParent) RootJoints.push_back(AllJoints[CheckingIdx]); } } else { AllJoints=RootJoints; } } for(i=0; i < AllJoints.size(); ++i) { AllJoints[i]->UseAnimationFrom=AllJoints[i]; } //Set array sizes... for (i=0; i() ); Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount()); } //Todo: optimise keys here... checkForAnimation(); if (HasAnimation) { //--- optimize and check keyframes --- for(i=0;i &PositionKeys =AllJoints[i]->PositionKeys; core::array &ScaleKeys = AllJoints[i]->ScaleKeys; core::array &RotationKeys = AllJoints[i]->RotationKeys; if (PositionKeys.size()>2) { for(u32 j=0;j1) { for(u32 j=0;j= PositionKeys[j+1].frame) //bad frame, unneed and may cause problems { PositionKeys.erase(j+1); --j; } } } if (ScaleKeys.size()>2) { for(u32 j=0;j1) { for(u32 j=0;j= ScaleKeys[j+1].frame) //bad frame, unneed and may cause problems { ScaleKeys.erase(j+1); --j; } } } if (RotationKeys.size()>2) { for(u32 j=0;j1) { for(u32 j=0;j= RotationKeys[j+1].frame) //bad frame, unneed and may cause problems { RotationKeys.erase(j+1); --j; } } } //Fill empty keyframe areas if (PositionKeys.size()) { SPositionKey *Key; Key=&PositionKeys[0];//getFirst if (Key->frame!=0) { PositionKeys.push_front(*Key); Key=&PositionKeys[0];//getFirst Key->frame=0; } Key=&PositionKeys.getLast(); if (Key->frame!=AnimationFrames) { PositionKeys.push_back(*Key); Key=&PositionKeys.getLast(); Key->frame=AnimationFrames; } } if (ScaleKeys.size()) { SScaleKey *Key; Key=&ScaleKeys[0];//getFirst if (Key->frame!=0) { ScaleKeys.push_front(*Key); Key=&ScaleKeys[0];//getFirst Key->frame=0; } Key=&ScaleKeys.getLast(); if (Key->frame!=AnimationFrames) { ScaleKeys.push_back(*Key); Key=&ScaleKeys.getLast(); Key->frame=AnimationFrames; } } if (RotationKeys.size()) { SRotationKey *Key; Key=&RotationKeys[0];//getFirst if (Key->frame!=0) { RotationKeys.push_front(*Key); Key=&RotationKeys[0];//getFirst Key->frame=0; } Key=&RotationKeys.getLast(); if (Key->frame!=AnimationFrames) { RotationKeys.push_back(*Key); Key=&RotationKeys.getLast(); Key->frame=AnimationFrames; } } } } //Needed for animation and skinning... CalculateGlobalMatrices(0,0); //animateMesh(0, 1); //buildAll_LocalAnimatedMatrices(); //buildAll_GlobalAnimatedMatrices(); //rigid animation for non animated meshes for (i=0; iAttachedMeshes.size(); ++j) { SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ]; Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix; } } //calculate bounding box if (LocalBuffers.empty()) BoundingBox.reset(0,0,0); else { irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox); LocalBuffers[0]->Transformation.transformBoxEx(bb); BoundingBox.reset(bb); for (u32 j=1; jBoundingBox; LocalBuffers[j]->Transformation.transformBoxEx(bb); BoundingBox.addInternalBox(bb); } } } void CSkinnedMesh::updateBoundingBox(void) { if(!SkinningBuffers) return; core::array & buffer = *SkinningBuffers; BoundingBox.reset(0,0,0); if (!buffer.empty()) { for (u32 j=0; jrecalculateBoundingBox(); core::aabbox3df bb = buffer[j]->BoundingBox; buffer[j]->Transformation.transformBoxEx(bb); BoundingBox.addInternalBox(bb); } } } scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer() { scene::SSkinMeshBuffer *buffer=new scene::SSkinMeshBuffer(); LocalBuffers.push_back(buffer); return buffer; } CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent) { SJoint *joint=new SJoint; AllJoints.push_back(joint); if (!parent) { //Add root joints to array in finalize() } else { //Set parent (Be careful of the mesh loader also setting the parent) parent->Children.push_back(joint); } return joint; } CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint) { if (!joint) return 0; joint->PositionKeys.push_back(SPositionKey()); return &joint->PositionKeys.getLast(); } CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint) { if (!joint) return 0; joint->ScaleKeys.push_back(SScaleKey()); return &joint->ScaleKeys.getLast(); } CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint) { if (!joint) return 0; joint->RotationKeys.push_back(SRotationKey()); return &joint->RotationKeys.getLast(); } CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint) { if (!joint) return 0; joint->Weights.push_back(SWeight()); return &joint->Weights.getLast(); } bool CSkinnedMesh::isStatic() { return !HasAnimation; } void CSkinnedMesh::normalizeWeights() { // note: unsure if weights ids are going to be used. // Normalise the weights on bones.... u32 i,j; core::array< core::array > Vertices_TotalWeight; for (i=0; i()); Vertices_TotalWeight[i].set_used(LocalBuffers[i]->getVertexCount()); } for (i=0; iWeights.size(); ++j) { if (joint->Weights[j].strength<=0)//Check for invalid weights { joint->Weights.erase(j); --j; } else { Vertices_TotalWeight[ joint->Weights[j].buffer_id ] [ joint->Weights[j].vertex_id ] += joint->Weights[j].strength; } } } for (i=0; iWeights.size(); ++j) { const f32 total = Vertices_TotalWeight[ joint->Weights[j].buffer_id ] [ joint->Weights[j].vertex_id ]; if (total != 0 && total != 1) joint->Weights[j].strength /= total; } } } void CSkinnedMesh::recoverJointsFromMesh(core::array &JointChildSceneNodes) { for (u32 i=0;isetPosition( joint->LocalAnimatedMatrix.getTranslation() ); node->setRotation( joint->LocalAnimatedMatrix.getRotationDegrees() ); //node->setScale( joint->LocalAnimatedMatrix.getScale() ); node->positionHint=joint->positionHint; node->scaleHint=joint->scaleHint; node->rotationHint=joint->rotationHint; //node->setAbsoluteTransformation(joint->GlobalMatrix); //not going to work //Note: This updateAbsolutePosition will not work well if joints are not nested like b3d //node->updateAbsolutePosition(); } } void CSkinnedMesh::transferJointsToMesh(const core::array &JointChildSceneNodes) { for (u32 i=0; iLocalAnimatedMatrix.setTranslation(node->getPosition()); joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation()); //joint->LocalAnimatedMatrix.setScale( node->getScale() ); joint->positionHint=node->positionHint; joint->scaleHint=node->scaleHint; joint->rotationHint=node->rotationHint; if (node->getSkinningSpace()==EBSS_GLOBAL) joint->GlobalSkinningSpace=true; else joint->GlobalSkinningSpace=false; } //Remove cache, temp... LastAnimatedFrame=-1; LastSkinnedFrame=-1; } void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array &JointChildSceneNodes) { for (u32 i=0;ipositionHint=node->positionHint; joint->scaleHint=node->scaleHint; joint->rotationHint=node->rotationHint; } } void CSkinnedMesh::createJoints(core::array &JointChildSceneNodes, IAnimatedMeshSceneNode* AnimatedMeshSceneNode, ISceneManager* SceneManager) { u32 i; //Create new joints for (i=0;iName.c_str())); } //Match up parents for (i=0;iChildren.size();++n) { if (parentTest->Children[n]==joint) { parentID=j; break; } } } } if (parentID!=-1) node->setParent( JointChildSceneNodes[parentID] ); else node->setParent( AnimatedMeshSceneNode ); node->drop(); } } void CSkinnedMesh::convertMeshToTangents() { // now calculate tangents for (u32 b=0; b < LocalBuffers.size(); ++b) { if (LocalBuffers[b]) { LocalBuffers[b]->convertToTangents(); const s32 idxCnt = LocalBuffers[b]->getIndexCount(); u16* idx = LocalBuffers[b]->getIndices(); video::S3DVertexTangents* v = (video::S3DVertexTangents*)LocalBuffers[b]->getVertices(); for (s32 i=0; i