1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #include "IrrCompileConfig.h"
\r
6 #ifdef _IRR_COMPILE_WITH_SKINNED_MESH_SUPPORT_
\r
8 #include "CSkinnedMesh.h"
\r
9 #include "CBoneSceneNode.h"
\r
10 #include "IAnimatedMeshSceneNode.h"
\r
15 // Frames must always be increasing, so we remove objects where this isn't the case
\r
16 // return number of kicked keys
\r
17 template <class T> // T = objects containing a "frame" variable
\r
18 irr::u32 dropBadKeys(irr::core::array<T>& array)
\r
23 irr::u32 n=1; // new index
\r
24 for(irr::u32 j=1;j<array.size();++j)
\r
26 if (array[j].frame < array[n-1].frame)
\r
27 continue; //bad frame, unneeded and may cause problems
\r
29 array[n] = array[j];
\r
32 irr::u32 d = array.size()-n; // remove already copied keys
\r
40 // drop identical middle keys - we only need the first and last
\r
41 // return number of kicked keys
\r
42 template <class T, typename Cmp> // Cmp = comparison for keys of type T
\r
43 irr::u32 dropMiddleKeys(irr::core::array<T>& array, Cmp & cmp)
\r
45 if ( array.size() < 3 )
\r
48 irr::u32 s = 0; // old index for current key
\r
49 irr::u32 n = 1; // new index for next key
\r
50 for(irr::u32 j=1;j<array.size();++j)
\r
52 if ( cmp(array[j], array[s]) )
\r
53 continue; // same key, handle later
\r
55 if ( j > s+1 ) // had there been identical keys?
\r
56 array[n++] = array[j-1]; // keep the last
\r
57 array[n++] = array[j]; // keep the new one
\r
60 if ( array.size() > s+1 ) // identical keys at the array end?
\r
61 array[n++] = array[array.size()-1]; // keep the last
\r
63 irr::u32 d = array.size()-n; // remove already copied keys
\r
71 bool identicalPos(const irr::scene::ISkinnedMesh::SPositionKey& a, const irr::scene::ISkinnedMesh::SPositionKey& b)
\r
73 return a.position == b.position;
\r
76 bool identicalScale(const irr::scene::ISkinnedMesh::SScaleKey& a, const irr::scene::ISkinnedMesh::SScaleKey& b)
\r
78 return a.scale == b.scale;
\r
81 bool identicalRotation(const irr::scene::ISkinnedMesh::SRotationKey& a, const irr::scene::ISkinnedMesh::SRotationKey& b)
\r
83 return a.rotation == b.rotation;
\r
94 CSkinnedMesh::CSkinnedMesh()
\r
95 : SkinningBuffers(0), EndFrame(0.f), FramesPerSecond(25.f),
\r
96 LastAnimatedFrame(-1), SkinnedLastFrame(false),
\r
97 InterpolationMode(EIM_LINEAR),
\r
98 HasAnimation(false), PreparedForSkinning(false),
\r
99 AnimateNormals(true), HardwareSkinning(false)
\r
102 setDebugName("CSkinnedMesh");
\r
105 SkinningBuffers=&LocalBuffers;
\r
110 CSkinnedMesh::~CSkinnedMesh()
\r
112 for (u32 i=0; i<AllJoints.size(); ++i)
\r
113 delete AllJoints[i];
\r
115 for (u32 j=0; j<LocalBuffers.size(); ++j)
\r
117 if (LocalBuffers[j])
\r
118 LocalBuffers[j]->drop();
\r
123 //! returns the amount of frames in milliseconds.
\r
124 //! If the amount is 1, it is a static (=non animated) mesh.
\r
125 u32 CSkinnedMesh::getFrameCount() const
\r
127 return core::floor32(EndFrame+1.f);
\r
131 //! Gets the default animation speed of the animated mesh.
\r
132 /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
\r
133 f32 CSkinnedMesh::getAnimationSpeed() const
\r
135 return FramesPerSecond;
\r
139 //! Gets the frame count of the animated mesh.
\r
140 /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
\r
141 The actual speed is set in the scene node the mesh is instantiated in.*/
\r
142 void CSkinnedMesh::setAnimationSpeed(f32 fps)
\r
144 FramesPerSecond=fps;
\r
148 //! 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.
\r
149 IMesh* CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
\r
151 //animate(frame,startFrameLoop, endFrameLoop);
\r
155 animateMesh((f32)frame, 1.0f);
\r
161 //--------------------------------------------------------------------------
\r
162 // Keyframe Animation
\r
163 //--------------------------------------------------------------------------
\r
166 //! Animates this mesh's joints based on frame input
\r
167 //! blend: {0-old position, 1-New position}
\r
168 void CSkinnedMesh::animateMesh(f32 frame, f32 blend)
\r
170 if (!HasAnimation || LastAnimatedFrame==frame)
\r
173 LastAnimatedFrame=frame;
\r
174 SkinnedLastFrame=false;
\r
177 return; //No need to animate
\r
179 for (u32 i=0; i<AllJoints.size(); ++i)
\r
181 //The joints can be animated here with no input from their
\r
182 //parents, but for setAnimationMode extra checks are needed
\r
184 SJoint *joint = AllJoints[i];
\r
186 const core::vector3df oldPosition = joint->Animatedposition;
\r
187 const core::vector3df oldScale = joint->Animatedscale;
\r
188 const core::quaternion oldRotation = joint->Animatedrotation;
\r
190 core::vector3df position = oldPosition;
\r
191 core::vector3df scale = oldScale;
\r
192 core::quaternion rotation = oldRotation;
\r
194 getFrameData(frame, joint,
\r
195 position, joint->positionHint,
\r
196 scale, joint->scaleHint,
\r
197 rotation, joint->rotationHint);
\r
201 //No blending needed
\r
202 joint->Animatedposition = position;
\r
203 joint->Animatedscale = scale;
\r
204 joint->Animatedrotation = rotation;
\r
209 joint->Animatedposition = core::lerp(oldPosition, position, blend);
\r
210 joint->Animatedscale = core::lerp(oldScale, scale, blend);
\r
211 joint->Animatedrotation.slerp(oldRotation, rotation, blend);
\r
216 //LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
\r
217 //one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
\r
218 //a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
\r
222 buildAllLocalAnimatedMatrices();
\r
223 //-----------------
\r
225 updateBoundingBox();
\r
229 void CSkinnedMesh::buildAllLocalAnimatedMatrices()
\r
231 for (u32 i=0; i<AllJoints.size(); ++i)
\r
233 SJoint *joint = AllJoints[i];
\r
237 if (joint->UseAnimationFrom &&
\r
238 (joint->UseAnimationFrom->PositionKeys.size() ||
\r
239 joint->UseAnimationFrom->ScaleKeys.size() ||
\r
240 joint->UseAnimationFrom->RotationKeys.size() ))
\r
242 joint->GlobalSkinningSpace=false;
\r
244 // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
\r
245 // Not tested so far if this was correct or wrong before quaternion fix!
\r
246 joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix);
\r
248 // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() ---
\r
249 f32 *m1 = joint->LocalAnimatedMatrix.pointer();
\r
250 core::vector3df &Pos = joint->Animatedposition;
\r
251 m1[0] += Pos.X*m1[3];
\r
252 m1[1] += Pos.Y*m1[3];
\r
253 m1[2] += Pos.Z*m1[3];
\r
254 m1[4] += Pos.X*m1[7];
\r
255 m1[5] += Pos.Y*m1[7];
\r
256 m1[6] += Pos.Z*m1[7];
\r
257 m1[8] += Pos.X*m1[11];
\r
258 m1[9] += Pos.Y*m1[11];
\r
259 m1[10] += Pos.Z*m1[11];
\r
260 m1[12] += Pos.X*m1[15];
\r
261 m1[13] += Pos.Y*m1[15];
\r
262 m1[14] += Pos.Z*m1[15];
\r
263 // -----------------------------------
\r
265 if (joint->ScaleKeys.size())
\r
268 core::matrix4 scaleMatrix;
\r
269 scaleMatrix.setScale(joint->Animatedscale);
\r
270 joint->LocalAnimatedMatrix *= scaleMatrix;
\r
273 // -------- joint->LocalAnimatedMatrix *= scaleMatrix -----------------
\r
274 core::matrix4& mat = joint->LocalAnimatedMatrix;
\r
275 mat[0] *= joint->Animatedscale.X;
\r
276 mat[1] *= joint->Animatedscale.X;
\r
277 mat[2] *= joint->Animatedscale.X;
\r
278 mat[3] *= joint->Animatedscale.X;
\r
279 mat[4] *= joint->Animatedscale.Y;
\r
280 mat[5] *= joint->Animatedscale.Y;
\r
281 mat[6] *= joint->Animatedscale.Y;
\r
282 mat[7] *= joint->Animatedscale.Y;
\r
283 mat[8] *= joint->Animatedscale.Z;
\r
284 mat[9] *= joint->Animatedscale.Z;
\r
285 mat[10] *= joint->Animatedscale.Z;
\r
286 mat[11] *= joint->Animatedscale.Z;
\r
287 // -----------------------------------
\r
292 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
295 SkinnedLastFrame=false;
\r
299 void CSkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
\r
303 for (u32 i=0; i<RootJoints.size(); ++i)
\r
304 buildAllGlobalAnimatedMatrices(RootJoints[i], 0);
\r
309 // Find global matrix...
\r
310 if (!parentJoint || joint->GlobalSkinningSpace)
\r
311 joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
\r
313 joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
\r
316 for (u32 j=0; j<joint->Children.size(); ++j)
\r
317 buildAllGlobalAnimatedMatrices(joint->Children[j], joint);
\r
321 void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint,
\r
322 core::vector3df &position, s32 &positionHint,
\r
323 core::vector3df &scale, s32 &scaleHint,
\r
324 core::quaternion &rotation, s32 &rotationHint)
\r
326 s32 foundPositionIndex = -1;
\r
327 s32 foundScaleIndex = -1;
\r
328 s32 foundRotationIndex = -1;
\r
330 if (joint->UseAnimationFrom)
\r
332 const core::array<SPositionKey> &PositionKeys=joint->UseAnimationFrom->PositionKeys;
\r
333 const core::array<SScaleKey> &ScaleKeys=joint->UseAnimationFrom->ScaleKeys;
\r
334 const core::array<SRotationKey> &RotationKeys=joint->UseAnimationFrom->RotationKeys;
\r
336 if (PositionKeys.size())
\r
338 foundPositionIndex = -1;
\r
340 //Test the Hints...
\r
341 if (positionHint>=0 && (u32)positionHint < PositionKeys.size())
\r
344 if (positionHint>0 && PositionKeys[positionHint].frame>=frame && PositionKeys[positionHint-1].frame<frame )
\r
345 foundPositionIndex=positionHint;
\r
346 else if (positionHint+1 < (s32)PositionKeys.size())
\r
348 //check the next index
\r
349 if ( PositionKeys[positionHint+1].frame>=frame &&
\r
350 PositionKeys[positionHint+0].frame<frame)
\r
353 foundPositionIndex=positionHint;
\r
358 //The hint test failed, do a full scan...
\r
359 if (foundPositionIndex==-1)
\r
361 for (u32 i=0; i<PositionKeys.size(); ++i)
\r
363 if (PositionKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
365 foundPositionIndex=i;
\r
372 //Do interpolation...
\r
373 if (foundPositionIndex!=-1)
\r
375 if (InterpolationMode==EIM_CONSTANT || foundPositionIndex==0)
\r
377 position = PositionKeys[foundPositionIndex].position;
\r
379 else if (InterpolationMode==EIM_LINEAR)
\r
381 const SPositionKey& KeyA = PositionKeys[foundPositionIndex];
\r
382 const SPositionKey& KeyB = PositionKeys[foundPositionIndex-1];
\r
384 const f32 fd1 = frame - KeyA.frame;
\r
385 const f32 fd2 = KeyB.frame - frame;
\r
386 position = ((KeyB.position-KeyA.position)/(fd1+fd2))*fd1 + KeyA.position;
\r
391 //------------------------------------------------------------
\r
393 if (ScaleKeys.size())
\r
395 foundScaleIndex = -1;
\r
397 //Test the Hints...
\r
398 if (scaleHint>=0 && (u32)scaleHint < ScaleKeys.size())
\r
401 if (scaleHint>0 && ScaleKeys[scaleHint].frame>=frame && ScaleKeys[scaleHint-1].frame<frame )
\r
402 foundScaleIndex=scaleHint;
\r
403 else if (scaleHint+1 < (s32)ScaleKeys.size())
\r
405 //check the next index
\r
406 if ( ScaleKeys[scaleHint+1].frame>=frame &&
\r
407 ScaleKeys[scaleHint+0].frame<frame)
\r
410 foundScaleIndex=scaleHint;
\r
416 //The hint test failed, do a full scan...
\r
417 if (foundScaleIndex==-1)
\r
419 for (u32 i=0; i<ScaleKeys.size(); ++i)
\r
421 if (ScaleKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
430 //Do interpolation...
\r
431 if (foundScaleIndex!=-1)
\r
433 if (InterpolationMode==EIM_CONSTANT || foundScaleIndex==0)
\r
435 scale = ScaleKeys[foundScaleIndex].scale;
\r
437 else if (InterpolationMode==EIM_LINEAR)
\r
439 const SScaleKey& KeyA = ScaleKeys[foundScaleIndex];
\r
440 const SScaleKey& KeyB = ScaleKeys[foundScaleIndex-1];
\r
442 const f32 fd1 = frame - KeyA.frame;
\r
443 const f32 fd2 = KeyB.frame - frame;
\r
444 scale = ((KeyB.scale-KeyA.scale)/(fd1+fd2))*fd1 + KeyA.scale;
\r
449 //-------------------------------------------------------------
\r
451 if (RotationKeys.size())
\r
453 foundRotationIndex = -1;
\r
455 //Test the Hints...
\r
456 if (rotationHint>=0 && (u32)rotationHint < RotationKeys.size())
\r
459 if (rotationHint>0 && RotationKeys[rotationHint].frame>=frame && RotationKeys[rotationHint-1].frame<frame )
\r
460 foundRotationIndex=rotationHint;
\r
461 else if (rotationHint+1 < (s32)RotationKeys.size())
\r
463 //check the next index
\r
464 if ( RotationKeys[rotationHint+1].frame>=frame &&
\r
465 RotationKeys[rotationHint+0].frame<frame)
\r
468 foundRotationIndex=rotationHint;
\r
474 //The hint test failed, do a full scan...
\r
475 if (foundRotationIndex==-1)
\r
477 for (u32 i=0; i<RotationKeys.size(); ++i)
\r
479 if (RotationKeys[i].frame >= frame) //Keys should be sorted by frame
\r
481 foundRotationIndex=i;
\r
488 //Do interpolation...
\r
489 if (foundRotationIndex!=-1)
\r
491 if (InterpolationMode==EIM_CONSTANT || foundRotationIndex==0)
\r
493 rotation = RotationKeys[foundRotationIndex].rotation;
\r
495 else if (InterpolationMode==EIM_LINEAR)
\r
497 const SRotationKey& KeyA = RotationKeys[foundRotationIndex];
\r
498 const SRotationKey& KeyB = RotationKeys[foundRotationIndex-1];
\r
500 const f32 fd1 = frame - KeyA.frame;
\r
501 const f32 fd2 = KeyB.frame - frame;
\r
502 const f32 t = fd1/(fd1+fd2);
\r
506 if (KeyA.frame!=KeyB.frame)
\r
507 t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame);
\r
510 rotation.slerp(KeyA.rotation, KeyB.rotation, t);
\r
517 //--------------------------------------------------------------------------
\r
518 // Software Skinning
\r
519 //--------------------------------------------------------------------------
\r
521 //! Preforms a software skin on this mesh based of joint positions
\r
522 void CSkinnedMesh::skinMesh()
\r
524 if (!HasAnimation || SkinnedLastFrame)
\r
528 // This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
\r
529 buildAllGlobalAnimatedMatrices();
\r
530 //-----------------
\r
532 SkinnedLastFrame=true;
\r
533 if (!HardwareSkinning)
\r
535 //Software skin....
\r
539 for (i=0; i<AllJoints.size(); ++i)
\r
541 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
543 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
544 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
548 //clear skinning helper array
\r
549 for (i=0; i<Vertices_Moved.size(); ++i)
\r
550 for (u32 j=0; j<Vertices_Moved[i].size(); ++j)
\r
551 Vertices_Moved[i][j]=false;
\r
553 //skin starting with the root joints
\r
554 for (i=0; i<RootJoints.size(); ++i)
\r
555 skinJoint(RootJoints[i], 0);
\r
557 for (i=0; i<SkinningBuffers->size(); ++i)
\r
558 (*SkinningBuffers)[i]->setDirty(EBT_VERTEX);
\r
560 updateBoundingBox();
\r
564 void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
\r
566 if (joint->Weights.size())
\r
568 //Find this joints pull on vertices...
\r
569 core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING);
\r
570 jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix);
\r
572 core::vector3df thisVertexMove, thisNormalMove;
\r
574 core::array<scene::SSkinMeshBuffer*> &buffersUsed=*SkinningBuffers;
\r
576 //Skin Vertices Positions and Normals...
\r
577 for (u32 i=0; i<joint->Weights.size(); ++i)
\r
579 SWeight& weight = joint->Weights[i];
\r
581 // Pull this vertex...
\r
582 jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
\r
584 if (AnimateNormals)
\r
585 jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal);
\r
587 if (! (*(weight.Moved)) )
\r
589 *(weight.Moved) = true;
\r
591 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
\r
593 if (AnimateNormals)
\r
594 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
\r
596 //*(weight._Pos) = thisVertexMove * weight.strength;
\r
600 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
\r
602 if (AnimateNormals)
\r
603 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
\r
605 //*(weight._Pos) += thisVertexMove * weight.strength;
\r
608 buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
\r
612 //Skin all children
\r
613 for (u32 j=0; j<joint->Children.size(); ++j)
\r
614 skinJoint(joint->Children[j], joint);
\r
618 E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const
\r
620 return EAMT_SKINNED;
\r
624 //! Gets joint count.
\r
625 u32 CSkinnedMesh::getJointCount() const
\r
627 return AllJoints.size();
\r
631 //! Gets the name of a joint.
\r
632 const c8* CSkinnedMesh::getJointName(u32 number) const
\r
634 if (number >= AllJoints.size())
\r
636 return AllJoints[number]->Name.c_str();
\r
640 //! Gets a joint number from its name
\r
641 s32 CSkinnedMesh::getJointNumber(const c8* name) const
\r
643 for (u32 i=0; i<AllJoints.size(); ++i)
\r
645 if (AllJoints[i]->Name == name)
\r
653 //! returns amount of mesh buffers.
\r
654 u32 CSkinnedMesh::getMeshBufferCount() const
\r
656 return LocalBuffers.size();
\r
660 //! returns pointer to a mesh buffer
\r
661 IMeshBuffer* CSkinnedMesh::getMeshBuffer(u32 nr) const
\r
663 if (nr < LocalBuffers.size())
\r
664 return LocalBuffers[nr];
\r
670 //! Returns pointer to a mesh buffer which fits a material
\r
671 IMeshBuffer* CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
\r
673 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
675 if (LocalBuffers[i]->getMaterial() == material)
\r
676 return LocalBuffers[i];
\r
682 //! returns an axis aligned bounding box
\r
683 const core::aabbox3d<f32>& CSkinnedMesh::getBoundingBox() const
\r
685 return BoundingBox;
\r
689 //! set user axis aligned bounding box
\r
690 void CSkinnedMesh::setBoundingBox( const core::aabbox3df& box)
\r
696 //! sets a flag of all contained materials to a new value
\r
697 void CSkinnedMesh::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
\r
699 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
700 LocalBuffers[i]->Material.setFlag(flag,newvalue);
\r
704 //! set the hardware mapping hint, for driver
\r
705 void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
\r
706 E_BUFFER_TYPE buffer)
\r
708 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
709 LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
\r
713 //! flags the meshbuffer as changed, reloads hardware buffers
\r
714 void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
\r
716 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
717 LocalBuffers[i]->setDirty(buffer);
\r
721 //! uses animation from another mesh
\r
722 bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh)
\r
724 bool unmatched=false;
\r
726 for(u32 i=0;i<AllJoints.size();++i)
\r
728 SJoint *joint=AllJoints[i];
\r
729 joint->UseAnimationFrom=0;
\r
731 if (joint->Name=="")
\r
735 for(u32 j=0;j<mesh->getAllJoints().size();++j)
\r
737 SJoint *otherJoint=mesh->getAllJoints()[j];
\r
738 if (joint->Name==otherJoint->Name)
\r
740 joint->UseAnimationFrom=otherJoint;
\r
743 if (!joint->UseAnimationFrom)
\r
748 checkForAnimation();
\r
754 //!Update Normals when Animating
\r
755 //!False= Don't animate them, faster
\r
756 //!True= Update normals (default)
\r
757 void CSkinnedMesh::updateNormalsWhenAnimating(bool on)
\r
759 AnimateNormals = on;
\r
763 //!Sets Interpolation Mode
\r
764 void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode)
\r
766 InterpolationMode = mode;
\r
770 core::array<scene::SSkinMeshBuffer*> &CSkinnedMesh::getMeshBuffers()
\r
772 return LocalBuffers;
\r
776 core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints()
\r
782 const core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints() const
\r
788 //! (This feature is not implemented in irrlicht yet)
\r
789 bool CSkinnedMesh::setHardwareSkinning(bool on)
\r
791 if (HardwareSkinning!=on)
\r
796 //set mesh to static pose...
\r
797 for (u32 i=0; i<AllJoints.size(); ++i)
\r
799 SJoint *joint=AllJoints[i];
\r
800 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
802 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
803 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
804 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
805 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
806 LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
\r
811 HardwareSkinning=on;
\r
813 return HardwareSkinning;
\r
816 void CSkinnedMesh::refreshJointCache()
\r
818 //copy cache from the mesh...
\r
819 for (u32 i=0; i<AllJoints.size(); ++i)
\r
821 SJoint *joint=AllJoints[i];
\r
822 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
824 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
825 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
826 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
827 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
832 void CSkinnedMesh::resetAnimation()
\r
834 //copy from the cache to the mesh...
\r
835 for (u32 i=0; i<AllJoints.size(); ++i)
\r
837 SJoint *joint=AllJoints[i];
\r
838 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
840 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
841 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
842 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
843 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
846 SkinnedLastFrame = false;
\r
847 LastAnimatedFrame = -1;
\r
850 void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint,SJoint *parentJoint)
\r
852 if (!joint && parentJoint) // bit of protection from endless loops
\r
855 //Go through the root bones
\r
858 for (u32 i=0; i<RootJoints.size(); ++i)
\r
859 calculateGlobalMatrices(RootJoints[i],0);
\r
864 joint->GlobalMatrix = joint->LocalMatrix;
\r
866 joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
\r
868 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
869 joint->GlobalAnimatedMatrix=joint->GlobalMatrix;
\r
871 if (joint->GlobalInversedMatrix.isIdentity())//might be pre calculated
\r
873 joint->GlobalInversedMatrix = joint->GlobalMatrix;
\r
874 joint->GlobalInversedMatrix.makeInverse(); // slow
\r
877 for (u32 j=0; j<joint->Children.size(); ++j)
\r
878 calculateGlobalMatrices(joint->Children[j],joint);
\r
879 SkinnedLastFrame=false;
\r
883 void CSkinnedMesh::checkForAnimation()
\r
886 //Check for animation...
\r
887 HasAnimation = false;
\r
888 for(i=0;i<AllJoints.size();++i)
\r
890 if (AllJoints[i]->UseAnimationFrom)
\r
892 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() ||
\r
893 AllJoints[i]->UseAnimationFrom->ScaleKeys.size() ||
\r
894 AllJoints[i]->UseAnimationFrom->RotationKeys.size() )
\r
896 HasAnimation = true;
\r
901 //meshes with weights, are still counted as animated for ragdolls, etc
\r
904 for(i=0;i<AllJoints.size();++i)
\r
906 if (AllJoints[i]->Weights.size())
\r
907 HasAnimation = true;
\r
913 //--- Find the length of the animation ---
\r
915 for(i=0;i<AllJoints.size();++i)
\r
917 if (AllJoints[i]->UseAnimationFrom)
\r
919 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size())
\r
920 if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > EndFrame)
\r
921 EndFrame=AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame;
\r
923 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size())
\r
924 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > EndFrame)
\r
925 EndFrame=AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame;
\r
927 if (AllJoints[i]->UseAnimationFrom->RotationKeys.size())
\r
928 if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > EndFrame)
\r
929 EndFrame=AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame;
\r
934 if (HasAnimation && !PreparedForSkinning)
\r
936 PreparedForSkinning=true;
\r
939 for(i=0; i < AllJoints.size(); ++i)
\r
941 SJoint *joint = AllJoints[i];
\r
942 for (j=0; j<joint->Weights.size(); ++j)
\r
944 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
945 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
947 //check for invalid ids
\r
948 if (buffer_id>=LocalBuffers.size())
\r
950 os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
\r
951 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
953 else if (vertex_id>=LocalBuffers[buffer_id]->getVertexCount())
\r
955 os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
\r
956 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
961 //An array used in skinning
\r
963 for (i=0; i<Vertices_Moved.size(); ++i)
\r
964 for (j=0; j<Vertices_Moved[i].size(); ++j)
\r
965 Vertices_Moved[i][j] = false;
\r
967 // For skinning: cache weight values for speed
\r
969 for (i=0; i<AllJoints.size(); ++i)
\r
971 SJoint *joint = AllJoints[i];
\r
972 for (j=0; j<joint->Weights.size(); ++j)
\r
974 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
975 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
977 joint->Weights[j].Moved = &Vertices_Moved[buffer_id] [vertex_id];
\r
978 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
979 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
981 //joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
985 // normalize weights
\r
986 normalizeWeights();
\r
988 SkinnedLastFrame=false;
\r
991 //! called by loader after populating with mesh and bone data
\r
992 void CSkinnedMesh::finalize()
\r
994 os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
\r
997 // Make sure we recalc the next frame
\r
998 LastAnimatedFrame=-1;
\r
999 SkinnedLastFrame=false;
\r
1001 //calculate bounding box
\r
1002 for (i=0; i<LocalBuffers.size(); ++i)
\r
1004 LocalBuffers[i]->recalculateBoundingBox();
\r
1007 if (AllJoints.size() || RootJoints.size())
\r
1009 // populate AllJoints or RootJoints, depending on which is empty
\r
1010 if (!RootJoints.size())
\r
1013 for(u32 CheckingIdx=0; CheckingIdx < AllJoints.size(); ++CheckingIdx)
\r
1016 bool foundParent=false;
\r
1017 for(i=0; i < AllJoints.size(); ++i)
\r
1019 for(u32 n=0; n < AllJoints[i]->Children.size(); ++n)
\r
1021 if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx])
\r
1027 RootJoints.push_back(AllJoints[CheckingIdx]);
\r
1032 AllJoints=RootJoints;
\r
1036 for(i=0; i < AllJoints.size(); ++i)
\r
1038 AllJoints[i]->UseAnimationFrom=AllJoints[i];
\r
1041 //Set array sizes...
\r
1043 for (i=0; i<LocalBuffers.size(); ++i)
\r
1045 Vertices_Moved.push_back( core::array<bool>() );
\r
1046 Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1049 checkForAnimation();
\r
1053 irr::u32 redundantPosKeys = 0;
\r
1054 irr::u32 unorderedPosKeys = 0;
\r
1055 irr::u32 redundantScaleKeys = 0;
\r
1056 irr::u32 unorderedScaleKeys = 0;
\r
1057 irr::u32 redundantRotationKeys = 0;
\r
1058 irr::u32 unorderedRotationKeys = 0;
\r
1060 //--- optimize and check keyframes ---
\r
1061 for(i=0;i<AllJoints.size();++i)
\r
1063 core::array<SPositionKey> &PositionKeys =AllJoints[i]->PositionKeys;
\r
1064 core::array<SScaleKey> &ScaleKeys = AllJoints[i]->ScaleKeys;
\r
1065 core::array<SRotationKey> &RotationKeys = AllJoints[i]->RotationKeys;
\r
1067 // redundant = identical middle keys - we only need the first and last frame
\r
1068 // unordered = frames which are out of order - we can't handle those
\r
1069 redundantPosKeys += dropMiddleKeys<SPositionKey>(PositionKeys, identicalPos);
\r
1070 unorderedPosKeys += dropBadKeys<SPositionKey>(PositionKeys);
\r
1071 redundantScaleKeys += dropMiddleKeys<SScaleKey>(ScaleKeys, identicalScale);
\r
1072 unorderedScaleKeys += dropBadKeys<SScaleKey>(ScaleKeys);
\r
1073 redundantRotationKeys += dropMiddleKeys<SRotationKey>(RotationKeys, identicalRotation);
\r
1074 unorderedRotationKeys += dropBadKeys<SRotationKey>(RotationKeys);
\r
1076 //Fill empty keyframe areas
\r
1077 if (PositionKeys.size())
\r
1079 SPositionKey *Key;
\r
1080 Key=&PositionKeys[0];//getFirst
\r
1081 if (Key->frame!=0)
\r
1083 PositionKeys.push_front(*Key);
\r
1084 Key=&PositionKeys[0];//getFirst
\r
1088 Key=&PositionKeys.getLast();
\r
1089 if (Key->frame!=EndFrame)
\r
1091 PositionKeys.push_back(*Key);
\r
1092 Key=&PositionKeys.getLast();
\r
1093 Key->frame=EndFrame;
\r
1097 if (ScaleKeys.size())
\r
1100 Key=&ScaleKeys[0];//getFirst
\r
1101 if (Key->frame!=0)
\r
1103 ScaleKeys.push_front(*Key);
\r
1104 Key=&ScaleKeys[0];//getFirst
\r
1108 Key=&ScaleKeys.getLast();
\r
1109 if (Key->frame!=EndFrame)
\r
1111 ScaleKeys.push_back(*Key);
\r
1112 Key=&ScaleKeys.getLast();
\r
1113 Key->frame=EndFrame;
\r
1117 if (RotationKeys.size())
\r
1119 SRotationKey *Key;
\r
1120 Key=&RotationKeys[0];//getFirst
\r
1121 if (Key->frame!=0)
\r
1123 RotationKeys.push_front(*Key);
\r
1124 Key=&RotationKeys[0];//getFirst
\r
1128 Key=&RotationKeys.getLast();
\r
1129 if (Key->frame!=EndFrame)
\r
1131 RotationKeys.push_back(*Key);
\r
1132 Key=&RotationKeys.getLast();
\r
1133 Key->frame=EndFrame;
\r
1138 if ( redundantPosKeys > 0 )
\r
1140 os::Printer::log("Skinned Mesh - redundant position frames kicked:", core::stringc(redundantPosKeys).c_str(), ELL_DEBUG);
\r
1142 if ( unorderedPosKeys > 0 )
\r
1144 irr::os::Printer::log("Skinned Mesh - unsorted position frames kicked:", irr::core::stringc(unorderedPosKeys).c_str(), irr::ELL_DEBUG);
\r
1146 if ( redundantScaleKeys > 0 )
\r
1148 os::Printer::log("Skinned Mesh - redundant scale frames kicked:", core::stringc(redundantScaleKeys).c_str(), ELL_DEBUG);
\r
1150 if ( unorderedScaleKeys > 0 )
\r
1152 irr::os::Printer::log("Skinned Mesh - unsorted scale frames kicked:", irr::core::stringc(unorderedScaleKeys).c_str(), irr::ELL_DEBUG);
\r
1154 if ( redundantRotationKeys > 0 )
\r
1156 os::Printer::log("Skinned Mesh - redundant rotation frames kicked:", core::stringc(redundantRotationKeys).c_str(), ELL_DEBUG);
\r
1158 if ( unorderedRotationKeys > 0 )
\r
1160 irr::os::Printer::log("Skinned Mesh - unsorted rotation frames kicked:", irr::core::stringc(unorderedRotationKeys).c_str(), irr::ELL_DEBUG);
\r
1164 //Needed for animation and skinning...
\r
1166 calculateGlobalMatrices(0,0);
\r
1168 //animateMesh(0, 1);
\r
1169 //buildAllLocalAnimatedMatrices();
\r
1170 //buildAllGlobalAnimatedMatrices();
\r
1172 //rigid animation for non animated meshes
\r
1173 for (i=0; i<AllJoints.size(); ++i)
\r
1175 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
1177 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
1178 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
1182 //calculate bounding box
\r
1183 if (LocalBuffers.empty())
\r
1184 BoundingBox.reset(0,0,0);
\r
1187 irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
\r
1188 LocalBuffers[0]->Transformation.transformBoxEx(bb);
\r
1189 BoundingBox.reset(bb);
\r
1191 for (u32 j=1; j<LocalBuffers.size(); ++j)
\r
1193 bb = LocalBuffers[j]->BoundingBox;
\r
1194 LocalBuffers[j]->Transformation.transformBoxEx(bb);
\r
1196 BoundingBox.addInternalBox(bb);
\r
1202 void CSkinnedMesh::updateBoundingBox(void)
\r
1204 if(!SkinningBuffers)
\r
1207 core::array<SSkinMeshBuffer*> & buffer = *SkinningBuffers;
\r
1208 BoundingBox.reset(0,0,0);
\r
1210 if (!buffer.empty())
\r
1212 for (u32 j=0; j<buffer.size(); ++j)
\r
1214 buffer[j]->recalculateBoundingBox();
\r
1215 core::aabbox3df bb = buffer[j]->BoundingBox;
\r
1216 buffer[j]->Transformation.transformBoxEx(bb);
\r
1218 BoundingBox.addInternalBox(bb);
\r
1224 scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer()
\r
1226 scene::SSkinMeshBuffer *buffer=new scene::SSkinMeshBuffer();
\r
1227 LocalBuffers.push_back(buffer);
\r
1232 CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent)
\r
1234 SJoint *joint=new SJoint;
\r
1236 AllJoints.push_back(joint);
\r
1239 //Add root joints to array in finalize()
\r
1243 //Set parent (Be careful of the mesh loader also setting the parent)
\r
1244 parent->Children.push_back(joint);
\r
1251 CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint)
\r
1256 joint->PositionKeys.push_back(SPositionKey());
\r
1257 return &joint->PositionKeys.getLast();
\r
1261 CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint)
\r
1266 joint->ScaleKeys.push_back(SScaleKey());
\r
1267 return &joint->ScaleKeys.getLast();
\r
1271 CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint)
\r
1276 joint->RotationKeys.push_back(SRotationKey());
\r
1277 return &joint->RotationKeys.getLast();
\r
1281 CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint)
\r
1286 joint->Weights.push_back(SWeight());
\r
1287 return &joint->Weights.getLast();
\r
1291 bool CSkinnedMesh::isStatic()
\r
1293 return !HasAnimation;
\r
1297 void CSkinnedMesh::normalizeWeights()
\r
1299 // note: unsure if weights ids are going to be used.
\r
1301 // Normalise the weights on bones....
\r
1304 core::array< core::array<f32> > verticesTotalWeight;
\r
1306 verticesTotalWeight.reallocate(LocalBuffers.size());
\r
1307 for (i=0; i<LocalBuffers.size(); ++i)
\r
1309 verticesTotalWeight.push_back(core::array<f32>());
\r
1310 verticesTotalWeight[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1313 for (i=0; i<verticesTotalWeight.size(); ++i)
\r
1314 for (j=0; j<verticesTotalWeight[i].size(); ++j)
\r
1315 verticesTotalWeight[i][j] = 0;
\r
1317 for (i=0; i<AllJoints.size(); ++i)
\r
1319 SJoint *joint=AllJoints[i];
\r
1320 for (j=0; j<joint->Weights.size(); ++j)
\r
1322 if (joint->Weights[j].strength<=0)//Check for invalid weights
\r
1324 joint->Weights.erase(j);
\r
1329 verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id] += joint->Weights[j].strength;
\r
1334 for (i=0; i<AllJoints.size(); ++i)
\r
1336 SJoint *joint=AllJoints[i];
\r
1337 for (j=0; j< joint->Weights.size(); ++j)
\r
1339 const f32 total = verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id];
\r
1340 if (total != 0 && total != 1)
\r
1341 joint->Weights[j].strength /= total;
\r
1347 void CSkinnedMesh::recoverJointsFromMesh(core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1349 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1351 IBoneSceneNode* node=jointChildSceneNodes[i];
\r
1352 SJoint *joint=AllJoints[i];
\r
1353 node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
\r
1354 node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
\r
1355 node->setScale(joint->LocalAnimatedMatrix.getScale());
\r
1357 node->positionHint=joint->positionHint;
\r
1358 node->scaleHint=joint->scaleHint;
\r
1359 node->rotationHint=joint->rotationHint;
\r
1361 node->updateAbsolutePosition();
\r
1366 void CSkinnedMesh::transferJointsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1368 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1370 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1371 SJoint *joint=AllJoints[i];
\r
1373 joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
\r
1374 joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
\r
1375 joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
\r
1377 joint->positionHint=node->positionHint;
\r
1378 joint->scaleHint=node->scaleHint;
\r
1379 joint->rotationHint=node->rotationHint;
\r
1381 joint->GlobalSkinningSpace=(node->getSkinningSpace()==EBSS_GLOBAL);
\r
1383 // Make sure we recalc the next frame
\r
1384 LastAnimatedFrame=-1;
\r
1385 SkinnedLastFrame=false;
\r
1389 void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1391 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1393 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1394 SJoint *joint=AllJoints[i];
\r
1396 joint->positionHint=node->positionHint;
\r
1397 joint->scaleHint=node->scaleHint;
\r
1398 joint->rotationHint=node->rotationHint;
\r
1400 SkinnedLastFrame=false;
\r
1404 void CSkinnedMesh::addJoints(core::array<IBoneSceneNode*> &jointChildSceneNodes,
\r
1405 IAnimatedMeshSceneNode* node, ISceneManager* smgr)
\r
1407 //Create new joints
\r
1408 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1410 jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name.c_str()));
\r
1413 //Match up parents
\r
1414 for (u32 i=0; i<jointChildSceneNodes.size(); ++i)
\r
1416 const SJoint* const joint=AllJoints[i]; //should be fine
\r
1420 for (u32 j=0;(parentID==-1)&&(j<AllJoints.size());++j)
\r
1424 const SJoint* const parentTest=AllJoints[j];
\r
1425 for (u32 n=0; n<parentTest->Children.size(); ++n)
\r
1427 if (parentTest->Children[n]==joint)
\r
1436 IBoneSceneNode* bone=jointChildSceneNodes[i];
\r
1438 bone->setParent(jointChildSceneNodes[parentID]);
\r
1440 bone->setParent(node);
\r
1444 SkinnedLastFrame=false;
\r
1448 void CSkinnedMesh::convertMeshToTangents()
\r
1450 // now calculate tangents
\r
1451 for (u32 b=0; b < LocalBuffers.size(); ++b)
\r
1453 if (LocalBuffers[b])
\r
1455 LocalBuffers[b]->convertToTangents();
\r
1457 const s32 idxCnt = LocalBuffers[b]->getIndexCount();
\r
1459 u16* idx = LocalBuffers[b]->getIndices();
\r
1460 video::S3DVertexTangents* v =
\r
1461 (video::S3DVertexTangents*)LocalBuffers[b]->getVertices();
\r
1463 for (s32 i=0; i<idxCnt; i+=3)
\r
1465 calculateTangents(
\r
1466 v[idx[i+0]].Normal,
\r
1467 v[idx[i+0]].Tangent,
\r
1468 v[idx[i+0]].Binormal,
\r
1472 v[idx[i+0]].TCoords,
\r
1473 v[idx[i+1]].TCoords,
\r
1474 v[idx[i+2]].TCoords);
\r
1476 calculateTangents(
\r
1477 v[idx[i+1]].Normal,
\r
1478 v[idx[i+1]].Tangent,
\r
1479 v[idx[i+1]].Binormal,
\r
1483 v[idx[i+1]].TCoords,
\r
1484 v[idx[i+2]].TCoords,
\r
1485 v[idx[i+0]].TCoords);
\r
1487 calculateTangents(
\r
1488 v[idx[i+2]].Normal,
\r
1489 v[idx[i+2]].Tangent,
\r
1490 v[idx[i+2]].Binormal,
\r
1494 v[idx[i+2]].TCoords,
\r
1495 v[idx[i+0]].TCoords,
\r
1496 v[idx[i+1]].TCoords);
\r
1503 void CSkinnedMesh::calculateTangents(
\r
1504 core::vector3df& normal,
\r
1505 core::vector3df& tangent,
\r
1506 core::vector3df& binormal,
\r
1507 const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices
\r
1508 const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords
\r
1510 core::vector3df v1 = vt1 - vt2;
\r
1511 core::vector3df v2 = vt3 - vt1;
\r
1512 normal = v2.crossProduct(v1);
\r
1513 normal.normalize();
\r
1517 f32 deltaX1 = tc1.X - tc2.X;
\r
1518 f32 deltaX2 = tc3.X - tc1.X;
\r
1519 binormal = (v1 * deltaX2) - (v2 * deltaX1);
\r
1520 binormal.normalize();
\r
1524 f32 deltaY1 = tc1.Y - tc2.Y;
\r
1525 f32 deltaY2 = tc3.Y - tc1.Y;
\r
1526 tangent = (v1 * deltaY2) - (v2 * deltaY1);
\r
1527 tangent.normalize();
\r
1531 core::vector3df txb = tangent.crossProduct(binormal);
\r
1532 if (txb.dotProduct(normal) < 0.0f)
\r
1535 binormal *= -1.0f;
\r
1540 } // end namespace scene
\r
1541 } // end namespace irr
\r
1543 #endif // _IRR_COMPILE_WITH_SKINNED_MESH_SUPPORT_
\r