// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/misc/hierarchical_timer.h" #include "nel/3d/skeleton_model.h" #include "nel/3d/hrc_trav.h" #include "nel/3d/clip_trav.h" #include "nel/3d/anim_detail_trav.h" #include "nel/3d/render_trav.h" #include "nel/3d/skeleton_shape.h" #include "nel/3d/scene.h" #include "nel/3d/lod_character_manager.h" #include "nel/3d/lod_character_shape.h" #include "nel/misc/rgba.h" #include "nel/misc/aabbox.h" #include "nel/3d/vertex_stream_manager.h" #include "nel/3d/mesh_base_instance.h" #include "nel/3d/async_texture_manager.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** void CSkeletonModel::registerBasic() { CScene::registerModel(SkeletonModelId, TransformShapeId, CSkeletonModel::creator); } // *************************************************************************** CTrackDefaultString CSkeletonModel::_DefaultSpawnScript; // *************************************************************************** IAnimatedValue *CSkeletonModel::getValue (uint valueId) { // what value ? switch (valueId) { case SpawnScriptValue: return &_SpawnScript; } return CTransformShape::getValue(valueId); } // *************************************************************************** const char *CSkeletonModel::getValueName (uint valueId) const { // what value ? switch (valueId) { case SpawnScriptValue: return getSpawnScriptValueName(); } return CTransformShape::getValueName(valueId); } // *************************************************************************** ITrack *CSkeletonModel::getDefaultTrack (uint valueId) { // what value ? switch (valueId) { case SpawnScriptValue: return &_DefaultSpawnScript; } return CTransformShape::getDefaultTrack(valueId); } // *************************************************************************** void CSkeletonModel::registerToChannelMixer(CChannelMixer *chanMixer, const std::string &prefix) { /* add the Spawn Script value. The animation is evaluated at detail time, as the script evaluation. This seems dangerous (create and delete models at evalDetail time) but works because: - deletedModels() in a current CScene::render() are delayed to end of render() and are "temp removed" from the render trav - createdModels() in CSkeletonSpawnScript are delayed to the end of CScene::render() - if a skeleton is not visible, or in LOD form, then its sticked SpawnedModels are not visible too, whether or not they are too old regarding the animation time */ _SpawnScriptChannelId= addValue(chanMixer, SpawnScriptValue, OwnerBit, prefix, true); // add standard CTransformShape::registerToChannelMixer(chanMixer, prefix); // Add any bones. for(uint i=0;ieraseSkeletonModelToList(_ItSkeletonInScene); } // detach skeleton sons from skins. while(_Skins.begin()!=_Skins.end()) { detachSkeletonSon(*_Skins.begin()); } // detach skeleton sons from sticked objects. while(_StickedObjects.begin()!=_StickedObjects.end()) { detachSkeletonSon(*_StickedObjects.begin()); } // Free Lod instance setLodCharacterShape(-1); // delete the shadowMap deleteShadowMap(); } // *************************************************************************** void CSkeletonModel::initModel() { // Link this skeleton to the CScene. _ItSkeletonInScene= getOwnerScene()->appendSkeletonModelToList(this); // Call base class CTransformShape::initModel(); } // *************************************************************************** void CSkeletonModel::initBoneUsages() { // reset all to 0. _BoneUsage.resize(Bones.size()); for(uint i=0; i<_BoneUsage.size(); i++) { _BoneUsage[i].Usage= 0; _BoneUsage[i].ForcedUsage= 0; _BoneUsage[i].CLodForcedUsage= 0; _BoneUsage[i].MustCompute= 0; _BoneUsage[i].ValidBoneSkinMatrix= 0; } // reserve space for bone to compute _BoneToCompute.reserve(Bones.size()); _BoneToComputeDirty= false; _CurLod= 0; _CurLodInterp= 1.f; // Default is 0.5 meters. _LodInterpMultiplier= 1.f / 0.5f; } // *************************************************************************** void CSkeletonModel::incBoneUsage(uint i, TBoneUsageType boneUsageType) { nlassert(i<_BoneUsage.size()); // Get ptr on according refCount uint16 *usagePtr; if(boneUsageType == UsageNormal) usagePtr= &_BoneUsage[i].Usage; else if(boneUsageType == UsageForced) usagePtr= &_BoneUsage[i].ForcedUsage; else usagePtr= &_BoneUsage[i].CLodForcedUsage; // If the bone was not used before, must update MustCompute. if(*usagePtr==0) _BoneToComputeDirty= true; // Inc the refCount of the bone. nlassert(*usagePtr<65535); (*usagePtr)++; } // *************************************************************************** void CSkeletonModel::decBoneUsage(uint i, TBoneUsageType boneUsageType) { nlassert(i<_BoneUsage.size()); // Get ptr on according refCount uint16 *usagePtr; if(boneUsageType == UsageNormal) usagePtr= &_BoneUsage[i].Usage; else if(boneUsageType == UsageForced) usagePtr= &_BoneUsage[i].ForcedUsage; else usagePtr= &_BoneUsage[i].CLodForcedUsage; // If the bone was used before (and now won't be more), must update MustCompute. if(*usagePtr==1) _BoneToComputeDirty= true; // Inc the refCount of the bone. nlassert(*usagePtr>0); (*usagePtr)--; } // *************************************************************************** void CSkeletonModel::flagBoneAndParents(uint32 boneId, std::vector &boneUsage) const { nlassert( boneUsage.size()==Bones.size() ); nlassert( boneId=0) flagBoneAndParents(fatherId, boneUsage); } // *************************************************************************** void CSkeletonModel::incForcedBoneUsageAndParents(uint i, bool forceCLod) { // inc forced. incBoneUsage(i, forceCLod?UsageCLodForced:UsageForced ); // recurs to father sint fatherId= Bones[i].getFatherId(); // if not a root bone... if(fatherId>=0) incForcedBoneUsageAndParents(fatherId, forceCLod); } // *************************************************************************** void CSkeletonModel::decForcedBoneUsageAndParents(uint i, bool forceCLod) { // dec forced decBoneUsage(i, forceCLod?UsageCLodForced:UsageForced); // recurs to father sint fatherId= Bones[i].getFatherId(); // if not a root bone... if(fatherId>=0) decForcedBoneUsageAndParents(fatherId, forceCLod); } // *************************************************************************** void CSkeletonModel::updateBoneToCompute() { // If already computed, skip if(!_BoneToComputeDirty) return; // get the channelMixer owned by CTransform. CChannelMixer *chanMixer= getChannelMixer(); // Get Lod infos from skeletonShape CSkeletonShape *skeShape= (CSkeletonShape*)(IShape*)Shape; const CSkeletonShape::CLod &lod= skeShape->getLod(_CurLod); // reset _BoneToCompute _BoneToCompute.clear(); // For all bones for(uint i=0; i<_BoneUsage.size(); i++) { // If we are in CLod mode if(isDisplayedAsLodCharacter()) // don't compute the bone _BoneUsage[i].MustCompute= 0; else { // set MustCompute to non 0 if (Usage && Lod) || ForcedUsage; _BoneUsage[i].MustCompute= (_BoneUsage[i].Usage & lod.ActiveBones[i]) | _BoneUsage[i].ForcedUsage; } // if CLodForcedUsage for the bone, it must be computed, whatever _DisplayedAsLodCharacter state _BoneUsage[i].MustCompute|= _BoneUsage[i].CLodForcedUsage; // If the bone must be computed (if !0) if(_BoneUsage[i].MustCompute) { // lodEnable the channels of this bone if(chanMixer) Bones[i].lodEnableChannels(chanMixer, true); // This bone is computed => take his valid boneSkinMatrix. _BoneUsage[i].ValidBoneSkinMatrix= i; // Append to the list to compute. //------- CBoneCompute bc; bc.Bone= &Bones[i]; sint fatherId= Bones[i].getFatherId(); // if a root bone... if(fatherId==-1) bc.Father= NULL; else bc.Father= &Bones[fatherId]; // MustInterpolate?? bc.MustInterpolate= false; const CSkeletonShape::CLod *lodNext= NULL; // if a lod exist after current lod, and if lod interpolation enabled if( _CurLod < skeShape->getNumLods()-1 && _LodInterpMultiplier>0 ) { // get next lod. lodNext= &skeShape->getLod(_CurLod+1); // Lod interpolation on this bone ?? only if at next lod, the bone is disabled. // And only if it is not enabed because of a "Forced reason" // Must also have a father, esle can't interpolate. if(lodNext->ActiveBones[i]==0 && _BoneUsage[i].ForcedUsage==0 && _BoneUsage[i].CLodForcedUsage==0 && bc.Father) bc.MustInterpolate= true; } // append _BoneToCompute.push_back(bc); } else { // lodDisable the channels of this bone if(chanMixer) Bones[i].lodEnableChannels(chanMixer, false); // This bone is not computed => take the valid boneSkinMatrix of his father sint fatherId= Bones[i].getFatherId(); if(fatherId<0) // just take me, even if not computed. _BoneUsage[i].ValidBoneSkinMatrix= i; else // NB: father ValidBoneSkinMatrix already computed because of the hierarchy order of Bones array. _BoneUsage[i].ValidBoneSkinMatrix= _BoneUsage[fatherId].ValidBoneSkinMatrix; } } // Enable SpawnScript animation only if we are not in CLod if(_SpawnScriptChannelId>=0 && chanMixer) chanMixer->lodEnableChannel(_SpawnScriptChannelId, !isDisplayedAsLodCharacter()); // computed _BoneToComputeDirty= false; } // *************************************************************************** bool CSkeletonModel::isBoneComputed(uint boneId) const { if(boneId>=_BoneUsage.size()) return false; else return _BoneUsage[boneId].MustCompute!=0 && isClipVisible(); } // struct used by CSkeletonModel::forceComputeBone only struct CForceComputeBoneInfo { CTransform *Transform; uint StickBoneID; // if the transform is a skeleton, gives the bone to which child of interest is sticked }; // *************************************************************************** bool CSkeletonModel::forceComputeBone(uint boneId) { if(boneId >= _BoneUsage.size()) return false; // build list of ancestor, then must build std::vector ancestors; // count the number of ancestors (to avoid unwanted alloc with vector) uint numAncestors = 1; CTransform *currTransform = this; for(;;) { currTransform = currTransform->_HrcParent ? currTransform->_HrcParent : currTransform->_FatherSkeletonModel; // find father transform (maybe a skeleton or a std transform) if (!currTransform) break; // root reached ? ++ numAncestors; } ancestors.reserve(numAncestors); // build list of ancestor currTransform = this; uint currStickBone = boneId; for(;;) { // if curr transform is a skeleton, animate all bone from stick bone to the root bone if (currTransform->isSkeleton()) { if (_ChannelMixer) { CSkeletonModel *skel = static_cast(currTransform); nlassert(boneId < skel->_BoneUsage.size()); nlassert(currStickBone < skel->Bones.size()); sint currBoneIndex = currStickBone; // force channel mixer to eval for that bone while (currBoneIndex != -1) { nlassert((uint) currBoneIndex < skel->Bones.size()); skel->Bones[currBoneIndex].forceAnimate(*_ChannelMixer); currBoneIndex = skel->Bones[currBoneIndex].getFatherId(); } } } else { // update stickBone ID (if father is a skeleton) currStickBone = _FatherBoneId; } CForceComputeBoneInfo fcbi; fcbi.StickBoneID = currStickBone; fcbi.Transform = currTransform; ancestors.push_back(fcbi); currTransform = currTransform->_HrcParent ? currTransform->_HrcParent : currTransform->_FatherSkeletonModel; // find father transform (maybe a skeleton or a std transform) if (!currTransform) break; // root reached ? } // bones must be recomputed from father bone to sons, so must traverse bones until root is reached to retrieve correct ordering CBone *OrderedBone[MaxNumBones]; // const CMatrix *parentWorldMatrix = &CMatrix::Identity; for(std::vector::reverse_iterator it = ancestors.rbegin(); it != ancestors.rend(); ++it) { // update world matrix (NB : the call to getmatrix will update the local matrix) it->Transform->setWorldMatrix(*parentWorldMatrix * it->Transform->getMatrix()); if (it->Transform->isSkeleton()) { CSkeletonModel *skel = static_cast(it->Transform); // reorder bones uint numBones = 0; nlassert(it->StickBoneID < skel->Bones.size()); sint currBoneIndex = it->StickBoneID; nlassert(currBoneIndex != -1); do { nlassert(numBones < MaxNumBones); nlassert((uint) currBoneIndex < skel->Bones.size()); OrderedBone[numBones] = &skel->Bones[currBoneIndex]; currBoneIndex = OrderedBone[numBones]->getFatherId(); ++ numBones; } while (currBoneIndex != -1); const CMatrix &modelWorldMatrix = it->Transform->getWorldMatrix(); // recompute bones CBone *fatherBone = NULL; while (numBones--) { OrderedBone[numBones]->compute(fatherBone, modelWorldMatrix, NULL); fatherBone = OrderedBone[numBones]; } parentWorldMatrix = &(OrderedBone[0]->getWorldMatrix()); } else { parentWorldMatrix = &it->Transform->getWorldMatrix(); } } return true; } // *************************************************************************** const NLMISC::CMatrix &CSkeletonModel::getActiveBoneSkinMatrix(uint boneId) const { // Get me or first father with MustCompute==true. uint validBoneId= _BoneUsage[boneId].ValidBoneSkinMatrix; // return his WorldMatrix. return Bones[validBoneId].getBoneSkinMatrix(); } // *************************************************************************** bool CSkeletonModel::bindSkin(CTransform *mi) { nlassert(mi); if( !mi->isSkinnable() ) return false; // try to detach this object from any skeleton first (possibly me). if(mi->_FatherSkeletonModel) mi->_FatherSkeletonModel->detachSkeletonSon(mi); // Then Add me. _Skins.insert(mi); // advert skin transform it is skinned. mi->_FatherSkeletonModel= this; // setApplySkin() use _FatherSkeletonModel to computeBonesId() and to update current skeleton bone usage. mi->setApplySkin(true); // Unlink the Skin from Hrc and clip, because SkeletonModel now does the job for him. // First ensure that the transform is not frozen (unlink from some quadGrids etc...) mi->unfreezeHRC(); // then never re-parse in validateList/Hrc/Clip mi->unlinkFromUpdateList(); mi->hrcUnlink(); // ClipTrav is a graph, so must unlink from ALL olds models. mi->clipUnlinkFromAll(); // Ensure flag is correct mi->_ClipLinkedInSonsOfAncestorSkeletonModelGroup= false; // must recompute lod vertex alpha when LodCharacter used dirtLodVertexAlpha(); // must recompute list of skins. dirtSkinRenderLists(); // Ok, skinned return true; } // *************************************************************************** void CSkeletonModel::stickObject(CTransform *mi, uint boneId) { // by default don't force display of "mi" if the skeleton become in CLod state stickObjectEx(mi, boneId, false); } // *************************************************************************** void CSkeletonModel::stickObjectEx(CTransform *mi, uint boneId, bool forceCLod) { nlassert(mi); // if "mi" is a skeleton, forceCLod must be true, for correct animation purpose if(dynamic_cast(mi)) forceCLod= true; // try to detach this object from any skeleton first (possibly me). if(mi->_FatherSkeletonModel) mi->_FatherSkeletonModel->detachSkeletonSon(mi); // Then Add me. _StickedObjects.insert(mi); // increment the refCount usage of the bone incForcedBoneUsageAndParents(boneId, forceCLod); // advert transform of its sticked state. mi->_FatherSkeletonModel= this; mi->_FatherBoneId= boneId; // advert him if it is "ForceCLod" sticked mi->_ForceCLodSticked= forceCLod; // link correctly Hrc only. ClipTrav grah updated in Hrc traversal. hrcLinkSon( mi ); // must recompute lod vertex alpha when LodCharacter used dirtLodVertexAlpha(); } // *************************************************************************** void CSkeletonModel::detachSkeletonSon(CTransform *tr) { nlassert(tr); // If the instance is not binded/sticked to the skeleton, exit. if(tr->_FatherSkeletonModel!=this) return; // try to erase from StickObject. _StickedObjects.erase(tr); // try to erase from Skins. _Skins.erase(tr); // If the instance is not skinned, then it is sticked bool wasSkinned= tr->isSkinned()!=0; if( !wasSkinned ) { // Then decrement Bone Usage RefCount. Decrement from CLodForcedUsage if was sticked with forceCLod==true decForcedBoneUsageAndParents(tr->_FatherBoneId, tr->_ForceCLodSticked); } else { // it is skinned, advert the skinning is no more OK. // setApplySkin() use _FatherSkeletonModel to update current skeleton bone usage. tr->setApplySkin(false); } // advert transform it is no more sticked/skinned. tr->_FatherSkeletonModel= NULL; tr->_ForceCLodSticked= false; // link correctly Hrc / Clip / UpdateList... getOwnerScene()->getRoot()->hrcLinkSon( tr ); if( !wasSkinned ) { // No-op. ClipTrav graph/UpdateList updated in Hrc traversal. } else { // Skin case: must do the Job here. // Update ClipTrav here. getOwnerScene()->getRoot()->clipAddChild(tr); // Must re-add to the update list. tr->linkToUpdateList(); } // must recompute lod vertex alpha when LodCharacter used dirtLodVertexAlpha(); // must recompute list of skins if was skinned if( wasSkinned ) dirtSkinRenderLists(); } // *************************************************************************** sint32 CSkeletonModel::getBoneIdByName(const std::string &name) const { CSkeletonShape *shp= safe_cast((IShape*)Shape); return shp->getBoneIdByName(name); } // *************************************************************************** void CSkeletonModel::setInterpolationDistance(float dist) { dist= std::max(0.f, dist); // disable interpolation? if(dist==0) _LodInterpMultiplier= 0.f; else _LodInterpMultiplier= 1.f / dist; } // *************************************************************************** float CSkeletonModel::getInterpolationDistance() const { if(_LodInterpMultiplier==0) return 0.f; else return 1.f / _LodInterpMultiplier; } // *************************************************************************** void CSkeletonModel::traverseAnimDetail() { CSkeletonShape *skeShape= ((CSkeletonShape*)(IShape*)Shape); /* NB: If "this" skeleton has an AncestorSkeletonModel displayed but "this" skeleton is clipped, it will be still animDetailed. So its possible sticked sons will be displayed with correct WorldMatrix (if not themselves clipped). */ /* If the Root Skeleton Model (ie me or my AncestorSM) is asked to render a ShadowMap, BUT I am in CLod Form (and visible in HRC else won't be rendered in shadowMap...), then temporarly Avoid CLod!! To really compute the bones for this frame only. */ bool tempAvoidCLod= false; bool genShadow; if(_AncestorSkeletonModel) genShadow= _AncestorSkeletonModel->isGeneratingShadowMap(); else genShadow= isGeneratingShadowMap(); // do the test. if(genShadow && isDisplayedAsLodCharacter() && isHrcVisible() ) { tempAvoidCLod= true; // Disable it just the time of this AnimDetail setDisplayLodCharacterFlag(false); } // Update Lod, and animate. //=============== /* CTransformShape::traverseAnimDetail() is torn in 2 here because channels may be enabled/disabled by updateBoneToCompute() */ // First update Skeleton WorldMatrix (case where the skeleton is sticked). CTransform::updateWorldMatrixFromFather(); // get dist from camera. float dist= (getWorldMatrix().getPos() - getOwnerScene()->getClipTrav().CamPos).norm(); // Use dist to get current lod to use for this skeleton uint newLod= skeShape->getLodForDistance( dist ); if(!_IsEnableLOD) newLod = 0; if(newLod != _CurLod) { // set new lod to use. _CurLod= newLod; // dirt the skeleton. _BoneToComputeDirty= true; } // If needed, let's know which bone has to be computed, and enable / disable (lod) channels in channelMixer. updateBoneToCompute(); // Animate skeleton. CTransformShape::traverseAnimDetailWithoutUpdateWorldMatrix(); // If in normal mode, must update the SpawnScript if(!isDisplayedAsLodCharacter()) { _SpawnScriptEvaluator.evaluate(this); } // Prepare Lod Bone interpolation. //=============== float lodBoneInterp; const CSkeletonShape::CLod *lodNext= NULL; // if a lod exist after current lod, and if lod interpolation enabled if( _CurLod < skeShape->getNumLods()-1 && _LodInterpMultiplier>0 && _IsEnableLOD) { // get next lod. lodNext= &skeShape->getLod(_CurLod+1); // get interp value to next. lodBoneInterp= (lodNext->Distance - dist) * _LodInterpMultiplier; NLMISC::clamp(lodBoneInterp, 0.f, 1.f); // if still 1, keep cur matrix => disable interpolation if(lodBoneInterp==1.f) lodNext=NULL; } // else, no interpolation else { lodBoneInterp=1.f; } _CurLodInterp= lodBoneInterp; // Compute bones //=============== // test if bones must be updated. either if animation change or if BoneUsage change. // Retrieve the WorldMatrix of the current CTransformShape. const CMatrix &modelWorldMatrix= getWorldMatrix(); // must test / update the hierarchy of Bones. // Since they are orderd in depth-first order, we are sure that parent are computed before sons. uint numBoneToCompute= (uint)_BoneToCompute.size(); CSkeletonModel::CBoneCompute *pBoneCompute= numBoneToCompute? &_BoneToCompute[0] : NULL; // traverse only bones which need to be computed for(;numBoneToCompute>0;numBoneToCompute--, pBoneCompute++) { // compute the bone with his father, if any pBoneCompute->Bone->compute( pBoneCompute->Father, modelWorldMatrix, this); // Lod interpolation on this bone .. only if interp is enabled now, and if bone wants it if(lodNext && pBoneCompute->MustInterpolate) { // interpolate with my father matrix. const CMatrix &fatherMatrix= pBoneCompute->Father->getBoneSkinMatrix(); pBoneCompute->Bone->interpolateBoneSkinMatrix(fatherMatrix, lodBoneInterp); } } IAnimatable::clearFlag(CSkeletonModel::OwnerBit); // Sticked Objects: // they will update their WorldMatrix after, because of the AnimDetail traverse scheme: // traverse visible Clip models, and if skeleton, traverse Hrc sons. // Restore the Initial CLod flag if needed (see above) if(tempAvoidCLod) { setDisplayLodCharacterFlag(true); } // Update Animated Skins. //=============== for(uint i=0;i<_AnimDetailSkins.size();i++) { // traverse it. NB: updateWorldMatrixFromFather() is called but no-op because isSkinned() _AnimDetailSkins[i]->traverseAnimDetail(); } } // *************************************************************************** void CSkeletonModel::computeAllBones(const CMatrix &modelWorldMatrix) { // must test / update the hierarchy of Bones. // Since they are orderd in depth-first order, we are sure that parent are computed before sons. for(uint i=0;i0) _OOLodCharacterDistance= 1.0f/_LodCharacterDistance; else _OOLodCharacterDistance= 0; } // *************************************************************************** void CSkeletonModel::setLodCharacterShape(sint shapeId) { // get a ptr on the scene which owns us, and so on the lodManager. CScene *scene= getOwnerScene(); CLodCharacterManager *mngr= scene->getLodCharacterManager(); // if mngr not setuped, noop (lod not possible). if(!mngr) return; // If a shape was setup, free the instance if(_CLodInstance.ShapeId>=0) { mngr->releaseInstance(_CLodInstance); _CLodInstance.ShapeId= -1; } // assign _CLodInstance.ShapeId= shapeId; // if a real shape is setuped, alloc an instance if(_CLodInstance.ShapeId>=0) { mngr->initInstance(_CLodInstance); } } // *************************************************************************** void CSkeletonModel::computeLodTexture() { // is lod setuped if(_CLodInstance.ShapeId<0) return; // get a ptr on the scene which owns us, and so on the lodManager. CScene *scene= getOwnerScene(); CLodCharacterManager *mngr= scene->getLodCharacterManager(); // mngr must be setuped since shape Id is >-1 nlassert(mngr); /* Get the asyncTextureManager. This is a Hack. We use the AsyncTextureManager to store very low version of Textures (kept in DXTC1 format for minimum memory overhead). HENCE Lod Texture can work only with Async Textured instances!! */ CAsyncTextureManager *asyncMngr= scene->getAsyncTextureManager(); // if not setuped, cancel if(!asyncMngr) return; // **** start process. If cannot (TextureId==no more texture space), just quit. if(!mngr->startTextureCompute(_CLodInstance)) return; uint maxNumBmpToReset= 0; // **** For all skins which have a LodTexture setuped ItTransformSet it= _Skins.begin(); for(;it!=_Skins.end();it++) { // the skin should be a meshBaseInstance setuped to asyncTexturing CMeshBaseInstance *mbi= dynamic_cast(*it); if(mbi && mbi->getAsyncTextureMode() && mbi->Shape) { CMeshBase *mb= (CMeshBase*)(IShape*)(mbi->Shape); // get the LodTexture info of this shape. const CLodCharacterTexture *lodText= mb->getLodCharacterTexture(); // if setuped if(lodText) { // Ok, compute influence of this instance on the Lod. // ---- Build all bmps of the instance with help of the asyncTextureManager uint numMats= (uint)mbi->Materials.size(); // 256 materials possibles for the lod Manager numMats= min(numMats, 256U); // for endTexturecompute maxNumBmpToReset= max(maxNumBmpToReset, numMats); // process each materials for(uint i=0;igetTmpBitmap(uint8(i)); // if the material stage 0 is not textured, or has not a valid async id, build the bitmap with a color. sint asyncTextId= mbi->getAsyncTextureId(i,0); const CBitmap *coarseBitmap= NULL; if(asyncTextId!=-1) { // get it from async manager coarseBitmap= asyncMngr->getCoarseBitmap(asyncTextId); } // So if we have no bmp here, build with material color, else build a texture if(!coarseBitmap) { dstBmp.build(mbi->Materials[i].getDiffuse()); } else { dstBmp.build(*coarseBitmap); } } // ---- add the lodTextureInfo to the current texture computed mngr->addTextureCompute(_CLodInstance, *lodText); } } } // **** compile the process mngr->endTextureCompute(_CLodInstance, maxNumBmpToReset); } // *************************************************************************** void CSkeletonModel::setLodCharacterAnimId(uint animId) { _CLodInstance.AnimId= animId; } // *************************************************************************** void CSkeletonModel::setLodCharacterAnimTime(TGlobalAnimationTime time) { _CLodInstance.AnimTime= time; } // *************************************************************************** void CSkeletonModel::setLodCharacterWrapMode(bool wrapMode) { _CLodInstance.WrapMode= wrapMode; } // *************************************************************************** float CSkeletonModel::computeDisplayLodCharacterPriority() const { // if enabled if(_LodCharacterDistance>0 && _CLodInstance.ShapeId>=0) { CVector globalPos; // Get object position, test visibility; // If has a skeleton ancestor, take his world position instead, because ours is invalid. if( _AncestorSkeletonModel != NULL) { // if the ancestore is clipped, quit if( !_AncestorSkeletonModel->isClipVisible() ) return 0; // take ancestor world position globalPos= _AncestorSkeletonModel->getWorldMatrix().getPos(); } else { // if the skeleton is clipped, quit if( !isClipVisible() ) return 0; // take our world position globalPos= getWorldMatrix().getPos(); } // compute distance from camera. float dist= (getOwnerScene()->getClipTrav().CamPos - globalPos).norm(); // compute priority return dist*_OOLodCharacterDistance; } else return 0; } // *************************************************************************** void CSkeletonModel::setDisplayLodCharacterFlag(bool displayCLod) { // if enabled if(_LodCharacterDistance>0 && _CLodInstance.ShapeId>=0) { // If the flag has changed since last frame, must recompute bone Usage. if(_DisplayedAsLodCharacter != displayCLod) _BoneToComputeDirty= true; // set new state _DisplayedAsLodCharacter= displayCLod; } } // *************************************************************************** void CSkeletonModel::traverseRender() { H_AUTO( NL3D_Skeleton_Render ); // render as CLod, or render Skins. if(isDisplayedAsLodCharacter()) renderCLod(); else renderSkins(); } // *************************************************************************** void CSkeletonModel::computeCLodVertexAlpha(CLodCharacterManager *mngr) { // if shape id set. if(_CLodInstance.ShapeId<0) return; // get the lod shape,a nd check exist in the manager const CLodCharacterShape *lodShape= mngr->getShape(_CLodInstance.ShapeId); if(lodShape) { // start process. //----------------- lodShape->startBoneAlpha(_CLodInstance.VertexAlphas); // build an Id map, from Skeleton Ids to the lodShapes ids. (because may be differents) static vector boneMap; // reset to -1 (ie not found) boneMap.clear(); boneMap.resize(Bones.size(), -1); uint i; // for all skeletons bones. for(i=0; igetBoneIdByName(Bones[i].getBoneName());; } // Parse all skins //----------------- ItTransformSet it; for(it= _Skins.begin(); it!=_Skins.end(); it++) { CTransform *skin= *it; // get array of bone used for this skin. const vector *skinUsage= skin->getSkinBoneUsage(); // check correct skin if(skinUsage) { // For all bones used for(uint i=0; isize(); i++) { // the id in the vector point to a bone in the skeleton. Hence use the boneMap to translate it // in the lodShape ids. sint idInLod= boneMap[(*skinUsage)[i]]; // only if id found in the lod shape if(idInLod>=0) // add color to this bone. lodShape->addBoneAlpha(idInLod, _CLodInstance.VertexAlphas); } } } // Parse all sticked objects //----------------- for(it= _StickedObjects.begin(); it!=_StickedObjects.end(); it++) { CTransform *object= *it; // get on which bone this object is linked. // use the boneMap to translate id to lodShape id. sint idInLod= boneMap[object->_FatherBoneId]; // only if id found in the lod shape if(idInLod>=0) // add color to this bone. lodShape->addBoneAlpha(idInLod, _CLodInstance.VertexAlphas); } } } // *************************************************************************** void CSkeletonModel::updateSkinRenderLists() { // If need to update array of skins to compute if(_SkinToRenderDirty) { uint i; _SkinToRenderDirty= false; // Reset the LevelDetail. _LevelDetail.MinFaceUsed= 0; _LevelDetail.MaxFaceUsed= 0; // If must follow default MRM setup from skins. if(_DefaultMRMSetup) { _LevelDetail.DistanceCoarsest= 0; _LevelDetail.DistanceMiddle= 0; _LevelDetail.DistanceFinest= 0; } // reset Bone Sphere of skeleton. static std::vector sphereEmpty; sphereEmpty.clear(); sphereEmpty.resize(Bones.size(), true); for(i=0;igetVisibility()==CHrcTrav::Hide) continue; // if transparent, then must fill in transparent list. if(skin->isTransparent()) transparentSize++; // else may fill in opaquelist. NB: for optimisation, don't add in opaqueList // if added to the transperent list (all materials are rendered) else if(skin->isOpaque()) opaqueSize++; // if animDetailable, then must fill list if(skin->isAnimDetailable()) animDetailSize++; // if the skin support MRM, then must update levelDetal number of faces CTransformShape *trShape= dynamic_cast(skin); if(trShape) { const CMRMLevelDetail *skinLevelDetail= trShape->getMRMLevelDetail(); if(skinLevelDetail) { // Add Faces to the Skeleton level detail _LevelDetail.MinFaceUsed+= skinLevelDetail->MinFaceUsed; _LevelDetail.MaxFaceUsed+= skinLevelDetail->MaxFaceUsed; // MRM Max skin setup. if(_DefaultMRMSetup) { // Get the maximum distance setup (ie the one which degrades the less) _LevelDetail.DistanceCoarsest= max(_LevelDetail.DistanceCoarsest, skinLevelDetail->DistanceCoarsest); _LevelDetail.DistanceMiddle= max(_LevelDetail.DistanceMiddle, skinLevelDetail->DistanceMiddle); _LevelDetail.DistanceFinest= max(_LevelDetail.DistanceFinest, skinLevelDetail->DistanceFinest); } } } // Enlarge Bone BBox const std::vector *boneUsage= skin->getSkinBoneUsage(); const std::vector *boneSphere= skin->getSkinBoneSphere(); if(boneUsage && boneSphere) { nlassert(boneUsage->size()==boneSphere->size()); for(i=0;isize();i++) { const CBSphere &sphere= (*boneSphere)[i]; sint boneId= (*boneUsage)[i]; nlassert(boneId<(sint)Bones.size()); // if valid boneId, and sphere not empty (ie not -1 radius) if(boneId>-1 && sphere.Radius>=0) { if(sphereEmpty[boneId]) { sphereEmpty[boneId]= false; Bones[boneId]._MaxSphere= sphere; } else { Bones[boneId]._MaxSphere.setUnion(Bones[boneId]._MaxSphere, sphere); } } } } // Whole skeleton model Support Fast intersection only if all // displayed skins support skin intersection _SupportFastIntersect= _SupportFastIntersect && skin->supportIntersectSkin(); } // MRM Max skin setup. if(_DefaultMRMSetup) { // compile LevelDetail. if(_LevelDetail.MaxFaceUsed==0) // build a bug-free level detail buildDefaultLevelDetail(); else _LevelDetail.compileDistanceSetup(); } // alloc array. _OpaqueSkins.clear(); _TransparentSkins.clear(); _AnimDetailSkins.clear(); _OpaqueSkins.resize(opaqueSize); _TransparentSkins.resize(transparentSize); _AnimDetailSkins.resize(animDetailSize); // ReParse, to fill array. uint opaqueId= 0; uint transparentId= 0; uint animDetailId= 0; for(it= _Skins.begin();it!=_Skins.end();it++) { CTransform *skin= *it; // If the skin is hidden, don't add it to any list! if(skin->getVisibility()==CHrcTrav::Hide) continue; // if transparent, then must fill in transparent list. if(skin->isTransparent()) { nlassert(transparentIdisOpaque()) { nlassert(opaqueIdisAnimDetailable()) { nlassert(animDetailId0 ); } } // *************************************************************************** void CSkeletonModel::buildDefaultLevelDetail() { // Avoid divide by zero. _LevelDetail.MinFaceUsed= 0; _LevelDetail.MaxFaceUsed= 0; _LevelDetail.DistanceFinest= 1; _LevelDetail.DistanceMiddle= 2; _LevelDetail.DistanceCoarsest= 3; _LevelDetail.compileDistanceSetup(); } // *************************************************************************** void CSkeletonModel::renderCLod() { CRenderTrav &renderTrav= getOwnerScene()->getRenderTrav(); IDriver *drv= renderTrav.getDriver(); CScene *scene= getOwnerScene(); // Transparent pass? quit if(!renderTrav.isCurrentPassOpaque()) return; // the lod manager. no op if not here CLodCharacterManager *mngr= scene->getLodCharacterManager(); if(!mngr) return; // Get global lighting on the instance. Suppose SunAmbient only. //================= const CLightContribution *lightContrib; // the std case is to take my model lightContribution if(_AncestorSkeletonModel==NULL) lightContrib= &getSkeletonLightContribution(); // but if skinned/sticked (directly or not) to a skeleton, take its. else lightContrib= &_AncestorSkeletonModel->getSkeletonLightContribution(); // compute his main light contribution result. Try first with sun CRGBA mainAmbient= scene->getSunAmbient(); CRGBA mainDiffuse= scene->getSunDiffuse(); // modulate sun contribution mainDiffuse.modulateFromuiRGBOnly(mainDiffuse, lightContrib->SunContribution ); CVector mainLightDir= scene->getSunDirection(); // Add ambient with Lod Emit mainAmbient.addRGBOnly(mainAmbient, _LodEmit); /* During night, and in the buildings, it may be better to use one of the other Points lights Test only with the first pointLight, for faster compute, even if It may fail in some cases. */ CPointLight *mainPL= lightContrib->PointLight[0]; if(mainPL) { CRGBA plDiffuse; // get the diffuse of the pointLight, attenuated from distance and importance. plDiffuse.modulateFromuiRGBOnly(mainPL->getDiffuse(), lightContrib->AttFactor[0]); // compare the 2 diffuse uint d0= mainDiffuse.R + mainDiffuse.G + mainDiffuse.B; uint d1= plDiffuse.R + plDiffuse.G + plDiffuse.B; // if the pointLight is lighter, take it. if(d1>d0) { // leave ambient, but take diffuse and pointLight fake Direction mainDiffuse= plDiffuse; mainLightDir= getWorldMatrix().getPos() - mainPL->getPosition(); mainLightDir.normalize(); } } // compute colors of the lods. //================= // NB: even if texturing is sufficient, still important for AlphaTest. // If must recompute alpha because of change of skin added/deleted if(_CLodVertexAlphaDirty) { // recompute vertex alpha computeCLodVertexAlpha(mngr); // set _CLodVertexAlphaDirty to false. _CLodVertexAlphaDirty= false; } // render the Lod in the LodManager. //================= // render must have been intialized nlassert(mngr->isRendering()); // add the instance to the manager. if(!mngr->addRenderCharacterKey(_CLodInstance, getWorldMatrix(), mainAmbient, mainDiffuse, mainLightDir) ) { // If failed to add it because no more vertex space in the manager, retry. // close vertexBlock, compile render mngr->endRender(); // and restart. mngr->beginRender(drv, renderTrav.CamPos); // retry. but no-op if refail. mngr->addRenderCharacterKey(_CLodInstance, getWorldMatrix(), mainAmbient, mainDiffuse, mainLightDir); } } // *************************************************************************** void CSkeletonModel::renderSkins() { // Render skins according to the pass. CRenderTrav &rdrTrav= getOwnerScene()->getRenderTrav(); // get a ptr on the driver IDriver *drv= rdrTrav.getDriver(); nlassert(drv); // Compute the levelOfDetail float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(getNumTrianglesAfterLoadBalancing()); // force normalisation of normals.. bool bkupNorm= drv->isForceNormalize(); drv->forceNormalize(true); // rdr good pass if(rdrTrav.isCurrentPassOpaque()) { // Compute in Pass Opaque only the light contribution. // Easier for skeleton: suppose lightable, no local attenuation // the std case is to take my model lightContribution if(_AncestorSkeletonModel==NULL) setupCurrentLightContribution(&_LightContribution, false); // but if sticked (directly or not) to a skeleton, take its. else setupCurrentLightContribution(&_AncestorSkeletonModel->_LightContribution, false); // Activate Driver setup: light and modelMatrix changeLightSetup( &rdrTrav ); rdrTrav.getDriver()->setupModelMatrix(getWorldMatrix()); // Render all totaly opaque skins. renderSkinList(_OpaqueSkins, alphaMRM); } else { // NB: must have some transparent skins, since thee skeletonModel is traversed in the transparent pass. // Activate Driver setup: light and modelMatrix changeLightSetup( &rdrTrav ); rdrTrav.getDriver()->setupModelMatrix(getWorldMatrix()); // render all opaque/transparent skins renderSkinList(_TransparentSkins, alphaMRM); } // bkup force normalisation. drv->forceNormalize(bkupNorm); } // *************************************************************************** void CSkeletonModel::renderSkinList(NLMISC::CObjectVector &skinList, float alphaMRM) { CRenderTrav &rdrTrav= getOwnerScene()->getRenderTrav(); // if the SkinManager is not possible at all, just rendered the std way. if( !rdrTrav.getMeshSkinManager() ) { for(uint i=0;irenderSkin(alphaMRM); } } else { // get the meshSkinManager CVertexStreamManager &meshSkinManager= *rdrTrav.getMeshSkinManager(); // array (rarely allocated) of skins with grouping support static std::vector skinsToGroup; static std::vector baseVertices; skinsToGroup.clear(); baseVertices.clear(); // get the maxVertices the manager support uint maxVertices= meshSkinManager.getMaxVertices(); uint vertexSize= meshSkinManager.getVertexSize(); // render any skins which do not support SkinGrouping, and fill array of skins to group for(uint i=0;isupportSkinGrouping()) { H_AUTO( NL3D_Skin_NotGrouped ); skinList[i]->renderSkin(alphaMRM); } else { skinsToGroup.push_back(skinList[i]); } } H_AUTO( NL3D_Skin_Grouped ); // For each skin, have an index which gives the decal of the vertices in the buffer baseVertices.resize(skinsToGroup.size()); // while there is skin to render in group uint skinId= 0; while(skinIdrenderSkinGroupGeom(alphaMRM, remainingVertices, vbDest + vertexSize*currentBaseVertex ); // -1 means that this skin can't render because no space left for her. Then stop for this block if(numVerticesAdded==-1) break; // Else ok, get the currentBaseVertex for this skin baseVertices[skinId]= currentBaseVertex; // and jump to the next place currentBaseVertex+= numVerticesAdded; remainingVertices-= numVerticesAdded; // go to the next skin skinId++; } // release buffer. ATI: release only vertices used. meshSkinManager.unlock(currentBaseVertex); // Second pass, render the primitives. //------------ meshSkinManager.activate(); /* Render any primitives that are not specular. Group specular ones into specularRdrPasses. NB: this speed a lot (specular setup is heavy)! */ static std::vector specularRdrPasses; specularRdrPasses.clear(); for(uint i=startSkinId;irenderSkinGroupPrimitives(baseVertices[i], specularRdrPasses, i); } // If any skin Specular rdrPass to render if(!specularRdrPasses.empty()) { // Sort by Specular Map. HTimerInfo: take 0.0% time sort(specularRdrPasses.begin(), specularRdrPasses.end()); // Batch Specular! HTimerInfo: take 0.2% rdrTrav.getDriver()->startSpecularBatch(); // Render all of them for(uint i=0;irenderSkinGroupSpecularRdrPass(specRdrPass.RdrPassIndex); } // End Batch Specular! HTimerInfo: take 0.0% rdrTrav.getDriver()->endSpecularBatch(); } // End of this block, swap to the next buffer. HTimerInfo: take 0.0% meshSkinManager.swapVBHard(); } } } // *************************************************************************** float CSkeletonModel::getNumTriangles (float distance) { // If the skeleton is displayed as a CLod suppose 0 triangles. if( isDisplayedAsLodCharacter() ) return 0; else // NB: this is an approximation, but this is continious. return _LevelDetail.getNumTriangles(distance); } // *************************************************************************** void CSkeletonModel::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest) { // check input. if(distanceFinest<0) return; if(distanceMiddle<=distanceFinest) return; if(distanceCoarsest<=distanceMiddle) return; // Change. _LevelDetail.DistanceFinest= distanceFinest; _LevelDetail.DistanceMiddle= distanceMiddle; _LevelDetail.DistanceCoarsest= distanceCoarsest; // compile _LevelDetail.compileDistanceSetup(); // Never more use MAX skin setup. _DefaultMRMSetup= false; } // *************************************************************************** void CSkeletonModel::resetDefaultMRMDistanceSetup() { _DefaultMRMSetup= true; // Must use Skins linked to know the MRM setup. dirtSkinRenderLists(); } // *************************************************************************** bool CSkeletonModel::computeRenderedBBox(NLMISC::CAABBox &bbox, bool computeInWorld) { // reset bbox CAABBox tmpBBox; tmpBBox.setCenter(CVector::Null); tmpBBox.setHalfSize(CVector::Null); bool empty= true; // Not visible => empty bbox if(!isClipVisible()) return false; // For all bones uint i; for(i=0;i &dest) const { dest.clear(); // Not visible => empty bbox if(!isClipVisible()) return; dest.resize(_BoneToCompute.size()); for(uint i=0;i<_BoneToCompute.size();i++) { CBone *bone= _BoneToCompute[i].Bone; bone->_MaxSphere.applyTransform(bone->getWorldMatrix(), dest[i]); } } // *************************************************************************** bool CSkeletonModel::computeRenderedBBoxWithBoneSphere(NLMISC::CAABBox &bbox, bool computeInWorld) { // Not visible => empty bbox if(!isClipVisible()) return false; if(_BoneToCompute.empty()) return false; if (_Skins.empty()) return false; updateSkinRenderLists(); // **** Compute The BBox with Bones of the skeleton CVector minBB, maxBB; for(uint i=0;i<_BoneToCompute.size();i++) { CBone *bone= _BoneToCompute[i].Bone; // compute the world / local sphere const CMatrix &boneMat = computeInWorld ? bone->getWorldMatrix() : bone->getLocalSkeletonMatrix(); CBSphere sphere; bone->_MaxSphere.applyTransform(boneMat, sphere); // compute bone min max bounding cube. CVector minBone, maxBone; minBone= maxBone= sphere.Center; float r= sphere.Radius; minBone.x-= r; minBone.y-= r; minBone.z-= r; maxBone.x+= r; maxBone.y+= r; maxBone.z+= r; // set or extend if(i==0) { minBB= minBone; maxBB= maxBone; } else { minBB.minof(minBB, minBone); maxBB.maxof(maxBB, maxBone); } } // build the bbox bbox.setMinMax(minBB, maxBB); return true; } // *************************************************************************** bool CSkeletonModel::computeCurrentBBox(NLMISC::CAABBox &bbox, bool forceCompute /* = false*/, bool computeInWorld) { // animate all bones channels (detail only channels). don't bother cur lod state. CChannelMixer *chanmix= getChannelMixer(); if (chanmix) { // Force detail evaluation. chanmix->resetEvalDetailDate(); chanmix->eval(true, 0); chanmix->resetEvalDetailDate(); } // compute all skeleton bones computeAllBones(CMatrix::Identity); // reset bbox CAABBox tmpBBox; tmpBBox.setCenter(CVector::Null); tmpBBox.setHalfSize(CVector::Null); bool empty= true; // For all bones uint i; for(i=0;i slowdown...) */ modelPos= _WorldMatrix.getPos(); } // Consider Skeletons as not big lightable modelRadius= 0; } // *************************************************************************** void CSkeletonModel::setBoneAnimCtrl(uint boneId, IAnimCtrl *ctrl) { if(boneId>=Bones.size()) return; CBone &bone= Bones[boneId]; // Update refCount if(ctrl && !bone._AnimCtrl) _AnimCtrlUsage++; else if(!ctrl && bone._AnimCtrl) _AnimCtrlUsage--; // set bone._AnimCtrl= ctrl; } // *************************************************************************** IAnimCtrl *CSkeletonModel::getBoneAnimCtrl(uint boneId) const { if(boneId>=Bones.size()) return NULL; return Bones[boneId]._AnimCtrl; } // *************************************************************************** bool CSkeletonModel::fastIntersect(const NLMISC::CVector &p0, const NLMISC::CVector &dir, float &dist2D, float &distZ, bool computeDist2D) { if(!_SupportFastIntersect) return false; // no intersection by default dist2D= FLT_MAX; distZ= FLT_MAX; // The skinning must be done in final RaySpace. CMatrix toRaySpace; // compute the ray matrix CVector dirn= dir; if(dirn.isNull()) dirn= CVector::K; dirn.normalize(); toRaySpace.setArbitraryRotK(dirn); toRaySpace.setPos(p0); // The skinning must be done in ray space: (RayMat-1)*skelWorldMatrix; toRaySpace.invert(); toRaySpace*= getWorldMatrix(); // displayed as a CLod? if(isDisplayedAsLodCharacter()) { // must do the test with the CLod, because Bones are not animated at all (hence skinning would be false) CLodCharacterManager *mngr= getOwnerScene()->getLodCharacterManager(); if(!mngr) return false; // test the instance if(!mngr->fastIntersect(_CLodInstance, toRaySpace, dist2D, distZ, computeDist2D)) return false; } else { // For all skins ItTransformSet it; for(it= _Skins.begin();it!=_Skins.end();it++) { CTransform *skin= *it; // If the skin is hidden, don't test intersection! if(skin->getVisibility()==CHrcTrav::Hide) continue; if(!skin->supportIntersectSkin()) continue; // compute intersection with this skin float skinDist2D, skinDistZ; if(skin->intersectSkin(toRaySpace, skinDist2D, skinDistZ, computeDist2D)) { // true intersection found? if(skinDist2D==0) { dist2D= 0; distZ= min(distZ, skinDistZ); } // else lower the distance to the skins? else if(dist2D>0) { dist2D= min(dist2D, skinDist2D); } } } } // no intersection found? set Z to 0 (to be clean) if(dist2D>0) distZ= 0; return true; } // *************************************************************************** void CSkeletonModel::remapSkinBones(const std::vector &bonesName, std::vector &bonesId, std::vector &remap) { // Resize boneId to the good size. bonesId.resize(bonesName.size()); remap.resize(bonesName.size()); // **** For each bones, compute remap for (uint bone=0; bonegetRenderTrav(); IDriver *driver= renderTrav.getAuxDriver(); if(!Shape) return; // update ShadowMap data if needed. // **** updateShadowMap(driver); // compute the ProjectionMatrix. // **** // Compute the BBox in World, with bounding Box of Bones, and with BoundingBox of sticked Objects CAABBox bbWorld; computeWorldBBoxForShadow(bbWorld); // Here the bbox is defined in world, hence must remove the World Pos. CMatrix localPosMatrix; localPosMatrix.setPos(-getWorldMatrix().getPos()); // setup cameraMatrix with BBox and Enlarge For 1 pixel CMatrix cameraMatrix; _ShadowMap->buildCasterCameraMatrix(lightDir, localPosMatrix, bbWorld, cameraMatrix); // Render. // **** // setup the orhtogonal frustum and viewMatrix to include all the object. driver->setFrustum(0,1,0,1,0,1,false); driver->setupViewMatrix(cameraMatrix.inverted()); // render the Skinned meshs, and also the Sticked Objects/Skeletons CMaterial &castMat= renderTrav.getShadowMapManager().getCasterShadowMaterial(); renderIntoSkeletonShadowMap(this, castMat); // Infos. // **** // Compute the BackPoint: the first point to be shadowed. CVector backPoint= bbWorld.getCenter(); // get the 3/4 bottom of the shape backPoint.z-= bbWorld.getHalfSize().z/2; // Use the 3/4 bottom of the BBox minus the light direction in XY. CVector ldir= lightDir; ldir.z= 0; ldir.normalize(); // NB: This way seems to works quite well, even if the worldBBox is changing every frame. float lenXY= (CVector(bbWorld.getHalfSize().x, bbWorld.getHalfSize().y, 0)).norm(); backPoint-= ldir*lenXY; // localPos. backPoint-= getWorldMatrix().getPos(); // Compute LocalProjectionMatrix and other infos from cameraMatrix and backPoint? _ShadowMap->buildProjectionInfos(cameraMatrix, backPoint, getShadowMapMaxDepth()); } // *************************************************************************** CShadowMap *CSkeletonModel::getShadowMap() { return _ShadowMap; } // *************************************************************************** void CSkeletonModel::createShadowMap() { // create the shadowMap if(!_ShadowMap) { _ShadowMap= new CShadowMap(&getOwnerScene()->getRenderTrav().getShadowMapManager()); getOwnerScene()->registerShadowCasterToList(this); } } // *************************************************************************** void CSkeletonModel::deleteShadowMap() { if(_ShadowMap) { delete _ShadowMap; _ShadowMap= NULL; getOwnerScene()->unregisterShadowCasterToList(this); } } // *************************************************************************** void CSkeletonModel::updateShadowMap(IDriver * /* driver */) { nlassert(_ShadowMap); // create/update texture if(_ShadowMap->getTextureSize()!=getOwnerScene()->getShadowMapTextureSize()) { _ShadowMap->initTexture(getOwnerScene()->getShadowMapTextureSize()); } } // *************************************************************************** void CSkeletonModel::renderShadowSkins(CMaterial &castMat) { H_AUTO( NL3D_Skin_RenderShadow ); CRenderTrav &rdrTrav= getOwnerScene()->getRenderTrav(); // Render Shadow in auxiliary driver. IDriver *driver= rdrTrav.getAuxDriver(); // if the SkinManager is not possible at all, just rendered the std way if( !rdrTrav.getShadowMeshSkinManager() ) { // can occurs????? // ABORT!! ... avoid Mesh Shadowing (free shadowMap)? Replace with a dummy Shadow? // For now, no-op... } else { uint i; // get the meshSkinManager CVertexStreamManager &meshSkinManager= *rdrTrav.getShadowMeshSkinManager(); // array (rarely allocated) of skins with grouping support static std::vector skinsToGroup; static std::vector baseVertices; skinsToGroup.clear(); baseVertices.clear(); // get the maxVertices the manager support uint maxVertices= meshSkinManager.getMaxVertices(); uint vertexSize= meshSkinManager.getVertexSize(); // fill array of skins to group (suppose all support else won't be rendered) for(i=0;i<_OpaqueSkins.size();i++) { if(_OpaqueSkins[i]->supportShadowSkinGrouping()) skinsToGroup.push_back(_OpaqueSkins[i]); } for(i=0;i<_TransparentSkins.size();i++) { if(_TransparentSkins[i]->supportShadowSkinGrouping()) skinsToGroup.push_back(_TransparentSkins[i]); } // For each skin, have an index which gives the decal of the vertices in the buffer baseVertices.resize(skinsToGroup.size()); // while there is skin to render in group uint skinId= 0; while(skinIdrenderShadowSkinGeom(remainingVertices, vbDest + vertexSize*currentBaseVertex ); // -1 means that this skin can't render because no space left for her. Then stop for this block if(numVerticesAdded==-1) break; // Else ok, get the currentBaseVertex for this skin baseVertices[skinId]= currentBaseVertex; // and jump to the next place currentBaseVertex+= numVerticesAdded; remainingVertices-= numVerticesAdded; // go to the next skin skinId++; } // release buffer. ATI: release only vertices used. meshSkinManager.unlock(currentBaseVertex); // Second pass, render the primitives. //------------ meshSkinManager.activate(); // Render any primitives for(uint i=startSkinId;irenderShadowSkinPrimitives(castMat, driver, baseVertices[i]); } // End of this block, swap to the next buffer meshSkinManager.swapVBHard(); } } } // *************************************************************************** bool CSkeletonModel::computeWorldBBoxForShadow(NLMISC::CAABBox &worldBB) { uint i; // If even not visible, no-op if(!isHrcVisible() || !Shape) return false; // **** Compute The BBox with Bones of the skeleton CVector minBB, maxBB; for(i=0;i<_BoneToCompute.size();i++) { CBone *bone= _BoneToCompute[i].Bone; // compute the world sphere const CMatrix &worldMat= bone->getWorldMatrix(); CBSphere worldSphere; bone->_MaxSphere.applyTransform(worldMat, worldSphere); // compute bone min max bounding cube. CVector minBone, maxBone; minBone= maxBone= worldSphere.Center; float r= worldSphere.Radius; minBone.x-= r; minBone.y-= r; minBone.z-= r; maxBone.x+= r; maxBone.y+= r; maxBone.z+= r; // set or extend if(i==0) { minBB= minBone; maxBB= maxBone; } else { minBB.minof(minBB, minBone); maxBB.maxof(maxBB, maxBone); } } // build the bbox worldBB.setMinMax(minBB, maxBB); /* // Fake Version. Faster (-0.2 ms for 8 compute each frame) but false. for(i=0;i<_BoneToCompute.size();i++) { CBone *bone= _BoneToCompute[i].Bone; const CMatrix &worldMat= bone->getWorldMatrix(); if(i==0) worldBB.setCenter(worldMat.getPos()); else worldBB.extend(worldMat.getPos()); } worldBB.setHalfSize(worldBB.getHalfSize() *1.5f); */ // **** Add to this bbox the ones of the Sticked objects. ItTransformSet it; for(it= _StickedObjects.begin();it!=_StickedObjects.end();it++) { CTransform *stickModel= *it; // Do the same for this son (NB: recurs, may be a skeleton too!!) CAABBox stickBB; if(stickModel->computeWorldBBoxForShadow(stickBB)) { // Make union of the 2 worldBB= CAABBox::computeAABBoxUnion(worldBB, stickBB); } } // Done! return true; } // *************************************************************************** void CSkeletonModel::renderIntoSkeletonShadowMap(CSkeletonModel *rootSkeleton, CMaterial &castMat) { // If even not visible, no-op if(!isHrcVisible() || !Shape) return; // render into aux Driver IDriver *driver= getOwnerScene()->getRenderTrav().getAuxDriver(); // **** Render the Skeleton Skins // The model Matrix is special here. It must be the Skeleton World Matrix, minus The Root Skeleton pos. CMatrix localPosMatrix; localPosMatrix.setRot( getWorldMatrix() ); // NB: if this==rootSkeleton, then the final pos will be CVector::Null localPosMatrix.setPos( getWorldMatrix().getPos() - rootSkeleton->getWorldMatrix().getPos() ); driver->setupModelMatrix(localPosMatrix); // render the skins. renderShadowSkins(castMat); // **** Render The Sticked Objects. ItTransformSet it; for(it= _StickedObjects.begin();it!=_StickedObjects.end();it++) { CTransform *stickModel= *it; stickModel->renderIntoSkeletonShadowMap(rootSkeleton, castMat); } } } // NL3D