1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
20 #include "micro_life_manager.h"
21 #include "sheet_manager.h"
23 #include "continent_manager.h"
24 #include "user_entity.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
;
57 bool DisplayMicroLifeZones
= false;
61 bool DisplayMicroLifeActiveTiles
= false;
64 H_AUTO_DECL(RZ_MicroLifeManager
)
67 // ********************************************************************************************
68 CMicroLifeManager::CMicroLifeManager()
71 H_AUTO_USE(RZ_MicroLifeManager
)
79 // ********************************************************************************************
80 CMicroLifeManager
&CMicroLifeManager::getInstance()
83 H_AUTO_USE(RZ_MicroLifeManager
)
84 static CMicroLifeManager instance
;
88 // ********************************************************************************************
89 void CMicroLifeManager::init(const NLMISC::CVector2f
&minCorner
, const NLMISC::CVector2f
&maxCorner
, float cellSize
/* = 30.f*/)
92 H_AUTO_USE(RZ_MicroLifeManager
)
94 if (!Landscape
) return;
95 Landscape
->addTileCallback(this);
98 nlwarning("Bad cell size");
101 if (minCorner
.x
>= maxCorner
.x
)
103 nlwarning("Corners not well ordered");
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
;
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
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
150 H_AUTO_USE(RZ_MicroLifeManager
)
151 return PrimitiveIndex
== other
.PrimitiveIndex
&&
152 Sheet
== other
.Sheet
&&
153 IsExcludePoly
== other
.IsExcludePoly
&&
154 IsFullyCovered
== other
.IsFullyCovered
&&
158 // *********************************************************************************************************************************
159 bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator < (const CMicroLifeManager::CGridOverlapPolyInfoVector
&other
) const
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
];
171 // *********************************************************************************************************************************
172 bool CMicroLifeManager::CGridOverlapPolyInfoVector::operator == (const CMicroLifeManager::CGridOverlapPolyInfoVector
&other
) const
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
,
184 CMicroLifeManager::TBuildGrid
&buildGrid
,
185 const CFloraSheet
*sheet
,
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");
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);
211 NLMISC::CPolygon2D::TRasterVect outerBorders
;
212 NLMISC::CPolygon2D::TRasterVect innerBorders
;
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
;
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
)
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
255 bool operator()(const CMicroLifeManager::CGridOverlapPolyInfo
&info
) const
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
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;
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
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
)
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());
335 if (!prims
.read(xmlIn
.getRootNode(), path
.c_str(), LigoConfig
))
337 nlwarning ("Error reading file %s", path
.c_str());
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());
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());
361 const CEntitySheet
*sheet
= SheetMngr
.get(NLMISC::CSheetId(formName
));
364 nlwarning("Can't get sheet %s", formName
.c_str());
367 const CFloraSheet
*floraSheet
= dynamic_cast<const CFloraSheet
*>(sheet
);
370 nlwarning("Sheet %s has bad type. Flora sheet wanted", formName
.c_str());
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());
384 if (NLMISC::nlstricmp("micro_life_zone", className
) != 0) continue;
386 NLLIGO::CPrimZone
*zone
= dynamic_cast<NLLIGO::CPrimZone
*>(mlZone
);
389 nlwarning("Child %d of primitive %s is not a zone", (int) k
, path
.c_str());
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());
405 if (NLMISC::nlstricmp("micro_life_exclude_zone", className
) == 0)
407 NLLIGO::CPrimZone
*exclZone
= dynamic_cast<NLLIGO::CPrimZone
*>(exclPrim
);
410 nlwarning("Child %d of child %d of primitive %s is not a zone", (int) l
, (int) m
, path
.c_str());
413 // draw exclude polygon
414 drawPolyInBuildGrid(exclZone
->VPoints
, primitiveIndex
, buildGrid
, floraSheet
, true);
422 // ********************************************************************************************
423 void CMicroLifeManager::build(const std::vector
<std::string
> &fileNames
)
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()
443 H_AUTO_USE(RZ_MicroLifeManager
)
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
);
457 NLMISC::contReset(_Grid
);
458 NLMISC::contReset(_PossibleOverlapPolyLists
);
460 _ActiveTiles
.clear();
461 _ActiveTilesWithFX
.clear();
465 // ********************************************************************************************
466 void CMicroLifeManager::tileAdded(const NL3D::CTileAddedInfo
&infos
)
469 H_AUTO_USE(RZ_MicroLifeManager
)
470 if (_CellSize
== 0.f
) return; // not initialized
472 _ActiveTiles
[infos
.TileID
] = infos
;
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
489 const CFloraSheet
*fs
= iv
.V
[k
].Sheet
;
491 if (fs
->getNumPlantInfos() == 0) continue;
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
)))
503 // jump to next primitive
506 if (k
== iv
.V
.size()) break;
507 if (iv
.V
[k
].PrimitiveIndex
!= currPrimIndex
) break;
515 if (iv
.V
[k
].IsFullyCovered
)
517 // the primitive covers the whole cell so we are inside
522 // test is center of tile is inside
523 if (iv
.V
[k
].Poly
.contains(NLMISC::CVector2f(infos
.Center
.x
, infos
.Center
.y
)))
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
);
544 // get pointer on .plant from sheet
545 CEntitySheet
*es
= SheetMngr
.get(NLMISC::CSheetId(pi
->SheetName
));
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
559 // for debug display, tells that it was generated dynamically
560 newFX
.FromIG
= false;
562 // spawn a primitive on the quad
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
)
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)
579 newFX
.SpawnPosition
= invTotalWeight
* (weight
[0] * infos
.Corners
[0] + weight
[1] * infos
.Corners
[1] + weight
[2] * infos
.Corners
[2]);
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
)
592 bool hasWater
= ContinentMngr
.cur()->WaterMap
.getWaterHeight(NLMISC::CVector2f(newFX
.SpawnPosition
.x
, newFX
.SpawnPosition
.y
), height
, splashEnabled
);
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
609 if (1.f
- infos
.Normal
.z
< 10e-4)
611 rotAxis
= NLMISC::CVector::I
; // any axis in the (x, y) plane will be ok
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
;
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
);
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
);
648 // no rotation at all
649 newFX
.Rot
= NLMISC::CQuat::Identity
;
653 if (!sfs
.WantScaling
)
655 newFX
.Scale
.set(1.f
, 1.f
, 1.f
);
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
;
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
);
685 // make sure that tile is not inserted twice
686 TTileIDToFX::iterator testIt
= _ActiveFXs
.find(infos
.TileID
);
687 nlassert(testIt
== _ActiveFXs
.end());
689 _ActiveFXs
[infos
.TileID
] = fxsHandle
;
691 _ActiveTilesWithFX
[infos
.TileID
] = infos
;
696 // ********************************************************************************************
697 void CMicroLifeManager::tileRemoved(uint64 id
)
700 H_AUTO_USE(RZ_MicroLifeManager
)
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
);
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
)
736 H_AUTO_USE(RZ_MicroLifeManager
)
739 nlwarning("Grid not built");
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);
755 pix
[x
+ y
* _GridWidth
] = DebugCols
[_Grid
[x
+ y
*_GridWidth
] % NumDebugCols
];
760 if (!f
.open(filename
))
762 nlwarning("Can't open %s for writing", filename
.c_str());
765 bm
.writeTGA(f
, 24, true);
769 // ********************************************************************************************
770 void CMicroLifeManager::renderMLZones(const NLMISC::CVector2f
&camPos
, float maxDist
/*=1000.f*/)
773 H_AUTO_USE(RZ_MicroLifeManager
)
774 if (_Grid
.empty()) return;
775 // no fast at all version
776 Driver
->setViewMatrix(Scene
->getCam().getMatrix().inverted());
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
);
833 drawBox(cornerMin
+ bias
, cornerMax
+ bias
, col
);
841 // ********************************************************************************************
842 void CMicroLifeManager::renderActiveTiles()
846 Driver->setViewMatrix(Scene->getCam().getMatrix().inverted());
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();
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);
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
);
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
);
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]);
916 // ******************************************************************************************************************
917 // reload micro-life zones
918 NLMISC_COMMAND(reloadMLZones
, "reload micro-life zones", "")
920 if (!args
.empty()) return false;
921 ClientSheetsStrings
.memoryUncompress();
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
;
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
);
941 // replace data in place
942 *(CPlantSheet
*) it
->second
.EntitySheet
= *(const CPlantSheet
*) other
;
948 ClientSheetsStrings
.memoryCompress();
950 ContinentMngr
.cur()->loadMicroLife();
951 if (Landscape
) Landscape
->invalidateAllTiles();