From 0ea5fc66924303d1bf73ba283a383e2aadee02f2 Mon Sep 17 00:00:00 2001 From: neodarz Date: Sat, 11 Aug 2018 20:21:34 +0200 Subject: Initial commit --- .../nel/ps__ribbon__look__at_8cpp-source.html | 629 +++++++++++++++++++++ 1 file changed, 629 insertions(+) create mode 100644 docs/doxygen/nel/ps__ribbon__look__at_8cpp-source.html (limited to 'docs/doxygen/nel/ps__ribbon__look__at_8cpp-source.html') diff --git a/docs/doxygen/nel/ps__ribbon__look__at_8cpp-source.html b/docs/doxygen/nel/ps__ribbon__look__at_8cpp-source.html new file mode 100644 index 00000000..3b50d44c --- /dev/null +++ b/docs/doxygen/nel/ps__ribbon__look__at_8cpp-source.html @@ -0,0 +1,629 @@ + + + + nevrax.org : docs + + + + + + + + + + + + + + +
# Home   # nevrax.com   
+ + + + +
Nevrax
+ + + + + + + + + + +
+ + +
+ Nevrax.org
+ + + + + + + +
#News
#Mailing-list
#Documentation
#CVS
#Bugs
#License
+
+ + +
+ + +
+Docs + +
+  + + + + + +
Documentation 
+ +
+Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members   Related Pages   Search  
+

ps_ribbon_look_at.cpp

