// 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/3d/mesh_base_instance.h" #include "nel/3d/mesh_base.h" #include "nel/3d/scene.h" #include "nel/3d/animation.h" #include "nel/misc/debug.h" #include "nel/3d/anim_detail_trav.h" #include "nel/3d/texture_file.h" #include "nel/3d/async_texture_manager.h" #include using namespace NLMISC; namespace NL3D { // *************************************************************************** CMeshBaseInstance::CMeshBaseInstance() { IAnimatable::resize(AnimValueLast); _AsyncTextureToLoadRefCount= 0; _AsyncTextureMode= false; _AsyncTextureReady= true; _AsyncTextureDirty= false; _AsyncTextureDistance= 0; _VPWindTreeFixed = false; // I am a CMeshBaseInstance!! CTransform::setIsMeshBaseInstance(true); } // *************************************************************************** CMeshBaseInstance::~CMeshBaseInstance() { // If AsyncTextureMode, must disable. This ensure that async loading stop, and that no ref still exist // in the AsyncTextureManager if(_AsyncTextureMode) enableAsyncTextureMode(false); } // *************************************************************************** void CMeshBaseInstance::registerBasic() { CScene::registerModel(MeshBaseInstanceId, TransformShapeId, CMeshBaseInstance::creator); } // *************************************************************************** void CMeshBaseInstance::registerToChannelMixer(CChannelMixer *chanMixer, const std::string &prefix) { uint32 i; CTransformShape::registerToChannelMixer(chanMixer, prefix); // Add any materials. for (i = 0; i < _AnimatedMaterials.size(); i++) { // append material matname.* _AnimatedMaterials[i].registerToChannelMixer(chanMixer, prefix + _AnimatedMaterials[i].getMaterialName() + "."); } // Add any morph for (i = 0; i < _AnimatedMorphFactor.size(); i++) { _AnimatedMorphFactor[i].registerToChannelMixer(chanMixer, prefix + _AnimatedMorphFactor[i].getName()); } } // *************************************************************************** ITrack* CMeshBaseInstance::getDefaultTrack (uint valueId) { // Pointer on the CMeshBase CMeshBase* pMesh=(CMeshBase*)(IShape*)Shape; // Switch the value switch (valueId) { case CTransform::PosValue: return pMesh->getDefaultPos(); case CTransform::RotEulerValue: return pMesh->getDefaultRotEuler(); case CTransform::RotQuatValue: return pMesh->getDefaultRotQuat(); case CTransform::ScaleValue: return pMesh->getDefaultScale(); case CTransform::PivotValue: return pMesh->getDefaultPivot(); default: // Problem, new values ? nlstop; }; return NULL; } // *************************************************************************** uint32 CMeshBaseInstance::getNbLightMap() { CMeshBase* pMesh=(CMeshBase*)(IShape*)Shape; return (uint32)pMesh->_LightInfos.size(); } // *************************************************************************** void CMeshBaseInstance::getLightMapName( uint32 nLightMapNb, std::string &LightMapName ) { CMeshBase* pMesh=(CMeshBase*)(IShape*)Shape; if( nLightMapNb >= pMesh->_LightInfos.size() ) return; LightMapName = pMesh->_LightInfos[nLightMapNb].AnimatedLight; } // *************************************************************************** uint32 CMeshBaseInstance::getNbBlendShape() { return (uint32)_AnimatedMorphFactor.size(); } // *************************************************************************** void CMeshBaseInstance::getBlendShapeName (uint32 nBlendShapeNb, std::string &BlendShapeName ) { if (nBlendShapeNb >= _AnimatedMorphFactor.size()) return; BlendShapeName = _AnimatedMorphFactor[nBlendShapeNb].getName(); } // *************************************************************************** void CMeshBaseInstance::setBlendShapeFactor (const std::string &BlendShapeName, float rFactor) { for (uint32 i = 0; i < _AnimatedMorphFactor.size(); ++i) if (BlendShapeName == _AnimatedMorphFactor[i].getName()) { _AnimatedMorphFactor[i].setFactor (rFactor); } } // *************************************************************************** void CMeshBaseInstance::traverseHrc() { CMeshBase *mb = NLMISC::safe_cast((IShape *) Shape); // if the base instance uses automatic animations, we must also setup the date of the channel mixer controlling this object if (mb->getAutoAnim()) { // Unfreeze HRC for those models CTransform *node = this; while (node) { node->unfreezeHRC(); node = node->hrcGetParent(); } // setup the channel mixer date CChannelMixer *chanMix = getChannelMixer(); if (chanMix) { const CAnimation *anim = chanMix->getSlotAnimation(0); /** We perform wrapping ourselves. * We avoid using a playlist, to not create one more obj. */ if (anim) { // Animation offset are setuped before clipping, they will be used for detail too. float animLength = anim->getEndTime() - anim->getBeginTime(); if (animLength > 0) { float currTime = (TAnimationTime) getOwnerScene()->getCurrentTime(); float startTime = (uint) (currTime / animLength) * animLength; // Set the channel mixer date using the global date of the scene chanMix->setSlotTime(0, anim->getBeginTime() + currTime - startTime); } else { chanMix->setSlotTime(0, anim->getBeginTime()); } /** Eval non detail animation */ chanMix->eval(false); } } } CTransformShape::traverseHrc(); } // *************************************************************************** void CMeshBaseInstance::traverseAnimDetail() { CMeshBase *mb = NLMISC::safe_cast((IShape *) Shape); CTransformShape::traverseAnimDetail(); // update animated materials. // test if animated materials must be updated. if(IAnimatable::isTouched(CMeshBaseInstance::OwnerBit)) { // must test / update all AnimatedMaterials. for(uint i=0;i<_AnimatedMaterials.size();i++) { // This test and update the pointed material. _AnimatedMaterials[i].update(); } IAnimatable::clearFlag(CMeshBaseInstance::OwnerBit); } // Lightmap automatic animation // Animated lightmap must have the same size than shape info lightmap. const uint count0 = (uint)_AnimatedLightmap.size(); const uint count1 = (uint)mb->_LightInfos.size (); nlassert (count0 == count1); if (count0 == count1) { for ( uint i = 0; i < count0; ++i ) { CMeshBase::CLightMapInfoList &groupInfo = mb->_LightInfos[i]; std::list::iterator ite = groupInfo.StageList.begin (); while (ite != groupInfo.StageList.end ()) { sint animatedLightmap = _AnimatedLightmap[i]; if (animatedLightmap != -1) { CRGBA factor = getOwnerScene ()->getAnimatedLightFactor (animatedLightmap, groupInfo.LightGroup); Materials[ite->MatId].setLightMapFactor ( ite->StageId, factor ); } else { CRGBA factor = getOwnerScene ()->getLightmapGroupColor (groupInfo.LightGroup); Materials[ite->MatId].setLightMapFactor ( ite->StageId, factor ); } ite++; } } } } // *************************************************************************** void CMeshBaseInstance::selectTextureSet(uint id) { nlassert(Shape); CMeshBase *mb = NLMISC::safe_cast((IShape *) Shape); const uint numMat = mb->getNbMaterial(); nlassert(numMat == Materials.size()); // see which material are selectable for(uint k = 0; k < numMat; ++k) { CMaterial &mat = mb->getMaterial(k); for(uint l = 0; l < IDRV_MAT_MAXTEXTURES; ++l) { if (mat.getTexture(uint8(l)) && mat.getTexture(uint8(l))->isSelectable()) { // use a smartPtr so the textFile will be released if just used to set the name for AsyncTextures. CSmartPtr texNSV= mat.getTexture(uint8(l))->buildNonSelectableVersion(id); // std case: just replace the texture. if(!_AsyncTextureMode) { Materials[k].setTexture(uint8(l), texNSV); } // Async case else { // If texture file, must copy the texture name if(AsyncTextures[k].IsTextureFile[l]) { CTextureFile *textFile= safe_cast((ITexture*)texNSV); AsyncTextures[k].TextureNames[l]= textFile->getFileName(); } // else replace the texture. else Materials[k].setTexture(uint8(l), texNSV); } } } } // Flag the instance as AsyncTextureDirty if in this mode if(_AsyncTextureMode) { setAsyncTextureDirty(true); } } // *************************************************************************** void CMeshBaseInstance::initAnimatedLightIndex (const CScene &scene) { /* Scan lightmaps used by the shape, and for each, bind the transform shape to an * animated lightmap index from the scene. This index will be used at runtime to * get quickly a lightmap factor. This index is not set in the CShape because * the CShape can be used with several CScene. */ // For each lightmap in the shape CMeshBase *pMB = static_cast (static_cast (Shape)); const uint count = (uint)pMB->_LightInfos.size (); uint i; // Resize the index array _AnimatedLightmap.resize (count); for (i=0; i_LightInfos[i]; // Get the lightmap info _AnimatedLightmap[i] = scene.getAnimatedLightNameToIndex (lightInfo.AnimatedLight); } // Must be traversed in AnimDetail, even if no channel mixer registered CTransform::setIsForceAnimDetail (count != 0); } // *************************************************************************** uint CMeshBaseInstance::getNumMaterial () const { return (uint)Materials.size (); } // *************************************************************************** const CMaterial *CMeshBaseInstance::getMaterial (uint materialId) const { return &(Materials[materialId]); } // *************************************************************************** CMaterial *CMeshBaseInstance::getMaterial (uint materialId) { return &(Materials[materialId]); } // *************************************************************************** bool CMeshBaseInstance::fastIntersect(const NLMISC::CVector &p0, const NLMISC::CVector &dir, float &dist2D, float &distZ, bool computeDist2D) { if(!Shape || !supportFastIntersect()) return false; // Use the system geometry to test the intersection CMeshBase *pMB = static_cast (static_cast (Shape)); return pMB->getSystemGeometry().fastIntersect(getWorldMatrix(), p0, dir, dist2D, distZ, computeDist2D); } // *************************************************************************** // *************************************************************************** // Async texture loading // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshBaseInstance::enableAsyncTextureMode(bool enable) { // if same, no-op. if(_AsyncTextureMode==enable) return; _AsyncTextureMode= enable; // if comes to async texture mode, must prepare AsyncTexturing if(_AsyncTextureMode) { _AsyncTextureReady= true; // For all TextureFiles in material for(uint i=0;i(Materials[i].getTexture(uint8(stage))); if(text) { // Must setup the AsyncTextures AsyncTextures[i].IsTextureFile[stage]= true; AsyncTextures[i].TextureNames[stage]= text->getFileName(); AsyncTextures[i].TextIds[stage]= std::numeric_limits::max(); // Now, must copy the textureFile, to Avoid writing in CMeshBase TextureFile descriptor !!! CTextureFile *tf = new CTextureFile(*text); // setup a dummy texture => Instance won't block rendering because texture not yet ready tf->setFileName("blank.tga"); Materials[i].setTexture(uint8(stage), tf); } else { AsyncTextures[i].IsTextureFile[stage]= false; } } } // For convenience, flag the instance as Dirty. setAsyncTextureDirty(true); } // else, AsyncTextureMode disabled else { // first, must stop and release all textures in the async manager. releaseCurrentAsyncTextures(); nlassert(_AsyncTextureToLoadRefCount==0); // clear the array => ensure good work if enableAsyncTextureMode(true) is made later contReset(_CurrentAsyncTextures); // For all TextureFiles in material, copy User setup from AsyncTextures, to real fileName for(uint i=0;i(Materials[i].getTexture(uint8(stage))); text->setFileName(AsyncTextures[i].TextureNames[stage]); // clear string space AsyncTextures[i].TextureNames[stage].clear(); } } } } } // *************************************************************************** void CMeshBaseInstance::startAsyncTextureLoading(const NLMISC::CVector &position) { if(!getAsyncTextureMode()) return; // If the async texutre manager is not setuped in the scene, abort. CAsyncTextureManager *asyncTextMgr= getOwnerScene()->getAsyncTextureManager(); if(!asyncTextMgr) return; uint i; /* for all new texture names to load, add them to the manager NB: done first before release because of RefCount Management (in case of same texture name). */ for(i=0;iaddTextureRef(AsyncTextures[i].TextureNames[stage], this, position); AsyncTextures[i].TextIds[stage]= id; } } } /* For all old textures (0 for the first time...), release them. */ releaseCurrentAsyncTextures(); // OK! bkup the setup _CurrentAsyncTextures= AsyncTextures; // texture async is not ready. _AsyncTextureReady= false; } // *************************************************************************** void CMeshBaseInstance::releaseCurrentAsyncTextures() { // If the async texutre manager is not setuped in the scene, abort. CAsyncTextureManager *asyncTextMgr= getOwnerScene()->getAsyncTextureManager(); if(!asyncTextMgr) return; // release all texture in the manager for(uint i=0;i<_CurrentAsyncTextures.size();i++) { for(uint stage=0;stagereleaseTexture(_CurrentAsyncTextures[i].TextIds[stage], this); } } } } // *************************************************************************** bool CMeshBaseInstance::isAsyncTextureReady() { // if ok, just quit if(_AsyncTextureReady) return true; // test if async loading ended if(_AsyncTextureToLoadRefCount==0) { // must copy all fileNames into the actual Texture Files. Those are the valid ones now! for(uint i=0;i<_CurrentAsyncTextures.size();i++) { for(uint stage=0;stage(Materials[i].getTexture(uint8(stage))); // Since the texture is really uploaded in the driver, the true driver Texture Id will // be bound to this texture. text->setFileName(_CurrentAsyncTextures[i].TextureNames[stage]); /* Since driver setup will only occurs when object become visible, it is a good idea to release Old driver info, because it may points to old driver texture data (eg: old shared textureFile). thus doing so release VRAM Texture Memory */ text->releaseDriverSetup(); } } } // Ok, we are now ready. _AsyncTextureReady= true; return true; } else return false; } // *************************************************************************** sint CMeshBaseInstance::getAsyncTextureId(uint matId, uint stage) const { if(matId>=_CurrentAsyncTextures.size()) return -1; if(!_CurrentAsyncTextures[matId].isTextureFile(stage)) return -1; return _CurrentAsyncTextures[matId].TextIds[stage]; } } // NL3D