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 #include "CSkinnedMesh.h"
\r
7 #include "CBoneSceneNode.h"
\r
8 #include "IAnimatedMeshSceneNode.h"
\r
13 // Frames must always be increasing, so we remove objects where this isn't the case
\r
14 // return number of kicked keys
\r
15 template <class T> // T = objects containing a "frame" variable
\r
16 irr::u32 dropBadKeys(irr::core::array<T>& array)
\r
21 irr::u32 n=1; // new index
\r
22 for(irr::u32 j=1;j<array.size();++j)
\r
24 if (array[j].frame < array[n-1].frame)
\r
25 continue; //bad frame, unneeded and may cause problems
\r
27 array[n] = array[j];
\r
30 irr::u32 d = array.size()-n; // remove already copied keys
\r
38 // drop identical middle keys - we only need the first and last
\r
39 // return number of kicked keys
\r
40 template <class T, typename Cmp> // Cmp = comparison for keys of type T
\r
41 irr::u32 dropMiddleKeys(irr::core::array<T>& array, Cmp & cmp)
\r
43 if ( array.size() < 3 )
\r
46 irr::u32 s = 0; // old index for current key
\r
47 irr::u32 n = 1; // new index for next key
\r
48 for(irr::u32 j=1;j<array.size();++j)
\r
50 if ( cmp(array[j], array[s]) )
\r
51 continue; // same key, handle later
\r
53 if ( j > s+1 ) // had there been identical keys?
\r
54 array[n++] = array[j-1]; // keep the last
\r
55 array[n++] = array[j]; // keep the new one
\r
58 if ( array.size() > s+1 ) // identical keys at the array end?
\r
59 array[n++] = array[array.size()-1]; // keep the last
\r
61 irr::u32 d = array.size()-n; // remove already copied keys
\r
69 bool identicalPos(const irr::scene::ISkinnedMesh::SPositionKey& a, const irr::scene::ISkinnedMesh::SPositionKey& b)
\r
71 return a.position == b.position;
\r
74 bool identicalScale(const irr::scene::ISkinnedMesh::SScaleKey& a, const irr::scene::ISkinnedMesh::SScaleKey& b)
\r
76 return a.scale == b.scale;
\r
79 bool identicalRotation(const irr::scene::ISkinnedMesh::SRotationKey& a, const irr::scene::ISkinnedMesh::SRotationKey& b)
\r
81 return a.rotation == b.rotation;
\r
92 CSkinnedMesh::CSkinnedMesh()
\r
93 : SkinningBuffers(0), EndFrame(0.f), FramesPerSecond(25.f),
\r
94 LastAnimatedFrame(-1), SkinnedLastFrame(false),
\r
95 InterpolationMode(EIM_LINEAR),
\r
96 HasAnimation(false), PreparedForSkinning(false),
\r
97 AnimateNormals(true), HardwareSkinning(false)
\r
100 setDebugName("CSkinnedMesh");
\r
103 SkinningBuffers=&LocalBuffers;
\r
108 CSkinnedMesh::~CSkinnedMesh()
\r
110 for (u32 i=0; i<AllJoints.size(); ++i)
\r
111 delete AllJoints[i];
\r
113 for (u32 j=0; j<LocalBuffers.size(); ++j)
\r
115 if (LocalBuffers[j])
\r
116 LocalBuffers[j]->drop();
\r
121 //! returns the amount of frames in milliseconds.
\r
122 //! If the amount is 1, it is a static (=non animated) mesh.
\r
123 u32 CSkinnedMesh::getFrameCount() const
\r
125 return core::floor32(EndFrame+1.f);
\r
129 //! Gets the default animation speed of the animated mesh.
\r
130 /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
\r
131 f32 CSkinnedMesh::getAnimationSpeed() const
\r
133 return FramesPerSecond;
\r
137 //! Gets the frame count of the animated mesh.
\r
138 /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
\r
139 The actual speed is set in the scene node the mesh is instantiated in.*/
\r
140 void CSkinnedMesh::setAnimationSpeed(f32 fps)
\r
142 FramesPerSecond=fps;
\r
146 //! 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
147 IMesh* CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
\r
149 //animate(frame,startFrameLoop, endFrameLoop);
\r
153 animateMesh((f32)frame, 1.0f);
\r
159 //--------------------------------------------------------------------------
\r
160 // Keyframe Animation
\r
161 //--------------------------------------------------------------------------
\r
164 //! Animates this mesh's joints based on frame input
\r
165 //! blend: {0-old position, 1-New position}
\r
166 void CSkinnedMesh::animateMesh(f32 frame, f32 blend)
\r
168 if (!HasAnimation || LastAnimatedFrame==frame)
\r
171 LastAnimatedFrame=frame;
\r
172 SkinnedLastFrame=false;
\r
175 return; //No need to animate
\r
177 for (u32 i=0; i<AllJoints.size(); ++i)
\r
179 //The joints can be animated here with no input from their
\r
180 //parents, but for setAnimationMode extra checks are needed
\r
182 SJoint *joint = AllJoints[i];
\r
184 const core::vector3df oldPosition = joint->Animatedposition;
\r
185 const core::vector3df oldScale = joint->Animatedscale;
\r
186 const core::quaternion oldRotation = joint->Animatedrotation;
\r
188 core::vector3df position = oldPosition;
\r
189 core::vector3df scale = oldScale;
\r
190 core::quaternion rotation = oldRotation;
\r
192 getFrameData(frame, joint,
\r
193 position, joint->positionHint,
\r
194 scale, joint->scaleHint,
\r
195 rotation, joint->rotationHint);
\r
199 //No blending needed
\r
200 joint->Animatedposition = position;
\r
201 joint->Animatedscale = scale;
\r
202 joint->Animatedrotation = rotation;
\r
207 joint->Animatedposition = core::lerp(oldPosition, position, blend);
\r
208 joint->Animatedscale = core::lerp(oldScale, scale, blend);
\r
209 joint->Animatedrotation.slerp(oldRotation, rotation, blend);
\r
214 //LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
\r
215 //one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
\r
216 //a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
\r
220 buildAllLocalAnimatedMatrices();
\r
221 //-----------------
\r
223 updateBoundingBox();
\r
227 void CSkinnedMesh::buildAllLocalAnimatedMatrices()
\r
229 for (u32 i=0; i<AllJoints.size(); ++i)
\r
231 SJoint *joint = AllJoints[i];
\r
235 if (joint->UseAnimationFrom &&
\r
236 (joint->UseAnimationFrom->PositionKeys.size() ||
\r
237 joint->UseAnimationFrom->ScaleKeys.size() ||
\r
238 joint->UseAnimationFrom->RotationKeys.size() ))
\r
240 joint->GlobalSkinningSpace=false;
\r
242 // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
\r
243 // Not tested so far if this was correct or wrong before quaternion fix!
\r
244 joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix);
\r
246 // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() ---
\r
247 f32 *m1 = joint->LocalAnimatedMatrix.pointer();
\r
248 core::vector3df &Pos = joint->Animatedposition;
\r
249 m1[0] += Pos.X*m1[3];
\r
250 m1[1] += Pos.Y*m1[3];
\r
251 m1[2] += Pos.Z*m1[3];
\r
252 m1[4] += Pos.X*m1[7];
\r
253 m1[5] += Pos.Y*m1[7];
\r
254 m1[6] += Pos.Z*m1[7];
\r
255 m1[8] += Pos.X*m1[11];
\r
256 m1[9] += Pos.Y*m1[11];
\r
257 m1[10] += Pos.Z*m1[11];
\r
258 m1[12] += Pos.X*m1[15];
\r
259 m1[13] += Pos.Y*m1[15];
\r
260 m1[14] += Pos.Z*m1[15];
\r
261 // -----------------------------------
\r
263 if (joint->ScaleKeys.size())
\r
266 core::matrix4 scaleMatrix;
\r
267 scaleMatrix.setScale(joint->Animatedscale);
\r
268 joint->LocalAnimatedMatrix *= scaleMatrix;
\r
271 // -------- joint->LocalAnimatedMatrix *= scaleMatrix -----------------
\r
272 core::matrix4& mat = joint->LocalAnimatedMatrix;
\r
273 mat[0] *= joint->Animatedscale.X;
\r
274 mat[1] *= joint->Animatedscale.X;
\r
275 mat[2] *= joint->Animatedscale.X;
\r
276 mat[3] *= joint->Animatedscale.X;
\r
277 mat[4] *= joint->Animatedscale.Y;
\r
278 mat[5] *= joint->Animatedscale.Y;
\r
279 mat[6] *= joint->Animatedscale.Y;
\r
280 mat[7] *= joint->Animatedscale.Y;
\r
281 mat[8] *= joint->Animatedscale.Z;
\r
282 mat[9] *= joint->Animatedscale.Z;
\r
283 mat[10] *= joint->Animatedscale.Z;
\r
284 mat[11] *= joint->Animatedscale.Z;
\r
285 // -----------------------------------
\r
290 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
293 SkinnedLastFrame=false;
\r
297 void CSkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
\r
301 for (u32 i=0; i<RootJoints.size(); ++i)
\r
302 buildAllGlobalAnimatedMatrices(RootJoints[i], 0);
\r
307 // Find global matrix...
\r
308 if (!parentJoint || joint->GlobalSkinningSpace)
\r
309 joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
\r
311 joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
\r
314 for (u32 j=0; j<joint->Children.size(); ++j)
\r
315 buildAllGlobalAnimatedMatrices(joint->Children[j], joint);
\r
319 void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint,
\r
320 core::vector3df &position, s32 &positionHint,
\r
321 core::vector3df &scale, s32 &scaleHint,
\r
322 core::quaternion &rotation, s32 &rotationHint)
\r
324 s32 foundPositionIndex = -1;
\r
325 s32 foundScaleIndex = -1;
\r
326 s32 foundRotationIndex = -1;
\r
328 if (joint->UseAnimationFrom)
\r
330 const core::array<SPositionKey> &PositionKeys=joint->UseAnimationFrom->PositionKeys;
\r
331 const core::array<SScaleKey> &ScaleKeys=joint->UseAnimationFrom->ScaleKeys;
\r
332 const core::array<SRotationKey> &RotationKeys=joint->UseAnimationFrom->RotationKeys;
\r
334 if (PositionKeys.size())
\r
336 foundPositionIndex = -1;
\r
338 //Test the Hints...
\r
339 if (positionHint>=0 && (u32)positionHint < PositionKeys.size())
\r
342 if (positionHint>0 && PositionKeys[positionHint].frame>=frame && PositionKeys[positionHint-1].frame<frame )
\r
343 foundPositionIndex=positionHint;
\r
344 else if (positionHint+1 < (s32)PositionKeys.size())
\r
346 //check the next index
\r
347 if ( PositionKeys[positionHint+1].frame>=frame &&
\r
348 PositionKeys[positionHint+0].frame<frame)
\r
351 foundPositionIndex=positionHint;
\r
356 //The hint test failed, do a full scan...
\r
357 if (foundPositionIndex==-1)
\r
359 for (u32 i=0; i<PositionKeys.size(); ++i)
\r
361 if (PositionKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
363 foundPositionIndex=i;
\r
370 //Do interpolation...
\r
371 if (foundPositionIndex!=-1)
\r
373 if (InterpolationMode==EIM_CONSTANT || foundPositionIndex==0)
\r
375 position = PositionKeys[foundPositionIndex].position;
\r
377 else if (InterpolationMode==EIM_LINEAR)
\r
379 const SPositionKey& KeyA = PositionKeys[foundPositionIndex];
\r
380 const SPositionKey& KeyB = PositionKeys[foundPositionIndex-1];
\r
382 const f32 fd1 = frame - KeyA.frame;
\r
383 const f32 fd2 = KeyB.frame - frame;
\r
384 position = ((KeyB.position-KeyA.position)/(fd1+fd2))*fd1 + KeyA.position;
\r
389 //------------------------------------------------------------
\r
391 if (ScaleKeys.size())
\r
393 foundScaleIndex = -1;
\r
395 //Test the Hints...
\r
396 if (scaleHint>=0 && (u32)scaleHint < ScaleKeys.size())
\r
399 if (scaleHint>0 && ScaleKeys[scaleHint].frame>=frame && ScaleKeys[scaleHint-1].frame<frame )
\r
400 foundScaleIndex=scaleHint;
\r
401 else if (scaleHint+1 < (s32)ScaleKeys.size())
\r
403 //check the next index
\r
404 if ( ScaleKeys[scaleHint+1].frame>=frame &&
\r
405 ScaleKeys[scaleHint+0].frame<frame)
\r
408 foundScaleIndex=scaleHint;
\r
414 //The hint test failed, do a full scan...
\r
415 if (foundScaleIndex==-1)
\r
417 for (u32 i=0; i<ScaleKeys.size(); ++i)
\r
419 if (ScaleKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
428 //Do interpolation...
\r
429 if (foundScaleIndex!=-1)
\r
431 if (InterpolationMode==EIM_CONSTANT || foundScaleIndex==0)
\r
433 scale = ScaleKeys[foundScaleIndex].scale;
\r
435 else if (InterpolationMode==EIM_LINEAR)
\r
437 const SScaleKey& KeyA = ScaleKeys[foundScaleIndex];
\r
438 const SScaleKey& KeyB = ScaleKeys[foundScaleIndex-1];
\r
440 const f32 fd1 = frame - KeyA.frame;
\r
441 const f32 fd2 = KeyB.frame - frame;
\r
442 scale = ((KeyB.scale-KeyA.scale)/(fd1+fd2))*fd1 + KeyA.scale;
\r
447 //-------------------------------------------------------------
\r
449 if (RotationKeys.size())
\r
451 foundRotationIndex = -1;
\r
453 //Test the Hints...
\r
454 if (rotationHint>=0 && (u32)rotationHint < RotationKeys.size())
\r
457 if (rotationHint>0 && RotationKeys[rotationHint].frame>=frame && RotationKeys[rotationHint-1].frame<frame )
\r
458 foundRotationIndex=rotationHint;
\r
459 else if (rotationHint+1 < (s32)RotationKeys.size())
\r
461 //check the next index
\r
462 if ( RotationKeys[rotationHint+1].frame>=frame &&
\r
463 RotationKeys[rotationHint+0].frame<frame)
\r
466 foundRotationIndex=rotationHint;
\r
472 //The hint test failed, do a full scan...
\r
473 if (foundRotationIndex==-1)
\r
475 for (u32 i=0; i<RotationKeys.size(); ++i)
\r
477 if (RotationKeys[i].frame >= frame) //Keys should be sorted by frame
\r
479 foundRotationIndex=i;
\r
486 //Do interpolation...
\r
487 if (foundRotationIndex!=-1)
\r
489 if (InterpolationMode==EIM_CONSTANT || foundRotationIndex==0)
\r
491 rotation = RotationKeys[foundRotationIndex].rotation;
\r
493 else if (InterpolationMode==EIM_LINEAR)
\r
495 const SRotationKey& KeyA = RotationKeys[foundRotationIndex];
\r
496 const SRotationKey& KeyB = RotationKeys[foundRotationIndex-1];
\r
498 const f32 fd1 = frame - KeyA.frame;
\r
499 const f32 fd2 = KeyB.frame - frame;
\r
500 const f32 t = fd1/(fd1+fd2);
\r
504 if (KeyA.frame!=KeyB.frame)
\r
505 t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame);
\r
508 rotation.slerp(KeyA.rotation, KeyB.rotation, t);
\r
515 //--------------------------------------------------------------------------
\r
516 // Software Skinning
\r
517 //--------------------------------------------------------------------------
\r
519 //! Preforms a software skin on this mesh based of joint positions
\r
520 void CSkinnedMesh::skinMesh()
\r
522 if (!HasAnimation || SkinnedLastFrame)
\r
526 // This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
\r
527 buildAllGlobalAnimatedMatrices();
\r
528 //-----------------
\r
530 SkinnedLastFrame=true;
\r
531 if (!HardwareSkinning)
\r
533 //Software skin....
\r
537 for (i=0; i<AllJoints.size(); ++i)
\r
539 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
541 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
542 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
546 //clear skinning helper array
\r
547 for (i=0; i<Vertices_Moved.size(); ++i)
\r
548 for (u32 j=0; j<Vertices_Moved[i].size(); ++j)
\r
549 Vertices_Moved[i][j]=false;
\r
551 //skin starting with the root joints
\r
552 for (i=0; i<RootJoints.size(); ++i)
\r
553 skinJoint(RootJoints[i], 0);
\r
555 for (i=0; i<SkinningBuffers->size(); ++i)
\r
556 (*SkinningBuffers)[i]->setDirty(EBT_VERTEX);
\r
558 updateBoundingBox();
\r
562 void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
\r
564 if (joint->Weights.size())
\r
566 //Find this joints pull on vertices...
\r
567 core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING);
\r
568 jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix);
\r
570 core::vector3df thisVertexMove, thisNormalMove;
\r
572 core::array<scene::SSkinMeshBuffer*> &buffersUsed=*SkinningBuffers;
\r
574 //Skin Vertices Positions and Normals...
\r
575 for (u32 i=0; i<joint->Weights.size(); ++i)
\r
577 SWeight& weight = joint->Weights[i];
\r
579 // Pull this vertex...
\r
580 jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
\r
582 if (AnimateNormals)
\r
583 jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal);
\r
585 if (! (*(weight.Moved)) )
\r
587 *(weight.Moved) = true;
\r
589 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
\r
591 if (AnimateNormals)
\r
592 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
\r
594 //*(weight._Pos) = thisVertexMove * weight.strength;
\r
598 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
\r
600 if (AnimateNormals)
\r
601 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
\r
603 //*(weight._Pos) += thisVertexMove * weight.strength;
\r
606 buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
\r
610 //Skin all children
\r
611 for (u32 j=0; j<joint->Children.size(); ++j)
\r
612 skinJoint(joint->Children[j], joint);
\r
616 E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const
\r
618 return EAMT_SKINNED;
\r
622 //! Gets joint count.
\r
623 u32 CSkinnedMesh::getJointCount() const
\r
625 return AllJoints.size();
\r
629 //! Gets the name of a joint.
\r
630 const c8* CSkinnedMesh::getJointName(u32 number) const
\r
632 if (number >= AllJoints.size())
\r
634 return AllJoints[number]->Name.c_str();
\r
638 //! Gets a joint number from its name
\r
639 s32 CSkinnedMesh::getJointNumber(const c8* name) const
\r
641 for (u32 i=0; i<AllJoints.size(); ++i)
\r
643 if (AllJoints[i]->Name == name)
\r
651 //! returns amount of mesh buffers.
\r
652 u32 CSkinnedMesh::getMeshBufferCount() const
\r
654 return LocalBuffers.size();
\r
658 //! returns pointer to a mesh buffer
\r
659 IMeshBuffer* CSkinnedMesh::getMeshBuffer(u32 nr) const
\r
661 if (nr < LocalBuffers.size())
\r
662 return LocalBuffers[nr];
\r
668 //! Returns pointer to a mesh buffer which fits a material
\r
669 IMeshBuffer* CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
\r
671 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
673 if (LocalBuffers[i]->getMaterial() == material)
\r
674 return LocalBuffers[i];
\r
680 //! returns an axis aligned bounding box
\r
681 const core::aabbox3d<f32>& CSkinnedMesh::getBoundingBox() const
\r
683 return BoundingBox;
\r
687 //! set user axis aligned bounding box
\r
688 void CSkinnedMesh::setBoundingBox( const core::aabbox3df& box)
\r
694 //! sets a flag of all contained materials to a new value
\r
695 void CSkinnedMesh::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
\r
697 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
698 LocalBuffers[i]->Material.setFlag(flag,newvalue);
\r
702 //! set the hardware mapping hint, for driver
\r
703 void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
\r
704 E_BUFFER_TYPE buffer)
\r
706 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
707 LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
\r
711 //! flags the meshbuffer as changed, reloads hardware buffers
\r
712 void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
\r
714 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
715 LocalBuffers[i]->setDirty(buffer);
\r
719 //! uses animation from another mesh
\r
720 bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh)
\r
722 bool unmatched=false;
\r
724 for(u32 i=0;i<AllJoints.size();++i)
\r
726 SJoint *joint=AllJoints[i];
\r
727 joint->UseAnimationFrom=0;
\r
729 if (joint->Name=="")
\r
733 for(u32 j=0;j<mesh->getAllJoints().size();++j)
\r
735 SJoint *otherJoint=mesh->getAllJoints()[j];
\r
736 if (joint->Name==otherJoint->Name)
\r
738 joint->UseAnimationFrom=otherJoint;
\r
741 if (!joint->UseAnimationFrom)
\r
746 checkForAnimation();
\r
752 //!Update Normals when Animating
\r
753 //!False= Don't animate them, faster
\r
754 //!True= Update normals (default)
\r
755 void CSkinnedMesh::updateNormalsWhenAnimating(bool on)
\r
757 AnimateNormals = on;
\r
761 //!Sets Interpolation Mode
\r
762 void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode)
\r
764 InterpolationMode = mode;
\r
768 core::array<scene::SSkinMeshBuffer*> &CSkinnedMesh::getMeshBuffers()
\r
770 return LocalBuffers;
\r
774 core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints()
\r
780 const core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints() const
\r
786 //! (This feature is not implemented in irrlicht yet)
\r
787 bool CSkinnedMesh::setHardwareSkinning(bool on)
\r
789 if (HardwareSkinning!=on)
\r
794 //set mesh to static pose...
\r
795 for (u32 i=0; i<AllJoints.size(); ++i)
\r
797 SJoint *joint=AllJoints[i];
\r
798 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
800 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
801 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
802 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
803 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
804 LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
\r
809 HardwareSkinning=on;
\r
811 return HardwareSkinning;
\r
814 void CSkinnedMesh::refreshJointCache()
\r
816 //copy cache from the mesh...
\r
817 for (u32 i=0; i<AllJoints.size(); ++i)
\r
819 SJoint *joint=AllJoints[i];
\r
820 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
822 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
823 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
824 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
825 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
830 void CSkinnedMesh::resetAnimation()
\r
832 //copy from the cache to the mesh...
\r
833 for (u32 i=0; i<AllJoints.size(); ++i)
\r
835 SJoint *joint=AllJoints[i];
\r
836 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
838 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
839 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
840 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
841 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
844 SkinnedLastFrame = false;
\r
845 LastAnimatedFrame = -1;
\r
848 void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint,SJoint *parentJoint)
\r
850 if (!joint && parentJoint) // bit of protection from endless loops
\r
853 //Go through the root bones
\r
856 for (u32 i=0; i<RootJoints.size(); ++i)
\r
857 calculateGlobalMatrices(RootJoints[i],0);
\r
862 joint->GlobalMatrix = joint->LocalMatrix;
\r
864 joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
\r
866 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
867 joint->GlobalAnimatedMatrix=joint->GlobalMatrix;
\r
869 if (joint->GlobalInversedMatrix.isIdentity())//might be pre calculated
\r
871 joint->GlobalInversedMatrix = joint->GlobalMatrix;
\r
872 joint->GlobalInversedMatrix.makeInverse(); // slow
\r
875 for (u32 j=0; j<joint->Children.size(); ++j)
\r
876 calculateGlobalMatrices(joint->Children[j],joint);
\r
877 SkinnedLastFrame=false;
\r
881 void CSkinnedMesh::checkForAnimation()
\r
884 //Check for animation...
\r
885 HasAnimation = false;
\r
886 for(i=0;i<AllJoints.size();++i)
\r
888 if (AllJoints[i]->UseAnimationFrom)
\r
890 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() ||
\r
891 AllJoints[i]->UseAnimationFrom->ScaleKeys.size() ||
\r
892 AllJoints[i]->UseAnimationFrom->RotationKeys.size() )
\r
894 HasAnimation = true;
\r
899 //meshes with weights, are still counted as animated for ragdolls, etc
\r
902 for(i=0;i<AllJoints.size();++i)
\r
904 if (AllJoints[i]->Weights.size())
\r
905 HasAnimation = true;
\r
911 //--- Find the length of the animation ---
\r
913 for(i=0;i<AllJoints.size();++i)
\r
915 if (AllJoints[i]->UseAnimationFrom)
\r
917 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size())
\r
918 if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > EndFrame)
\r
919 EndFrame=AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame;
\r
921 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size())
\r
922 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > EndFrame)
\r
923 EndFrame=AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame;
\r
925 if (AllJoints[i]->UseAnimationFrom->RotationKeys.size())
\r
926 if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > EndFrame)
\r
927 EndFrame=AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame;
\r
932 if (HasAnimation && !PreparedForSkinning)
\r
934 PreparedForSkinning=true;
\r
937 for(i=0; i < AllJoints.size(); ++i)
\r
939 SJoint *joint = AllJoints[i];
\r
940 for (j=0; j<joint->Weights.size(); ++j)
\r
942 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
943 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
945 //check for invalid ids
\r
946 if (buffer_id>=LocalBuffers.size())
\r
948 os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
\r
949 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
951 else if (vertex_id>=LocalBuffers[buffer_id]->getVertexCount())
\r
953 os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
\r
954 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
959 //An array used in skinning
\r
961 for (i=0; i<Vertices_Moved.size(); ++i)
\r
962 for (j=0; j<Vertices_Moved[i].size(); ++j)
\r
963 Vertices_Moved[i][j] = false;
\r
965 // For skinning: cache weight values for speed
\r
967 for (i=0; i<AllJoints.size(); ++i)
\r
969 SJoint *joint = AllJoints[i];
\r
970 for (j=0; j<joint->Weights.size(); ++j)
\r
972 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
973 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
975 joint->Weights[j].Moved = &Vertices_Moved[buffer_id] [vertex_id];
\r
976 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
977 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
979 //joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
983 // normalize weights
\r
984 normalizeWeights();
\r
986 SkinnedLastFrame=false;
\r
989 //! called by loader after populating with mesh and bone data
\r
990 void CSkinnedMesh::finalize()
\r
992 os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
\r
995 // Make sure we recalc the next frame
\r
996 LastAnimatedFrame=-1;
\r
997 SkinnedLastFrame=false;
\r
999 //calculate bounding box
\r
1000 for (i=0; i<LocalBuffers.size(); ++i)
\r
1002 LocalBuffers[i]->recalculateBoundingBox();
\r
1005 if (AllJoints.size() || RootJoints.size())
\r
1007 // populate AllJoints or RootJoints, depending on which is empty
\r
1008 if (!RootJoints.size())
\r
1011 for(u32 CheckingIdx=0; CheckingIdx < AllJoints.size(); ++CheckingIdx)
\r
1014 bool foundParent=false;
\r
1015 for(i=0; i < AllJoints.size(); ++i)
\r
1017 for(u32 n=0; n < AllJoints[i]->Children.size(); ++n)
\r
1019 if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx])
\r
1025 RootJoints.push_back(AllJoints[CheckingIdx]);
\r
1030 AllJoints=RootJoints;
\r
1034 for(i=0; i < AllJoints.size(); ++i)
\r
1036 AllJoints[i]->UseAnimationFrom=AllJoints[i];
\r
1039 //Set array sizes...
\r
1041 for (i=0; i<LocalBuffers.size(); ++i)
\r
1043 Vertices_Moved.push_back( core::array<char>() );
\r
1044 Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1047 checkForAnimation();
\r
1051 irr::u32 redundantPosKeys = 0;
\r
1052 irr::u32 unorderedPosKeys = 0;
\r
1053 irr::u32 redundantScaleKeys = 0;
\r
1054 irr::u32 unorderedScaleKeys = 0;
\r
1055 irr::u32 redundantRotationKeys = 0;
\r
1056 irr::u32 unorderedRotationKeys = 0;
\r
1058 //--- optimize and check keyframes ---
\r
1059 for(i=0;i<AllJoints.size();++i)
\r
1061 core::array<SPositionKey> &PositionKeys =AllJoints[i]->PositionKeys;
\r
1062 core::array<SScaleKey> &ScaleKeys = AllJoints[i]->ScaleKeys;
\r
1063 core::array<SRotationKey> &RotationKeys = AllJoints[i]->RotationKeys;
\r
1065 // redundant = identical middle keys - we only need the first and last frame
\r
1066 // unordered = frames which are out of order - we can't handle those
\r
1067 redundantPosKeys += dropMiddleKeys<SPositionKey>(PositionKeys, identicalPos);
\r
1068 unorderedPosKeys += dropBadKeys<SPositionKey>(PositionKeys);
\r
1069 redundantScaleKeys += dropMiddleKeys<SScaleKey>(ScaleKeys, identicalScale);
\r
1070 unorderedScaleKeys += dropBadKeys<SScaleKey>(ScaleKeys);
\r
1071 redundantRotationKeys += dropMiddleKeys<SRotationKey>(RotationKeys, identicalRotation);
\r
1072 unorderedRotationKeys += dropBadKeys<SRotationKey>(RotationKeys);
\r
1074 //Fill empty keyframe areas
\r
1075 if (PositionKeys.size())
\r
1077 SPositionKey *Key;
\r
1078 Key=&PositionKeys[0];//getFirst
\r
1079 if (Key->frame!=0)
\r
1081 PositionKeys.push_front(*Key);
\r
1082 Key=&PositionKeys[0];//getFirst
\r
1086 Key=&PositionKeys.getLast();
\r
1087 if (Key->frame!=EndFrame)
\r
1089 PositionKeys.push_back(*Key);
\r
1090 Key=&PositionKeys.getLast();
\r
1091 Key->frame=EndFrame;
\r
1095 if (ScaleKeys.size())
\r
1098 Key=&ScaleKeys[0];//getFirst
\r
1099 if (Key->frame!=0)
\r
1101 ScaleKeys.push_front(*Key);
\r
1102 Key=&ScaleKeys[0];//getFirst
\r
1106 Key=&ScaleKeys.getLast();
\r
1107 if (Key->frame!=EndFrame)
\r
1109 ScaleKeys.push_back(*Key);
\r
1110 Key=&ScaleKeys.getLast();
\r
1111 Key->frame=EndFrame;
\r
1115 if (RotationKeys.size())
\r
1117 SRotationKey *Key;
\r
1118 Key=&RotationKeys[0];//getFirst
\r
1119 if (Key->frame!=0)
\r
1121 RotationKeys.push_front(*Key);
\r
1122 Key=&RotationKeys[0];//getFirst
\r
1126 Key=&RotationKeys.getLast();
\r
1127 if (Key->frame!=EndFrame)
\r
1129 RotationKeys.push_back(*Key);
\r
1130 Key=&RotationKeys.getLast();
\r
1131 Key->frame=EndFrame;
\r
1136 if ( redundantPosKeys > 0 )
\r
1138 os::Printer::log("Skinned Mesh - redundant position frames kicked:", core::stringc(redundantPosKeys).c_str(), ELL_DEBUG);
\r
1140 if ( unorderedPosKeys > 0 )
\r
1142 irr::os::Printer::log("Skinned Mesh - unsorted position frames kicked:", irr::core::stringc(unorderedPosKeys).c_str(), irr::ELL_DEBUG);
\r
1144 if ( redundantScaleKeys > 0 )
\r
1146 os::Printer::log("Skinned Mesh - redundant scale frames kicked:", core::stringc(redundantScaleKeys).c_str(), ELL_DEBUG);
\r
1148 if ( unorderedScaleKeys > 0 )
\r
1150 irr::os::Printer::log("Skinned Mesh - unsorted scale frames kicked:", irr::core::stringc(unorderedScaleKeys).c_str(), irr::ELL_DEBUG);
\r
1152 if ( redundantRotationKeys > 0 )
\r
1154 os::Printer::log("Skinned Mesh - redundant rotation frames kicked:", core::stringc(redundantRotationKeys).c_str(), ELL_DEBUG);
\r
1156 if ( unorderedRotationKeys > 0 )
\r
1158 irr::os::Printer::log("Skinned Mesh - unsorted rotation frames kicked:", irr::core::stringc(unorderedRotationKeys).c_str(), irr::ELL_DEBUG);
\r
1162 //Needed for animation and skinning...
\r
1164 calculateGlobalMatrices(0,0);
\r
1166 //animateMesh(0, 1);
\r
1167 //buildAllLocalAnimatedMatrices();
\r
1168 //buildAllGlobalAnimatedMatrices();
\r
1170 //rigid animation for non animated meshes
\r
1171 for (i=0; i<AllJoints.size(); ++i)
\r
1173 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
1175 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
1176 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
1180 //calculate bounding box
\r
1181 if (LocalBuffers.empty())
\r
1182 BoundingBox.reset(0,0,0);
\r
1185 irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
\r
1186 LocalBuffers[0]->Transformation.transformBoxEx(bb);
\r
1187 BoundingBox.reset(bb);
\r
1189 for (u32 j=1; j<LocalBuffers.size(); ++j)
\r
1191 bb = LocalBuffers[j]->BoundingBox;
\r
1192 LocalBuffers[j]->Transformation.transformBoxEx(bb);
\r
1194 BoundingBox.addInternalBox(bb);
\r
1200 void CSkinnedMesh::updateBoundingBox(void)
\r
1202 if(!SkinningBuffers)
\r
1205 core::array<SSkinMeshBuffer*> & buffer = *SkinningBuffers;
\r
1206 BoundingBox.reset(0,0,0);
\r
1208 if (!buffer.empty())
\r
1210 for (u32 j=0; j<buffer.size(); ++j)
\r
1212 buffer[j]->recalculateBoundingBox();
\r
1213 core::aabbox3df bb = buffer[j]->BoundingBox;
\r
1214 buffer[j]->Transformation.transformBoxEx(bb);
\r
1216 BoundingBox.addInternalBox(bb);
\r
1222 scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer()
\r
1224 scene::SSkinMeshBuffer *buffer=new scene::SSkinMeshBuffer();
\r
1225 LocalBuffers.push_back(buffer);
\r
1230 CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent)
\r
1232 SJoint *joint=new SJoint;
\r
1234 AllJoints.push_back(joint);
\r
1237 //Add root joints to array in finalize()
\r
1241 //Set parent (Be careful of the mesh loader also setting the parent)
\r
1242 parent->Children.push_back(joint);
\r
1249 CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint)
\r
1254 joint->PositionKeys.push_back(SPositionKey());
\r
1255 return &joint->PositionKeys.getLast();
\r
1259 CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint)
\r
1264 joint->ScaleKeys.push_back(SScaleKey());
\r
1265 return &joint->ScaleKeys.getLast();
\r
1269 CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint)
\r
1274 joint->RotationKeys.push_back(SRotationKey());
\r
1275 return &joint->RotationKeys.getLast();
\r
1279 CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint)
\r
1284 joint->Weights.push_back(SWeight());
\r
1285 return &joint->Weights.getLast();
\r
1289 bool CSkinnedMesh::isStatic()
\r
1291 return !HasAnimation;
\r
1295 void CSkinnedMesh::normalizeWeights()
\r
1297 // note: unsure if weights ids are going to be used.
\r
1299 // Normalise the weights on bones....
\r
1302 core::array< core::array<f32> > verticesTotalWeight;
\r
1304 verticesTotalWeight.reallocate(LocalBuffers.size());
\r
1305 for (i=0; i<LocalBuffers.size(); ++i)
\r
1307 verticesTotalWeight.push_back(core::array<f32>());
\r
1308 verticesTotalWeight[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1311 for (i=0; i<verticesTotalWeight.size(); ++i)
\r
1312 for (j=0; j<verticesTotalWeight[i].size(); ++j)
\r
1313 verticesTotalWeight[i][j] = 0;
\r
1315 for (i=0; i<AllJoints.size(); ++i)
\r
1317 SJoint *joint=AllJoints[i];
\r
1318 for (j=0; j<joint->Weights.size(); ++j)
\r
1320 if (joint->Weights[j].strength<=0)//Check for invalid weights
\r
1322 joint->Weights.erase(j);
\r
1327 verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id] += joint->Weights[j].strength;
\r
1332 for (i=0; i<AllJoints.size(); ++i)
\r
1334 SJoint *joint=AllJoints[i];
\r
1335 for (j=0; j< joint->Weights.size(); ++j)
\r
1337 const f32 total = verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id];
\r
1338 if (total != 0 && total != 1)
\r
1339 joint->Weights[j].strength /= total;
\r
1345 void CSkinnedMesh::recoverJointsFromMesh(core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1347 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1349 IBoneSceneNode* node=jointChildSceneNodes[i];
\r
1350 SJoint *joint=AllJoints[i];
\r
1351 node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
\r
1352 node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
\r
1353 node->setScale(joint->LocalAnimatedMatrix.getScale());
\r
1355 node->positionHint=joint->positionHint;
\r
1356 node->scaleHint=joint->scaleHint;
\r
1357 node->rotationHint=joint->rotationHint;
\r
1359 node->updateAbsolutePosition();
\r
1364 void CSkinnedMesh::transferJointsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1366 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1368 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1369 SJoint *joint=AllJoints[i];
\r
1371 joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
\r
1372 joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
\r
1373 joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
\r
1375 joint->positionHint=node->positionHint;
\r
1376 joint->scaleHint=node->scaleHint;
\r
1377 joint->rotationHint=node->rotationHint;
\r
1379 joint->GlobalSkinningSpace=(node->getSkinningSpace()==EBSS_GLOBAL);
\r
1381 // Make sure we recalc the next frame
\r
1382 LastAnimatedFrame=-1;
\r
1383 SkinnedLastFrame=false;
\r
1387 void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1389 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1391 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1392 SJoint *joint=AllJoints[i];
\r
1394 joint->positionHint=node->positionHint;
\r
1395 joint->scaleHint=node->scaleHint;
\r
1396 joint->rotationHint=node->rotationHint;
\r
1398 SkinnedLastFrame=false;
\r
1402 void CSkinnedMesh::addJoints(core::array<IBoneSceneNode*> &jointChildSceneNodes,
\r
1403 IAnimatedMeshSceneNode* node, ISceneManager* smgr)
\r
1405 //Create new joints
\r
1406 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1408 jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name.c_str()));
\r
1411 //Match up parents
\r
1412 for (u32 i=0; i<jointChildSceneNodes.size(); ++i)
\r
1414 const SJoint* const joint=AllJoints[i]; //should be fine
\r
1418 for (u32 j=0;(parentID==-1)&&(j<AllJoints.size());++j)
\r
1422 const SJoint* const parentTest=AllJoints[j];
\r
1423 for (u32 n=0; n<parentTest->Children.size(); ++n)
\r
1425 if (parentTest->Children[n]==joint)
\r
1434 IBoneSceneNode* bone=jointChildSceneNodes[i];
\r
1436 bone->setParent(jointChildSceneNodes[parentID]);
\r
1438 bone->setParent(node);
\r
1442 SkinnedLastFrame=false;
\r
1446 void CSkinnedMesh::convertMeshToTangents()
\r
1448 // now calculate tangents
\r
1449 for (u32 b=0; b < LocalBuffers.size(); ++b)
\r
1451 if (LocalBuffers[b])
\r
1453 LocalBuffers[b]->convertToTangents();
\r
1455 const s32 idxCnt = LocalBuffers[b]->getIndexCount();
\r
1457 u16* idx = LocalBuffers[b]->getIndices();
\r
1458 video::S3DVertexTangents* v =
\r
1459 (video::S3DVertexTangents*)LocalBuffers[b]->getVertices();
\r
1461 for (s32 i=0; i<idxCnt; i+=3)
\r
1463 calculateTangents(
\r
1464 v[idx[i+0]].Normal,
\r
1465 v[idx[i+0]].Tangent,
\r
1466 v[idx[i+0]].Binormal,
\r
1470 v[idx[i+0]].TCoords,
\r
1471 v[idx[i+1]].TCoords,
\r
1472 v[idx[i+2]].TCoords);
\r
1474 calculateTangents(
\r
1475 v[idx[i+1]].Normal,
\r
1476 v[idx[i+1]].Tangent,
\r
1477 v[idx[i+1]].Binormal,
\r
1481 v[idx[i+1]].TCoords,
\r
1482 v[idx[i+2]].TCoords,
\r
1483 v[idx[i+0]].TCoords);
\r
1485 calculateTangents(
\r
1486 v[idx[i+2]].Normal,
\r
1487 v[idx[i+2]].Tangent,
\r
1488 v[idx[i+2]].Binormal,
\r
1492 v[idx[i+2]].TCoords,
\r
1493 v[idx[i+0]].TCoords,
\r
1494 v[idx[i+1]].TCoords);
\r
1501 void CSkinnedMesh::calculateTangents(
\r
1502 core::vector3df& normal,
\r
1503 core::vector3df& tangent,
\r
1504 core::vector3df& binormal,
\r
1505 const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices
\r
1506 const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords
\r
1508 core::vector3df v1 = vt1 - vt2;
\r
1509 core::vector3df v2 = vt3 - vt1;
\r
1510 normal = v2.crossProduct(v1);
\r
1511 normal.normalize();
\r
1515 f32 deltaX1 = tc1.X - tc2.X;
\r
1516 f32 deltaX2 = tc3.X - tc1.X;
\r
1517 binormal = (v1 * deltaX2) - (v2 * deltaX1);
\r
1518 binormal.normalize();
\r
1522 f32 deltaY1 = tc1.Y - tc2.Y;
\r
1523 f32 deltaY2 = tc3.Y - tc1.Y;
\r
1524 tangent = (v1 * deltaY2) - (v2 * deltaY1);
\r
1525 tangent.normalize();
\r
1529 core::vector3df txb = tangent.crossProduct(binormal);
\r
1530 if (txb.dotProduct(normal) < 0.0f)
\r
1533 binormal *= -1.0f;
\r
1538 } // end namespace scene
\r
1539 } // end namespace irr
\r