1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
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/>.
19 #include "nel/3d/cluster.h"
20 #include "nel/3d/portal.h"
21 #include "nel/misc/stream.h"
22 #include "nel/misc/string_mapper.h"
23 #include "nel/3d/scene.h"
24 #include "nel/3d/transform_shape.h"
25 //#include "mesh_instance.h"
26 #include "nel/3d/scene_group.h"
28 using namespace NLMISC
;
38 // 0.5 cm of precision
39 #define CLUSTERPRECISION 0.005
42 // ***************************************************************************
45 /* ***********************************************
46 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
47 * It can be loaded/called through CAsyncFileManager for instance
48 * ***********************************************/
50 FatherVisible
= VisibleFromFather
= false;
51 FatherAudible
= AudibleFromFather
= false;
56 _EnvironmentFxId
= CStringMapper::map("no fx");
57 // map a no soundgroup string
58 _SoundGroupId
= CStringMapper::map("");
60 // I am a transform cluster
61 CTransform::setIsCluster(true);
63 // Default: not traversed
69 // ***************************************************************************
72 /* ***********************************************
73 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
74 * It can be loaded/called through CAsyncFileManager for instance
75 * ***********************************************/
77 unlinkFromClusterTree();
81 void CCluster::setSoundGroup(const std::string
&soundGroup
)
83 _SoundGroupId
= CStringMapper::map(soundGroup
);
85 void CCluster::setSoundGroup(const NLMISC::TStringId
&soundGroupId
)
87 _SoundGroupId
= soundGroupId
;
89 const std::string
&CCluster::getSoundGroup()
91 return CStringMapper::unmap(_SoundGroupId
);
94 NLMISC::TStringId
CCluster::getSoundGroupId()
98 void CCluster::setEnvironmentFx(const std::string
&environmentFx
)
100 _EnvironmentFxId
= CStringMapper::map(environmentFx
);
102 void CCluster::setEnvironmentFx(const NLMISC::TStringId
&environmentFxId
)
104 _EnvironmentFxId
= environmentFxId
;
106 const std::string
&CCluster::getEnvironmentFx()
108 return CStringMapper::unmap(_EnvironmentFxId
);
111 NLMISC::TStringId
CCluster::getEnvironmentFxId()
113 return _EnvironmentFxId
;
118 // ***************************************************************************
119 void CCluster::unlinkFromParent()
121 /* ***********************************************
122 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
123 * It can be loaded/called through CAsyncFileManager for instance
124 * ***********************************************/
126 // unlink from father sons list
129 Father
->Children
.erase(remove(Father
->Children
.begin(), Father
->Children
.end(), this), Father
->Children
.end());
134 // ***************************************************************************
135 void CCluster::unlinkSons()
137 /* ***********************************************
138 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
139 * It can be loaded/called through CAsyncFileManager for instance
140 * ***********************************************/
142 // tells all sons that they have no more father
143 for(uint k
= 0; k
< Children
.size(); ++k
)
145 if (Children
[k
]->Father
== this)
147 Children
[k
]->Father
= NULL
;
150 NLMISC::contReset(Children
);
155 // ***************************************************************************
156 void CCluster::unlinkFromClusterTree()
158 /* ***********************************************
159 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
160 * It can be loaded/called through CAsyncFileManager for instance
161 * ***********************************************/
169 // ***************************************************************************
170 void CCluster::registerBasic ()
172 CScene::registerModel (ClusterId
, 0, CCluster::creator
);
175 // ***************************************************************************
176 bool CCluster::makeVolume (const CVector
& p1
, const CVector
& p2
, const CVector
& p3
)
179 // Check if the plane is not close to a plane that already define the cluster
180 for (i
= 0; i
< _LocalVolume
.size(); ++i
)
182 float f1
= fabsf (_LocalVolume
[i
]*p1
);
183 float f2
= fabsf (_LocalVolume
[i
]*p2
);
184 float f3
= fabsf (_LocalVolume
[i
]*p3
);
185 if ((f1
< CLUSTERPRECISION
) && (f2
< CLUSTERPRECISION
) && (f3
< CLUSTERPRECISION
))
188 // Check if we want to add a triangle not completely in the predefined volume
189 for (i
= 0; i
< _LocalVolume
.size(); ++i
)
191 float f1
= _LocalVolume
[i
]*p1
;
192 float f2
= _LocalVolume
[i
]*p2
;
193 float f3
= _LocalVolume
[i
]*p3
;
194 if ((f1
> CLUSTERPRECISION
) && (f2
> CLUSTERPRECISION
) && (f3
> CLUSTERPRECISION
))
201 _LocalVolume
.push_back (p
);
203 if (_LocalVolume
.size() == 1)
204 _LocalBBox
.setCenter(p1
);
206 _LocalBBox
.extend(p1
);
207 _LocalBBox
.extend(p2
);
208 _LocalBBox
.extend(p3
);
209 _Volume
= _LocalVolume
;
214 // ***************************************************************************
215 bool CCluster::isIn (const CVector
& p
)
217 for (uint i
= 0; i
< _Volume
.size(); ++i
)
218 if (_Volume
[i
]*p
> CLUSTERPRECISION
)
224 // ***************************************************************************
225 bool CCluster::isIn (const CAABBox
& b
)
227 for (uint i
= 0; i
< _Volume
.size(); ++i
)
229 if (!b
.clipBack (_Volume
[i
]))
235 // ***************************************************************************
236 bool CCluster::isIn (const NLMISC::CVector
& center
, float size
)
238 for (uint i
= 0; i
< _Volume
.size(); ++i
)
239 if (_Volume
[i
]*center
> size
)
244 // ***************************************************************************
245 bool CCluster::clipSegment (NLMISC::CVector
&p0
, NLMISC::CVector
&p1
)
247 for (uint i
= 0; i
< _Volume
.size(); ++i
)
249 if (!_Volume
[i
].clipSegmentBack(p0
, p1
))
255 // ***************************************************************************
256 void CCluster::resetPortalLinks ()
261 // ***************************************************************************
262 void CCluster::link (CPortal
* portal
)
264 _Portals
.push_back (portal
);
267 // ***************************************************************************
268 void CCluster::unlink (CPortal
* portal
)
271 for (pos
= 0; pos
< _Portals
.size(); ++pos
)
273 if (_Portals
[pos
] == portal
)
276 if (pos
< _Portals
.size())
277 _Portals
.erase (_Portals
.begin()+pos
);
280 // ***************************************************************************
281 void CCluster::serial (NLMISC::IStream
&f
)
283 /* ***********************************************
284 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
285 * It can be loaded/called through CAsyncFileManager for instance
286 * ***********************************************/
288 sint version
= f
.serialVersion (3);
293 f
.serialCont (_LocalVolume
);
294 f
.serial (_LocalBBox
);
295 f
.serial (FatherVisible
);
296 f
.serial (VisibleFromFather
);
299 _Volume
= _LocalVolume
;
307 std::string soundGroup
;
308 std::string envFxName
;
310 f
.serial(soundGroup
);
311 _SoundGroupId
= CStringMapper::map(soundGroup
);
314 if (envFxName
.empty())
316 _EnvironmentFxId
= CStringMapper::map(envFxName
);
320 // write the sound group
321 std::string soundGroup
= CStringMapper::unmap(_SoundGroupId
);
322 f
.serial(soundGroup
);
323 // write the env fx name
324 std::string envFxName
= CStringMapper::unmap(_EnvironmentFxId
);
325 if (envFxName
== "no fx")
330 // nldebug("Cluster %s, sound group [%s]", Name.c_str(), CStringMapper::unmap(_SoundGroupId).c_str());
335 f
.serial(AudibleFromFather
);
336 f
.serial(FatherAudible
);
340 // copy the visual property
341 AudibleFromFather
= VisibleFromFather
;
342 FatherAudible
= FatherVisible
;
346 // ***************************************************************************
347 void CCluster::setWorldMatrix (const CMatrix
&WM
)
353 // Transform the volume
354 for (i
= 0; i
< _LocalVolume
.size(); ++i
)
355 _Volume
[i
] = _LocalVolume
[i
] * invWM
;
357 _BBox
= NLMISC::CAABBox::transformAABBox(WM
, _LocalBBox
);
359 // Transform the bounding box
361 p[0].x = _LocalBBox.getMin().x;
362 p[0].y = _LocalBBox.getMin().y;
363 p[0].z = _LocalBBox.getMin().z;
365 p[1].x = _LocalBBox.getMax().x;
366 p[1].y = _LocalBBox.getMin().y;
367 p[1].z = _LocalBBox.getMin().z;
369 p[2].x = _LocalBBox.getMin().x;
370 p[2].y = _LocalBBox.getMax().y;
371 p[2].z = _LocalBBox.getMin().z;
373 p[3].x = _LocalBBox.getMax().x;
374 p[3].y = _LocalBBox.getMax().y;
375 p[3].z = _LocalBBox.getMin().z;
377 p[4].x = _LocalBBox.getMin().x;
378 p[4].y = _LocalBBox.getMin().y;
379 p[4].z = _LocalBBox.getMax().z;
381 p[5].x = _LocalBBox.getMax().x;
382 p[5].y = _LocalBBox.getMin().y;
383 p[5].z = _LocalBBox.getMax().z;
385 p[6].x = _LocalBBox.getMin().x;
386 p[6].y = _LocalBBox.getMax().y;
387 p[6].z = _LocalBBox.getMax().z;
389 p[7].x = _LocalBBox.getMax().x;
390 p[7].y = _LocalBBox.getMax().y;
391 p[7].z = _LocalBBox.getMax().z;
393 for (i = 0; i < 8; ++i)
394 p[i] = WM.mulPoint(p[i]);
398 boxTemp.setCenter(p[0]);
399 for (i = 1; i < 8; ++i)
400 boxTemp.extend(p[i]);
404 // ***************************************************************************
405 void CCluster::traverseHrc ()
407 CTransform::traverseHrc ();
409 setWorldMatrix (_WorldMatrix
);
411 for (uint32 i
= 0; i
< getNbPortals(); ++i
)
413 CPortal
*pPortal
= getPortal(i
);
414 pPortal
->setWorldMatrix (_WorldMatrix
);
417 // Re affect the cluster to the accelerator if not the root
420 Group
->_ClipTrav
->unregisterCluster(this);
421 Group
->_ClipTrav
->registerCluster (this);
425 // ***************************************************************************
426 bool CCluster::clip ()
432 // ***************************************************************************
433 void CCluster::traverseClip ()
435 // This is the root call called by the SceneRoot
436 recursTraverseClip(NULL
);
440 // ***************************************************************************
441 void CCluster::recursTraverseClip(CTransform
*caller
)
447 // The cluster is visible because we are in it
448 // So clip the models attached (with MOT links) to the cluster
449 uint num
= clipGetNumChildren();
452 clipGetChild(i
)->traverseClip();
454 // Debug visible clusters
455 CClipTrav
&clipTrav
= getOwnerScene()->getClipTrav();
456 if (clipTrav
.getClusterVisibilityTracking())
458 clipTrav
.addVisibleCluster(this);
461 // And look through portals
462 for (i
= 0; i
< getNbPortals(); ++i
)
464 CPortal
*pPortal
= getPortal (i
);
465 vector
<CPlane
> WorldPyrTemp
= clipTrav
.WorldPyramid
;
466 bool backfaceclipped
= false;
467 CCluster
*pOtherSideCluster
;
468 if (pPortal
->getCluster(0) == this)
469 pOtherSideCluster
= pPortal
->getCluster (1);
471 pOtherSideCluster
= pPortal
->getCluster (0);
474 if (caller
== Father
) // If the caller is the father
475 if (VisibleFromFather
)
477 if( !pPortal
->isInFront( clipTrav
.CamPos
))
478 backfaceclipped
= true;
480 if (!backfaceclipped
&& pOtherSideCluster
)
482 /* If the otherSide cluster is fully visible because the camera is IN, then don't need to clip.
483 This is important to landscape test, to ensure that pyramid are strictly equal from 2 paths which
484 come from the 2 clusters where the camera start
486 if (pOtherSideCluster
->isCameraIn() || pPortal
->clipPyramid (clipTrav
.CamPos
, clipTrav
.WorldPyramid
))
488 pOtherSideCluster
->recursTraverseClip(this);
492 clipTrav
.WorldPyramid
= WorldPyrTemp
;
495 // Link up in hierarchy
496 if ((FatherVisible
)&&(Father
!= NULL
))
498 Father
->recursTraverseClip(this);
501 // Link down in hierarchy
502 for (i
= 0; i
< Children
.size(); ++i
)
503 if (Children
[i
]->VisibleFromFather
)
505 Children
[i
]->recursTraverseClip(this);
512 // ***************************************************************************
513 void CCluster::applyMatrix(const NLMISC::CMatrix
&m
)
518 nlassert(_Volume
.size() == _LocalVolume
.size());
520 // Transform the volume
521 for (i
= 0; i
< _LocalVolume
.size(); ++i
)
523 _Volume
[i
] = _Volume
[i
] * invM
;
524 _LocalVolume
[i
] = _LocalVolume
[i
] * invM
;
527 // Transform the bounding boxes
528 _BBox
= NLMISC::CAABBox::transformAABBox(m
, _BBox
);
529 _LocalBBox
= NLMISC::CAABBox::transformAABBox(m
, _LocalBBox
);
533 // ***************************************************************************
534 void CCluster::cameraRayClip(const CVector
&start
, const CVector
&end
, std::vector
<CCluster
*> &clusterVisited
)
541 // The cluster is visible because we are in it. add it to the list of cluster (if not already inserted)
542 for(i
=0;i
<clusterVisited
.size();i
++)
544 if(clusterVisited
[i
]==this)
547 if(i
==clusterVisited
.size())
548 clusterVisited
.push_back(this);
550 // look through portals
551 for (i
= 0; i
< getNbPortals(); ++i
)
553 CPortal
*pPortal
= getPortal (i
);
554 CCluster
*pOtherSideCluster
;
555 if (pPortal
->getCluster(0) == this)
556 pOtherSideCluster
= pPortal
->getCluster (1);
558 pOtherSideCluster
= pPortal
->getCluster (0);
561 bool backfaceclipped = false;
563 if (caller == Father) // If the caller is the father
564 if (VisibleFromFather)
566 if( !pPortal->isInFront( clipTrav.CamPos ))
567 backfaceclipped = true;
568 if (!backfaceclipped && pOtherSideCluster)*/
570 if (pOtherSideCluster
)
572 if (pPortal
->clipRay (start
, end
))
574 pOtherSideCluster
->cameraRayClip(start
, end
, clusterVisited
);
579 /* Link up in hierarchy. Test the Inverse Flag, cause the path is inverted here!!!
580 ie: if I allow the camera to go out, it MUST can re-enter (ie if I am VisibleFromFather)
582 if ((VisibleFromFather
)&&(Father
!= NULL
))
584 Father
->cameraRayClip(start
, end
, clusterVisited
);
587 // Link down in hierarchy
588 for (i
= 0; i
< Children
.size(); ++i
)
590 // same remark. test FatherVisible, not VisibleFromFather
591 if (Children
[i
]->FatherVisible
)
593 Children
[i
]->cameraRayClip(start
, end
, clusterVisited
);