Merge branch '138-toggle-free-look-with-hotkey' into main/gingo-test
[ryzomcore.git] / ryzom / client / src / micro_life_manager.cpp
blob0d56b5d61bbf8b3dd875297e790f15c162d1c004
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "stdpch.h"
20 #include "micro_life_manager.h"
21 #include "sheet_manager.h"
22 #include "misc.h"
23 #include "continent_manager.h"
24 #include "user_entity.h"
25 #include "weather.h"
26 #include "water_map.h"
28 #include "client_sheets/flora_sheet.h"
30 #include "nel/misc/polygon.h"
31 #include "nel/misc/bitmap.h"
32 #include "nel/misc/file.h"
33 #include "nel/misc/i_xml.h"
34 #include "nel/misc/line.h"
36 #include "nel/ligo/primitive.h"
38 #include "nel/3d/u_landscape.h"
39 #include "nel/3d/u_material.h"
40 #include "nel/3d/u_driver.h"
41 #include "nel/3d/u_scene.h"
43 #include "nel/misc/check_fpu.h"
45 using namespace std::rel_ops;
46 using namespace NLMISC;
48 extern NLLIGO::CLigoConfig LigoConfig;
49 extern CContinentManager ContinentMngr;
50 extern NL3D::ULandscape *Landscape;
51 extern NL3D::UDriver *Driver;
52 extern NL3D::UScene *Scene;
53 extern CUserEntity *Userentity;
54 extern NL3D::UMaterial GenericMat;
56 #if !FINAL_VERSION
57 bool DisplayMicroLifeZones = false;
58 #endif
60 #ifdef NL_DEBUG
61 bool DisplayMicroLifeActiveTiles = false;
62 #endif
64 H_AUTO_DECL(RZ_MicroLifeManager)
67 // ********************************************************************************************
68 CMicroLifeManager::CMicroLifeManager()
70 FPU_CHECKER
71 H_AUTO_USE(RZ_MicroLifeManager)
72 _CellSize = 0.f;
73 _GridWidth = 0;
74 _GridHeight = 0;
75 _Noise.Abs = 0.f;
76 _Noise.Rand = 1.f;
79 // ********************************************************************************************
80 CMicroLifeManager &CMicroLifeManager::getInstance()
82 FPU_CHECKER
83 H_AUTO_USE(RZ_MicroLifeManager)
84 static CMicroLifeManager instance;
85 return instance;
88 // ********************************************************************************************
89 void CMicroLifeManager::init(const NLMISC::CVector2f &minCorner, const NLMISC::CVector2f &maxCorner, float cellSize /* = 30.f*/)
91 FPU_CHECKER
92 H_AUTO_USE(RZ_MicroLifeManager)
93 release();
94 if (!Landscape) return;
95 Landscape->addTileCallback(this);
96 if (cellSize == 0.f)
98 nlwarning("Bad cell size");
99 return;
101 if (minCorner.x >= maxCorner.x)
103 nlwarning("Corners not well ordered");
104 return;
106 _CellSize = cellSize;
108 NLMISC::CVector2f minCornerFinal = minCorner;
109 NLMISC::CVector2f maxCornerFinal = maxCorner;
110 if (minCornerFinal.x > maxCornerFinal.x) std::swap(minCornerFinal.x, maxCornerFinal.x);
111 if (minCornerFinal.y > maxCornerFinal.y) std::swap(minCornerFinal.y, maxCornerFinal.y);
113 _GridWidth = (uint) (floorf(maxCornerFinal.x - minCornerFinal.x) / cellSize);
114 _GridHeight = (uint) (floorf(maxCornerFinal.y - minCornerFinal.y) / cellSize);
115 _MinCorner = minCornerFinal;
116 _Grid.resize(_GridWidth * _GridHeight, 0);
118 _Noise.Frequency = 10.1346541f / cellSize;
121 ///////////////////
122 // BUILD PROCESS //
123 ///////////////////
125 // To quickly know if a tile is inside a zone of micro-life, we subdivide the world into a grid. Each grid cell
126 // 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
127 // the grid, because such lists are repeated several times (it would end up wasting too much space). What we just need to keep is
128 // a set of possible polygon lists that can overlap a grid cell. Afterward, we just need a grid of index into
129 // the list of possible polygon lists.
130 // To do that : - we first store complete lists in each grid cells : this is done by several calls to drawPolyInBuildGrid
131 // - we search for redundant lists and store the result as index in the final grid. This is done in packBuildGrid
134 // *********************************************************************************************************************************
135 bool CMicroLifeManager::CGridOverlapPolyInfo::operator < (const CMicroLifeManager::CGridOverlapPolyInfo &other) const
137 FPU_CHECKER
138 H_AUTO_USE(RZ_MicroLifeManager)
139 if (PrimitiveIndex != other.PrimitiveIndex) return PrimitiveIndex < other.PrimitiveIndex;
140 if (Sheet != other.Sheet) return Sheet < other.Sheet;
141 if (IsExcludePoly != other.IsExcludePoly) return !other.IsExcludePoly; // want to test exclude polys first
142 if (IsFullyCovered != other.IsFullyCovered) return other.IsFullyCovered;
143 return Poly < other.Poly;
146 // *********************************************************************************************************************************
147 bool CMicroLifeManager::CGridOverlapPolyInfo::operator == (const CMicroLifeManager::CGridOverlapPolyInfo &other) const
149 FPU_CHECKER
150 H_AUTO_USE(RZ_MicroLifeManager)
151 return PrimitiveIndex == other.PrimitiveIndex &&
152 Sheet == other.Sheet &&
153 IsExcludePoly == other.IsExcludePoly &&
154 IsFullyCovered == other.IsFullyCovered &&
155 Poly == other.Poly;
158 // *********************************************************************************************************************************
159 bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator < (const CMicroLifeManager::CGridOverlapPolyInfoVector &other) const
161 FPU_CHECKER
162 H_AUTO_USE(RZ_MicroLifeManager)
163 if (V.size() != other.V.size()) return V.size() < other.V.size();
164 for(uint k = 0; k < V.size(); ++k)
166 if (V[k] != other.V[k]) return V[k] < other.V[k];
168 return false;
171 // *********************************************************************************************************************************
172 bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator == (const CMicroLifeManager::CGridOverlapPolyInfoVector &other) const
174 FPU_CHECKER
175 H_AUTO_USE(RZ_MicroLifeManager)
176 if (V.size() != other.V.size()) return false;
177 return std::equal(V.begin(), V.end(), other.V.begin());
181 // ***************************************************************************************************************************
182 void CMicroLifeManager::drawPolyInBuildGrid(const std::vector<NLLIGO::CPrimVector> &primPoly,
183 uint primitiveIndex,
184 CMicroLifeManager::TBuildGrid &buildGrid,
185 const CFloraSheet *sheet,
186 bool isExcludeTri)
188 FPU_CHECKER
189 H_AUTO_USE(RZ_MicroLifeManager)
190 NLMISC::CPolygon poly;
191 poly.Vertices.resize(primPoly.size());
192 std::copy(primPoly.begin(), primPoly.end(), poly.Vertices.begin());
193 std::list<NLMISC::CPolygon> convexPolys;
194 if (!poly.toConvexPolygons(convexPolys, NLMISC::CMatrix::Identity))
196 nlwarning("Can't convert to convex polys");
197 return;
199 NLMISC::CPolygon2D poly2D;
200 for(std::list<NLMISC::CPolygon>::iterator it = convexPolys.begin(); it != convexPolys.end(); ++it)
202 poly2D.Vertices.resize(it->Vertices.size());
203 // convert poly in cell units
204 for(uint k = 0; k < it->Vertices.size(); ++k)
206 poly2D.Vertices[k].x = (it->Vertices[k].x - _MinCorner.x) / _CellSize;
207 poly2D.Vertices[k].y = (it->Vertices[k].y - _MinCorner.y) / _CellSize;
208 //nlinfo("poly2D.Vertices[k].x = %.1f, poly2D.Vertices[k].y = %.1f", poly2D.Vertices[k].x, poly2D.Vertices[k].y);
210 // rasterize poly
211 NLMISC::CPolygon2D::TRasterVect outerBorders;
212 NLMISC::CPolygon2D::TRasterVect innerBorders;
213 sint outerMinY;
214 sint innerMinY;
215 poly2D.computeOuterBorders(outerBorders, outerMinY);
216 poly2D.computeInnerBorders(innerBorders, innerMinY);
217 if (outerBorders.empty()) continue; // no contribution for that poly
218 CGridOverlapPolyInfo gti;
219 gti.IsExcludePoly = isExcludeTri;
220 gti.Sheet = sheet;
221 gti.Poly = *it;
222 gti.PrimitiveIndex = primitiveIndex;
223 sint maxY = std::min(outerMinY + (sint)outerBorders.size(), (sint)_GridHeight);
224 sint startY = std::max(0, outerMinY);
225 for (sint y = startY; y < maxY; ++y)
227 sint maxX = std::min(outerBorders[y - startY].second, (sint) (_GridWidth - 1));
228 for (sint x = std::max((sint) 0, outerBorders[y - startY].first); x <= maxX; ++x)
230 nlassert(x >= 0);
231 nlassert(y >= 0);
232 nlassert(x < (sint) _GridWidth);
233 nlassert(y < (sint) _GridHeight);
234 gti.IsFullyCovered = false;
235 // see if primitive covers entirely this grid cell
236 if (y >= innerMinY && y < innerMinY + (sint) innerBorders.size())
238 if (x >= innerBorders[y - innerMinY].first && x <= innerBorders[y - innerMinY].second)
240 gti.IsFullyCovered = true;
243 buildGrid[x + y * _GridWidth].V.push_back(gti);
249 // predicate to remove a cell that is fully covered by an exclude poly
250 class CCleanFullyCoveredGridCellPred
252 public:
253 uint PrimitiveIndex;
254 public:
255 bool operator()(const CMicroLifeManager::CGridOverlapPolyInfo &info) const
257 FPU_CHECKER
258 return info.PrimitiveIndex == PrimitiveIndex;
263 // ***************************************************************************************************************************
264 // pack the build grid so that each cell only contains index into a small set of poly list, instead of a full poly list
265 // (a lot of cells share the same poly list)
266 void CMicroLifeManager::packBuildGrid(TBuildGrid &buildGrid,
267 CMicroLifeManager::TPossibleOverlapingPolyLists &finalPossibleLists
270 FPU_CHECKER
271 H_AUTO_USE(RZ_MicroLifeManager)
272 // the already possible lists
273 std::set<CGridOverlapPolyInfoVector> possibleLists;
274 // for each cell : - remove all polys of the same zone if there's an exclude tri that covers the whole grid
275 // - compute the set of possible lists
276 for(uint k = 0; k < buildGrid.size(); ++k)
278 CGridOverlapPolyInfoVector &gopiv = buildGrid[k];
279 bool restart = false;
282 restart = false;
283 for(std::vector<CGridOverlapPolyInfo>::iterator it = gopiv.V.begin(); it != gopiv.V.end(); ++it)
285 if (it->IsExcludePoly && it->IsFullyCovered)
287 // remove everything about that primitive
288 CCleanFullyCoveredGridCellPred pred;
289 pred.PrimitiveIndex = it->PrimitiveIndex;
290 gopiv.V.erase(std::remove_if(gopiv.V.begin(), gopiv.V.end(), pred), gopiv.V.end());
291 restart = true; // must restart scan from start of list because iterator is now invalid
292 break;
296 while (restart);
297 // insert in possible lists
298 possibleLists.insert(gopiv);
300 // second pass : flatten the set of possible lists to build an index for each grid cell
301 nlassert(possibleLists.size() <= 65536);
302 TPossibleOverlapingPolyLists flatSet(possibleLists.size());
303 std::copy(possibleLists.begin(), possibleLists.end(), flatSet.begin());
304 _Grid.resize(buildGrid.size());
305 for(uint k = 0; k < buildGrid.size(); ++k)
307 TPossibleOverlapingPolyLists::const_iterator it = std::lower_bound(flatSet.begin(), flatSet.end(), buildGrid[k]);
308 nlassert(it != flatSet.end());
309 nlassert(*it == buildGrid[k]);
310 _Grid[k] = (uint16) (it - flatSet.begin());
312 finalPossibleLists.swap(flatSet);
315 // ***************************************************************************************************************************
316 // read a .primitive file and add its content to a build grid
317 void CMicroLifeManager::addPrimitiveToBuildGrid(const std::string &fileName, uint &primitiveIndex, CMicroLifeManager::TBuildGrid &buildGrid)
319 FPU_CHECKER
320 H_AUTO_USE(RZ_MicroLifeManager)
321 // read the file into memory and parse to generate 'prims' data tree
322 NLLIGO::CPrimitives prims;
323 NLMISC::CIFile fileIn;
324 std::string path = NLMISC::CPath::lookup(fileName, false, true);
325 if (path.empty()) return;
326 if (!fileIn.open (path))
328 nlwarning("Can't open %s", path.c_str());
329 return;
331 // Xml stream
332 NLMISC::CIXml xmlIn;
333 xmlIn.init (fileIn);
334 // Read it
335 if (!prims.read(xmlIn.getRootNode(), path.c_str(), LigoConfig))
337 nlwarning ("Error reading file %s", path.c_str());
338 return;
340 // get each son zone
341 for(uint k = 0; k < prims.RootNode->getNumChildren(); ++k)
343 NLLIGO::IPrimitive *child;
344 if (!(prims.RootNode->getChild(child, k) && child)) continue;
346 std::string className;
347 // make sure it is a 'micro_life' primitive
348 if (!child->getPropertyByName("class", className))
350 nlwarning("Can't get class for child %d of primitive %s", (int) k, path.c_str());
351 continue;
353 if (NLMISC::nlstricmp(className.c_str(), "micro_life") != 0) continue; // only deals with micro life
354 // read flora sheet in the primitive
355 std::string formName;
356 if (!child->getPropertyByName("form", formName))
358 nlwarning("Can't get form name for child %d of primitive %s", (int) k, path.c_str());
359 continue;
361 const CEntitySheet *sheet = SheetMngr.get(NLMISC::CSheetId(formName));
362 if (!sheet)
364 nlwarning("Can't get sheet %s", formName.c_str());
365 continue;
367 const CFloraSheet *floraSheet = dynamic_cast<const CFloraSheet *>(sheet);
368 if (!floraSheet)
370 nlwarning("Sheet %s has bad type. Flora sheet wanted", formName.c_str());
371 continue;
374 for(uint m = 0; m < child->getNumChildren(); ++m)
376 NLLIGO::IPrimitive *mlZone;
377 if (!(child->getChild(mlZone, m) && mlZone)) continue;
378 // make sure it is a 'micro_life_zone'
379 if (!mlZone->getPropertyByName("class", className))
381 nlwarning("Can't get class for child %d of primitive %s", (int) m, path.c_str());
382 continue;
384 if (NLMISC::nlstricmp("micro_life_zone", className) != 0) continue;
386 NLLIGO::CPrimZone *zone = dynamic_cast<NLLIGO::CPrimZone *>(mlZone);
387 if (!zone)
389 nlwarning("Child %d of primitive %s is not a zone", (int) k, path.c_str());
390 continue;
392 drawPolyInBuildGrid(zone->VPoints, primitiveIndex, buildGrid, floraSheet, false);
393 // remove exlcusion zones
394 for(uint l = 0; l < zone->getNumChildren(); ++l)
396 NLLIGO::IPrimitive *exclPrim;
397 if (zone->getChild(exclPrim, l) && exclPrim)
399 if (!exclPrim->getPropertyByName("class", className))
401 nlwarning("Can't get class of sub-zone %d for child %d of primitive %s", (int) l, (int) m, path.c_str());
402 continue;
405 if (NLMISC::nlstricmp("micro_life_exclude_zone", className) == 0)
407 NLLIGO::CPrimZone *exclZone = dynamic_cast<NLLIGO::CPrimZone *>(exclPrim);
408 if (!zone)
410 nlwarning("Child %d of child %d of primitive %s is not a zone", (int) l, (int) m, path.c_str());
411 continue;
413 // draw exclude polygon
414 drawPolyInBuildGrid(exclZone->VPoints, primitiveIndex, buildGrid, floraSheet, true);
417 ++ primitiveIndex;
422 // ********************************************************************************************
423 void CMicroLifeManager::build(const std::vector<std::string> &fileNames)
425 FPU_CHECKER
426 H_AUTO_USE(RZ_MicroLifeManager)
427 TBuildGrid buildGrid;
428 buildGrid.resize(_Grid.size());
429 uint currPrimitiveIndex = 0;
430 for(uint l = 0; l < fileNames.size(); ++l)
432 addPrimitiveToBuildGrid(fileNames[l], currPrimitiveIndex, buildGrid);
434 // build the final grid
435 packBuildGrid(buildGrid, _PossibleOverlapPolyLists);
439 // ********************************************************************************************
440 void CMicroLifeManager::release()
442 FPU_CHECKER
443 H_AUTO_USE(RZ_MicroLifeManager)
444 if (Landscape)
446 if (Landscape->isTileCallback(this))
448 Landscape->removeTileCallback(this);
451 // release all registered fxs
452 for(TTileIDToFX::iterator it = _ActiveFXs.begin(); it != _ActiveFXs.end(); ++it)
454 CTimedFXManager::getInstance().remove(it->second);
456 _ActiveFXs.clear();
457 NLMISC::contReset(_Grid);
458 NLMISC::contReset(_PossibleOverlapPolyLists);
459 #ifdef NL_DEBUG
460 _ActiveTiles.clear();
461 _ActiveTilesWithFX.clear();
462 #endif
465 // ********************************************************************************************
466 void CMicroLifeManager::tileAdded(const NL3D::CTileAddedInfo &infos)
468 FPU_CHECKER
469 H_AUTO_USE(RZ_MicroLifeManager)
470 if (_CellSize == 0.f) return; // not initialized
471 #ifdef NL_DEBUG
472 _ActiveTiles[infos.TileID] = infos;
473 #endif
474 // get coords in grid
475 sint gridCoordX = (sint) floorf((infos.Center.x - _MinCorner.x) / _CellSize);
476 if (gridCoordX < 0 || gridCoordX >= (sint) _GridWidth) return;
477 sint gridCoordY = (sint) floorf((infos.Center.y - _MinCorner.y) / _CellSize);
478 if (gridCoordY < 0 || gridCoordY >= (sint) _GridHeight) return;
479 // get list of primitives over which the center of the tile is
480 if( _PossibleOverlapPolyLists.empty() )
481 return; // AJM for when called during zone destructor
482 const CGridOverlapPolyInfoVector &iv = _PossibleOverlapPolyLists[_Grid[gridCoordX + gridCoordY * _GridWidth]];
483 if (iv.V.empty()) return;
484 static std::vector<CTimedFX> fxToSpawn; // static for fast alloc
485 fxToSpawn.clear();
486 uint k = 0;
489 const CFloraSheet *fs = iv.V[k].Sheet;
490 if (!fs) continue;
491 if (fs->getNumPlantInfos() == 0) continue;
493 bool inside = false;
494 uint currPrimIndex = iv.V[k].PrimitiveIndex;
497 if (iv.V[k].IsExcludePoly)
499 // test if center of tile is inside of the primitive
500 if (iv.V[k].Poly.contains(NLMISC::CVector2f(infos.Center.x, infos.Center.y)))
502 inside = false;
503 // jump to next primitive
504 for(;;)
506 if (k == iv.V.size()) break;
507 if (iv.V[k].PrimitiveIndex != currPrimIndex) break;
508 ++ k;
510 break;
513 else
515 if (iv.V[k].IsFullyCovered)
517 // the primitive covers the whole cell so we are inside
518 inside = true;
520 else
522 // test is center of tile is inside
523 if (iv.V[k].Poly.contains(NLMISC::CVector2f(infos.Center.x, infos.Center.y)))
525 inside = true;
529 ++k;
530 if (k == iv.V.size()) break;
532 while(iv.V[k].PrimitiveIndex == currPrimIndex);
533 if (!inside) continue; // no inside this primitive, try the next
534 // Compute noise at center of the tile, and if it is over the micro-life threshold, then spawn a fx
535 // To avoid that each kind of primitive be created at the same time, add an abitrary bias to the position for each primitive type
536 float noise = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.2564f * NLMISC::CVector::K);
537 if (noise > fs->MicroLifeThreshold)
539 // choose a category of plant/microlife to instanciate by computing some noise around
540 float mlCategory = computeUniformNoise(_Noise, infos.Center + (float) (k + 1) * _CellSize * 1.254f * NLMISC::CVector::J + 1.421f * NLMISC::CVector::I);
541 uint64 plantWeightedIndex = (uint64) ((double) mlCategory * fs->getPlantInfoTotalWeight());
542 const CPlantInfo *pi = fs->getPlantInfoFromWeightedIndex(plantWeightedIndex);
543 if (!pi) continue;
544 // get pointer on .plant from sheet
545 CEntitySheet *es = SheetMngr.get(NLMISC::CSheetId(pi->SheetName));
546 if (!es) continue;
547 if (es->Type != CEntitySheet::PLANT) continue;
548 CPlantSheet *fs = static_cast<CPlantSheet *>(es);
549 const CSeasonFXSheet &sfs = fs->getFXSheet(CurrSeason);
550 // if orientation is ok for that kind of plant
551 float cosMax = cosf(NLMISC::degToRad(sfs.AngleMin));
552 float cosMin = cosf(NLMISC::degToRad(sfs.AngleMax));
553 if (cosMax < cosMin) std::swap(cosMin, cosMax);
554 if (infos.Normal.z < cosMin) continue; // not valid
555 if (infos.Normal.z > cosMax) continue; // not valid
557 CTimedFX newFX;
558 #if !FINAL_VERSION
559 // for debug display, tells that it was generated dynamically
560 newFX.FromIG = false;
561 #endif
562 // spawn a primitive on the quad
563 float weight[3];
564 // compute weight values by computing some noise values around
565 weight[0] = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.415f * NLMISC::CVector::I);
566 weight[1] = computeUniformNoise(_Noise, infos.Center - (float) (currPrimIndex + 1) * _CellSize * 1.568f * NLMISC::CVector::I);
567 weight[2] = computeUniformNoise(_Noise, infos.Center - (float) (currPrimIndex + 1) * _CellSize * 1.512f * NLMISC::CVector::J);
568 bool tri = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.898f * NLMISC::CVector::I) > 0.5f; // choose a tri to use
569 // if by extraordinary...
570 if (weight[0] == 0.f && weight[1] == 0.f && weight[2] == 0.f)
572 weight[0] = 1.f;
574 // normalize weights
575 float invTotalWeight = 1.f / (weight[0] + weight[1] + weight[2]);
576 // choose one of the tri to spawn fx (the 2 tri of the quad are not planar)
577 if (tri)
579 newFX.SpawnPosition = invTotalWeight * (weight[0] * infos.Corners[0] + weight[1] * infos.Corners[1] + weight[2] * infos.Corners[2]);
581 else
583 newFX.SpawnPosition = invTotalWeight * (weight[0] * infos.Corners[1] + weight[1] * infos.Corners[2] + weight[2] * infos.Corners[3]);
587 // see if the fx must be aligned on water
588 if (sfs.AlignOnWater)
590 float height;
591 bool splashEnabled;
592 bool hasWater = ContinentMngr.cur()->WaterMap.getWaterHeight(NLMISC::CVector2f(newFX.SpawnPosition.x, newFX.SpawnPosition.y), height, splashEnabled);
593 if (hasWater)
595 newFX.SpawnPosition.z = height;
598 newFX.SpawnPosition.z += sfs.ZOffset;
600 // fit K axis of model with the normal, by doing a rotation around K ^ Normal by an angle alpha
601 // whose cos(alpha) is K * Normal = Normal.Z
602 // K ^ Normal resolves to [-y x 0]
603 // 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)
604 CVector rotAxis; // rotation axis to match Z of model with normal
605 float angle = 0.f; // angle of rotation to match Z of model with normal
606 // see if want rotation
607 if (!sfs.DontRotate)
609 if (1.f - infos.Normal.z < 10e-4)
611 rotAxis = NLMISC::CVector::I; // any axis in the (x, y) plane will be ok
612 angle = 0.f;
614 else if (infos.Normal.z + 1.f < 10e-4)
616 rotAxis = NLMISC::CVector::I; // any axis in the (x, y) plane will be ok
617 angle = (float) NLMISC::Pi;
619 else
621 rotAxis.set(- infos.Normal.y, infos.Normal.x, 0.f);
622 angle = acosf(infos.Normal.z);
626 float angleAroundNormal = 0.f;
627 // see if want rotation around normal
628 if (!sfs.DontRotateAroundLocalZ)
630 // Once instance is positionned, rotate it around its normal for more variety
631 angleAroundNormal = computeUniformNoise(_Noise, infos.Center + (float) (currPrimIndex + 1) * _CellSize * 1.1213f * NLMISC::CVector::K);
633 // build final rot
634 if (!sfs.DontRotate && !sfs.DontRotateAroundLocalZ)
636 newFX.Rot = NLMISC::CQuat(infos.Normal, angleAroundNormal) * NLMISC::CQuat(rotAxis, angle);
638 else if (!sfs.DontRotate)
640 newFX.Rot = NLMISC::CQuat(rotAxis, angle);
642 else if (!sfs.DontRotateAroundLocalZ)
644 newFX.Rot = NLMISC::CQuat(NLMISC::CVector::K, angleAroundNormal);
646 else
648 // no rotation at all
649 newFX.Rot = NLMISC::CQuat::Identity;
652 // deal with scale
653 if (!sfs.WantScaling)
655 newFX.Scale.set(1.f, 1.f, 1.f);
657 else
659 if (sfs.UniformScale)
661 float scaleBlend = computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 3.2371f * NLMISC::CVector::J);
662 newFX.Scale = scaleBlend * sfs.ScaleMax + (1.f - scaleBlend) * sfs.ScaleMin;
664 else
666 // compute a different blend factor for each axis
667 CVector scaleBlend(computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 3.2371f * NLMISC::CVector::J),
668 computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 2.9784f * NLMISC::CVector::J),
669 computeUniformNoise(_Noise, infos.Center + (float) currPrimIndex * _CellSize * 1.1782f * NLMISC::CVector::J));
670 newFX.Scale.set(scaleBlend.x * sfs.ScaleMax.x + (1.f - scaleBlend.x) * sfs.ScaleMin.x,
671 scaleBlend.y * sfs.ScaleMax.y + (1.f - scaleBlend.y) * sfs.ScaleMin.y,
672 scaleBlend.z * sfs.ScaleMax.z + (1.f - scaleBlend.z) * sfs.ScaleMin.z);
676 newFX.FXSheet = &sfs;
677 fxToSpawn.push_back(newFX);
680 while (k != iv.V.size());
681 if (!fxToSpawn.empty())
683 CTimedFXManager::TFXGroupHandle fxsHandle = CTimedFXManager::getInstance(). add(fxToSpawn, CurrSeason);
684 #ifdef NL_DEBUG
685 // make sure that tile is not inserted twice
686 TTileIDToFX::iterator testIt = _ActiveFXs.find(infos.TileID);
687 nlassert(testIt == _ActiveFXs.end());
688 #endif
689 _ActiveFXs[infos.TileID] = fxsHandle;
690 #ifdef NL_DEBUG
691 _ActiveTilesWithFX[infos.TileID] = infos;
692 #endif
696 // ********************************************************************************************
697 void CMicroLifeManager::tileRemoved(uint64 id)
699 FPU_CHECKER
700 H_AUTO_USE(RZ_MicroLifeManager)
701 #ifdef NL_DEBUG
702 CHashMap<uint64, NL3D::CTileAddedInfo>::iterator tileIt = _ActiveTiles.find(id);
703 if (tileIt != _ActiveTiles.end())
705 _ActiveTiles.erase(tileIt);
707 tileIt = _ActiveTilesWithFX.find(id);
708 if (tileIt != _ActiveTilesWithFX.end())
710 _ActiveTilesWithFX.erase(tileIt);
712 #endif
713 TTileIDToFX::iterator it = _ActiveFXs.find(id);
714 if (it == _ActiveFXs.end()) return;
715 // remove FX from the manager
716 CTimedFXManager::getInstance().shutDown(it->second);
717 _ActiveFXs.erase(it);
720 static const NLMISC::CRGBA DebugCols[] =
722 NLMISC::CRGBA(255, 32, 32),
723 NLMISC::CRGBA(32, 255, 32),
724 NLMISC::CRGBA(255, 255, 32),
725 NLMISC::CRGBA(32, 255, 255),
726 NLMISC::CRGBA(255, 32, 255),
727 NLMISC::CRGBA(255, 127, 32),
728 NLMISC::CRGBA(255, 255, 255)
730 static const uint NumDebugCols = sizeof(DebugCols) / sizeof(DebugCols[0]);
732 // ********************************************************************************************
733 void CMicroLifeManager::dumpMLGrid(const std::string &filename)
735 FPU_CHECKER
736 H_AUTO_USE(RZ_MicroLifeManager)
737 if (_Grid.empty())
739 nlwarning("Grid not built");
740 return;
742 NLMISC::CBitmap bm;
743 bm.resize(_GridWidth, _GridHeight, NLMISC::CBitmap::RGBA);
744 NLMISC::CRGBA *pix = (NLMISC::CRGBA *) bm.getPixels(0).getPtr();
745 for(uint x = 0; x < _GridWidth; ++x)
747 for(uint y = 0; y < _GridHeight; ++y)
749 if (_Grid[x + y *_GridWidth] == 0)
751 pix[x + y * _GridWidth] = CRGBA(127, 127, 127);
753 else
755 pix[x + y * _GridWidth] = DebugCols[_Grid[x + y *_GridWidth] % NumDebugCols];
759 NLMISC::COFile f;
760 if (!f.open(filename))
762 nlwarning("Can't open %s for writing", filename.c_str());
763 return;
765 bm.writeTGA(f, 24, true);
766 f.close();
769 // ********************************************************************************************
770 void CMicroLifeManager::renderMLZones(const NLMISC::CVector2f &camPos, float maxDist /*=1000.f*/)
772 FPU_CHECKER
773 H_AUTO_USE(RZ_MicroLifeManager)
774 if (_Grid.empty()) return;
775 // no fast at all version
776 Driver->setViewMatrix(Scene->getCam().getMatrix().inverted());
777 NL3D::CFrustum fr;
778 Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far);
779 fr.Perspective = true;
780 Driver->setFrustum(fr);
781 Driver->setModelMatrix(NLMISC::CMatrix::Identity);
782 float userZ = UserEntity ? (float) UserEntity->pos().z : 0.f;
783 for(uint k = 0; k < _PossibleOverlapPolyLists.size(); ++k)
785 const CGridOverlapPolyInfoVector &currPolyList = _PossibleOverlapPolyLists[k];
786 for(uint l = 0; l < currPolyList.V.size(); ++l)
788 const std::vector<NLMISC::CVector2f> &verts = currPolyList.V[l].Poly.Vertices;
789 NLMISC::CLineColor line;
790 line.Color0 = DebugCols[currPolyList.V[l].PrimitiveIndex % NumDebugCols];
791 line.Color0.add(line.Color0, NLMISC::CRGBA(127, 127, 127));
792 line.Color1 = line.Color0;
793 for(uint m = 0; m < verts.size(); ++m)
795 line.V0.set(verts[m].x, verts[m].y, userZ);
796 line.V1.set(verts[(m + 1) % verts.size()].x, verts[(m + 1) % verts.size()].y, userZ);
797 Driver->drawLine(line, GenericMat);
798 line.V0.z = userZ + 5.f;
799 line.V1.z = userZ + 5.f;
800 Driver->drawLine(line, GenericMat);
801 line.V0.set(verts[m].x, verts[m].y, userZ);
802 line.V1.set(verts[m].x, verts[m].y, userZ + 5.f);
803 Driver->drawLine(line, GenericMat);
807 for (uint x = 0; x < _GridWidth; ++x)
809 for (uint y = 0; y < _GridHeight; ++y)
811 const CGridOverlapPolyInfoVector &currPolyList = _PossibleOverlapPolyLists[_Grid[x + _GridWidth * y]];
812 if (currPolyList.V.empty()) continue;
813 // see if cell not too far
814 NLMISC::CVector2f pos(x * _CellSize + _MinCorner.x, y * _CellSize + _MinCorner.y);
815 if ((camPos - pos).norm() > maxDist) continue; // too far, don't display
816 // display box for each primitive type
817 NLMISC::CVector cornerMin(pos.x, pos.y, userZ - 5.f);
818 NLMISC::CVector cornerMax(pos.x + _CellSize, pos.y + _CellSize, userZ + 5.f);
819 for(uint l = 0; l < currPolyList.V.size(); ++l)
821 // add a bias each time to see when several primitives are overlapped
822 NLMISC::CVector bias = (float) currPolyList.V[l].PrimitiveIndex * NLMISC::CVector(0.01f, 0.f, 0.1f);
823 CRGBA col = DebugCols[currPolyList.V[l].PrimitiveIndex % NumDebugCols];
824 if (!currPolyList.V[l].IsFullyCovered)
826 drawBox(cornerMin + bias, cornerMax + bias, col);
828 else
830 col.R /= 2;
831 col.G /= 2;
832 col.B /= 2;
833 drawBox(cornerMin + bias, cornerMax + bias, col);
840 #ifdef NL_DEBUG
841 // ********************************************************************************************
842 void CMicroLifeManager::renderActiveTiles()
844 FPU_CHECKER
846 Driver->setViewMatrix(Scene->getCam().getMatrix().inverted());
847 NL3D::CFrustum fr;
848 Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far);
849 fr.Perspective = true;
850 Driver->setFrustum(fr);
851 Driver->setModelMatrix(NLMISC::CMatrix::Identity);
852 NL3D::UDriver::TPolygonMode oldPolyMode = Driver->getPolygonMode();
853 Driver->setPolygonMode(NL3D::UDriver::Line);
854 NL3D::UMaterial mat = Driver->createMaterial();
855 mat->initUnlit();
856 mat->setDoubleSided(true);
857 mat->setColor(CRGBA::Green);
858 for(std::hash_map<uint64, NL3D::CTileAddedInfo>::iterator it = _ActiveTiles.begin(); it != _ActiveTiles.end(); ++it)
860 Driver->drawLine(NLMISC::CLine(it->second.Corners[0], it->second.Corners[1]), *mat);
861 Driver->drawLine(NLMISC::CLine(it->second.Corners[1], it->second.Corners[2]), *mat);
862 Driver->drawLine(NLMISC::CLine(it->second.Corners[2], it->second.Corners[3]), *mat);
863 Driver->drawLine(NLMISC::CLine(it->second.Corners[3], it->second.Corners[0]), *mat);
865 mat->setColor(CRGBA::Red);
866 for(std::hash_map<uint64, NL3D::CTileAddedInfo>::iterator it = _ActiveTilesWithFX.begin(); it != _ActiveTilesWithFX.end(); ++it)
868 Driver->drawLine(NLMISC::CLine(it->second.Corners[0], it->second.Corners[1]), *mat);
869 Driver->drawLine(NLMISC::CLine(it->second.Corners[1], it->second.Corners[2]), *mat);
870 Driver->drawLine(NLMISC::CLine(it->second.Corners[2], it->second.Corners[3]), *mat);
871 Driver->drawLine(NLMISC::CLine(it->second.Corners[3], it->second.Corners[0]), *mat);
873 Driver->deleteMaterial(mat);
874 Driver->setPolygonMode(oldPolyMode);
877 #endif
879 ////////////////////
880 // DEBUG COMMANDS //
881 ////////////////////
883 #ifdef NL_DEBUG
884 // display micro-life active tiles
885 NLMISC_COMMAND(showMLActiveTiles,"display micro-life active tiles", "<0 = off, 1 = on>")
887 if (args.size() != 1) return false;
888 fromString(args[0], DisplayMicroLifeActiveTiles);
889 return true;
891 #endif
893 #if !FINAL_VERSION
894 #include "continent_manager.h"
896 // ******************************************************************************************************************
897 // display micro-life zone on screen
898 NLMISC_COMMAND(showMLZones,"display micro-life zones", "<0 = off, 1 = on>")
900 if (args.size() != 1) return false;
901 fromString(args[0], DisplayMicroLifeZones);
902 return true;
906 // ******************************************************************************************************************
907 // dump micro-life zone in a tga file
908 NLMISC_COMMAND(dumpMLZones,"display micro-life zones", "<filename>")
910 if (args.size() != 1) return false;
911 CMicroLifeManager::getInstance().dumpMLGrid(args[0]);
912 return true;
916 // ******************************************************************************************************************
917 // reload micro-life zones
918 NLMISC_COMMAND(reloadMLZones, "reload micro-life zones", "")
920 if (!args.empty()) return false;
921 ClientSheetsStrings.memoryUncompress();
922 // reload .flora
923 std::vector<std::string> exts;
924 exts.push_back("flora");
925 NLMISC::IProgressCallback progress;
926 SheetMngr.loadAllSheet(progress, true, false, true, true, &exts);
927 // reload .plant 5but keep at their current adress)
928 CSheetManager sheetManager;
929 exts[0] = "plant";
930 sheetManager.loadAllSheet(progress, true, false, false, true, &exts);
932 const CSheetManager::TEntitySheetMap &sm = SheetMngr.getSheets();
933 for(CSheetManager::TEntitySheetMap::const_iterator it = sm.begin(); it != sm.end(); ++it)
935 if (it->second.EntitySheet && it->second.EntitySheet->Type == CEntitySheet::PLANT)
937 // find matching sheet in new sheetManager
938 const CEntitySheet *other = sheetManager.get(it->first);
939 if (other)
941 // replace data in place
942 *(CPlantSheet *) it->second.EntitySheet = *(const CPlantSheet *) other;
948 ClientSheetsStrings.memoryCompress();
949 // reload prims
950 ContinentMngr.cur()->loadMicroLife();
951 if (Landscape) Landscape->invalidateAllTiles();
952 return true;
954 #endif