997 lines
33 KiB
C++
997 lines
33 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "stdpch.h"
|
|
#include "micro_life_manager.h"
|
|
#include "sheet_manager.h"
|
|
#include "misc.h"
|
|
#include "continent_manager.h"
|
|
#include "user_entity.h"
|
|
#include "weather.h"
|
|
#include "water_map.h"
|
|
//
|
|
#include "client_sheets/flora_sheet.h"
|
|
//
|
|
#include "nel/misc/polygon.h"
|
|
#include "nel/misc/bitmap.h"
|
|
#include "nel/misc/file.h"
|
|
#include "nel/misc/i_xml.h"
|
|
#include "nel/misc/line.h"
|
|
//
|
|
#include "nel/ligo/primitive.h"
|
|
//
|
|
#include "nel/3d/u_landscape.h"
|
|
#include "nel/3d/u_material.h"
|
|
#include "nel/3d/u_driver.h"
|
|
#include "nel/3d/u_scene.h"
|
|
//
|
|
#include "nel/misc/check_fpu.h"
|
|
|
|
using namespace std::rel_ops;
|
|
using namespace NLMISC;
|
|
|
|
extern NLLIGO::CLigoConfig LigoConfig;
|
|
extern CContinentManager ContinentMngr;
|
|
extern NL3D::ULandscape *Landscape;
|
|
extern NL3D::UDriver *Driver;
|
|
extern NL3D::UScene *Scene;
|
|
extern CUserEntity *Userentity;
|
|
extern NL3D::UMaterial GenericMat;
|
|
|
|
#if !FINAL_VERSION
|
|
bool DisplayMicroLifeZones = false;
|
|
#endif
|
|
|
|
#ifdef NL_DEBUG
|
|
bool DisplayMicroLifeActiveTiles = false;
|
|
#endif
|
|
|
|
H_AUTO_DECL(RZ_MicroLifeManager)
|
|
|
|
|
|
// ********************************************************************************************
|
|
CMicroLifeManager::CMicroLifeManager()
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
_CellSize = 0.f;
|
|
_GridWidth = 0;
|
|
_GridHeight = 0;
|
|
_Noise.Abs = 0.f;
|
|
_Noise.Rand = 1.f;
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
CMicroLifeManager &CMicroLifeManager::getInstance()
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
static CMicroLifeManager instance;
|
|
return instance;
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::init(const NLMISC::CVector2f &minCorner, const NLMISC::CVector2f &maxCorner, float cellSize /* = 30.f*/)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
release();
|
|
if (!Landscape) return;
|
|
Landscape->addTileCallback(this);
|
|
if (cellSize == 0.f)
|
|
{
|
|
nlwarning("Bad cell size");
|
|
return;
|
|
}
|
|
if (minCorner.x >= maxCorner.x)
|
|
{
|
|
nlwarning("Corners not well ordered");
|
|
return;
|
|
}
|
|
_CellSize = cellSize;
|
|
//
|
|
NLMISC::CVector2f minCornerFinal = minCorner;
|
|
NLMISC::CVector2f maxCornerFinal = maxCorner;
|
|
if (minCornerFinal.x > maxCornerFinal.x) std::swap(minCornerFinal.x, maxCornerFinal.x);
|
|
if (minCornerFinal.y > maxCornerFinal.y) std::swap(minCornerFinal.y, maxCornerFinal.y);
|
|
//
|
|
_GridWidth = (uint) (floorf(maxCornerFinal.x - minCornerFinal.x) / cellSize);
|
|
_GridHeight = (uint) (floorf(maxCornerFinal.y - minCornerFinal.y) / cellSize);
|
|
_MinCorner = minCornerFinal;
|
|
_Grid.resize(_GridWidth * _GridHeight, 0);
|
|
|
|
_Noise.Frequency = 10.1346541f / cellSize;
|
|
}
|
|
|
|
///////////////////
|
|
// BUILD PROCESS //
|
|
///////////////////
|
|
|
|
// To quickly know if a tile is inside a zone of micro-life, we subdivide the world into a grid. Each grid cell
|
|
// may be overlapped by [0, n] polygons of a micro-life zone. We don't want to store a full list for each cell of
|
|
// the grid, because such lists are repeated several times (it would end up wasting too much space). What we just need to keep is
|
|
// a set of possible polygon lists that can overlap a grid cell. Afterward, we just need a grid of index into
|
|
// the list of possible polygon lists.
|
|
// To do that : - we first store complete lists in each grid cells : this is done by several calls to drawPolyInBuildGrid
|
|
// - we search for redundant lists and store the result as index in the final grid. This is done in packBuildGrid
|
|
|
|
|
|
// *********************************************************************************************************************************
|
|
bool CMicroLifeManager::CGridOverlapPolyInfo::operator < (const CMicroLifeManager::CGridOverlapPolyInfo &other) const
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (PrimitiveIndex != other.PrimitiveIndex) return PrimitiveIndex < other.PrimitiveIndex;
|
|
if (Sheet != other.Sheet) return Sheet < other.Sheet;
|
|
if (IsExcludePoly != other.IsExcludePoly) return !other.IsExcludePoly; // want to test exclude polys first
|
|
if (IsFullyCovered != other.IsFullyCovered) return other.IsFullyCovered;
|
|
return Poly < other.Poly;
|
|
}
|
|
|
|
// *********************************************************************************************************************************
|
|
bool CMicroLifeManager::CGridOverlapPolyInfo::operator == (const CMicroLifeManager::CGridOverlapPolyInfo &other) const
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
return PrimitiveIndex == other.PrimitiveIndex &&
|
|
Sheet == other.Sheet &&
|
|
IsExcludePoly == other.IsExcludePoly &&
|
|
IsFullyCovered == other.IsFullyCovered &&
|
|
Poly == other.Poly;
|
|
}
|
|
|
|
// *********************************************************************************************************************************
|
|
bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator < (const CMicroLifeManager::CGridOverlapPolyInfoVector &other) const
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (V.size() != other.V.size()) return V.size() < other.V.size();
|
|
for(uint k = 0; k < V.size(); ++k)
|
|
{
|
|
if (V[k] != other.V[k]) return V[k] < other.V[k];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// *********************************************************************************************************************************
|
|
bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator == (const CMicroLifeManager::CGridOverlapPolyInfoVector &other) const
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (V.size() != other.V.size()) return false;
|
|
return std::equal(V.begin(), V.end(), other.V.begin());
|
|
}
|
|
|
|
|
|
// ***************************************************************************************************************************
|
|
void CMicroLifeManager::drawPolyInBuildGrid(const std::vector<NLLIGO::CPrimVector> &primPoly,
|
|
uint primitiveIndex,
|
|
CMicroLifeManager::TBuildGrid &buildGrid,
|
|
const CFloraSheet *sheet,
|
|
bool isExcludeTri)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
NLMISC::CPolygon poly;
|
|
poly.Vertices.resize(primPoly.size());
|
|
std::copy(primPoly.begin(), primPoly.end(), poly.Vertices.begin());
|
|
std::list<NLMISC::CPolygon> convexPolys;
|
|
if (!poly.toConvexPolygons(convexPolys, NLMISC::CMatrix::Identity))
|
|
{
|
|
nlwarning("Can't convert to convex polys");
|
|
return;
|
|
}
|
|
NLMISC::CPolygon2D poly2D;
|
|
for(std::list<NLMISC::CPolygon>::iterator it = convexPolys.begin(); it != convexPolys.end(); ++it)
|
|
{
|
|
poly2D.Vertices.resize(it->Vertices.size());
|
|
// convert poly in cell units
|
|
for(uint k = 0; k < it->Vertices.size(); ++k)
|
|
{
|
|
poly2D.Vertices[k].x = (it->Vertices[k].x - _MinCorner.x) / _CellSize;
|
|
poly2D.Vertices[k].y = (it->Vertices[k].y - _MinCorner.y) / _CellSize;
|
|
//nlinfo("poly2D.Vertices[k].x = %.1f, poly2D.Vertices[k].y = %.1f", poly2D.Vertices[k].x, poly2D.Vertices[k].y);
|
|
}
|
|
// rasterize poly
|
|
NLMISC::CPolygon2D::TRasterVect outerBorders;
|
|
NLMISC::CPolygon2D::TRasterVect innerBorders;
|
|
sint outerMinY;
|
|
sint innerMinY;
|
|
poly2D.computeOuterBorders(outerBorders, outerMinY);
|
|
poly2D.computeInnerBorders(innerBorders, innerMinY);
|
|
if (outerBorders.empty()) continue; // no contribution for that poly
|
|
CGridOverlapPolyInfo gti;
|
|
gti.IsExcludePoly = isExcludeTri;
|
|
gti.Sheet = sheet;
|
|
gti.Poly = *it;
|
|
gti.PrimitiveIndex = primitiveIndex;
|
|
sint maxY = std::min(outerMinY + (sint)outerBorders.size(), (sint)_GridHeight);
|
|
sint startY = std::max(0, outerMinY);
|
|
for (sint y = startY; y < maxY; ++y)
|
|
{
|
|
sint maxX = std::min(outerBorders[y - startY].second, (sint) (_GridWidth - 1));
|
|
for (sint x = std::max((sint) 0, outerBorders[y - startY].first); x <= maxX; ++x)
|
|
{
|
|
nlassert(x >= 0);
|
|
nlassert(y >= 0);
|
|
nlassert(x < (sint) _GridWidth);
|
|
nlassert(y < (sint) _GridHeight);
|
|
gti.IsFullyCovered = false;
|
|
// see if primitive covers entirely this grid cell
|
|
if (y >= innerMinY && y < innerMinY + (sint) innerBorders.size())
|
|
{
|
|
if (x >= innerBorders[y - innerMinY].first && x <= innerBorders[y - innerMinY].second)
|
|
{
|
|
gti.IsFullyCovered = true;
|
|
}
|
|
}
|
|
buildGrid[x + y * _GridWidth].V.push_back(gti);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// predicate to remove a cell that is fully covered by an exclude poly
|
|
class CCleanFullyCoveredGridCellPred
|
|
{
|
|
public:
|
|
uint PrimitiveIndex;
|
|
public:
|
|
bool operator()(const CMicroLifeManager::CGridOverlapPolyInfo &info) const
|
|
{
|
|
FPU_CHECKER
|
|
return info.PrimitiveIndex == PrimitiveIndex;
|
|
}
|
|
};
|
|
|
|
|
|
// ***************************************************************************************************************************
|
|
// pack the build grid so that each cell only contains index into a small set of poly list, instead of a full poly list
|
|
// (a lot of cells share the same poly list)
|
|
void CMicroLifeManager::packBuildGrid(TBuildGrid &buildGrid,
|
|
CMicroLifeManager::TPossibleOverlapingPolyLists &finalPossibleLists
|
|
)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
// the already possible lists
|
|
std::set<CGridOverlapPolyInfoVector> possibleLists;
|
|
// for each cell : - remove all polys of the same zone if there's an exclude tri that covers the whole grid
|
|
// - compute the set of possible lists
|
|
for(uint k = 0; k < buildGrid.size(); ++k)
|
|
{
|
|
CGridOverlapPolyInfoVector &gopiv = buildGrid[k];
|
|
bool restart = false;
|
|
do
|
|
{
|
|
restart = false;
|
|
for(std::vector<CGridOverlapPolyInfo>::iterator it = gopiv.V.begin(); it != gopiv.V.end(); ++it)
|
|
{
|
|
if (it->IsExcludePoly && it->IsFullyCovered)
|
|
{
|
|
// remove everything about that primitive
|
|
CCleanFullyCoveredGridCellPred pred;
|
|
pred.PrimitiveIndex = it->PrimitiveIndex;
|
|
gopiv.V.erase(std::remove_if(gopiv.V.begin(), gopiv.V.end(), pred), gopiv.V.end());
|
|
restart = true; // must restart scan from start of list because iterator is now invalid
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (restart);
|
|
// insert in possible lists
|
|
possibleLists.insert(gopiv);
|
|
}
|
|
// second pass : flatten the set of possible lists to build an index for each grid cell
|
|
nlassert(possibleLists.size() <= 65536);
|
|
TPossibleOverlapingPolyLists flatSet(possibleLists.size());
|
|
std::copy(possibleLists.begin(), possibleLists.end(), flatSet.begin());
|
|
_Grid.resize(buildGrid.size());
|
|
for(uint k = 0; k < buildGrid.size(); ++k)
|
|
{
|
|
TPossibleOverlapingPolyLists::const_iterator it = std::lower_bound(flatSet.begin(), flatSet.end(), buildGrid[k]);
|
|
nlassert(it != flatSet.end());
|
|
nlassert(*it == buildGrid[k]);
|
|
_Grid[k] = (uint16) (it - flatSet.begin());
|
|
}
|
|
finalPossibleLists.swap(flatSet);
|
|
}
|
|
|
|
// ***************************************************************************************************************************
|
|
// read a .primitive file and add its content to a build grid
|
|
void CMicroLifeManager::addPrimitiveToBuildGrid(const std::string &fileName, uint &primitiveIndex, CMicroLifeManager::TBuildGrid &buildGrid)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
// read the file into memory and parse to generate 'prims' data tree
|
|
NLLIGO::CPrimitives prims;
|
|
NLMISC::CIFile fileIn;
|
|
std::string path = NLMISC::CPath::lookup(fileName, false, true);
|
|
if (path.empty()) return;
|
|
if (!fileIn.open (path))
|
|
{
|
|
nlwarning("Can't open %s", path.c_str());
|
|
return;
|
|
}
|
|
// Xml stream
|
|
NLMISC::CIXml xmlIn;
|
|
xmlIn.init (fileIn);
|
|
// Read it
|
|
if (!prims.read(xmlIn.getRootNode(), path.c_str(), LigoConfig))
|
|
{
|
|
nlwarning ("Error reading file %s", path.c_str());
|
|
return;
|
|
}
|
|
// get each son zone
|
|
for(uint k = 0; k < prims.RootNode->getNumChildren(); ++k)
|
|
{
|
|
NLLIGO::IPrimitive *child;
|
|
if (!(prims.RootNode->getChild(child, k) && child)) continue;
|
|
//
|
|
std::string className;
|
|
// make sure it is a 'micro_life' primitive
|
|
if (!child->getPropertyByName("class", className))
|
|
{
|
|
nlwarning("Can't get class for child %d of primitive %s", (int) k, path.c_str());
|
|
continue;
|
|
}
|
|
if (NLMISC::nlstricmp(className.c_str(), "micro_life") != 0) continue; // only deals with micro life
|
|
// read flora sheet in the primitive
|
|
std::string formName;
|
|
if (!child->getPropertyByName("form", formName))
|
|
{
|
|
nlwarning("Can't get form name for child %d of primitive %s", (int) k, path.c_str());
|
|
continue;
|
|
}
|
|
const CEntitySheet *sheet = SheetMngr.get(NLMISC::CSheetId(formName));
|
|
if (!sheet)
|
|
{
|
|
nlwarning("Can't get sheet %s", formName.c_str());
|
|
continue;
|
|
}
|
|
const CFloraSheet *floraSheet = dynamic_cast<const CFloraSheet *>(sheet);
|
|
if (!floraSheet)
|
|
{
|
|
nlwarning("Sheet %s has bad type. Flora sheet wanted", formName.c_str());
|
|
continue;
|
|
}
|
|
//
|
|
for(uint m = 0; m < child->getNumChildren(); ++m)
|
|
{
|
|
NLLIGO::IPrimitive *mlZone;
|
|
if (!(child->getChild(mlZone, m) && mlZone)) continue;
|
|
// make sure it is a 'micro_life_zone'
|
|
if (!mlZone->getPropertyByName("class", className))
|
|
{
|
|
nlwarning("Can't get class for child %d of primitive %s", (int) m, path.c_str());
|
|
continue;
|
|
}
|
|
if (NLMISC::nlstricmp("micro_life_zone", className) != 0) continue;
|
|
|
|
NLLIGO::CPrimZone *zone = dynamic_cast<NLLIGO::CPrimZone *>(mlZone);
|
|
if (!zone)
|
|
{
|
|
nlwarning("Child %d of primitive %s is not a zone", (int) k, path.c_str());
|
|
continue;
|
|
}
|
|
drawPolyInBuildGrid(zone->VPoints, primitiveIndex, buildGrid, floraSheet, false);
|
|
// remove exlcusion zones
|
|
for(uint l = 0; l < zone->getNumChildren(); ++l)
|
|
{
|
|
NLLIGO::IPrimitive *exclPrim;
|
|
if (zone->getChild(exclPrim, l) && exclPrim)
|
|
{
|
|
if (!exclPrim->getPropertyByName("class", className))
|
|
{
|
|
nlwarning("Can't get class of sub-zone %d for child %d of primitive %s", (int) l, (int) m, path.c_str());
|
|
continue;
|
|
}
|
|
}
|
|
if (NLMISC::nlstricmp("micro_life_exclude_zone", className) == 0)
|
|
{
|
|
NLLIGO::CPrimZone *exclZone = dynamic_cast<NLLIGO::CPrimZone *>(exclPrim);
|
|
if (!zone)
|
|
{
|
|
nlwarning("Child %d of child %d of primitive %s is not a zone", (int) l, (int) m, path.c_str());
|
|
continue;
|
|
}
|
|
// draw exclude polygon
|
|
drawPolyInBuildGrid(exclZone->VPoints, primitiveIndex, buildGrid, floraSheet, true);
|
|
}
|
|
}
|
|
++ primitiveIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::build(const std::vector<std::string> &fileNames)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
TBuildGrid buildGrid;
|
|
buildGrid.resize(_Grid.size());
|
|
uint currPrimitiveIndex = 0;
|
|
for(uint l = 0; l < fileNames.size(); ++l)
|
|
{
|
|
addPrimitiveToBuildGrid(fileNames[l], currPrimitiveIndex, buildGrid);
|
|
}
|
|
// build the final grid
|
|
packBuildGrid(buildGrid, _PossibleOverlapPolyLists);
|
|
}
|
|
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::release()
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (Landscape)
|
|
{
|
|
if (Landscape->isTileCallback(this))
|
|
{
|
|
Landscape->removeTileCallback(this);
|
|
}
|
|
}
|
|
// release all registered fxs
|
|
for(TTileIDToFX::iterator it = _ActiveFXs.begin(); it != _ActiveFXs.end(); ++it)
|
|
{
|
|
CTimedFXManager::getInstance().remove(it->second);
|
|
}
|
|
_ActiveFXs.clear();
|
|
NLMISC::contReset(_Grid);
|
|
NLMISC::contReset(_PossibleOverlapPolyLists);
|
|
#ifdef NL_DEBUG
|
|
_ActiveTiles.clear();
|
|
_ActiveTilesWithFX.clear();
|
|
#endif
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::tileAdded(const NL3D::CTileAddedInfo &infos)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (_CellSize == 0.f) return; // not initialized
|
|
#ifdef NL_DEBUG
|
|
_ActiveTiles[infos.TileID] = infos;
|
|
#endif
|
|
// get coords in grid
|
|
sint gridCoordX = (sint) floorf((infos.Center.x - _MinCorner.x) / _CellSize);
|
|
if (gridCoordX < 0 || gridCoordX >= (sint) _GridWidth) return;
|
|
sint gridCoordY = (sint) floorf((infos.Center.y - _MinCorner.y) / _CellSize);
|
|
if (gridCoordY < 0 || gridCoordY >= (sint) _GridHeight) return;
|
|
// get list of primitives over which the center of the tile is
|
|
if( _PossibleOverlapPolyLists.empty() )
|
|
return; // AJM for when called during zone destructor
|
|
const CGridOverlapPolyInfoVector &iv = _PossibleOverlapPolyLists[_Grid[gridCoordX + gridCoordY * _GridWidth]];
|
|
if (iv.V.empty()) return;
|
|
static std::vector<CTimedFX> fxToSpawn; // static for fast alloc
|
|
fxToSpawn.clear();
|
|
uint k = 0;
|
|
do
|
|
{
|
|
const CFloraSheet *fs = iv.V[k].Sheet;
|
|
if (!fs) continue;
|
|
if (fs->getNumPlantInfos() == 0) continue;
|
|
//
|
|
bool inside = false;
|
|
uint currPrimIndex = iv.V[k].PrimitiveIndex;
|
|
do
|
|
{
|
|
if (iv.V[k].IsExcludePoly)
|
|
{
|
|
// test if center of tile is inside of the primitive
|
|
if (iv.V[k].Poly.contains(NLMISC::CVector2f(infos.Center.x, infos.Center.y)))
|
|
{
|
|
inside = false;
|
|
// jump to next primitive
|
|
for(;;)
|
|
{
|
|
if (k == iv.V.size()) break;
|
|
if (iv.V[k].PrimitiveIndex != currPrimIndex) break;
|
|
++ k;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iv.V[k].IsFullyCovered)
|
|
{
|
|
// the primitive covers the whole cell so we are inside
|
|
inside = true;
|
|
}
|
|
else
|
|
{
|
|
// test is center of tile is inside
|
|
if (iv.V[k].Poly.contains(NLMISC::CVector2f(infos.Center.x, infos.Center.y)))
|
|
{
|
|
inside = true;
|
|
}
|
|
}
|
|
}
|
|
++k;
|
|
if (k == iv.V.size()) break;
|
|
}
|
|
while(iv.V[k].PrimitiveIndex == currPrimIndex);
|
|
if (!inside) continue; // no inside this primitive, try the next
|
|
// Compute noise at center of the tile, and if it is over the micro-life threshold, then spawn a fx
|
|
// To avoid that each kind of primitive be created at the same time, add an abitrary bias to the position for each primitive type
|
|
float noise = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.2564f * NLMISC::CVector::K);
|
|
if (noise > fs->MicroLifeThreshold)
|
|
{
|
|
// choose a category of plant/microlife to instanciate by computing some noise around
|
|
float mlCategory = computeUniformNoise(_Noise, infos.Center + (float) (k + 1) * _CellSize * 1.254f * NLMISC::CVector::J + 1.421f * NLMISC::CVector::I);
|
|
uint64 plantWeightedIndex = (uint64) ((double) mlCategory * fs->getPlantInfoTotalWeight());
|
|
const CPlantInfo *pi = fs->getPlantInfoFromWeightedIndex(plantWeightedIndex);
|
|
if (!pi) continue;
|
|
// get pointer on .plant from sheet
|
|
CEntitySheet *es = SheetMngr.get(NLMISC::CSheetId(pi->SheetName));
|
|
if (!es) continue;
|
|
if (es->Type != CEntitySheet::PLANT) continue;
|
|
CPlantSheet *fs = static_cast<CPlantSheet *>(es);
|
|
const CSeasonFXSheet &sfs = fs->getFXSheet(CurrSeason);
|
|
// if orientation is ok for that kind of plant
|
|
float cosMax = cosf(NLMISC::degToRad(sfs.AngleMin));
|
|
float cosMin = cosf(NLMISC::degToRad(sfs.AngleMax));
|
|
if (cosMax < cosMin) std::swap(cosMin, cosMax);
|
|
if (infos.Normal.z < cosMin) continue; // not valid
|
|
if (infos.Normal.z > cosMax) continue; // not valid
|
|
|
|
CTimedFX newFX;
|
|
#if !FINAL_VERSION
|
|
// for debug display, tells that it was generated dynamically
|
|
newFX.FromIG = false;
|
|
#endif
|
|
// spawn a primitive on the quad
|
|
float weight[3];
|
|
// compute weight values by computing some noise values around
|
|
weight[0] = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.415f * NLMISC::CVector::I);
|
|
weight[1] = computeUniformNoise(_Noise, infos.Center - (float) (currPrimIndex + 1) * _CellSize * 1.568f * NLMISC::CVector::I);
|
|
weight[2] = computeUniformNoise(_Noise, infos.Center - (float) (currPrimIndex + 1) * _CellSize * 1.512f * NLMISC::CVector::J);
|
|
bool tri = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.898f * NLMISC::CVector::I) > 0.5f; // choose a tri to use
|
|
// if by extraordinary...
|
|
if (weight[0] == 0.f && weight[1] == 0.f && weight[2] == 0.f)
|
|
{
|
|
weight[0] = 1.f;
|
|
}
|
|
// normalize weights
|
|
float invTotalWeight = 1.f / (weight[0] + weight[1] + weight[2]);
|
|
// choose one of the tri to spawn fx (the 2 tri of the quad are not planar)
|
|
if (tri)
|
|
{
|
|
newFX.SpawnPosition = invTotalWeight * (weight[0] * infos.Corners[0] + weight[1] * infos.Corners[1] + weight[2] * infos.Corners[2]);
|
|
}
|
|
else
|
|
{
|
|
newFX.SpawnPosition = invTotalWeight * (weight[0] * infos.Corners[1] + weight[1] * infos.Corners[2] + weight[2] * infos.Corners[3]);
|
|
}
|
|
|
|
|
|
// see if the fx must be aligned on water
|
|
if (sfs.AlignOnWater)
|
|
{
|
|
float height;
|
|
bool splashEnabled;
|
|
bool hasWater = ContinentMngr.cur()->WaterMap.getWaterHeight(NLMISC::CVector2f(newFX.SpawnPosition.x, newFX.SpawnPosition.y), height, splashEnabled);
|
|
if (hasWater)
|
|
{
|
|
newFX.SpawnPosition.z = height;
|
|
}
|
|
}
|
|
newFX.SpawnPosition.z += sfs.ZOffset;
|
|
|
|
// fit K axis of model with the normal, by doing a rotation around K ^ Normal by an angle alpha
|
|
// whose cos(alpha) is K * Normal = Normal.Z
|
|
// K ^ Normal resolves to [-y x 0]
|
|
// if z is near from 1.f or -1.f then no rotation is performed (because [-y x 0] tends to 0 and can't be normalized)
|
|
CVector rotAxis; // rotation axis to match Z of model with normal
|
|
float angle = 0.f; // angle of rotation to match Z of model with normal
|
|
// see if want rotation
|
|
if (!sfs.DontRotate)
|
|
{
|
|
if (1.f - infos.Normal.z < 10e-4)
|
|
{
|
|
rotAxis = NLMISC::CVector::I; // any axis in the (x, y) plane will be ok
|
|
angle = 0.f;
|
|
}
|
|
else if (infos.Normal.z + 1.f < 10e-4)
|
|
{
|
|
rotAxis = NLMISC::CVector::I; // any axis in the (x, y) plane will be ok
|
|
angle = (float) NLMISC::Pi;
|
|
}
|
|
else
|
|
{
|
|
rotAxis.set(- infos.Normal.y, infos.Normal.x, 0.f);
|
|
angle = acosf(infos.Normal.z);
|
|
}
|
|
}
|
|
|
|
float angleAroundNormal = 0.f;
|
|
// see if want rotation around normal
|
|
if (!sfs.DontRotateAroundLocalZ)
|
|
{
|
|
// Once instance is positionned, rotate it around its normal for more variety
|
|
angleAroundNormal = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.1213f * NLMISC::CVector::K);
|
|
}
|
|
// build final rot
|
|
if (!sfs.DontRotate && !sfs.DontRotateAroundLocalZ)
|
|
{
|
|
newFX.Rot = NLMISC::CQuat(infos.Normal, angleAroundNormal) * NLMISC::CQuat(rotAxis, angle);
|
|
}
|
|
else if (!sfs.DontRotate)
|
|
{
|
|
newFX.Rot = NLMISC::CQuat(rotAxis, angle);
|
|
}
|
|
else if (!sfs.DontRotateAroundLocalZ)
|
|
{
|
|
newFX.Rot = NLMISC::CQuat(NLMISC::CVector::K, angleAroundNormal);
|
|
}
|
|
else
|
|
{
|
|
// no rotation at all
|
|
newFX.Rot = NLMISC::CQuat::Identity;
|
|
}
|
|
|
|
// deal with scale
|
|
if (!sfs.WantScaling)
|
|
{
|
|
newFX.Scale.set(1.f, 1.f, 1.f);
|
|
}
|
|
else
|
|
{
|
|
if (sfs.UniformScale)
|
|
{
|
|
float scaleBlend = computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 3.2371f * NLMISC::CVector::J);
|
|
newFX.Scale = scaleBlend * sfs.ScaleMax + (1.f - scaleBlend) * sfs.ScaleMin;
|
|
}
|
|
else
|
|
{
|
|
// compute a different blend factor for each axis
|
|
CVector scaleBlend(computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 3.2371f * NLMISC::CVector::J),
|
|
computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 2.9784f * NLMISC::CVector::J),
|
|
computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 1.1782f * NLMISC::CVector::J));
|
|
newFX.Scale.set(scaleBlend.x * sfs.ScaleMax.x + (1.f - scaleBlend.x) * sfs.ScaleMin.x,
|
|
scaleBlend.y * sfs.ScaleMax.y + (1.f - scaleBlend.y) * sfs.ScaleMin.y,
|
|
scaleBlend.z * sfs.ScaleMax.z + (1.f - scaleBlend.z) * sfs.ScaleMin.z);
|
|
|
|
}
|
|
}
|
|
newFX.FXSheet = &sfs;
|
|
fxToSpawn.push_back(newFX);
|
|
}
|
|
}
|
|
while (k != iv.V.size());
|
|
if (!fxToSpawn.empty())
|
|
{
|
|
CTimedFXManager::TFXGroupHandle fxsHandle = CTimedFXManager::getInstance(). add(fxToSpawn, CurrSeason);
|
|
#ifdef NL_DEBUG
|
|
// make sure that tile is not inserted twice
|
|
TTileIDToFX::iterator testIt = _ActiveFXs.find(infos.TileID);
|
|
nlassert(testIt == _ActiveFXs.end());
|
|
#endif
|
|
_ActiveFXs[infos.TileID] = fxsHandle;
|
|
#ifdef NL_DEBUG
|
|
_ActiveTilesWithFX[infos.TileID] = infos;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::tileRemoved(uint64 id)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
#ifdef NL_DEBUG
|
|
CHashMap<uint64, NL3D::CTileAddedInfo>::iterator tileIt = _ActiveTiles.find(id);
|
|
if (tileIt != _ActiveTiles.end())
|
|
{
|
|
_ActiveTiles.erase(tileIt);
|
|
}
|
|
tileIt = _ActiveTilesWithFX.find(id);
|
|
if (tileIt != _ActiveTilesWithFX.end())
|
|
{
|
|
_ActiveTilesWithFX.erase(tileIt);
|
|
}
|
|
#endif
|
|
TTileIDToFX::iterator it = _ActiveFXs.find(id);
|
|
if (it == _ActiveFXs.end()) return;
|
|
// remove FX from the manager
|
|
CTimedFXManager::getInstance().shutDown(it->second);
|
|
_ActiveFXs.erase(it);
|
|
}
|
|
|
|
static const NLMISC::CRGBA DebugCols[] =
|
|
{
|
|
NLMISC::CRGBA(255, 32, 32),
|
|
NLMISC::CRGBA(32, 255, 32),
|
|
NLMISC::CRGBA(255, 255, 32),
|
|
NLMISC::CRGBA(32, 255, 255),
|
|
NLMISC::CRGBA(255, 32, 255),
|
|
NLMISC::CRGBA(255, 127, 32),
|
|
NLMISC::CRGBA(255, 255, 255)
|
|
};
|
|
static const uint NumDebugCols = sizeof(DebugCols) / sizeof(DebugCols[0]);
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::dumpMLGrid(const std::string &filename)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (_Grid.empty())
|
|
{
|
|
nlwarning("Grid not built");
|
|
return;
|
|
}
|
|
NLMISC::CBitmap bm;
|
|
bm.resize(_GridWidth, _GridHeight, NLMISC::CBitmap::RGBA);
|
|
NLMISC::CRGBA *pix = (NLMISC::CRGBA *) bm.getPixels(0).getPtr();
|
|
for(uint x = 0; x < _GridWidth; ++x)
|
|
{
|
|
for(uint y = 0; y < _GridHeight; ++y)
|
|
{
|
|
if (_Grid[x + y *_GridWidth] == 0)
|
|
{
|
|
pix[x + y * _GridWidth] = CRGBA(127, 127, 127);
|
|
}
|
|
else
|
|
{
|
|
pix[x + y * _GridWidth] = DebugCols[_Grid[x + y *_GridWidth] % NumDebugCols];
|
|
}
|
|
}
|
|
}
|
|
NLMISC::COFile f;
|
|
if (!f.open(filename))
|
|
{
|
|
nlwarning("Can't open %s for writing", filename.c_str());
|
|
return;
|
|
}
|
|
bm.writeTGA(f, 24, true);
|
|
f.close();
|
|
}
|
|
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::renderMLZones(const NLMISC::CVector2f &camPos, float maxDist /*=1000.f*/)
|
|
{
|
|
FPU_CHECKER
|
|
H_AUTO_USE(RZ_MicroLifeManager)
|
|
if (_Grid.empty()) return;
|
|
// no fast at all version
|
|
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);
|
|
Driver->setModelMatrix(NLMISC::CMatrix::Identity);
|
|
float userZ = UserEntity ? (float) UserEntity->pos().z : 0.f;
|
|
for(uint k = 0; k < _PossibleOverlapPolyLists.size(); ++k)
|
|
{
|
|
const CGridOverlapPolyInfoVector &currPolyList = _PossibleOverlapPolyLists[k];
|
|
for(uint l = 0; l < currPolyList.V.size(); ++l)
|
|
{
|
|
const std::vector<NLMISC::CVector2f> &verts = currPolyList.V[l].Poly.Vertices;
|
|
NLMISC::CLineColor line;
|
|
line.Color0 = DebugCols[currPolyList.V[l].PrimitiveIndex % NumDebugCols];
|
|
line.Color0.add(line.Color0, NLMISC::CRGBA(127, 127, 127));
|
|
line.Color1 = line.Color0;
|
|
for(uint m = 0; m < verts.size(); ++m)
|
|
{
|
|
line.V0.set(verts[m].x, verts[m].y, userZ);
|
|
line.V1.set(verts[(m + 1) % verts.size()].x, verts[(m + 1) % verts.size()].y, userZ);
|
|
Driver->drawLine(line, GenericMat);
|
|
line.V0.z = userZ + 5.f;
|
|
line.V1.z = userZ + 5.f;
|
|
Driver->drawLine(line, GenericMat);
|
|
line.V0.set(verts[m].x, verts[m].y, userZ);
|
|
line.V1.set(verts[m].x, verts[m].y, userZ + 5.f);
|
|
Driver->drawLine(line, GenericMat);
|
|
}
|
|
}
|
|
}
|
|
for (uint x = 0; x < _GridWidth; ++x)
|
|
{
|
|
for (uint y = 0; y < _GridHeight; ++y)
|
|
{
|
|
const CGridOverlapPolyInfoVector &currPolyList = _PossibleOverlapPolyLists[_Grid[x + _GridWidth * y]];
|
|
if (currPolyList.V.empty()) continue;
|
|
// see if cell not too far
|
|
NLMISC::CVector2f pos(x * _CellSize + _MinCorner.x, y * _CellSize + _MinCorner.y);
|
|
if ((camPos - pos).norm() > maxDist) continue; // too far, don't display
|
|
// display box for each primitive type
|
|
NLMISC::CVector cornerMin(pos.x, pos.y, userZ - 5.f);
|
|
NLMISC::CVector cornerMax(pos.x + _CellSize, pos.y + _CellSize, userZ + 5.f);
|
|
for(uint l = 0; l < currPolyList.V.size(); ++l)
|
|
{
|
|
// add a bias each time to see when several primitives are overlapped
|
|
NLMISC::CVector bias = (float) currPolyList.V[l].PrimitiveIndex * NLMISC::CVector(0.01f, 0.f, 0.1f);
|
|
CRGBA col = DebugCols[currPolyList.V[l].PrimitiveIndex % NumDebugCols];
|
|
if (!currPolyList.V[l].IsFullyCovered)
|
|
{
|
|
drawBox(cornerMin + bias, cornerMax + bias, col);
|
|
}
|
|
else
|
|
{
|
|
col.R /= 2;
|
|
col.G /= 2;
|
|
col.B /= 2;
|
|
drawBox(cornerMin + bias, cornerMax + bias, col);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef NL_DEBUG
|
|
// ********************************************************************************************
|
|
void CMicroLifeManager::renderActiveTiles()
|
|
{
|
|
FPU_CHECKER
|
|
/*
|
|
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);
|
|
Driver->setModelMatrix(NLMISC::CMatrix::Identity);
|
|
NL3D::UDriver::TPolygonMode oldPolyMode = Driver->getPolygonMode();
|
|
Driver->setPolygonMode(NL3D::UDriver::Line);
|
|
NL3D::UMaterial mat = Driver->createMaterial();
|
|
mat->initUnlit();
|
|
mat->setDoubleSided(true);
|
|
mat->setColor(CRGBA::Green);
|
|
for(std::hash_map<uint64, NL3D::CTileAddedInfo>::iterator it = _ActiveTiles.begin(); it != _ActiveTiles.end(); ++it)
|
|
{
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[0], it->second.Corners[1]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[1], it->second.Corners[2]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[2], it->second.Corners[3]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[3], it->second.Corners[0]), *mat);
|
|
}
|
|
mat->setColor(CRGBA::Red);
|
|
for(std::hash_map<uint64, NL3D::CTileAddedInfo>::iterator it = _ActiveTilesWithFX.begin(); it != _ActiveTilesWithFX.end(); ++it)
|
|
{
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[0], it->second.Corners[1]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[1], it->second.Corners[2]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[2], it->second.Corners[3]), *mat);
|
|
Driver->drawLine(NLMISC::CLine(it->second.Corners[3], it->second.Corners[0]), *mat);
|
|
}
|
|
Driver->deleteMaterial(mat);
|
|
Driver->setPolygonMode(oldPolyMode);
|
|
*/
|
|
}
|
|
#endif
|
|
|
|
////////////////////
|
|
// DEBUG COMMANDS //
|
|
////////////////////
|
|
|
|
#ifdef NL_DEBUG
|
|
// display micro-life active tiles
|
|
NLMISC_COMMAND(showMLActiveTiles,"display micro-life active tiles", "<0 = off, 1 = on>")
|
|
{
|
|
if (args.size() != 1) return false;
|
|
fromString(args[0], DisplayMicroLifeActiveTiles);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if !FINAL_VERSION
|
|
#include "continent_manager.h"
|
|
|
|
// ******************************************************************************************************************
|
|
// display micro-life zone on screen
|
|
NLMISC_COMMAND(showMLZones,"display micro-life zones", "<0 = off, 1 = on>")
|
|
{
|
|
if (args.size() != 1) return false;
|
|
fromString(args[0], DisplayMicroLifeZones);
|
|
return true;
|
|
}
|
|
|
|
|
|
// ******************************************************************************************************************
|
|
// dump micro-life zone in a tga file
|
|
NLMISC_COMMAND(dumpMLZones,"display micro-life zones", "<filename>")
|
|
{
|
|
if (args.size() != 1) return false;
|
|
CMicroLifeManager::getInstance().dumpMLGrid(args[0]);
|
|
return true;
|
|
}
|
|
|
|
|
|
// ******************************************************************************************************************
|
|
// reload micro-life zones
|
|
NLMISC_COMMAND(reloadMLZones, "reload micro-life zones", "")
|
|
{
|
|
if (!args.empty()) return false;
|
|
ClientSheetsStrings.memoryUncompress();
|
|
// reload .flora
|
|
std::vector<std::string> exts;
|
|
exts.push_back("flora");
|
|
NLMISC::IProgressCallback progress;
|
|
SheetMngr.loadAllSheet(progress, true, false, true, true, &exts);
|
|
// reload .plant 5but keep at their current adress)
|
|
CSheetManager sheetManager;
|
|
exts[0] = "plant";
|
|
sheetManager.loadAllSheet(progress, true, false, false, true, &exts);
|
|
//
|
|
const CSheetManager::TEntitySheetMap &sm = SheetMngr.getSheets();
|
|
for(CSheetManager::TEntitySheetMap::const_iterator it = sm.begin(); it != sm.end(); ++it)
|
|
{
|
|
if (it->second.EntitySheet && it->second.EntitySheet->Type == CEntitySheet::PLANT)
|
|
{
|
|
// find matching sheet in new sheetManager
|
|
const CEntitySheet *other = sheetManager.get(it->first);
|
|
if (other)
|
|
{
|
|
// replace data in place
|
|
*(CPlantSheet *) it->second.EntitySheet = *(const CPlantSheet *) other;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
ClientSheetsStrings.memoryCompress();
|
|
// reload prims
|
|
ContinentMngr.cur()->loadMicroLife();
|
|
if (Landscape) Landscape->invalidateAllTiles();
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|