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 "CSkinnedMesh.h"
\r
6 #include "CBoneSceneNode.h"
\r
7 #include "IAnimatedMeshSceneNode.h"
\r
12 // Frames must always be increasing, so we remove objects where this isn't the case
\r
13 // return number of kicked keys
\r
14 template <class T> // T = objects containing a "frame" variable
\r
15 irr::u32 dropBadKeys(irr::core::array<T>& array)
\r
20 irr::u32 n=1; // new index
\r
21 for(irr::u32 j=1;j<array.size();++j)
\r
23 if (array[j].frame < array[n-1].frame)
\r
24 continue; //bad frame, unneeded and may cause problems
\r
26 array[n] = array[j];
\r
29 irr::u32 d = array.size()-n; // remove already copied keys
\r
37 // drop identical middle keys - we only need the first and last
\r
38 // return number of kicked keys
\r
39 template <class T, typename Cmp> // Cmp = comparison for keys of type T
\r
40 irr::u32 dropMiddleKeys(irr::core::array<T>& array, Cmp & cmp)
\r
42 if ( array.size() < 3 )
\r
45 irr::u32 s = 0; // old index for current key
\r
46 irr::u32 n = 1; // new index for next key
\r
47 for(irr::u32 j=1;j<array.size();++j)
\r
49 if ( cmp(array[j], array[s]) )
\r
50 continue; // same key, handle later
\r
52 if ( j > s+1 ) // had there been identical keys?
\r
53 array[n++] = array[j-1]; // keep the last
\r
54 array[n++] = array[j]; // keep the new one
\r
57 if ( array.size() > s+1 ) // identical keys at the array end?
\r
58 array[n++] = array[array.size()-1]; // keep the last
\r
60 irr::u32 d = array.size()-n; // remove already copied keys
\r
68 bool identicalPos(const irr::scene::ISkinnedMesh::SPositionKey& a, const irr::scene::ISkinnedMesh::SPositionKey& b)
\r
70 return a.position == b.position;
\r
73 bool identicalScale(const irr::scene::ISkinnedMesh::SScaleKey& a, const irr::scene::ISkinnedMesh::SScaleKey& b)
\r
75 return a.scale == b.scale;
\r
78 bool identicalRotation(const irr::scene::ISkinnedMesh::SRotationKey& a, const irr::scene::ISkinnedMesh::SRotationKey& b)
\r
80 return a.rotation == b.rotation;
\r
91 CSkinnedMesh::CSkinnedMesh()
\r
92 : SkinningBuffers(0), EndFrame(0.f), FramesPerSecond(25.f),
\r
93 LastAnimatedFrame(-1), SkinnedLastFrame(false),
\r
94 InterpolationMode(EIM_LINEAR),
\r
95 HasAnimation(false), PreparedForSkinning(false),
\r
96 AnimateNormals(true), HardwareSkinning(false)
\r
99 setDebugName("CSkinnedMesh");
\r
102 SkinningBuffers=&LocalBuffers;
\r
107 CSkinnedMesh::~CSkinnedMesh()
\r
109 for (u32 i=0; i<AllJoints.size(); ++i)
\r
110 delete AllJoints[i];
\r
112 for (u32 j=0; j<LocalBuffers.size(); ++j)
\r
114 if (LocalBuffers[j])
\r
115 LocalBuffers[j]->drop();
\r
120 //! returns the amount of frames in milliseconds.
\r
121 //! If the amount is 1, it is a static (=non animated) mesh.
\r
122 u32 CSkinnedMesh::getFrameCount() const
\r
124 return core::floor32(EndFrame+1.f);
\r
128 //! Gets the default animation speed of the animated mesh.
\r
129 /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
\r
130 f32 CSkinnedMesh::getAnimationSpeed() const
\r
132 return FramesPerSecond;
\r
136 //! Gets the frame count of the animated mesh.
\r
137 /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
\r
138 The actual speed is set in the scene node the mesh is instantiated in.*/
\r
139 void CSkinnedMesh::setAnimationSpeed(f32 fps)
\r
141 FramesPerSecond=fps;
\r
145 //! 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
146 IMesh* CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
\r
148 //animate(frame,startFrameLoop, endFrameLoop);
\r
152 animateMesh((f32)frame, 1.0f);
\r
158 //--------------------------------------------------------------------------
\r
159 // Keyframe Animation
\r
160 //--------------------------------------------------------------------------
\r
163 //! Animates this mesh's joints based on frame input
\r
164 //! blend: {0-old position, 1-New position}
\r
165 void CSkinnedMesh::animateMesh(f32 frame, f32 blend)
\r
167 if (!HasAnimation || LastAnimatedFrame==frame)
\r
170 LastAnimatedFrame=frame;
\r
171 SkinnedLastFrame=false;
\r
174 return; //No need to animate
\r
176 for (u32 i=0; i<AllJoints.size(); ++i)
\r
178 //The joints can be animated here with no input from their
\r
179 //parents, but for setAnimationMode extra checks are needed
\r
181 SJoint *joint = AllJoints[i];
\r
183 const core::vector3df oldPosition = joint->Animatedposition;
\r
184 const core::vector3df oldScale = joint->Animatedscale;
\r
185 const core::quaternion oldRotation = joint->Animatedrotation;
\r
187 core::vector3df position = oldPosition;
\r
188 core::vector3df scale = oldScale;
\r
189 core::quaternion rotation = oldRotation;
\r
191 getFrameData(frame, joint,
\r
192 position, joint->positionHint,
\r
193 scale, joint->scaleHint,
\r
194 rotation, joint->rotationHint);
\r
198 //No blending needed
\r
199 joint->Animatedposition = position;
\r
200 joint->Animatedscale = scale;
\r
201 joint->Animatedrotation = rotation;
\r
206 joint->Animatedposition = core::lerp(oldPosition, position, blend);
\r
207 joint->Animatedscale = core::lerp(oldScale, scale, blend);
\r
208 joint->Animatedrotation.slerp(oldRotation, rotation, blend);
\r
213 //LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
\r
214 //one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
\r
215 //a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
\r
219 buildAllLocalAnimatedMatrices();
\r
220 //-----------------
\r
222 updateBoundingBox();
\r
226 void CSkinnedMesh::buildAllLocalAnimatedMatrices()
\r
228 for (u32 i=0; i<AllJoints.size(); ++i)
\r
230 SJoint *joint = AllJoints[i];
\r
234 if (joint->UseAnimationFrom &&
\r
235 (joint->UseAnimationFrom->PositionKeys.size() ||
\r
236 joint->UseAnimationFrom->ScaleKeys.size() ||
\r
237 joint->UseAnimationFrom->RotationKeys.size() ))
\r
239 joint->GlobalSkinningSpace=false;
\r
241 // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
\r
242 // Not tested so far if this was correct or wrong before quaternion fix!
\r
243 joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix);
\r
245 // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() ---
\r
246 f32 *m1 = joint->LocalAnimatedMatrix.pointer();
\r
247 core::vector3df &Pos = joint->Animatedposition;
\r
248 m1[0] += Pos.X*m1[3];
\r
249 m1[1] += Pos.Y*m1[3];
\r
250 m1[2] += Pos.Z*m1[3];
\r
251 m1[4] += Pos.X*m1[7];
\r
252 m1[5] += Pos.Y*m1[7];
\r
253 m1[6] += Pos.Z*m1[7];
\r
254 m1[8] += Pos.X*m1[11];
\r
255 m1[9] += Pos.Y*m1[11];
\r
256 m1[10] += Pos.Z*m1[11];
\r
257 m1[12] += Pos.X*m1[15];
\r
258 m1[13] += Pos.Y*m1[15];
\r
259 m1[14] += Pos.Z*m1[15];
\r
260 // -----------------------------------
\r
262 if (joint->ScaleKeys.size())
\r
265 core::matrix4 scaleMatrix;
\r
266 scaleMatrix.setScale(joint->Animatedscale);
\r
267 joint->LocalAnimatedMatrix *= scaleMatrix;
\r
270 // -------- joint->LocalAnimatedMatrix *= scaleMatrix -----------------
\r
271 core::matrix4& mat = joint->LocalAnimatedMatrix;
\r
272 mat[0] *= joint->Animatedscale.X;
\r
273 mat[1] *= joint->Animatedscale.X;
\r
274 mat[2] *= joint->Animatedscale.X;
\r
275 mat[3] *= joint->Animatedscale.X;
\r
276 mat[4] *= joint->Animatedscale.Y;
\r
277 mat[5] *= joint->Animatedscale.Y;
\r
278 mat[6] *= joint->Animatedscale.Y;
\r
279 mat[7] *= joint->Animatedscale.Y;
\r
280 mat[8] *= joint->Animatedscale.Z;
\r
281 mat[9] *= joint->Animatedscale.Z;
\r
282 mat[10] *= joint->Animatedscale.Z;
\r
283 mat[11] *= joint->Animatedscale.Z;
\r
284 // -----------------------------------
\r
289 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
292 SkinnedLastFrame=false;
\r
296 void CSkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
\r
300 for (u32 i=0; i<RootJoints.size(); ++i)
\r
301 buildAllGlobalAnimatedMatrices(RootJoints[i], 0);
\r
306 // Find global matrix...
\r
307 if (!parentJoint || joint->GlobalSkinningSpace)
\r
308 joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
\r
310 joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
\r
313 for (u32 j=0; j<joint->Children.size(); ++j)
\r
314 buildAllGlobalAnimatedMatrices(joint->Children[j], joint);
\r
318 void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint,
\r
319 core::vector3df &position, s32 &positionHint,
\r
320 core::vector3df &scale, s32 &scaleHint,
\r
321 core::quaternion &rotation, s32 &rotationHint)
\r
323 s32 foundPositionIndex = -1;
\r
324 s32 foundScaleIndex = -1;
\r
325 s32 foundRotationIndex = -1;
\r
327 if (joint->UseAnimationFrom)
\r
329 const core::array<SPositionKey> &PositionKeys=joint->UseAnimationFrom->PositionKeys;
\r
330 const core::array<SScaleKey> &ScaleKeys=joint->UseAnimationFrom->ScaleKeys;
\r
331 const core::array<SRotationKey> &RotationKeys=joint->UseAnimationFrom->RotationKeys;
\r
333 if (PositionKeys.size())
\r
335 foundPositionIndex = -1;
\r
337 //Test the Hints...
\r
338 if (positionHint>=0 && (u32)positionHint < PositionKeys.size())
\r
341 if (positionHint>0 && PositionKeys[positionHint].frame>=frame && PositionKeys[positionHint-1].frame<frame )
\r
342 foundPositionIndex=positionHint;
\r
343 else if (positionHint+1 < (s32)PositionKeys.size())
\r
345 //check the next index
\r
346 if ( PositionKeys[positionHint+1].frame>=frame &&
\r
347 PositionKeys[positionHint+0].frame<frame)
\r
350 foundPositionIndex=positionHint;
\r
355 //The hint test failed, do a full scan...
\r
356 if (foundPositionIndex==-1)
\r
358 for (u32 i=0; i<PositionKeys.size(); ++i)
\r
360 if (PositionKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
362 foundPositionIndex=i;
\r
369 //Do interpolation...
\r
370 if (foundPositionIndex!=-1)
\r
372 if (InterpolationMode==EIM_CONSTANT || foundPositionIndex==0)
\r
374 position = PositionKeys[foundPositionIndex].position;
\r
376 else if (InterpolationMode==EIM_LINEAR)
\r
378 const SPositionKey& KeyA = PositionKeys[foundPositionIndex];
\r
379 const SPositionKey& KeyB = PositionKeys[foundPositionIndex-1];
\r
381 const f32 fd1 = frame - KeyA.frame;
\r
382 const f32 fd2 = KeyB.frame - frame;
\r
383 position = ((KeyB.position-KeyA.position)/(fd1+fd2))*fd1 + KeyA.position;
\r
388 //------------------------------------------------------------
\r
390 if (ScaleKeys.size())
\r
392 foundScaleIndex = -1;
\r
394 //Test the Hints...
\r
395 if (scaleHint>=0 && (u32)scaleHint < ScaleKeys.size())
\r
398 if (scaleHint>0 && ScaleKeys[scaleHint].frame>=frame && ScaleKeys[scaleHint-1].frame<frame )
\r
399 foundScaleIndex=scaleHint;
\r
400 else if (scaleHint+1 < (s32)ScaleKeys.size())
\r
402 //check the next index
\r
403 if ( ScaleKeys[scaleHint+1].frame>=frame &&
\r
404 ScaleKeys[scaleHint+0].frame<frame)
\r
407 foundScaleIndex=scaleHint;
\r
413 //The hint test failed, do a full scan...
\r
414 if (foundScaleIndex==-1)
\r
416 for (u32 i=0; i<ScaleKeys.size(); ++i)
\r
418 if (ScaleKeys[i].frame >= frame) //Keys should to be sorted by frame
\r
427 //Do interpolation...
\r
428 if (foundScaleIndex!=-1)
\r
430 if (InterpolationMode==EIM_CONSTANT || foundScaleIndex==0)
\r
432 scale = ScaleKeys[foundScaleIndex].scale;
\r
434 else if (InterpolationMode==EIM_LINEAR)
\r
436 const SScaleKey& KeyA = ScaleKeys[foundScaleIndex];
\r
437 const SScaleKey& KeyB = ScaleKeys[foundScaleIndex-1];
\r
439 const f32 fd1 = frame - KeyA.frame;
\r
440 const f32 fd2 = KeyB.frame - frame;
\r
441 scale = ((KeyB.scale-KeyA.scale)/(fd1+fd2))*fd1 + KeyA.scale;
\r
446 //-------------------------------------------------------------
\r
448 if (RotationKeys.size())
\r
450 foundRotationIndex = -1;
\r
452 //Test the Hints...
\r
453 if (rotationHint>=0 && (u32)rotationHint < RotationKeys.size())
\r
456 if (rotationHint>0 && RotationKeys[rotationHint].frame>=frame && RotationKeys[rotationHint-1].frame<frame )
\r
457 foundRotationIndex=rotationHint;
\r
458 else if (rotationHint+1 < (s32)RotationKeys.size())
\r
460 //check the next index
\r
461 if ( RotationKeys[rotationHint+1].frame>=frame &&
\r
462 RotationKeys[rotationHint+0].frame<frame)
\r
465 foundRotationIndex=rotationHint;
\r
471 //The hint test failed, do a full scan...
\r
472 if (foundRotationIndex==-1)
\r
474 for (u32 i=0; i<RotationKeys.size(); ++i)
\r
476 if (RotationKeys[i].frame >= frame) //Keys should be sorted by frame
\r
478 foundRotationIndex=i;
\r
485 //Do interpolation...
\r
486 if (foundRotationIndex!=-1)
\r
488 if (InterpolationMode==EIM_CONSTANT || foundRotationIndex==0)
\r
490 rotation = RotationKeys[foundRotationIndex].rotation;
\r
492 else if (InterpolationMode==EIM_LINEAR)
\r
494 const SRotationKey& KeyA = RotationKeys[foundRotationIndex];
\r
495 const SRotationKey& KeyB = RotationKeys[foundRotationIndex-1];
\r
497 const f32 fd1 = frame - KeyA.frame;
\r
498 const f32 fd2 = KeyB.frame - frame;
\r
499 const f32 t = fd1/(fd1+fd2);
\r
503 if (KeyA.frame!=KeyB.frame)
\r
504 t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame);
\r
507 rotation.slerp(KeyA.rotation, KeyB.rotation, t);
\r
514 //--------------------------------------------------------------------------
\r
515 // Software Skinning
\r
516 //--------------------------------------------------------------------------
\r
518 //! Preforms a software skin on this mesh based of joint positions
\r
519 void CSkinnedMesh::skinMesh()
\r
521 if (!HasAnimation || SkinnedLastFrame)
\r
525 // This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
\r
526 buildAllGlobalAnimatedMatrices();
\r
527 //-----------------
\r
529 SkinnedLastFrame=true;
\r
530 if (!HardwareSkinning)
\r
532 //Software skin....
\r
536 for (i=0; i<AllJoints.size(); ++i)
\r
538 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
540 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
541 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
545 //clear skinning helper array
\r
546 for (i=0; i<Vertices_Moved.size(); ++i)
\r
547 for (u32 j=0; j<Vertices_Moved[i].size(); ++j)
\r
548 Vertices_Moved[i][j]=false;
\r
550 //skin starting with the root joints
\r
551 for (i=0; i<RootJoints.size(); ++i)
\r
552 skinJoint(RootJoints[i], 0);
\r
554 for (i=0; i<SkinningBuffers->size(); ++i)
\r
555 (*SkinningBuffers)[i]->setDirty(EBT_VERTEX);
\r
557 updateBoundingBox();
\r
561 void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
\r
563 if (joint->Weights.size())
\r
565 //Find this joints pull on vertices...
\r
566 core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING);
\r
567 jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix);
\r
569 core::vector3df thisVertexMove, thisNormalMove;
\r
571 core::array<scene::SSkinMeshBuffer*> &buffersUsed=*SkinningBuffers;
\r
573 //Skin Vertices Positions and Normals...
\r
574 for (u32 i=0; i<joint->Weights.size(); ++i)
\r
576 SWeight& weight = joint->Weights[i];
\r
578 // Pull this vertex...
\r
579 jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
\r
581 if (AnimateNormals)
\r
582 jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal);
\r
584 if (! (*(weight.Moved)) )
\r
586 *(weight.Moved) = true;
\r
588 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
\r
590 if (AnimateNormals)
\r
591 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
\r
593 //*(weight._Pos) = thisVertexMove * weight.strength;
\r
597 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
\r
599 if (AnimateNormals)
\r
600 buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
\r
602 //*(weight._Pos) += thisVertexMove * weight.strength;
\r
605 buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
\r
609 //Skin all children
\r
610 for (u32 j=0; j<joint->Children.size(); ++j)
\r
611 skinJoint(joint->Children[j], joint);
\r
615 E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const
\r
617 return EAMT_SKINNED;
\r
621 //! Gets joint count.
\r
622 u32 CSkinnedMesh::getJointCount() const
\r
624 return AllJoints.size();
\r
628 //! Gets the name of a joint.
\r
629 const c8* CSkinnedMesh::getJointName(u32 number) const
\r
631 if (number >= AllJoints.size())
\r
633 return AllJoints[number]->Name.c_str();
\r
637 //! Gets a joint number from its name
\r
638 s32 CSkinnedMesh::getJointNumber(const c8* name) const
\r
640 for (u32 i=0; i<AllJoints.size(); ++i)
\r
642 if (AllJoints[i]->Name == name)
\r
650 //! returns amount of mesh buffers.
\r
651 u32 CSkinnedMesh::getMeshBufferCount() const
\r
653 return LocalBuffers.size();
\r
657 //! returns pointer to a mesh buffer
\r
658 IMeshBuffer* CSkinnedMesh::getMeshBuffer(u32 nr) const
\r
660 if (nr < LocalBuffers.size())
\r
661 return LocalBuffers[nr];
\r
667 //! Returns pointer to a mesh buffer which fits a material
\r
668 IMeshBuffer* CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
\r
670 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
672 if (LocalBuffers[i]->getMaterial() == material)
\r
673 return LocalBuffers[i];
\r
679 //! returns an axis aligned bounding box
\r
680 const core::aabbox3d<f32>& CSkinnedMesh::getBoundingBox() const
\r
682 return BoundingBox;
\r
686 //! set user axis aligned bounding box
\r
687 void CSkinnedMesh::setBoundingBox( const core::aabbox3df& box)
\r
693 //! sets a flag of all contained materials to a new value
\r
694 void CSkinnedMesh::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
\r
696 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
697 LocalBuffers[i]->Material.setFlag(flag,newvalue);
\r
701 //! set the hardware mapping hint, for driver
\r
702 void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
\r
703 E_BUFFER_TYPE buffer)
\r
705 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
706 LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
\r
710 //! flags the meshbuffer as changed, reloads hardware buffers
\r
711 void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
\r
713 for (u32 i=0; i<LocalBuffers.size(); ++i)
\r
714 LocalBuffers[i]->setDirty(buffer);
\r
718 //! uses animation from another mesh
\r
719 bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh)
\r
721 bool unmatched=false;
\r
723 for(u32 i=0;i<AllJoints.size();++i)
\r
725 SJoint *joint=AllJoints[i];
\r
726 joint->UseAnimationFrom=0;
\r
728 if (joint->Name=="")
\r
732 for(u32 j=0;j<mesh->getAllJoints().size();++j)
\r
734 SJoint *otherJoint=mesh->getAllJoints()[j];
\r
735 if (joint->Name==otherJoint->Name)
\r
737 joint->UseAnimationFrom=otherJoint;
\r
740 if (!joint->UseAnimationFrom)
\r
745 checkForAnimation();
\r
751 //!Update Normals when Animating
\r
752 //!False= Don't animate them, faster
\r
753 //!True= Update normals (default)
\r
754 void CSkinnedMesh::updateNormalsWhenAnimating(bool on)
\r
756 AnimateNormals = on;
\r
760 //!Sets Interpolation Mode
\r
761 void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode)
\r
763 InterpolationMode = mode;
\r
767 core::array<scene::SSkinMeshBuffer*> &CSkinnedMesh::getMeshBuffers()
\r
769 return LocalBuffers;
\r
773 core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints()
\r
779 const core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints() const
\r
785 //! (This feature is not implemented in irrlicht yet)
\r
786 bool CSkinnedMesh::setHardwareSkinning(bool on)
\r
788 if (HardwareSkinning!=on)
\r
793 //set mesh to static pose...
\r
794 for (u32 i=0; i<AllJoints.size(); ++i)
\r
796 SJoint *joint=AllJoints[i];
\r
797 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
799 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
800 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
801 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
802 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
803 LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
\r
808 HardwareSkinning=on;
\r
810 return HardwareSkinning;
\r
813 void CSkinnedMesh::refreshJointCache()
\r
815 //copy cache from the mesh...
\r
816 for (u32 i=0; i<AllJoints.size(); ++i)
\r
818 SJoint *joint=AllJoints[i];
\r
819 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
821 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
822 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
823 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
824 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
829 void CSkinnedMesh::resetAnimation()
\r
831 //copy from the cache to the mesh...
\r
832 for (u32 i=0; i<AllJoints.size(); ++i)
\r
834 SJoint *joint=AllJoints[i];
\r
835 for (u32 j=0; j<joint->Weights.size(); ++j)
\r
837 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
838 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
839 LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
\r
840 LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
\r
843 SkinnedLastFrame = false;
\r
844 LastAnimatedFrame = -1;
\r
847 void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint,SJoint *parentJoint)
\r
849 if (!joint && parentJoint) // bit of protection from endless loops
\r
852 //Go through the root bones
\r
855 for (u32 i=0; i<RootJoints.size(); ++i)
\r
856 calculateGlobalMatrices(RootJoints[i],0);
\r
861 joint->GlobalMatrix = joint->LocalMatrix;
\r
863 joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
\r
865 joint->LocalAnimatedMatrix=joint->LocalMatrix;
\r
866 joint->GlobalAnimatedMatrix=joint->GlobalMatrix;
\r
868 if (joint->GlobalInversedMatrix.isIdentity())//might be pre calculated
\r
870 joint->GlobalInversedMatrix = joint->GlobalMatrix;
\r
871 joint->GlobalInversedMatrix.makeInverse(); // slow
\r
874 for (u32 j=0; j<joint->Children.size(); ++j)
\r
875 calculateGlobalMatrices(joint->Children[j],joint);
\r
876 SkinnedLastFrame=false;
\r
880 void CSkinnedMesh::checkForAnimation()
\r
883 //Check for animation...
\r
884 HasAnimation = false;
\r
885 for(i=0;i<AllJoints.size();++i)
\r
887 if (AllJoints[i]->UseAnimationFrom)
\r
889 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() ||
\r
890 AllJoints[i]->UseAnimationFrom->ScaleKeys.size() ||
\r
891 AllJoints[i]->UseAnimationFrom->RotationKeys.size() )
\r
893 HasAnimation = true;
\r
898 //meshes with weights, are still counted as animated for ragdolls, etc
\r
901 for(i=0;i<AllJoints.size();++i)
\r
903 if (AllJoints[i]->Weights.size())
\r
904 HasAnimation = true;
\r
910 //--- Find the length of the animation ---
\r
912 for(i=0;i<AllJoints.size();++i)
\r
914 if (AllJoints[i]->UseAnimationFrom)
\r
916 if (AllJoints[i]->UseAnimationFrom->PositionKeys.size())
\r
917 if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > EndFrame)
\r
918 EndFrame=AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame;
\r
920 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size())
\r
921 if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > EndFrame)
\r
922 EndFrame=AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame;
\r
924 if (AllJoints[i]->UseAnimationFrom->RotationKeys.size())
\r
925 if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > EndFrame)
\r
926 EndFrame=AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame;
\r
931 if (HasAnimation && !PreparedForSkinning)
\r
933 PreparedForSkinning=true;
\r
936 for(i=0; i < AllJoints.size(); ++i)
\r
938 SJoint *joint = AllJoints[i];
\r
939 for (j=0; j<joint->Weights.size(); ++j)
\r
941 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
942 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
944 //check for invalid ids
\r
945 if (buffer_id>=LocalBuffers.size())
\r
947 os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
\r
948 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
950 else if (vertex_id>=LocalBuffers[buffer_id]->getVertexCount())
\r
952 os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
\r
953 joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
\r
958 //An array used in skinning
\r
960 for (i=0; i<Vertices_Moved.size(); ++i)
\r
961 for (j=0; j<Vertices_Moved[i].size(); ++j)
\r
962 Vertices_Moved[i][j] = false;
\r
964 // For skinning: cache weight values for speed
\r
966 for (i=0; i<AllJoints.size(); ++i)
\r
968 SJoint *joint = AllJoints[i];
\r
969 for (j=0; j<joint->Weights.size(); ++j)
\r
971 const u16 buffer_id=joint->Weights[j].buffer_id;
\r
972 const u32 vertex_id=joint->Weights[j].vertex_id;
\r
974 joint->Weights[j].Moved = &Vertices_Moved[buffer_id] [vertex_id];
\r
975 joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
976 joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
\r
978 //joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
\r
982 // normalize weights
\r
983 normalizeWeights();
\r
985 SkinnedLastFrame=false;
\r
988 //! called by loader after populating with mesh and bone data
\r
989 void CSkinnedMesh::finalize()
\r
991 os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
\r
994 // Make sure we recalc the next frame
\r
995 LastAnimatedFrame=-1;
\r
996 SkinnedLastFrame=false;
\r
998 //calculate bounding box
\r
999 for (i=0; i<LocalBuffers.size(); ++i)
\r
1001 LocalBuffers[i]->recalculateBoundingBox();
\r
1004 if (AllJoints.size() || RootJoints.size())
\r
1006 // populate AllJoints or RootJoints, depending on which is empty
\r
1007 if (!RootJoints.size())
\r
1010 for(u32 CheckingIdx=0; CheckingIdx < AllJoints.size(); ++CheckingIdx)
\r
1013 bool foundParent=false;
\r
1014 for(i=0; i < AllJoints.size(); ++i)
\r
1016 for(u32 n=0; n < AllJoints[i]->Children.size(); ++n)
\r
1018 if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx])
\r
1024 RootJoints.push_back(AllJoints[CheckingIdx]);
\r
1029 AllJoints=RootJoints;
\r
1033 for(i=0; i < AllJoints.size(); ++i)
\r
1035 AllJoints[i]->UseAnimationFrom=AllJoints[i];
\r
1038 //Set array sizes...
\r
1040 for (i=0; i<LocalBuffers.size(); ++i)
\r
1042 Vertices_Moved.push_back( core::array<char>() );
\r
1043 Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1046 checkForAnimation();
\r
1050 irr::u32 redundantPosKeys = 0;
\r
1051 irr::u32 unorderedPosKeys = 0;
\r
1052 irr::u32 redundantScaleKeys = 0;
\r
1053 irr::u32 unorderedScaleKeys = 0;
\r
1054 irr::u32 redundantRotationKeys = 0;
\r
1055 irr::u32 unorderedRotationKeys = 0;
\r
1057 //--- optimize and check keyframes ---
\r
1058 for(i=0;i<AllJoints.size();++i)
\r
1060 core::array<SPositionKey> &PositionKeys =AllJoints[i]->PositionKeys;
\r
1061 core::array<SScaleKey> &ScaleKeys = AllJoints[i]->ScaleKeys;
\r
1062 core::array<SRotationKey> &RotationKeys = AllJoints[i]->RotationKeys;
\r
1064 // redundant = identical middle keys - we only need the first and last frame
\r
1065 // unordered = frames which are out of order - we can't handle those
\r
1066 redundantPosKeys += dropMiddleKeys<SPositionKey>(PositionKeys, identicalPos);
\r
1067 unorderedPosKeys += dropBadKeys<SPositionKey>(PositionKeys);
\r
1068 redundantScaleKeys += dropMiddleKeys<SScaleKey>(ScaleKeys, identicalScale);
\r
1069 unorderedScaleKeys += dropBadKeys<SScaleKey>(ScaleKeys);
\r
1070 redundantRotationKeys += dropMiddleKeys<SRotationKey>(RotationKeys, identicalRotation);
\r
1071 unorderedRotationKeys += dropBadKeys<SRotationKey>(RotationKeys);
\r
1073 //Fill empty keyframe areas
\r
1074 if (PositionKeys.size())
\r
1076 SPositionKey *Key;
\r
1077 Key=&PositionKeys[0];//getFirst
\r
1078 if (Key->frame!=0)
\r
1080 PositionKeys.push_front(*Key);
\r
1081 Key=&PositionKeys[0];//getFirst
\r
1085 Key=&PositionKeys.getLast();
\r
1086 if (Key->frame!=EndFrame)
\r
1088 PositionKeys.push_back(*Key);
\r
1089 Key=&PositionKeys.getLast();
\r
1090 Key->frame=EndFrame;
\r
1094 if (ScaleKeys.size())
\r
1097 Key=&ScaleKeys[0];//getFirst
\r
1098 if (Key->frame!=0)
\r
1100 ScaleKeys.push_front(*Key);
\r
1101 Key=&ScaleKeys[0];//getFirst
\r
1105 Key=&ScaleKeys.getLast();
\r
1106 if (Key->frame!=EndFrame)
\r
1108 ScaleKeys.push_back(*Key);
\r
1109 Key=&ScaleKeys.getLast();
\r
1110 Key->frame=EndFrame;
\r
1114 if (RotationKeys.size())
\r
1116 SRotationKey *Key;
\r
1117 Key=&RotationKeys[0];//getFirst
\r
1118 if (Key->frame!=0)
\r
1120 RotationKeys.push_front(*Key);
\r
1121 Key=&RotationKeys[0];//getFirst
\r
1125 Key=&RotationKeys.getLast();
\r
1126 if (Key->frame!=EndFrame)
\r
1128 RotationKeys.push_back(*Key);
\r
1129 Key=&RotationKeys.getLast();
\r
1130 Key->frame=EndFrame;
\r
1135 if ( redundantPosKeys > 0 )
\r
1137 os::Printer::log("Skinned Mesh - redundant position frames kicked:", core::stringc(redundantPosKeys).c_str(), ELL_DEBUG);
\r
1139 if ( unorderedPosKeys > 0 )
\r
1141 irr::os::Printer::log("Skinned Mesh - unsorted position frames kicked:", irr::core::stringc(unorderedPosKeys).c_str(), irr::ELL_DEBUG);
\r
1143 if ( redundantScaleKeys > 0 )
\r
1145 os::Printer::log("Skinned Mesh - redundant scale frames kicked:", core::stringc(redundantScaleKeys).c_str(), ELL_DEBUG);
\r
1147 if ( unorderedScaleKeys > 0 )
\r
1149 irr::os::Printer::log("Skinned Mesh - unsorted scale frames kicked:", irr::core::stringc(unorderedScaleKeys).c_str(), irr::ELL_DEBUG);
\r
1151 if ( redundantRotationKeys > 0 )
\r
1153 os::Printer::log("Skinned Mesh - redundant rotation frames kicked:", core::stringc(redundantRotationKeys).c_str(), ELL_DEBUG);
\r
1155 if ( unorderedRotationKeys > 0 )
\r
1157 irr::os::Printer::log("Skinned Mesh - unsorted rotation frames kicked:", irr::core::stringc(unorderedRotationKeys).c_str(), irr::ELL_DEBUG);
\r
1161 //Needed for animation and skinning...
\r
1163 calculateGlobalMatrices(0,0);
\r
1165 //animateMesh(0, 1);
\r
1166 //buildAllLocalAnimatedMatrices();
\r
1167 //buildAllGlobalAnimatedMatrices();
\r
1169 //rigid animation for non animated meshes
\r
1170 for (i=0; i<AllJoints.size(); ++i)
\r
1172 for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
\r
1174 SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
\r
1175 Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
\r
1179 //calculate bounding box
\r
1180 if (LocalBuffers.empty())
\r
1181 BoundingBox.reset(0,0,0);
\r
1184 irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
\r
1185 LocalBuffers[0]->Transformation.transformBoxEx(bb);
\r
1186 BoundingBox.reset(bb);
\r
1188 for (u32 j=1; j<LocalBuffers.size(); ++j)
\r
1190 bb = LocalBuffers[j]->BoundingBox;
\r
1191 LocalBuffers[j]->Transformation.transformBoxEx(bb);
\r
1193 BoundingBox.addInternalBox(bb);
\r
1199 void CSkinnedMesh::updateBoundingBox(void)
\r
1201 if(!SkinningBuffers)
\r
1204 core::array<SSkinMeshBuffer*> & buffer = *SkinningBuffers;
\r
1205 BoundingBox.reset(0,0,0);
\r
1207 if (!buffer.empty())
\r
1209 for (u32 j=0; j<buffer.size(); ++j)
\r
1211 buffer[j]->recalculateBoundingBox();
\r
1212 core::aabbox3df bb = buffer[j]->BoundingBox;
\r
1213 buffer[j]->Transformation.transformBoxEx(bb);
\r
1215 BoundingBox.addInternalBox(bb);
\r
1221 scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer()
\r
1223 scene::SSkinMeshBuffer *buffer=new scene::SSkinMeshBuffer();
\r
1224 LocalBuffers.push_back(buffer);
\r
1229 CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent)
\r
1231 SJoint *joint=new SJoint;
\r
1233 AllJoints.push_back(joint);
\r
1236 //Add root joints to array in finalize()
\r
1240 //Set parent (Be careful of the mesh loader also setting the parent)
\r
1241 parent->Children.push_back(joint);
\r
1248 CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint)
\r
1253 joint->PositionKeys.push_back(SPositionKey());
\r
1254 return &joint->PositionKeys.getLast();
\r
1258 CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint)
\r
1263 joint->ScaleKeys.push_back(SScaleKey());
\r
1264 return &joint->ScaleKeys.getLast();
\r
1268 CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint)
\r
1273 joint->RotationKeys.push_back(SRotationKey());
\r
1274 return &joint->RotationKeys.getLast();
\r
1278 CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint)
\r
1283 joint->Weights.push_back(SWeight());
\r
1284 return &joint->Weights.getLast();
\r
1288 bool CSkinnedMesh::isStatic()
\r
1290 return !HasAnimation;
\r
1294 void CSkinnedMesh::normalizeWeights()
\r
1296 // note: unsure if weights ids are going to be used.
\r
1298 // Normalise the weights on bones....
\r
1301 core::array< core::array<f32> > verticesTotalWeight;
\r
1303 verticesTotalWeight.reallocate(LocalBuffers.size());
\r
1304 for (i=0; i<LocalBuffers.size(); ++i)
\r
1306 verticesTotalWeight.push_back(core::array<f32>());
\r
1307 verticesTotalWeight[i].set_used(LocalBuffers[i]->getVertexCount());
\r
1310 for (i=0; i<verticesTotalWeight.size(); ++i)
\r
1311 for (j=0; j<verticesTotalWeight[i].size(); ++j)
\r
1312 verticesTotalWeight[i][j] = 0;
\r
1314 for (i=0; i<AllJoints.size(); ++i)
\r
1316 SJoint *joint=AllJoints[i];
\r
1317 for (j=0; j<joint->Weights.size(); ++j)
\r
1319 if (joint->Weights[j].strength<=0)//Check for invalid weights
\r
1321 joint->Weights.erase(j);
\r
1326 verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id] += joint->Weights[j].strength;
\r
1331 for (i=0; i<AllJoints.size(); ++i)
\r
1333 SJoint *joint=AllJoints[i];
\r
1334 for (j=0; j< joint->Weights.size(); ++j)
\r
1336 const f32 total = verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id];
\r
1337 if (total != 0 && total != 1)
\r
1338 joint->Weights[j].strength /= total;
\r
1344 void CSkinnedMesh::recoverJointsFromMesh(core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1346 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1348 IBoneSceneNode* node=jointChildSceneNodes[i];
\r
1349 SJoint *joint=AllJoints[i];
\r
1350 node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
\r
1351 node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
\r
1352 node->setScale(joint->LocalAnimatedMatrix.getScale());
\r
1354 node->positionHint=joint->positionHint;
\r
1355 node->scaleHint=joint->scaleHint;
\r
1356 node->rotationHint=joint->rotationHint;
\r
1358 node->updateAbsolutePosition();
\r
1363 void CSkinnedMesh::transferJointsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1365 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1367 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1368 SJoint *joint=AllJoints[i];
\r
1370 joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
\r
1371 joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
\r
1372 joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
\r
1374 joint->positionHint=node->positionHint;
\r
1375 joint->scaleHint=node->scaleHint;
\r
1376 joint->rotationHint=node->rotationHint;
\r
1378 joint->GlobalSkinningSpace=(node->getSkinningSpace()==EBSS_GLOBAL);
\r
1380 // Make sure we recalc the next frame
\r
1381 LastAnimatedFrame=-1;
\r
1382 SkinnedLastFrame=false;
\r
1386 void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
\r
1388 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1390 const IBoneSceneNode* const node=jointChildSceneNodes[i];
\r
1391 SJoint *joint=AllJoints[i];
\r
1393 joint->positionHint=node->positionHint;
\r
1394 joint->scaleHint=node->scaleHint;
\r
1395 joint->rotationHint=node->rotationHint;
\r
1397 SkinnedLastFrame=false;
\r
1401 void CSkinnedMesh::addJoints(core::array<IBoneSceneNode*> &jointChildSceneNodes,
\r
1402 IAnimatedMeshSceneNode* node, ISceneManager* smgr)
\r
1404 //Create new joints
\r
1405 for (u32 i=0; i<AllJoints.size(); ++i)
\r
1407 jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name.c_str()));
\r
1410 //Match up parents
\r
1411 for (u32 i=0; i<jointChildSceneNodes.size(); ++i)
\r
1413 const SJoint* const joint=AllJoints[i]; //should be fine
\r
1417 for (u32 j=0;(parentID==-1)&&(j<AllJoints.size());++j)
\r
1421 const SJoint* const parentTest=AllJoints[j];
\r
1422 for (u32 n=0; n<parentTest->Children.size(); ++n)
\r
1424 if (parentTest->Children[n]==joint)
\r
1433 IBoneSceneNode* bone=jointChildSceneNodes[i];
\r
1435 bone->setParent(jointChildSceneNodes[parentID]);
\r
1437 bone->setParent(node);
\r
1441 SkinnedLastFrame=false;
\r
1445 void CSkinnedMesh::convertMeshToTangents()
\r
1447 // now calculate tangents
\r
1448 for (u32 b=0; b < LocalBuffers.size(); ++b)
\r
1450 if (LocalBuffers[b])
\r
1452 LocalBuffers[b]->convertToTangents();
\r
1454 const s32 idxCnt = LocalBuffers[b]->getIndexCount();
\r
1456 u16* idx = LocalBuffers[b]->getIndices();
\r
1457 video::S3DVertexTangents* v =
\r
1458 (video::S3DVertexTangents*)LocalBuffers[b]->getVertices();
\r
1460 for (s32 i=0; i<idxCnt; i+=3)
\r
1462 calculateTangents(
\r
1463 v[idx[i+0]].Normal,
\r
1464 v[idx[i+0]].Tangent,
\r
1465 v[idx[i+0]].Binormal,
\r
1469 v[idx[i+0]].TCoords,
\r
1470 v[idx[i+1]].TCoords,
\r
1471 v[idx[i+2]].TCoords);
\r
1473 calculateTangents(
\r
1474 v[idx[i+1]].Normal,
\r
1475 v[idx[i+1]].Tangent,
\r
1476 v[idx[i+1]].Binormal,
\r
1480 v[idx[i+1]].TCoords,
\r
1481 v[idx[i+2]].TCoords,
\r
1482 v[idx[i+0]].TCoords);
\r
1484 calculateTangents(
\r
1485 v[idx[i+2]].Normal,
\r
1486 v[idx[i+2]].Tangent,
\r
1487 v[idx[i+2]].Binormal,
\r
1491 v[idx[i+2]].TCoords,
\r
1492 v[idx[i+0]].TCoords,
\r
1493 v[idx[i+1]].TCoords);
\r
1500 void CSkinnedMesh::calculateTangents(
\r
1501 core::vector3df& normal,
\r
1502 core::vector3df& tangent,
\r
1503 core::vector3df& binormal,
\r
1504 const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices
\r
1505 const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords
\r
1507 core::vector3df v1 = vt1 - vt2;
\r
1508 core::vector3df v2 = vt3 - vt1;
\r
1509 normal = v2.crossProduct(v1);
\r
1510 normal.normalize();
\r
1514 f32 deltaX1 = tc1.X - tc2.X;
\r
1515 f32 deltaX2 = tc3.X - tc1.X;
\r
1516 binormal = (v1 * deltaX2) - (v2 * deltaX1);
\r
1517 binormal.normalize();
\r
1521 f32 deltaY1 = tc1.Y - tc2.Y;
\r
1522 f32 deltaY2 = tc3.Y - tc1.Y;
\r
1523 tangent = (v1 * deltaY2) - (v2 * deltaY1);
\r
1524 tangent.normalize();
\r
1528 core::vector3df txb = tangent.crossProduct(binormal);
\r
1529 if (txb.dotProduct(normal) < 0.0f)
\r
1532 binormal *= -1.0f;
\r
1537 } // end namespace scene
\r
1538 } // end namespace irr
\r