Go to the documentation of this file.
00001 
+00007 /* Copyright, 2001 Nevrax Ltd.
+00008  *
+00009  * This file is part of NEVRAX NEL.
+00010  * NEVRAX NEL is free software; you can redistribute it and/or modify
+00011  * it under the terms of the GNU General Public License as published by
+00012  * the Free Software Foundation; either version 2, or (at your option)
+00013  * any later version.
+00014 
+00015  * NEVRAX NEL is distributed in the hope that it will be useful, but
+00016  * WITHOUT ANY WARRANTY; without even the implied warranty of
+00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+00018  * General Public License for more details.
+00019 
+00020  * You should have received a copy of the GNU General Public License
+00021  * along with NEVRAX NEL; see the file COPYING. If not, write to the
+00022  * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+00023  * MA 02111-1307, USA.
+00024  */
+00025 
+00026 #include "std3d.h"
+00027 
+00028 #include "3d/ps_ribbon_look_at.h"
+00029 #include "3d/particle_system.h"
+00030 #include "3d/ps_macro.h"
+00031 #include "3d/driver.h"
+00032 
+00033 namespace NL3D 
+00034 {
+00035 
+00037 // CPSRibbonLookAt implementation //
+00039 
+00040 const float ZEpsilon = 10E-3f;
+00041 const float NormEpsilon = 10E-8f;
+00042 
+00043 
+00044 struct CVectInfo
+00045 {
+00046         NLMISC::CVector Interp;
+00047         NLMISC::CVector Proj;   
+00048 };
+00049 typedef std::vector<CVectInfo> TRibbonVect; // a vector used for intermediate computations
+00050 
+00051 CPSRibbonLookAt::TVBMap         CPSRibbonLookAt::_VBMap;                        // index buffers with no color
+00052 CPSRibbonLookAt::TVBMap         CPSRibbonLookAt::_ColoredVBMap;  // index buffer + colors       
+00053 
+00054 //=======================================================       
+00055 CPSRibbonLookAt::CPSRibbonLookAt()
+00056 {
+00057 }
+00058 
+00059 //=======================================================       
+00060 CPSRibbonLookAt::~CPSRibbonLookAt()
+00061 {
+00062 //      delete _DyingRibbons;
+00063 }
+00064 
+00065 //=======================================================       
+00066 void CPSRibbonLookAt::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
+00067 {
+00071         sint ver = f.serialVersion(4);
+00072         if (ver > 3)
+00073         {
+00074                 CPSRibbonBase::serial(f);
+00075         }
+00076         else
+00077         {
+00078                 CPSParticle::serial(f);
+00079         }
+00080         CPSColoredParticle::serialColorScheme(f);       
+00081         CPSSizedParticle::serialSizeScheme(f);          
+00082         serialMaterial(f);
+00083         uint32 dummy = 0; /* _NbDyingRibbons */
+00084         if (ver <= 3)
+00085         {
+00086                 f.serial(_SegDuration, _NbSegs, dummy /*_NbDyingRibbons*/);
+00087         }
+00088         ITexture *tex = NULL;
+00089 
+00090         if (ver > 2)
+00091         {
+00092                 f.serial(_Parametric);
+00093         }
+00094 
+00095 
+00096         if (!f.isReading())
+00097         {               
+00098                 tex = _Tex;
+00099                 f.serialPolyPtr(tex);   
+00100         }
+00101         else
+00102         {               
+00103                 f.serialPolyPtr(tex);
+00104                 setTexture(tex);
+00105                 setTailNbSeg(_NbSegs); // force to build the vb
+00106         }       
+00107 }
+00108 
+00109 
+00110 //=======================================================       
+00111 void CPSRibbonLookAt::setTexture(CSmartPtr<ITexture> tex)
+00112 {
+00113         _Tex = tex;
+00114         updateMatAndVbForColor();
+00115 }
+00116 
+00117 
+00118 //=======================================================       
+00119 void CPSRibbonLookAt::step(TPSProcessPass pass, TAnimationTime ellapsedTime, TAnimationTime realEt)
+00120 {       
+00121         if (pass == PSMotion)
+00122         {               
+00123                 if (!_Parametric)
+00124                 {
+00125                         updateGlobals();
+00126                 }
+00127         }
+00128         else
+00129         if (
+00130                 (pass == PSBlendRender && hasTransparentFaces())
+00131                 || (pass == PSSolidRender && hasOpaqueFaces())
+00132                 )
+00133         {
+00134                 uint32 step;
+00135                 uint   numToProcess;
+00136                 computeSrcStep(step, numToProcess);     
+00137                 if (!numToProcess) return;
+00138                 
+00140                 CParticleSystem &ps = *(_Owner->getOwner());    
+00141                 _Mat.setColor(ps.getGlobalColor());
+00142                 
+00147                 displayRibbons(numToProcess, step);
+00148 
+00149         }
+00150         else 
+00151         if (pass == PSToolRender) // edition mode only
+00152         {                       
+00153                 //showTool();
+00154         }       
+00155 }
+00156 
+00157 
+00158 //=======================================================       
+00159 void CPSRibbonLookAt::newElement(CPSLocated *emitterLocated, uint32 emitterIndex)
+00160 {
+00161         CPSRibbonBase::newElement(emitterLocated, emitterIndex);
+00162         newColorElement(emitterLocated, emitterIndex);
+00163         newSizeElement(emitterLocated, emitterIndex);   
+00164 }
+00165 
+00166 
+00167 //=======================================================       
+00168 void CPSRibbonLookAt::deleteElement(uint32 index)
+00169 {
+00170         CPSRibbonBase::deleteElement(index);
+00171         deleteColorElement(index);
+00172         deleteSizeElement(index);       
+00173 }
+00174 
+00175 
+00176 //=======================================================       
+00177 void CPSRibbonLookAt::resize(uint32 size)
+00178 {
+00179         nlassert(size < (1 << 16));
+00180         CPSRibbonBase::resize(size);    
+00181         resizeColor(size);
+00182         resizeSize(size);                       
+00183 }
+00184 
+00185 //=======================================================       
+00186 void CPSRibbonLookAt::updateMatAndVbForColor(void)
+00187 {
+00188         _Mat.setTexture(0, _Tex);
+00189 }
+00190 
+00191 //=======================================================       
+00192 static inline void MakeProj(NLMISC::CVector &dest, const NLMISC::CVector &src)
+00193 {               
+00194         if (fabsf(src.y) > NormEpsilon * NormEpsilon)
+00195         {
+00196                 dest.x = src.x / src.y;
+00197                 dest.z = src.z / src.y;
+00198                 dest.y = src.y; 
+00199         }       
+00200 }
+00201 
+00202 static inline void BuildSlice(const NLMISC::CMatrix &mat, CVertexBuffer &vb, uint8 *currVert, uint32 vertexSize,
+00203                                                           const NLMISC::CVector &I, 
+00204                                                           const NLMISC::CVector &K, 
+00205                                                           TRibbonVect::iterator  pos,
+00206                                                           TRibbonVect::iterator  prev,
+00207                                                           TRibbonVect::iterator  next,
+00208                                                           float ribSize)
+00210 {
+00211         CHECK_VERTEX_BUFFER(vb, currVert);
+00212         CHECK_VERTEX_BUFFER(vb, currVert);
+00213         NLMISC::CVector tangent;
+00214 
+00215         float invTgNorm; // inverse of the' norm of the projected segment
+00216         float tgNorm;
+00217 
+00218         if (prev->Proj.y > ZEpsilon && next->Proj.y > ZEpsilon) // the 2 points are in front of the camera
+00219         {
+00220                 tangent = next->Proj - prev->Proj;
+00221                 tangent.y = 0;
+00222                 tgNorm = tangent.norm();
+00223                 if (fabs(tgNorm) > 10E-8)
+00224                 {
+00225                         invTgNorm = 1.f / tgNorm;
+00226                 }
+00227                 else
+00228                 {
+00229                         invTgNorm = 1.f;
+00230                 }
+00231                 // build orthogonals vectors to tangent
+00232                 
+00233                 *(NLMISC::CVector *) currVert = pos->Interp + ribSize * invTgNorm * (tangent.x * K - tangent.z * I);                                            
+00234                 *(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);                   
+00235         }
+00236         else if (prev->Proj.y > ZEpsilon) // second point cross the near plane
+00237         {
+00238                 // compute intersection point
+00239                 NLMISC::CVector inter;
+00240                 NLMISC::CVector tInter;         
+00241                 if (fabsf(prev->Proj.y - next->Proj.y) > NormEpsilon)
+00242                 {
+00243                                 float lambda = (next->Proj.y - ZEpsilon) / (next->Proj.y - prev->Proj.y);
+00244                                 inter = lambda * prev->Interp + (1.f - lambda) * next->Interp;                          
+00245                                 MakeProj(tInter, mat * inter);
+00246                 }
+00247                 else // 
+00248                 {
+00249                         *(NLMISC::CVector *) currVert = pos->Interp;
+00250                         *(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;                     
+00251                         return;
+00252                 }
+00253                 
+00254                 tangent = tInter - prev->Proj;          
+00255                 tangent.y = 0;
+00256 
+00257                 tgNorm = tangent.norm();
+00258                 if (fabs(tgNorm > 10E-8))
+00259                 {
+00260                         invTgNorm = 1.f / tgNorm;                       
+00261                 }
+00262                 else
+00263                 {
+00264                         invTgNorm = 1.f;
+00265                 }
+00266                 // build orthogonals vectors to tangent
+00267                 
+00268                 *(NLMISC::CVector *) currVert = inter + ribSize *  invTgNorm * (tangent.x * K - tangent.z * I);         
+00269                 *(NLMISC::CVector *) (currVert + vertexSize) = inter + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);
+00270         }
+00271         else if (next->Proj.y > ZEpsilon) // first point cross the near plane
+00272         {
+00273                         // compute intersection point
+00274                 // compute intersection point
+00275                 NLMISC::CVector inter;
+00276                 NLMISC::CVector tInter;
+00277                 if (fabsf(prev->Proj.y - next->Proj.y) > NormEpsilon)
+00278                 {
+00279                                 float lambda = (next->Proj.y - ZEpsilon) / (next->Proj.y - prev->Proj.y);
+00280                                 inter = lambda * prev->Interp + (1.f - lambda) * next->Interp;                          
+00281                                 MakeProj(tInter, mat * inter);
+00282                 }
+00283                 else // 
+00284                 {
+00285                         *(NLMISC::CVector *) currVert = pos->Interp;
+00286                         *(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;
+00287                         return;
+00288                 }
+00289                 
+00290                 tangent = next->Proj - tInter;          
+00291                 tangent.y = 0;
+00292                 tgNorm = tangent.norm();
+00293                 if (fabs(tgNorm > 10E-8))
+00294                 {
+00295                         invTgNorm = 1.f / tgNorm;                       
+00296                 }
+00297                 else
+00298                 {
+00299                         invTgNorm = 1.f;
+00300                 }
+00301                 // build orthogonals vectors to tangent
+00302                 
+00303                 *(NLMISC::CVector *) currVert = inter + ribSize * invTgNorm * (tangent.x * K - tangent.z * I);          
+00304                 *(NLMISC::CVector *) (currVert + vertexSize) = inter + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);
+00305 
+00306         }
+00307         else // two points are not visible
+00308         {
+00309                 *(NLMISC::CVector *) currVert = pos->Interp;
+00310                 *(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;
+00311         }
+00312         
+00313 }
+00314 
+00315 
+00316 //==========================================================================    
+00317 void CPSRibbonLookAt::displayRibbons(uint32 nbRibbons, uint32 srcStep)
+00318 {       
+00319         if (!nbRibbons) return;
+00320         nlassert(_Owner);       
+00321         CPSRibbonBase::updateLOD();
+00322         if (_UsedNbSegs < 2) return;
+00323 
+00324         const float date = _Owner->getOwner()->getSystemDate();
+00325         uint8                                           *currVert;
+00326 
+00327         CVBnPB                                          &VBnPB = getVBnPB(); // get the appropriate vb (build it if needed)
+00328         CVertexBuffer                           &VB = VBnPB.VB;
+00329         CPrimitiveBlock                         &PB = VBnPB.PB;
+00330 
+00331         const uint32                            vertexSize  = VB.getVertexSize();
+00332         uint                                            colorOffset=0;
+00333         const uint32                            vertexSizeX2  = vertexSize << 1;
+00334         const NLMISC::CVector       I = _Owner->computeI();
+00335         const NLMISC::CVector       K = _Owner->computeK();
+00336 
+00337         
+00338         CMatrix mat =  _Owner->isInSystemBasis() ? getViewMat()  *  getSysMat()
+00339                                                                                                                                   : getViewMat();
+00340         IDriver *drv = this->getDriver();
+00341         setupDriverModelMatrix();
+00342         drv->activeVertexBuffer(VB);
+00343         _Owner->incrementNbDrawnParticles(nbRibbons); // for benchmark purpose  
+00344         
+00345         const uint numRibbonBatch = getNumRibbonsInVB(); // number of ribons to process at once
+00346         
+00347 
+00348         static TRibbonVect                                 currRibbon;
+00349         static std::vector<float>                  sizes;
+00350         static std::vector<NLMISC::CRGBA>  colors;
+00351 
+00352 
+00353         
+00354         
+00355 
+00356         if (_UsedNbSegs == 0) return;
+00357 
+00358         currRibbon.resize(_UsedNbSegs + 1);
+00359         sizes.resize(numRibbonBatch);
+00360 
+00361 
+00363         CParticleSystem &ps = *(_Owner->getOwner());
+00364         if (ps.getColorAttenuationScheme() != NULL)
+00365         {               
+00366                 CPSMaterial::forceModulateConstantColor(true, ps.getGlobalColor());             
+00367         }
+00368         else
+00369         {       
+00370                 forceModulateConstantColor(false);
+00371                 _Mat.setColor(ps.getGlobalColor());
+00372         }
+00373 
+00374         if (_ColorScheme)
+00375         {
+00376                 colorOffset = VB.getColorOff();
+00377                 colors.resize(numRibbonBatch);          
+00378         }
+00379         
+00380         
+00381         uint toProcess;
+00382         uint ribbonIndex = 0; // index of the first ribbon in the batch being processed
+00383         
+00384         uint32 fpRibbonIndex = 0;
+00385 
+00386         do
+00387         {
+00388                 toProcess = std::min((uint) (nbRibbons - ribbonIndex) /* = left to do */, numRibbonBatch);
+00389         
+00390 
+00392                 const float     *ptCurrSize;
+00393                 uint32  ptCurrSizeIncrement;
+00394                 if (_SizeScheme)
+00395                 {                       
+00396                         ptCurrSize = (float *) _SizeScheme->make(this->_Owner, ribbonIndex, &sizes[0], sizeof(float), toProcess, true, srcStep);                        
+00397                         ptCurrSizeIncrement = 1;
+00398                 }
+00399                 else
+00400                 {
+00401                         ptCurrSize = &_ParticleSize;
+00402                         ptCurrSizeIncrement = 0;
+00403                 }
+00404 
+00406                 NLMISC::CRGBA   *ptCurrColor=0;
+00407                 if (_ColorScheme)
+00408                 {
+00409                         colors.resize(nbRibbons);
+00410                         ptCurrColor = (NLMISC::CRGBA *) _ColorScheme->make(this->_Owner, ribbonIndex, &colors[0], sizeof(NLMISC::CRGBA), toProcess, true, srcStep);                     
+00411                 }               
+00412                 
+00413                 currVert = (uint8 *) VB.getVertexCoordPointer();
+00414                 for (uint k = ribbonIndex; k < ribbonIndex + toProcess; ++k)
+00415                 {                       
+00416                         
+00417                         TRibbonVect::iterator rIt = currRibbon.begin(), rItEnd = currRibbon.end(), rItEndMinusOne = rItEnd - 1;
+00418 
+00420                         // interpolate and project points //
+00422                 
+00423                                 if (!_Parametric)
+00424                                 {
+00425 
+00427                                         // INCREMENTAL CASE //
+00429 
+00430                                         // the parent class has a method to get the ribbons positions
+00431                                         computeRibbon((uint) (fpRibbonIndex >> 16), &rIt->Interp, sizeof(CVectInfo));                           
+00432                                         do
+00433                                         {                                       
+00434                                                 MakeProj(rIt->Proj, mat * rIt->Interp); 
+00435                                                 ++rIt;                          
+00436                                         }
+00437                                         while (rIt != rItEnd);                  
+00438                                 }
+00439                                 else
+00440                                 {
+00442                                         // PARAMETRIC  CASE //
+00444                                         // we compute each pos thanks to the parametric curve                           
+00445                                         _Owner->integrateSingle(date - _UsedSegDuration * (_UsedNbSegs + 1), _UsedSegDuration, _UsedNbSegs + 1, (uint) (fpRibbonIndex >> 16),
+00446                                                                                          &rIt->Interp, sizeof(CVectInfo) );                             
+00447                                         // project each position now
+00448                                         do
+00449                                         {                                       
+00450                                                 MakeProj(rIt->Proj, mat * rIt->Interp); 
+00451                                                 ++rIt;                          
+00452                                         }
+00453                                         while (rIt != rItEnd);                  
+00454                                 }
+00455 
+00456                                 rIt = currRibbon.begin();
+00457 
+00458                 
+00459                                 // setup colors
+00460                                 if (_ColorScheme)
+00461                                 {
+00462                                         uint8 *currColVertex = currVert + colorOffset;
+00463                                         uint colCount = (_UsedNbSegs + 1) << 1;
+00464                                         do
+00465                                         {
+00466                                                 * (CRGBA *) currColVertex = *ptCurrColor;
+00467                                                 currColVertex += vertexSize;
+00468                                         }
+00469                                         while (--colCount);
+00470 
+00471                                         ++ptCurrColor;                  
+00472                                 }
+00473 
+00475                                 // deals with first point                               
+00476                                 BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt, rIt, rIt + 1, *ptCurrSize);                                
+00477                                 currVert += vertexSizeX2;               
+00478                                 ++rIt;
+00479 
+00480                                 
+00481                                 // deals with other points                              
+00482                                 for (;;) // we assume at least 2 segments, so we must have a middle point               
+00483                                 {                                               
+00484                                         // build 2 vertices with the right tangent. /* to project 2 */ is old projected point
+00485                                         BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt, rIt - 1, rIt + 1, *ptCurrSize); 
+00486                                         // next position                
+00487                                         ++rIt;
+00488                                         if (rIt == rItEndMinusOne) break;                                       
+00489                                         // next vertex                  
+00490                                         currVert += vertexSizeX2;               
+00491                                 }
+00492                                 currVert += vertexSizeX2;
+00493                                 // last point.
+00494                                 BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt , rIt - 1, rIt, *ptCurrSize);                                                                               
+00495                                 ptCurrSize += ptCurrSizeIncrement;
+00496                                 currVert += vertexSizeX2;
+00497 
+00498                                 fpRibbonIndex += srcStep;
+00499                 }
+00500 
+00501                 PB.setNumTri((_UsedNbSegs << 1) * toProcess);
+00502                 // display the result
+00503                 drv->render(PB, _Mat);
+00504                 ribbonIndex += toProcess;               
+00505         }
+00506         while (ribbonIndex != nbRibbons);
+00507 }       
+00508 
+00509 //==========================================================================    
+00510 bool CPSRibbonLookAt::hasTransparentFaces(void)
+00511 {
+00512         return getBlendingMode() != CPSMaterial::alphaTest ;
+00513 }
+00514 
+00515 
+00516 //==========================================================================    
+00517 bool CPSRibbonLookAt::hasOpaqueFaces(void)
+00518 {
+00519         return !hasTransparentFaces();
+00520 }
+00521 
+00522 //==========================================================================    
+00523 uint32 CPSRibbonLookAt::getMaxNumFaces(void) const
+00524 {
+00525         nlassert(_Owner);
+00526         return _Owner->getMaxSize() * _NbSegs * 2;      
+00527 }
+00528 
+00529 
+00530 
+00531 //==========================================================================    
+00532 CPSRibbonLookAt::CVBnPB &CPSRibbonLookAt::getVBnPB()
+00533 {
+00534         TVBMap &map = _ColorScheme ? _VBMap : _ColoredVBMap;    
+00535         TVBMap::iterator it = map.find(_UsedNbSegs + 1);
+00536         if (it != map.end())
+00537         {
+00538                 return it->second;
+00539         }
+00540         else    // must create this vb
+00541         {
+00542                 const uint numRibbonInVB = getNumRibbonsInVB();
+00543                 CVBnPB &VBnPB = map[_UsedNbSegs + 1]; // make an entry
+00544 
+00546                 CVertexBuffer &vb = VBnPB.VB;
+00547                 vb.setVertexFormat(CVertexBuffer::PositionFlag |
+00548                                                    CVertexBuffer::TexCoord0Flag | 
+00549                                                    (_ColorScheme ? CVertexBuffer::PrimaryColorFlag : 0));
+00550                 vb.setNumVertices(2 * (_UsedNbSegs + 1) * numRibbonInVB );
+00551 
+00552                 // set the primitive block size
+00553                 CPrimitiveBlock &pb = VBnPB.PB;
+00554                 pb.setNumTri((_UsedNbSegs << 1) * numRibbonInVB);
+00556                 uint vbIndex = 0;
+00557                 uint pbIndex = 0; 
+00558                 for (uint i = 0; i < numRibbonInVB; ++i)
+00559                 {
+00560                         for (uint k = 0; k < (_UsedNbSegs + 1); ++k)
+00561                         {                               
+00562                                 vb.setTexCoord(vbIndex, 0, CUV((1.f - k / (float) _UsedNbSegs), 0)); 
+00563                                 vb.setTexCoord(vbIndex + 1, 0, CUV((1.f - k / (float) _UsedNbSegs), 1)); 
+00564                                 if (k != _UsedNbSegs)
+00565                                 {
+00567                                         pb.setTri(pbIndex ++, vbIndex + 1, vbIndex + 2, vbIndex);
+00568                                         pb.setTri(pbIndex ++, vbIndex + 1, vbIndex + 3, vbIndex + 2);
+00569                                 }
+00570                                 vbIndex += 2;
+00571                         }
+00572                 }
+00573                 return VBnPB;
+00574         }
+00575 }
+00576 
+00577 //==========================================================================    
+00578 uint    CPSRibbonLookAt::getNumRibbonsInVB() const
+00579 {
+00581         const uint vertexInVB = 256;    
+00582         return std::max(1u, (uint) (vertexInVB / (_UsedNbSegs + 1)));
+00583 }
+00584 
+00585 } // NL3D
+
+ + +
                                                                                                                                                                    +
+ + -- cgit v1.2.1