// Ryzom - 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 "stdpch.h" #include "ground_fx_manager.h" #include "pacs_client.h" #include "sheet_manager.h" #include "misc.h" #include "entities.h" #include "user_entity.h" #include "time_client.h" #include "nel/3d/u_particle_system_instance.h" #include "nel/3d/u_text_context.h" #include "nel/3d/u_scene.h" #include "nel/3d/u_camera.h" #include "nel/misc/vector.h" #include "client_sheets/race_stats_sheet.h" #include "client_sheets/character_sheet.h" #include "client_sheets/ground_fx_sheet.h" #include "continent_manager.h" #include "network_connection.h" #ifdef NL_DEBUG #define DEBUG_FX #endif #ifdef DEBUG_FX #define CHECK_INTEGRITY checkIntegrity(); #else #define CHECK_INTEGRITY #endif H_AUTO_DECL(RZ_GroundFXManager) extern NL3D::UTextContext *TextContext; extern NL3D::UDriver *Driver; extern NL3D::UScene *Scene; extern CContinentManager ContinentMngr; // max dist to reuse an old fx static const float MAX_DIST_TO_REUSE_OLD_FX = 1.5f; using namespace NLMISC; // ***************************************************************************** CGroundFXManager::CGroundFXManager() : _MinSpeed(1.5f), _MaxSpeed(6.f), _SpeedWaterWalkFast(3.f), _SpeedWaterSwimFast(3.f), _MaxDist(50.f), _MaxNumFX(10), _NumFX(0), _MaxNumCachedFX(10), _NumCachedFX(0), _NumInstances(0), _Scene(NULL) { H_AUTO_USE(RZ_GroundFXManager) // Construct } // ***************************************************************************** CGroundFXManager::~CGroundFXManager() { H_AUTO_USE(RZ_GroundFXManager) reset(); CHECK_INTEGRITY } // ***************************************************************************** void CGroundFXManager::reset() { H_AUTO_USE(RZ_GroundFXManager) CHECK_INTEGRITY if (_Scene) { TGroundFXList::iterator it; // release all active FXs for(it = _ActiveFXs.begin(); it != _ActiveFXs.end(); ++it) { if (!it->FX.empty()) _Scene->deleteInstance(it->FX); if (!it->FXUnderWater.empty()) _Scene->deleteInstance(it->FXUnderWater); } // release all inactive FXs for(it = _InactiveFXs.begin(); it != _InactiveFXs.end(); ++it) { if (!it->FX.empty()) _Scene->deleteInstance(it->FX); if (!it->FXUnderWater.empty()) _Scene->deleteInstance(it->FXUnderWater); } // release all cached FXs for(it = _CachedFXs.begin(); it != _CachedFXs.end(); ++it) { if (!it->FX.empty()) _Scene->deleteInstance(it->FX); if (!it->FXUnderWater.empty()) _Scene->deleteInstance(it->FXUnderWater); } } _ActiveFXs.clear(); _InactiveFXs.clear(); _CachedFXs.clear(); _InstancesList.clear(); _SortedInstances.clear(); _NumFX = 0; _NumCachedFX = 0; _NumInstances = 0; _Scene = NULL; } // ***************************************************************************** void CGroundFXManager::init(NL3D::UScene *scene, float maxDist, uint maxNumFX, uint fxCacheSize) { H_AUTO_USE(RZ_GroundFXManager) nlassert(_Scene == NULL); // init already called _Scene = scene; _MaxDist = maxDist; _MaxNumFX = maxNumFX; _MaxNumCachedFX = fxCacheSize; } // ***************************************************************************** CGroundFXManager::TEntityHandle CGroundFXManager::add(CEntityCL *entity) { H_AUTO_USE(RZ_GroundFXManager) CHECK_INTEGRITY nlassert(entity); #ifdef NL_DEBUG for(TInstanceList::iterator it = _InstancesList.begin(); it != _InstancesList.end(); ++it) { if (it->Entity == entity) { nlerror("entity added twice"); // } } #endif _InstancesList.push_front(CInstance()); CInstance &i = _InstancesList.front(); i.Entity = entity; i.HasFX = false; i.EmittersActive = false; i.EmittersUnderWaterActive = false; i.InstanciateDelay = 2; ++ _NumInstances; CHECK_INTEGRITY return _InstancesList.begin(); } // ***************************************************************************** void CGroundFXManager::remove(TEntityHandle handle) { H_AUTO_USE(RZ_GroundFXManager) CHECK_INTEGRITY if (handle->HasFX) { // shutdown fx if (!handle->FXHandle->FX.empty()) { handle->FXHandle->FX.activateEmitters(false); handle->FXHandle->FX.stopSound(); } if (!handle->FXHandle->FXUnderWater.empty()) { handle->FXHandle->FXUnderWater.activateEmitters(false); handle->FXHandle->FXUnderWater.stopSound(); } // put in inactive list _InactiveFXs.splice(_InactiveFXs.begin(), _ActiveFXs, handle->FXHandle); nlassert(_NumFX > 0); -- _NumFX; handle->EmittersActive = false; handle->EmittersUnderWaterActive = false; } _InstancesList.erase(handle); nlassert(_NumInstances != 0); -- _NumInstances; CHECK_INTEGRITY } // predicate to test a ground id // ***************************************************************************** // Predicate for binary search in a vector of sorted ground fx sheets struct CCmpGroundIDPred { inline bool operator()(const CGroundFXSheet &lhs, const CGroundFXSheet &rhs) const { return lhs.GroundID < rhs.GroundID; } }; // ***************************************************************************** void CGroundFXManager::CInstance::getFXNameFromGroundType(uint32 groundID, std::string &fxName) const { H_AUTO_USE(RZ_GroundFXManager) fxName= ""; if (!Entity) return; const CEntitySheet *es; es = SheetMngr.get(Entity->sheetId()); if (!es) return; const std::vector *gfx = Entity->getGroundFX(); if (!gfx) return; CGroundFXSheet tmp; tmp.GroundID = groundID; std::vector::const_iterator it = std::lower_bound(gfx->begin(), gfx->end(), tmp, CCmpGroundIDPred()); if (it ==gfx->end()) return; if (it->GroundID != groundID) return; fxName= it->getFXName(); } // ***************************************************************************** /** Predicate to sort instances by distances. */ struct CSortInstancePred { bool operator()(CGroundFXManager::TEntityHandle lhs, CGroundFXManager::TEntityHandle rhs) const { return lhs->Dist2 < rhs->Dist2; } }; // ***************************************************************************** void CGroundFXManager::invalidateFX(TEntityHandle instance) { H_AUTO_USE(RZ_GroundFXManager) bool present = false; if (!instance->FXHandle->FX.empty()) { present = instance->FXHandle->FX.isSystemPresent(); instance->FXHandle->FX.activateEmitters(false); instance->FXHandle->FX.stopSound(); } if (!instance->FXHandle->FXUnderWater.empty()) { present &= instance->FXHandle->FXUnderWater.isSystemPresent(); instance->FXHandle->FXUnderWater.activateEmitters(false); instance->FXHandle->FXUnderWater.stopSound(); } // instance->EmittersActive = false; instance->EmittersUnderWaterActive = false; instance->HasFX = false; if (present) { // put in inactive list _InactiveFXs.splice(_InactiveFXs.begin(), _ActiveFXs, instance->FXHandle); nlassert(_NumFX > 0); -- _NumFX; } else { // directly put in cache without (fx already shut down) moveFXInCache(_ActiveFXs, instance->FXHandle); -- _NumFX; } } // ***************************************************************************** void CGroundFXManager::moveFXInCache(TGroundFXList &ownerList, TGroundFXHandle fx) { H_AUTO_USE(RZ_GroundFXManager) if (!fx->FX.empty()) fx->FX.hide(); // cache full ? nlassert(_NumCachedFX <= _MaxNumCachedFX); if (_NumCachedFX == _MaxNumCachedFX) { // remove least recently added fx if (!_CachedFXs.back().FX.empty()) _Scene->deleteInstance(_CachedFXs.back().FX); if (!_CachedFXs.back().FXUnderWater.empty()) _Scene->deleteInstance(_CachedFXs.back().FXUnderWater); _CachedFXs.pop_back(); } else { ++ _NumCachedFX; } // move in cache front _CachedFXs.splice(_CachedFXs.begin(), ownerList, fx); } // ******************************************************************************************* void CGroundFXManager::checkIntegrity() { H_AUTO_USE(RZ_GroundFXManager) for(TGroundFXList::iterator it = _InactiveFXs.begin(); it != _InactiveFXs.end(); ++it) { if (!it->FX.empty()) nlassert(!it->FX.hasActiveEmitters()); if (!it->FXUnderWater.empty()) nlassert(!it->FXUnderWater.hasActiveEmitters()); } for(TGroundFXList::iterator it = _CachedFXs.begin(); it != _CachedFXs.end(); ++it) { if (!it->FX.empty()) { nlassert(!it->FX.hasActiveEmitters()); nlassert(!it->FX.hasParticles()); } if (!it->FXUnderWater.empty()) { nlassert(!it->FXUnderWater.hasActiveEmitters()); nlassert(!it->FXUnderWater.hasParticles()); if (it->FXUnderWater.hasParticles()) { nlinfo(it->FXName.c_str()); } } } uint numFX = (uint)_ActiveFXs.size(); nlassert(numFX == _NumFX); nlassert(numFX <= _MaxNumFX); uint numCachedFX = (uint)_CachedFXs.size(); nlassert(numCachedFX == _NumCachedFX); nlassert(numCachedFX <= _MaxNumCachedFX); } // ***************************************************************************** void CGroundFXManager::update(const NLMISC::CVectorD &camPos) { H_AUTO_USE(RZ_GroundFXManager) if (!_Scene) return; CHECK_INTEGRITY // check all inactive fxs, and move in cache those that have no more particles TGroundFXList::iterator currIt, nextIt; for(TGroundFXList::iterator currIt = _InactiveFXs.begin(); currIt != _InactiveFXs.end();) { nextIt = currIt; ++ nextIt; bool somethingVisible = false; if (!currIt->FX.empty() && currIt->FX.isSystemPresent() && currIt->FX.hasParticles()) { somethingVisible = true; } if (!currIt->FXUnderWater.empty() && currIt->FXUnderWater.isSystemPresent() && currIt->FXUnderWater.hasParticles()) { somethingVisible = true; } if (!somethingVisible) { moveFXInCache(_InactiveFXs, currIt); } currIt = nextIt; } float maxDist2 = _MaxDist * _MaxDist; // _SortedInstances.clear(); _SortedInstances.reserve(_NumInstances); // check all candidates entities for(TInstanceList::iterator instanceIt = _InstancesList.begin(); instanceIt != _InstancesList.end(); ++instanceIt) { const NLMISC::CVectorD &entityPos = instanceIt->Entity->pos(); if (instanceIt->InstanciateDelay != 0) { -- instanceIt->InstanciateDelay; continue; } if (instanceIt->Entity->getLastClip()) { // entity is clipped // if a fx was attached, put it in the inactive list // When the entity is not visible, we can't update its ground fx : // As a matter of fact, snapToGround is not called at each frame for entities that are not visible, so this causes a // very noticeable shift on z for the ground fx when the entity enters the view pyramid. if (instanceIt->HasFX) { invalidateFX(instanceIt); } } else { // entity is not clipped instanceIt->Dist2 = (float) (entityPos - camPos).sqrnorm(); if (instanceIt->Dist2 > maxDist2) { // entity not eligible for fx (too far), remove any attached fx if (instanceIt->HasFX) { invalidateFX(instanceIt); } } else { instanceIt->Mode = CInstance::Ground; // if entity is in water, fx can be generated even if the entity hasn't moved if (instanceIt->Entity->mode() == MBEHAV::SWIM || instanceIt->Entity->mode() == MBEHAV::MOUNT_SWIM) { instanceIt->Mode = CInstance::Swim; // retrieve water height CContinent *cont = ContinentMngr.cur(); if (cont) { bool splashEnabled; if (cont->WaterMap.getWaterHeight(CVector2f((float) entityPos.x, (float) entityPos.y), instanceIt->WaterHeight, splashEnabled)) { if (splashEnabled) { // put in candidate list, even if not moving _SortedInstances.push_back(instanceIt); } else { // there's water, but with no splashs instanceIt->Mode = CInstance::Ground; } } else { // water height not retrieved ? -> set ground mode instanceIt->Mode = CInstance::Ground; } } } else { // check if entity walking in water CContinent *cont = ContinentMngr.cur(); if (cont) { bool splashEnabled; if (cont->WaterMap.getWaterHeight(CVector2f((float) entityPos.x, (float) entityPos.y), instanceIt->WaterHeight, splashEnabled)) { if (splashEnabled) { // see if feet are underwater if (entityPos.z < instanceIt->WaterHeight) { instanceIt->Mode = CInstance::Water; // put in candidate list, even if not moving _SortedInstances.push_back(instanceIt); } } } } } if (instanceIt->Mode == CInstance::Ground) { // entity not in water if (instanceIt->Entity->getSpeed() >= _MinSpeed) { // put in sort list _SortedInstances.push_back(instanceIt); } else { if (instanceIt->HasFX) { invalidateFX(instanceIt); } } } } } } CHECK_INTEGRITY // sort instances by distance (number of chosen instances is likely to be a lot less than 200, so quick sort is ok) std::sort(_SortedInstances.begin(), _SortedInstances.end(), CSortInstancePred()); uint numValidInstances = std::min(_MaxNumFX, uint(_SortedInstances.size())); uint k; for(k = 0; k < numValidInstances; ++k) { std::string fxName; std::string stdFXName; // standardized name for retrieval in cache std::string fxNameUnderWater; bool createNewFx = false; // float fxZ; // z at which the instance must be put static float fxZBias = 0.2f; // double speed = _SortedInstances[k]->Entity->getSpeed(); // switch(_SortedInstances[k]->Mode) { case CInstance::Water: if (speed == 0.f) fxName = "StepSwimIdle.ps"; else if (speed > _SpeedWaterWalkFast) fxName = "StepSwimRun.ps"; else fxName = "StepSwimWalk.ps"; fxZ = _SortedInstances[k]->WaterHeight; break; case CInstance::Swim: if (speed == 0.f) fxName = "StepSwimIdle.ps"; else if (speed > _SpeedWaterWalkFast) { fxName = "StepSwimSpeed.ps"; fxNameUnderWater = "StepSwimSpeedUnderWater.ps"; } else { fxName = "StepSwim.ps"; fxNameUnderWater = "StepSwimUnderWater.ps"; } fxZ = _SortedInstances[k]->WaterHeight; break; case CInstance::Ground: { uint32 groundId = (uint32) _SortedInstances[k]->Entity->getGroundType(); _SortedInstances[k]->getFXNameFromGroundType(groundId, fxName); fxZ = (float) _SortedInstances[k]->Entity->pos().z; } break; default: nlassert(0); break; } if (!fxName.empty()) { stdFXName = NLMISC::strlwr(NLMISC::CFile::getFilenameWithoutExtension(fxName)); } // is an fx already attached to the entity ? if (_SortedInstances[k]->HasFX) { if (_SortedInstances[k]->FXHandle->FXName == stdFXName) { // name is the same // ok, activate emitters if (!_SortedInstances[k]->EmittersActive) { // NB : we dont activate emitters has soon as the fx is allocated by an entity, because of the way the fx works. // As a matter of fact, if an object move from A to B, the fx may spawn several particles on [AB] in a single frame // So if the FX was previously used by an other entity, a trail of particles may appear between the 2 entities when // fx is deallocated from first entity and allocated by the new one if (!_SortedInstances[k]->FXHandle->FX.empty()) { if (_SortedInstances[k]->FXHandle->FX.isSystemPresent()) { _SortedInstances[k]->FXHandle->FX.activateEmitters(true); _SortedInstances[k]->FXHandle->FX.reactivateSound(); _SortedInstances[k]->EmittersActive = true; } } } // underwater fx if (!_SortedInstances[k]->EmittersUnderWaterActive) { // NB : we dont activate emitters has soon as the fx is allocated by an entity, because of the way the fx works. // As a matter of fact, if an object move from A to B, the fx may spawn several particles on [AB] in a single frame // So if the FX was previously used by an other entity, a trail of particles may appear between the 2 entities when // fx is deallocated from first entity and allocated by the new one if (!_SortedInstances[k]->FXHandle->FXUnderWater.empty()) { if (_SortedInstances[k]->FXHandle->FXUnderWater.isSystemPresent()) { _SortedInstances[k]->FXHandle->FXUnderWater.activateEmitters(true); _SortedInstances[k]->FXHandle->FXUnderWater.reactivateSound(); _SortedInstances[k]->EmittersUnderWaterActive = true; } } } } else { // name has changed // put in inactive list invalidateFX(_SortedInstances[k]); if (!fxName.empty()) { // must create a new fx createNewFx = true; } } } else { if (!fxName.empty()) { // must create a new fx createNewFx = true; } } if (createNewFx) { nlassert(!fxName.empty()); bool instanciate = true; // should we instanciate a new fx ? if (!_InactiveFXs.empty()) { // simple linear search should suffice for(TGroundFXList::iterator it = _InactiveFXs.begin(); it != _InactiveFXs.end(); ++it) { if (it->FXName == stdFXName) { // fx must be not too far because of transparancy sorting issues if ((_SortedInstances[k]->Entity->pos() - it->FX.getMatrix().getPos()).sqrnorm() < MAX_DIST_TO_REUSE_OLD_FX * MAX_DIST_TO_REUSE_OLD_FX) { // put fx in active list _ActiveFXs.splice(_ActiveFXs.begin(), _InactiveFXs, it); ++ _NumFX; _SortedInstances[k]->HasFX = true; nlassert(!_SortedInstances[k]->EmittersActive); nlassert(!_SortedInstances[k]->EmittersUnderWaterActive); _SortedInstances[k]->FXHandle = _ActiveFXs.begin(); instanciate = false; break; } } } } // Look for a matching fx in the cache. // We can't reuse inactive fx (those that are shuttingdown), because we wait for all particles to disappear, so that the bbox is null if (instanciate && !_CachedFXs.empty()) { for(TGroundFXList::iterator it = _CachedFXs.begin(); it != _CachedFXs.end(); ++it) { if (it->FXName == stdFXName) { // if (!it->FX.empty()) it->FX.show(); // put fx in active list _ActiveFXs.splice(_ActiveFXs.begin(), _CachedFXs, it); nlassert(_NumCachedFX > 0); -- _NumCachedFX; ++ _NumFX; // _SortedInstances[k]->HasFX = true; nlassert(!_SortedInstances[k]->EmittersActive); nlassert(!_SortedInstances[k]->EmittersUnderWaterActive); _SortedInstances[k]->FXHandle = _ActiveFXs.begin(); instanciate = false; break; } } } if (instanciate) { // must create new fx _ActiveFXs.push_front(CGroundFX()); ++ _NumFX; CGroundFX &gfx = _ActiveFXs.front(); gfx.FX = NULL; gfx.FXUnderWater = NULL; NL3D::UInstance fxInstance = _Scene->createInstance(fxName); if (!fxInstance.empty()) { gfx.FX.cast (fxInstance); if (gfx.FX.empty()) { // not a particle system instance _Scene->deleteInstance(fxInstance); } else { if (_SortedInstances[k]->Mode != CInstance::Ground) { // add z-bias for fx in water gfx.FX.setZBias(-fxZBias); } } } if (!fxNameUnderWater.empty()) { fxInstance = _Scene->createInstance(fxNameUnderWater); if (!fxInstance.empty()) { gfx.FXUnderWater.cast (fxInstance); if (gfx.FXUnderWater.empty()) { // not a particle system instance _Scene->deleteInstance(fxInstance); } else { gfx.FXUnderWater.setZBias(fxZBias); } } } gfx.FXName = stdFXName; _SortedInstances[k]->HasFX = true; nlassert(!_SortedInstances[k]->EmittersActive); nlassert(!_SortedInstances[k]->EmittersUnderWaterActive); _SortedInstances[k]->FXHandle = _ActiveFXs.begin(); if (!gfx.FX.empty()) { gfx.FX.setTransformMode(NL3D::UTransform::DirectMatrix); gfx.FX.setOrderingLayer(2); } if (!gfx.FXUnderWater.empty()) { gfx.FXUnderWater.setTransformMode(NL3D::UTransform::DirectMatrix); gfx.FXUnderWater.setOrderingLayer(0); } } } // update Pos & matrix if (_SortedInstances[k]->HasFX && !_SortedInstances[k]->FXHandle->FX.empty()) { NLMISC::CMatrix mat = _SortedInstances[k]->Entity->dirMatrix(); const NLMISC::CVector &front = _SortedInstances[k]->Entity->front(); mat.setRot(CVector::K ^ front, - front, CVector::K); const NLMISC::CVector &entityPos = _SortedInstances[k]->Entity->pos(); mat.setPos(NLMISC::CVector(entityPos.x, entityPos.y, (float) fxZ)); _SortedInstances[k]->FXHandle->FX.setMatrix(mat); _SortedInstances[k]->FXHandle->FX.setClusterSystem(_SortedInstances[k]->Entity->getClusterSystem()); if (!_SortedInstances[k]->FXHandle->FXUnderWater.empty()) { _SortedInstances[k]->FXHandle->FXUnderWater.setMatrix(mat); _SortedInstances[k]->FXHandle->FXUnderWater.setClusterSystem(_SortedInstances[k]->Entity->getClusterSystem()); } if (_SortedInstances[k]->Mode == CInstance::Ground) { float intensity; if (_MinSpeed == _MaxSpeed) { intensity = 0.f; } else { intensity = (float) ((speed - _MinSpeed) / (_MaxSpeed - _MinSpeed)); } clamp(intensity, 0.f, 1.f); _SortedInstances[k]->FXHandle->FX.setUserParam(0, intensity); } /* else { // adjust z-bias (tmp) if (_SortedInstances[k]->FXHandle->FXUnderWater) _SortedInstances[k]->FXHandle->FXUnderWater.setZBias(-fxZBias); if (_SortedInstances[k]->FXHandle->FX) _SortedInstances[k]->FXHandle->FX.setZBias(fxZBias); }*/ } } // remove fx for instances that are too far / not taken in account for (; k < _SortedInstances.size(); ++k) { if (_SortedInstances[k]->HasFX) { invalidateFX(_SortedInstances[k]); } } CHECK_INTEGRITY } ////////////////////////////////// // tmp tmp : test of ground fxs // ////////////////////////////////// #if !FINAL_VERSION #include "interface_v3/interface_manager.h" using NLMISC::toString; CTestGroundFX TestGroundFX; // *********************************************************************************************** void CTestGroundFX::update() { H_AUTO_USE(RZ_GroundFXManager) for(uint k = 0; k < Entities.size(); ++k) { if (MoveAll || Entities[k].Move) { Entities[k].Entity->snapToGround(); NLMISC::CVectorD newPos = NLMISC::CVectorD(DT * Entities[k].Dir) + Entities[k].Entity->pos(); NLMISC::CVectorD startToPos = newPos - NLMISC::CVectorD(Entities[k].StartPos); if (startToPos.norm() > 10.0) { newPos = NLMISC::CVectorD(Entities[k].StartPos) + 10.0 * startToPos.normed(); } Entities[k].Entity->pacsPos(newPos); Entities[k].Duration -= DT; if (Entities[k].Duration <= 0.f) { if (MoveAll) { Entities[k].Move = true; Entities[k].Duration = 5.f; Entities[k].Dir.set(NLMISC::frand(1.f), NLMISC::frand(1.f), 0.f); Entities[k].Dir.normalize(); } else { Entities[k].Move = false; } } } } } // *********************************************************************************************** void CTestGroundFX::displayFXBoxes() const { H_AUTO_USE(RZ_GroundFXManager) Driver->setViewMatrix(Scene->getCam().getMatrix().inverted()); NL3D::CFrustum fr; Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far); fr.Perspective = true; Driver->setFrustum(fr); TextContext->setColor(CRGBA::Green); TextContext->setShaded(false); TextContext->setFontSize(12); // float size = 0.4f; float textSize = 2.5f; // display fx to add for(uint k = 0; k < TestGroundFX.Entities.size(); ++k) { NLMISC::CMatrix mat; mat.identity(); CVector pos = (CVector) TestGroundFX.Entities[k].Entity->pos(); mat.setPos(pos); Driver->setModelMatrix(mat); drawBox(CVector(- size, - size, - size), CVector(size, size, size), CRGBA::Magenta); mat.identity(); mat.setRot(Scene->getCam().getRotQuat()); mat.setPos(pos + 5.f * size * CVector::K); CVector distVect = pos - Scene->getCam().getPos(); mat.scale(textSize * distVect.norm()); TextContext->render3D(mat, NLMISC::toString("gfx %d, dist = %.1f", (int) k, (pos - Scene->getCam().getMatrix().getPos()).norm())); } } // ******************************************************************************************* void CGroundFXManager::setMinSpeed(float minSpeed) { H_AUTO_USE(RZ_GroundFXManager) nlassert(minSpeed >= 0.f); _MinSpeed = minSpeed; } // ******************************************************************************************* void CGroundFXManager::setMaxSpeed(float maxSpeed) { H_AUTO_USE(RZ_GroundFXManager) nlassert(maxSpeed >= 0.f); _MaxSpeed = maxSpeed; } // ******************************************************************************************* void CGroundFXManager::setSpeedWaterWalkFast(float speed) { H_AUTO_USE(RZ_GroundFXManager) nlassert(speed >= 0.f); _SpeedWaterWalkFast = speed; } // ******************************************************************************************* void CGroundFXManager::setSpeedWaterSwimFast(float speed) { H_AUTO_USE(RZ_GroundFXManager) nlassert(speed >= 0.f); _SpeedWaterSwimFast = speed; } // ******************************************************************************************* // temp, for debug // add an entity for test NLMISC_COMMAND(gfxAdd, "gfxAdd", "<>") { uint slot; for(slot = 0; slot < EntitiesMngr.nbEntitiesAllocated(); ++slot) { if (EntitiesMngr.entity(slot) == NULL) { break; } } int category = 0; uint32 form; if (args.size() == 1) { if (fromString(args[0], category)) { switch(category) { case 1: { NLMISC::CSheetId sheet("dag_for_lvl_01.creature"); form = sheet.asInt(); } break; default: { NLMISC::CSheetId sheet("fyros.race_stats"); form = sheet.asInt(); } break; } } else { NLMISC::CSheetId sheet(args[0]); form = sheet.asInt(); } } TNewEntityInfo emptyEntityInfo; emptyEntityInfo.reset(); CEntityCL *entity = EntitiesMngr.create(slot, form, emptyEntityInfo); if(entity) { sint64 *prop = 0; CCDBNodeLeaf *node = 0; CInterfaceManager *IM = CInterfaceManager::getInstance(); // Set Position node = IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_POSX), false); NLMISC::CVectorD pos; pos = UserEntity->pos() + 2.f * UserEntity->front(); if(node) { sint64 x = (sint64)(pos.x * 1000.0); sint64 y = (sint64)(pos.y * 1000.0); sint64 z = (sint64)(pos.z * 1000.0); node->setValue64(x); node = IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_POSY), false); if(node) { node->setValue64(y); node = IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_POSZ), false); if(node) { node->setValue64(z); EntitiesMngr.updateVisualProperty(0, slot, CLFECOMMON::PROPERTY_POSITION); } } } // Set Direction entity->front(UserEntity->front()); entity->dir(UserEntity->front()); // Set Mode node = IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_MODE), false); if(node) { MBEHAV::EMode m = MBEHAV::NORMAL; prop = (sint64 *)&m; node->setValue64(*prop); EntitiesMngr.updateVisualProperty(0, slot, CLFECOMMON::PROPERTY_MODE); } // Set Visual Properties if(dynamic_cast(entity)) { SPropVisualA visualA; visualA.PropertySubData.Sex = ClientCfg.Sex; SPropVisualB visualB; // Initialize the Visual Property C (Default parameters). SPropVisualC visualC; visualC.PropertySubData.CharacterHeight = 7; visualC.PropertySubData.ArmsWidth = 7; visualC.PropertySubData.LegsWidth = 7; visualC.PropertySubData.TorsoWidth = 7; visualC.PropertySubData.BreastSize = 7; // Set The Database prop = (sint64 *)&visualB; IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_VPB))->setValue64(*prop); prop = (sint64 *)&visualC; IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_VPC))->setValue64(*prop); prop = (sint64 *)&visualA; IM->getDbProp("SERVER:Entities:E"+NLMISC::toString("%d", slot)+":P"+NLMISC::toString("%d", CLFECOMMON::PROPERTY_VPA))->setValue64(*prop); // Apply Changes. EntitiesMngr.updateVisualProperty(0, slot, CLFECOMMON::PROPERTY_VPA); } TestGroundFX.Entities.push_back(CTestGroundFX::CEntity()); TestGroundFX.Entities.back().Entity = entity; TestGroundFX.Entities.back().Slot = slot; TestGroundFX.Entities.back().Move = false; TestGroundFX.Entities.back().Dir.set(0.f, 0.f, 0.f); TestGroundFX.Entities.back().Duration = 0; TestGroundFX.Entities.back().StartPos = pos; entity->pacsPos(pos); } return true; } // remove all test entities NLMISC_COMMAND(gfxReset, "gfxReset", "<>") { for(uint k = 0; k < TestGroundFX.Entities.size(); ++k) { EntitiesMngr.remove(TestGroundFX.Entities[k].Slot, false); } TestGroundFX.Entities.clear(); return true; } // move one entity NLMISC_COMMAND(gfxMove, "gfxMove", "<>") { if (args.size() != 1) return false; uint index; fromString(args[0], index); if (index > TestGroundFX.Entities.size()) return false; TestGroundFX.Entities[index].Move = true; TestGroundFX.Entities[index].Dir.set(NLMISC::frand(1.f), NLMISC::frand(1.f), 0.f); TestGroundFX.Entities[index].Dir.normalize(); TestGroundFX.Entities[index].Duration = 5.f; return true; } // move all entities NLMISC_COMMAND(gfxMoveAll, "gfxMoveAll", "<>") { for(uint k = 0; k < TestGroundFX.Entities.size(); ++k) { TestGroundFX.Entities[k].Dir.set(NLMISC::frand(1.f), NLMISC::frand(1.f), 0.f); TestGroundFX.Entities[k].Dir.normalize(); TestGroundFX.Entities[k].Duration = 5.f; } TestGroundFX.MoveAll = true; return true; } // stop all entities NLMISC_COMMAND(gfxStopAll, "gfxStopAll", "<>") { for(uint k = 0; k < TestGroundFX.Entities.size(); ++k) { TestGroundFX.Entities[k].Move = false; } TestGroundFX.MoveAll = false; return true; } #endif