// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/zone_lighter.h" #include "nel/3d/landscape.h" #include "nel/3d/patchuv_locator.h" #include "nel/3d/shape.h" #include "nel/3d/mesh.h" #include "nel/3d/mesh_multi_lod.h" #include "nel/3d/mesh_mrm.h" #include "nel/3d/transform_shape.h" #include "nel/3d/water_shape.h" #include "nel/3d/texture_file.h" #include "nel/misc/common.h" #include "nel/misc/thread.h" #include "nel/misc/path.h" #include "nel/misc/file.h" #include "nel/misc/aabbox.h" #include "nel/misc/algo.h" #ifdef NL_OS_WINDOWS # define WIN32_LEAN_AND_MEAN # define NOMINMAX # include # include #endif // NL_OS_WINDOWS using namespace NLMISC; using namespace NL3D; using namespace std; // Define this to render the zbuffers into a bitmap zbuffer and save it into a jpeg // #define SAVE_ZBUFFER "c:/temp" #define DEFAULT_JITTER 0.4f #define DEFAULT_ZBUFFER_LANDSCAPE_SIZE 32768 #define DEFAULT_ZBUFFER_OBJECT_SIZE (32768*3) #define DEFAULT_SUN_DISTANCE 50000 #define DEFAULT_SUN_FOV (NLMISC::Pi/6) #define DEFAULT_SUN_CENTER (CVector (0, 0, 0)) #define DEFAULT_SUN_RADIUS 5000 #define DEFAULT_SUN_SRQT_SAMPLES 4 // Bad coded: don't set too big else it allocates too much memory. #define NL3D_ZONE_LIGHTER_CUBE_GRID_SIZE 16 // *************************************************************************** /* Documentation: To light a zone, you must first adding shadow caster triangles using the addTriangle() methods. Triangles can come from landscape zones or IG meshes. Then call the lighting process with ligth(). addTriangle () - Add landscape triangles to shadow caster triangle list - Tesselate the landscape to shadow accuracy (2 meters) - Add others triangles to shadow caster triangle list (trees, building) - AlphaTest textures can be used here light () The lighting process uses a software zbuffers render to compute shadow attenuation of the zone. CRenderZBuffer () (multithread) - Render shadow caster triangles into the light zbuffers for shadows. Each z value is tested and written in the pixel and in the 8 neighbor pixels. - There is a zbuffer per landscape softshadow sample and an additionnal zbuffer for objects. So landscape triangles cast softshadows and object (trees and building) cast antialiased shadows. - Render shadow caster triangles into a heightfield used for radiosity buildZoneInformation () - Tesselate the landscape to shadow accuracy (2 meters) - Compute lumel positions. Lumel position is the average of lumel triangles center overlapping the lumel but using the shadow accuracy triangle position because we need the same triangles than shadow caster polygons. Border lumel position are extended to fit the patch border. - Tesselate to lumel accuracy (0.5 meter) - Compute lumel normal - Lumel normal is the average of lumel triangles normals. The normals comes from the lumel accuracy to get more precise lighting. So normals are interpolated from the center of the lumel but they will be rendered on the patch border. Unlike the position, we can extand the normal to the border without loosing normal precision because normal interpolation is aligned with the tesselation. So we need some border normal smoothing. - Border normal smoothing - Normals on the border of the patches are smoothed with neighbor normals. CLightRunnable () (multithread) - For each patches and for each lumels attenuation () - Compute shadow attenuation - Get an antialised attenuation value in each lansdcape softshadow zbuffer. Average it with jitter. - Get an antialised attenuation value from the object zbuffer. - Return the smaller value of the both. - Compute sun lighting (dot product) getSkyContribution () - Compute sky lighting (radiosity) - Algorithm - Get the lumel position in the heightfield - Lookup in the 8 2d directions for max height - Compute an approximation of the sky surface visible from the lumel position - Store final lumel luminosity */ // *************************************************************************** inline float easineasout(float x) { float y; // cubic tq f(0)=0, f'(0)=0, f(1)=1, f'(1)=0. float x2=x*x; float x3=x2*x; y= -2*x3 + 3*x2; return y; } // *************************************************************************** inline void transformVectorToZBuffer (const CZoneLighter::CZBuffer& zbuffer, const CVector &world, CVector &projected) { projected = zbuffer.WorldToZBuffer * world; float temp = projected.z; projected.z = projected.y; projected.y = -temp; projected = zbuffer.WorldToZBufferFrustum.project (projected); // Scale to zbuffer size projected.x *= zbuffer.ZBufferPixelSize; projected.y *= zbuffer.ZBufferPixelSize; projected.z = temp; } // *********************************************************** static const sint DeltaZ[9][2]= { {0, 0}, {-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, 1}, {1, -1}, {-1, 1}, }; // *************************************************************************** inline float testZPercentageCloserFilter (float x, float y, float z, CZoneLighter::CZBuffer &zbuffer, const CZoneLighter::CLightDesc &description, bool &zBufferOverflowFlag) { // See "Rendering Antialiased Shadows With Depth Maps" Reeves, Salesint, Cook, ACM 1987 // Bilinear filtering float biliValues[2][2]; float ix = (float)floor (x-0.5f); float factorX = x - (ix+0.5f); nlassert (factorX>=0); nlassert (factorX<=1); float iy = (float)floor (y-0.5f); float factorY = y - (iy+0.5f); nlassert (factorY>=0); nlassert (factorY<=1); sint dx, dy; for (dy=0; dy<2; dy++) for (dx=0; dx<2; dx++) { const sint fx = dx + (sint)ix; const sint fy = dy + (sint)iy; if ((fx >= 0) && (fx < zbuffer.LocalZBufferWidth) && (fy >= 0) && (fy < zbuffer.LocalZBufferHeight)) { const float zRed = zbuffer.Pixels[fx + (zbuffer.LocalZBufferHeight - 1 - fy) * zbuffer.LocalZBufferWidth]; biliValues[dx][dy] = (zRed < (-z)) ? 0.f : 1.f; } else { biliValues[dx][dy] = 1; zBufferOverflowFlag = true; } } // Bilinear return (biliValues[0][0] * (1 - factorX) + biliValues[1][0] * factorX) * (1 - factorY) + (biliValues[0][1] * (1 - factorX) + biliValues[1][1] * factorX) * factorY; } // *************************************************************************** CZoneLighter::CZoneLighter () : _PatchComputed ("PatchComputed") { } // *************************************************************************** void CZoneLighter::init () { // Precalc some values for (uint i=0; i<8; i++) { // Precalc sinP and cosP float sinP=(float)(sin((Pi/4)*(i+0.5))-sin((Pi/4)*(i-0.5))); float cosP=(float)(cos((Pi/4)*(i-0.5))-cos((Pi/4)*(i+0.5))); for (uint phi=0; phi<256; phi++) { // Real phi float fPhi=(float)((Pi/2)*phi/256.0); // Tmp result float tmp0=(float)(fPhi-sin(2*fPhi)/2); float tmp1=(float)sin(fPhi); // Calc K _K[phi][i].set (tmp0*sinP, tmp0*cosP, (float)((Pi/4)*tmp1*tmp1)); } } // Init some containers _ZBufferOverflow = false; _Bitmaps.clear (); } // *************************************************************************** // N - NW - W - SW - S - SE - E - NE static const sint deltaDirection[8][2]= { {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}, }; // *************************************************************************** float CZoneLighter::calcSkyContribution (sint s, sint t, float height, float skyIntensity, const CVector& normal) const { // Sky contribution float skyContribution; // Calc k CVector k (0, 0, 0); // For the height direction for (uint i=0; i<8; i++) { // Get phi for this point uint8 phi=getMaxPhi (s, t, deltaDirection[i][0], deltaDirection[i][1], height); // Add to k k+=_K[phi][i]; } // Finalize sky contribution skyContribution=(float)(skyIntensity*(normal*k)/(2*Pi)); skyContribution=(float)(skyIntensity*(normal*k)/(2*Pi)); clamp (skyContribution, 0.f, 1.f); return skyContribution; } // *************************************************************************** void NEL3DCalcBase (CVector &direction, CMatrix& matrix) { direction.normalize(); CVector K=-direction; CVector I=CVector::K^K; CVector J=K^I; J.normalize(); I=J^K; I.normalize(); matrix.identity(); matrix.setRot(I,J,K, true); } // *************************************************************************** void setCPUMask (IThread *thread, uint process) { // Set the processor mask uint64 mask = IProcess::getCurrentProcess()->getCPUMask (); // Mask must not be NULL nlassert (mask != 0); if (mask != 0) { uint i=0; uint count = 0; for(;;) { if (mask & (UINT64_CONSTANT(1)<setCPUMask (1<processCalc (_Process, *_Description); _ZoneLighter->_ProcessExited++; } }; // *************************************************************************** class NL3D::CRenderZBuffer : public IRunnable { // Members uint _Process; CZoneLighter *_ZoneLighter; // The lighting decription const CZoneLighter::CLightDesc *_Description; // Triangles to render uint _FirstTriangle; uint _NumTriangle; const vector *_Triangles; public: IThread *Thread; public: // Ctor CRenderZBuffer (uint process, CZoneLighter *zoneLighter, const CZoneLighter::CLightDesc *description, uint firstTriangle, uint numTriangle, const vector *triangles) { _ZoneLighter = zoneLighter; _Description = description; _Process = process; _FirstTriangle = firstTriangle; _NumTriangle = numTriangle; _Triangles = triangles; } // Run method virtual void run (); }; // *************************************************************************** #define CLIPPED_TOP 1 #define CLIPPED_BOTTOM 2 #define CLIPPED_RIGHT 3 #define CLIPPED_LEFT 4 #define CLIPPED_ALL (CLIPPED_TOP|CLIPPED_BOTTOM|CLIPPED_LEFT|CLIPPED_RIGHT) void RenderTriangle (const CZoneLighter::CTriangle &triangle, const CZoneLighter::CLightDesc &description, CPolygon2D::TRasterVect &borders, CFastMutex &mutex, CZoneLighter::CZBuffer &zbuffer, uint radius) { // *** Transform it in the zbuffer basis // 2d polygon used for rasteriation CPolygon2D zBasis; zBasis.Vertices.resize (3); // 3d polygon used for the gradient NLMISC::CTriangle gradientTriangle; // One over z value float ooz[3]; // Clipping uint8 in = 0; // For each vertex for (uint j=0; j<3; j++) { // Pointer on the vector const CVector *pt = (&triangle.Triangle.V0)+j; CVector *ptDest = (&gradientTriangle.V0)+j; // Transform it in the zbuffer basis transformVectorToZBuffer (zbuffer, *pt, *ptDest); // Clip if (ptDest->x >= zbuffer.LocalZBufferXMin) in |= CLIPPED_LEFT; if (ptDest->x <= zbuffer.LocalZBufferXMax) in |= CLIPPED_RIGHT; if (ptDest->y >= zbuffer.LocalZBufferYMin) in |= CLIPPED_TOP; if (ptDest->y <= zbuffer.LocalZBufferYMax) in |= CLIPPED_BOTTOM; // Set the 2d points zBasis.Vertices[j].x = ptDest->x - (float)zbuffer.LocalZBufferXMin; zBasis.Vertices[j].y = ptDest->y - (float)zbuffer.LocalZBufferYMin; ooz[j] = 1.f / ptDest->z; // No z ptDest->z = 0; } // Not clipped ? if (in == CLIPPED_ALL) { // Rasterise sint minimumY; borders.clear (); zBasis.computeBorders (borders, minimumY); // Compute the gradient for one over z CVector ozzGradient; gradientTriangle.computeGradient (ooz[0], ooz[1], ooz[2], ozzGradient); // Need uv ? bool needUV = triangle.Texture != NULL; // Compute the gradient for uv CVector uGradient; CVector vGradient; if (needUV) { gradientTriangle.computeGradient (triangle.U[0], triangle.U[1], triangle.U[2], uGradient); gradientTriangle.computeGradient (triangle.V[0], triangle.V[1], triangle.V[2], vGradient); } // Texture information uint width=0; uint height=0; const CObjectVector *pixels = 0; if (needUV) { // Get pixels pixels = &triangle.Texture->getPixels (); // Get width and height width = triangle.Texture->getWidth (); height = triangle.Texture->getHeight (); } // For each scanlines sint y = std::max (minimumY, 0); sint yMax = std::min ((sint)(minimumY+borders.size ()), zbuffer.LocalZBufferHeight); for (; yA; // Alpha test alphaTest = alpha >= triangle.AlphaTestThreshold; } // Good alpha test ? if (alphaTest) { // Enter the mutex mutex.enter (); // Write Z around uint d; for (d=0; d= 0) && (fx < zbuffer.LocalZBufferWidth) && (fy >= 0) && (fy < zbuffer.LocalZBufferHeight) ) { float &zValue = zbuffer.Pixels[fx+(zbuffer.LocalZBufferHeight-fy-1)*zbuffer.LocalZBufferWidth]; // Z test if (z < zValue) { // Render z in zbuffer zValue = z; } } } // Leave the mutex mutex.leave (); } } } } } void NL3D::CRenderZBuffer::run() { // Set the CPU mask setCPUMask (Thread, _Process); // Span array CPolygon2D::TRasterVect borders; // For each triangles uint i; for (i=_FirstTriangle; i<_FirstTriangle+_NumTriangle; i++) { // Triangle reference const CZoneLighter::CTriangle &triangle = (*_Triangles)[i]; // Keep backface and doublesided polygons if ((triangle.Flags & CZoneLighter::CTriangle::DoubleSided) || ((triangle.getPlane ().getNormal() * _ZoneLighter->_SunDirection) > 0)) { // Landscape triangle ? if (triangle.Flags & CZoneLighter::CTriangle::Landscape) { // For each landscape zbuffer uint sample; const uint samples = _Description->SoftShadowSamplesSqrt*_Description->SoftShadowSamplesSqrt; for (sample=0; sample_Mutex, _ZoneLighter->_ZBufferLandscape[sample], 9); } } else { // Render in a high resolution zbuffer RenderTriangle (triangle, *_Description, borders, _ZoneLighter->_Mutex, _ZoneLighter->_ZBufferObject, 1); } } _ZoneLighter->_NumberOfPatchComputed++; } // Exit _ZoneLighter->_ProcessExited++; } // *************************************************************************** class NL3D::CCalcLightableShapeRunnable : public IRunnable { public: CCalcLightableShapeRunnable(uint process, CZoneLighter *zoneLighter, const CZoneLighter::CLightDesc *description, CZoneLighter::TShapeVect *shapeToLit, uint firstShape, uint lastShape ) : _ZoneLighter(zoneLighter), _Description(description), _ShapesToLit(shapeToLit), _FirstShape(firstShape), _LastShape(lastShape), _Process(process) { } void run() { _ZoneLighter->processLightableShapeCalc(_Process, _ShapesToLit, _FirstShape, _LastShape, *_Description); _ZoneLighter->_ProcessExited++; } private: CZoneLighter *_ZoneLighter; const CZoneLighter::CLightDesc *_Description; CZoneLighter::TShapeVect *_ShapesToLit; uint _FirstShape, _LastShape; uint _Process; }; // *************************************************************************** void draw2dLine (CBitmap &bitmap, float x0, float y0, float x1, float y1, const CRGBA &color) { static vector< std::pair > lines; drawFullLine (x0, y0, x1, y1, lines); // Bitmap pixels CRGBA *pixels = (CRGBA*)&(bitmap.getPixels ()[0]); // Bitmap size sint width = (sint)bitmap.getWidth (); sint height = (sint)bitmap.getHeight (); // Draw the line uint i; for (i=0; i= 0) && (x < width) && (y >= 0) && (y < height) ) { pixels[x+(height-y-1)*width] = color; } } } // *************************************************************************** void InitZBuffer (CZoneLighter::CZBuffer &zbuffer, const CVector &SunPosition, const CMatrix &rayBasis, const CAABBoxExt &zoneBB, uint zBufferPixelSize, const CZoneLighter::CLightDesc& description) { // Clac the zbuffer world size const float zBufferWorldSize = (float)(tan (description.SunFOV/2)*description.SunDistance*2); // ** Compute the zbuffer basis zbuffer.WorldToZBuffer.identity (); zbuffer.WorldToZBuffer = rayBasis; zbuffer.WorldToZBuffer.setPos (SunPosition); zbuffer.WorldToZBuffer.invert (); zbuffer.WorldToZBufferFrustum.init ((float)zBufferWorldSize, (float)zBufferWorldSize, description.SunDistance, description.SunDistance*2); // Zbuffer size zbuffer.ZBufferPixelSize = zBufferPixelSize; // Evaluate the size of the local zbuffer // The zone bounding box CVector bMin = zoneBB.getMin (); CVector bMax = zoneBB.getMax (); transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMax.y, bMin.z), zbuffer.BoundingBoxVectors[0]); transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMin.y, bMin.z), zbuffer.BoundingBoxVectors[1]); transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMin.y, bMin.z), zbuffer.BoundingBoxVectors[2]); transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMax.y, bMin.z), zbuffer.BoundingBoxVectors[3]); transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMax.y, bMax.z), zbuffer.BoundingBoxVectors[4]); transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMin.y, bMax.z), zbuffer.BoundingBoxVectors[5]); transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMin.y, bMax.z), zbuffer.BoundingBoxVectors[6]); transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMax.y, bMax.z), zbuffer.BoundingBoxVectors[7]); // Get the min and max zbuffer.LocalZBufferXMin = 0x7fffffff; zbuffer.LocalZBufferYMin = 0x7fffffff; zbuffer.LocalZBufferXMax = 0x80000000; zbuffer.LocalZBufferYMax = 0x80000000; zbuffer.LocalZBufferZMin = FLT_MAX; zbuffer.LocalZBufferZMax = -FLT_MAX; uint j; for (j=0; j<8; j++) { sint minX = (sint)floor (zbuffer.BoundingBoxVectors[j].x); sint maxX = (sint)ceil (zbuffer.BoundingBoxVectors[j].x); sint minY = (sint)floor (zbuffer.BoundingBoxVectors[j].y); sint maxY = (sint)ceil (zbuffer.BoundingBoxVectors[j].y); if (minXzbuffer.LocalZBufferXMax) zbuffer.LocalZBufferXMax = maxX; if (minYzbuffer.LocalZBufferYMax) zbuffer.LocalZBufferYMax = maxY; if ((-zbuffer.BoundingBoxVectors[j].z)zbuffer.LocalZBufferZMax) zbuffer.LocalZBufferZMax = -zbuffer.BoundingBoxVectors[j].z; } // Expand the zbuffer zbuffer.LocalZBufferXMax++; zbuffer.LocalZBufferXMin--; zbuffer.LocalZBufferYMax++; zbuffer.LocalZBufferYMin--; zbuffer.LocalZBufferWidth = zbuffer.LocalZBufferXMax-zbuffer.LocalZBufferXMin; zbuffer.LocalZBufferHeight = zbuffer.LocalZBufferYMax-zbuffer.LocalZBufferYMin; // Resize and clear the zbuffer zbuffer.Pixels.resize (0); zbuffer.Pixels.resize (zbuffer.LocalZBufferWidth*zbuffer.LocalZBufferHeight, FLT_MAX); } // *************************************************************************** #ifdef SAVE_ZBUFFER void SaveZBuffer (CZoneLighter::CZBuffer &zbuffer, const char *filename) { // Resize the bitmap CBitmap bitmap; bitmap.resize (zbuffer.LocalZBufferWidth, zbuffer.LocalZBufferHeight, CBitmap::Luminance); // Get pixels CObjectVector &pixels = bitmap.getPixels (); // Draw it uint samples = zbuffer.LocalZBufferWidth*zbuffer.LocalZBufferHeight; for (uint i=0; i tempPixels; tempPixels = zbuffer.Pixels; sint x, y; for (y=0; y=0) && (fx < zbuffer.LocalZBufferWidth) && (fy>=0) && (fy < zbuffer.LocalZBufferHeight) ) { const float &testValue = zbuffer.Pixels[fx+fy*zbuffer.LocalZBufferWidth]; if (testValue < newValue) newValue = testValue; } } } // Copy the new zbuffer zbuffer.Pixels = tempPixels; } // *************************************************************************** void CZoneLighter::light (CLandscape &landscape, CZone& output, uint zoneToLight, const CLightDesc& description, std::vector& obstacles, vector &listZone) { /* * Lighting algorithm * ------------------ * * - Create a quad grid to store shadow casting triangles * - Create a heightfield used for global illumination. Cells are initialized with -FLT_MAX * - Insert each shadow casting triangles in the quad grid and fill the heightfield's cells overlapped by the bounding box of the triangle with * the max height of the triangle if its height is > than the current height in the heightfield's cell. * - */ // Backup thread mask IThread *currentThread = IThread::getCurrentThread (); uint64 threadMask = currentThread->getCPUMask(); currentThread->setCPUMask (1); // Calc the ray basis _SunDirection=description.SunDirection; NEL3DCalcBase (_SunDirection, _RayBasis); // Zone to light _ZoneToLight=zoneToLight; // Landscape _Landscape=&landscape; // Process count _ProcessCount=description.NumCPU; if (_ProcessCount==0) { // Create a doomy thread IProcess *pProcess=IProcess::getCurrentProcess (); _CPUMask = pProcess->getCPUMask(); _ProcessCount = 0; uint64 i; for (i=0; i<64; i++) { if (_CPUMask&((uint64)1<MAX_CPU_PROCESS) _ProcessCount=MAX_CPU_PROCESS; // Number of obstacle polygones nlinfo ("Obstacle polygones : %u", (uint)obstacles.size ()); // Number of CPUS used nlinfo ("Number of CPU used: %u", _ProcessCount); // Zone pointer CZone *pZone=landscape.getZone (_ZoneToLight); if (pZone) { // *** Compute center of the object // Get the zone bounding box const CAABBoxExt &zoneBB=pZone->getZoneBB(); // Get the center CVector center = zoneBB.getCenter (); // *** Compute planes const uint size=(uint)obstacles.size(); uint triangleId; for (triangleId=0; triangleIdobstacles.size ()) lastTriangle=(uint)obstacles.size (); // Create a thread CRenderZBuffer *runnable = new CRenderZBuffer (process, this, &description, firstTriangle, lastTriangle - firstTriangle, &obstacles); IThread *pThread=IThread::create (runnable); runnable->Thread = pThread; // New first patch firstTriangle = lastTriangle; // Launch pThread->start(); } // Wait for others processes while (_ProcessExited!=_ProcessCount) { nlSleep (1000); // Call the progress callback progress ("Render triangles", (float)_NumberOfPatchComputed/(float)obstacles.size()); } // * Save the zbuffer uint sample; const uint samples = description.SoftShadowSamplesSqrt*description.SoftShadowSamplesSqrt; #ifdef SAVE_ZBUFFER for (sample=0; sample_HeightField[x+y*_HeightFieldCellCount]) { // New height in this cell _HeightField[x+y*_HeightFieldCellCount]=maxv.z; } } } } // Retrieve the zone to fill its shaded value pZone->retrieve (_PatchInfo, _BorderVertices); // Number of patch uint patchCount=(uint)_PatchInfo.size(); // Bit array to know if the lumel is shadowed if (description.Shadow) _ShadowArray.resize (patchCount); // A lumel vector by patch vector > lumels; lumels.resize (patchCount); // Build zone information buildZoneInformation (landscape, listZone, description); } // Number of patch uint patchCount=(uint)_PatchInfo.size(); // Reset patch count { CSynchronized >::CAccessor access (&_PatchComputed); access.value().resize (0); access.value().resize (patchCount, false); } // Patch by thread uint patchCountByThread = patchCount/_ProcessCount; patchCountByThread++; // Patch to allocate uint firstPatch=0; _NumberOfPatchComputed = 0; // Reset exited process _ProcessExited=0; // Set the thread state _LastPatchComputed.resize (_ProcessCount); // Launch threads uint process; for (process=0; process<_ProcessCount; process++) { // Last patch uint lastPatch=firstPatch+patchCountByThread; lastPatch %= patchCount; // Last patch computed _LastPatchComputed[process] = firstPatch; // Create a thread CLightRunnable *runnable = new CLightRunnable (process, this, &description); IThread *pThread=IThread::create (runnable); runnable->Thread = pThread; // New first patch firstPatch=lastPatch; // Launch pThread->start(); } // Wait for others processes while (_ProcessExited!=_ProcessCount) { nlSleep (1000); // Call the progress callback progress ("Lighting patches", (float)_NumberOfPatchComputed/(float)_PatchInfo.size()); } // Reset old thread mask currentThread->setCPUMask (threadMask); // overflow ? if (_ZBufferOverflow) nlwarning ("Error : zbuffer overflow"); // Progress bar progress ("Compute Influences of PointLights", 0.f); // Compute PointLight influences on zone. // Some precalc. compilePointLightRT(description.GridSize, description.GridCellSize, obstacles, description.Shadow); // Influence patchs and get light list of interest std::vector listPointLight; processZonePointLightRT(listPointLight); // Rebuild the zone // Progress bar progress ("Compress the lightmap", 0.6f); // Build, with list of lights. CZoneInfo zinfo; zinfo.ZoneId= _ZoneToLight; zinfo.Patchs= _PatchInfo; zinfo.BorderVertices= _BorderVertices; zinfo.PointLights= listPointLight; output.build (zinfo); /// copy the tiles flags from the zone to light to the output zone copyTileFlags(output, *(landscape.getZone(zoneToLight))); /// Perform lightning of some ig's of the current zone (if any) lightShapes(zoneToLight, description); } // ************************************************************************************* void CZoneLighter::copyTileFlags(CZone &destZone, const CZone &srcZone) { nlassert(destZone.getZoneId() == srcZone.getZoneId()); nlassert(destZone.getNumPatchs() == srcZone.getNumPatchs()); for (sint k = 0; k < srcZone.getNumPatchs(); ++k) { destZone.copyTilesFlags(k, srcZone.getPatch(k)); } } // *************************************************************************** float CZoneLighter::getSkyContribution(const CVector &pos, const CVector &normal, float skyIntensity) const { float s=(pos.x-_OrigineHeightField.x)/_HeightfieldCellSize; float t=(pos.y-_OrigineHeightField.y)/_HeightfieldCellSize; sint sInt=(sint)(floor (s+0.5f)); sint tInt=(sint)(floor (t+0.5f)); // Bilinear float skyContributionTab[2][2]; skyContributionTab[0][0] = calcSkyContribution (sInt-1, tInt-1, pos.z, skyIntensity, normal); skyContributionTab[1][0] = calcSkyContribution (sInt, tInt-1, pos.z, skyIntensity, normal); skyContributionTab[1][1] = calcSkyContribution (sInt, tInt, pos.z, skyIntensity, normal); skyContributionTab[0][1] = calcSkyContribution (sInt-1, tInt, pos.z, skyIntensity, normal); float sFact=s+0.5f-sInt; float tFact=t+0.5f-tInt; return (skyContributionTab[0][0]*(1.f-sFact) + skyContributionTab[1][0]*sFact)*(1.f-tFact) + (skyContributionTab[0][1]*(1.f-sFact) + skyContributionTab[1][1]*sFact)*tFact; } // *************************************************************************** void CZoneLighter::processCalc (uint process, const CLightDesc& description) { // *** Raytrace each patches // Get a patch uint patch = getAPatch (process); while (patch != 0xffffffff) { // For each patch if (description.Shadow) { // Lumels std::vector &lumels=_Lumels[patch]; // Lumel count uint lumelCount=(uint)lumels.size(); CPatchInfo &patchInfo=_PatchInfo[patch]; nlassert (patchInfo.Lumels.size()==lumelCount); // Resize shadow array _ShadowArray[patch].resize (lumelCount); // For each lumel for (uint lumel=0; lumel &lumels=_Lumels[patch]; // Lumel count uint lumelCount=(uint)lumels.size(); CPatchInfo &patchInfo=_PatchInfo[patch]; nlassert (patchInfo.Lumels.size()==lumelCount); // For each lumel for (uint lumel=0; lumel &lumels=_Lumels[patch]; // Go for light each lumel for (uint lumel=0; lumel=0)&&(t>=0)) { // Get height float height=_HeightField[s+t*_HeightFieldCellCount]; height-=heightPos; // Better ? if (height>maxHeight) { // Calc sin teta float tanTeta=height/distance; nlassert (tanTeta>=0); // Better ? if (tanTeta>maxTanTeta) { // New max height maxHeight=height; maxTanTeta=tanTeta; } } s+=deltaS; t+=deltaT; distance+=stepDistance; } // return phi float teta=(float)atan (maxTanTeta); nlassert (teta>=0); nlassert (teta<=Pi/2); clamp (teta, 0.f, (float)Pi/2); sint res=(sint)((Pi/2-teta)*256/(Pi/2)); clamp (res, 0, 255); return (uint8)res; } // *************************************************************************** #define AllFront 0 #define AllBack 1 #define Clipped 2 // *************************************************************************** bool CZoneLighter::isLumelOnEdgeMustBeOversample (uint patch, uint edge, sint s, sint t, const vector &binded, const vector &oversampleEdges, vector &locator, uint8 shadowed, vector >& shadowBuffer) { // Must force oversampling of this edge ? if (oversampleEdges[edge]) return true; else { // binded ? if (binded[edge]) { // Lumel coord CVector2f lumelCoord (((float)(s+_GetNormalDeltaS[edge])+0.5f)/4.f, ((float)(t+_GetNormalDeltaT[edge])+0.5f)/4.f); uint otherPatch=locator[edge].selectPatch(lumelCoord); // Get uv CVector2f neighborUV; CPatch *patchOut; locator[edge].locateUV (lumelCoord, otherPatch, patchOut, neighborUV); // Is the same shadowed flag ? sint ss=(sint)(neighborUV.x*4.f); sint tt=(sint)(neighborUV.y*4.f); return (shadowBuffer[patchOut->getPatchId()][ss+(patchOut->getOrderS()<<2)*tt]!=shadowed); } else { // Not oversample if not binded return false; } } } // *************************************************************************** float easineasoutC2(float x) { float y; // 5-nome tq f(0)=0, f'(0)=0, f''(0)=0, f(1)=1, f'(1)=0, f''(1)=0. float x3=x*x*x; float x4=x3*x; float x5=x4*x; y= 6*x5 -15*x4 +10*x3; return y; } // *************************************************************************** sint16 CZoneLighter::_GetNormalDeltaS[4]={ -1, 0, 1, 0 }; sint16 CZoneLighter::_GetNormalDeltaT[4]={ 0, 1, 0, -1 }; // *************************************************************************** void CZoneLighter::getNormal (const CPatch *pPatch, sint16 lumelS, sint16 lumelT, vector &locator, const vector &bindInfo, const vector &binded, set& visited, float deltaS, float deltaT, uint rotation, const CBezierPatch &bezierPatch, uint lastEdge) { // Build a desc srructure uint64 id=(uint64)lumelS|(((uint64)lumelT)<<16)|(((uint64)pPatch->getPatchId())<<32)|(((uint64)pPatch->getZone()->getZoneId())<<48); // Insert it if (visited.insert (id).second) { // Clip float sqDist=deltaS*deltaS+deltaT*deltaT; if ( sqDist < 1 ) { // Continue... sint orderSx4=pPatch->getOrderS()<<2; sint orderTx4=pPatch->getOrderT()<<2; sint16 _GetNormalBorderS[4]={ 0, -10, 1, -10 }; sint16 _GetNormalBorderT[4]={ -10, 1, -10, 0 }; _GetNormalBorderS[2]=orderSx4-1; _GetNormalBorderT[1]=orderTx4-1; // Add normal _GetNormalNormal+=bezierPatch.evalNormal ( ((float)lumelS+0.5f)/(float)orderSx4, ((float)lumelT+0.5f)/(float)orderTx4 ); // For the four neighbors for (uint edge=0; edge<4; edge++) { // Not last edge ? if (edge!=lastEdge) { // Direction uint globalDirection=(edge+(4-rotation))&0x3; // Neighbor if ( (lumelS==_GetNormalBorderS[edge]) || (lumelT==_GetNormalBorderT[edge]) ) { // Binded ? bool bind=binded[edge]; bool smooth=pPatch->getSmoothFlag (edge); if (bind&&smooth) { // Lumel coord CVector2f lumelCoord ( ((float)(lumelS+_GetNormalDeltaS[edge])+0.5f)/4, ((float)(lumelT+_GetNormalDeltaT[edge])+0.5f)/4 ); // Get neighbor pixel uint otherPatch=locator[edge].selectPatch(lumelCoord); // Get uv CVector2f neighborUV; CPatch *patchOut; locator[edge].locateUV (lumelCoord, otherPatch, patchOut, neighborUV); // New coordinates sint16 newLumelS=(sint16)(4.f*neighborUV.x); sint16 newLumelT=(sint16)(4.f*neighborUV.y); // Zone id uint16 patchId=patchOut->getPatchId(); uint16 zoneId=_ZoneId[patchOut->getZone()->getZoneId ()]; // Get edge uint newEdge=0; uint i; for (i=0; i<=(uint)bindInfo[edge].NPatchs; i++) { // Good patch ? if (bindInfo[edge].Next[i]==patchOut) { // Get its edge newEdge=bindInfo[edge].Edge[i]; break; } } // Rotation uint newRotation=(2-edge+rotation+newEdge)&0x3; // Must found it nlassert (i!=(uint)bindInfo[edge].NPatchs); // Get the bezier patch CBezierPatch &NewBezierPatch=_BezierPatch[zoneId][patchId]; // Next lumel getNormal (patchOut, newLumelS, newLumelT, _Locator[zoneId][patchId], _BindInfo[zoneId][patchId], _Binded[zoneId][patchId], visited, deltaS+_GetNormalDeltaS[globalDirection], deltaT+_GetNormalDeltaT[globalDirection], newRotation, NewBezierPatch, newEdge); } } else { // Left internal getNormal (pPatch, lumelS+_GetNormalDeltaS[edge], lumelT+_GetNormalDeltaT[edge], locator, bindInfo, binded, visited, deltaS+_GetNormalDeltaS[globalDirection], deltaT+_GetNormalDeltaT[globalDirection], rotation, bezierPatch, (edge+2)&0x3); } } } } } } // *************************************************************************** void CZoneLighter::addTriangles (CLandscape &landscape, vector &listZone, uint order, std::vector& triangleArray) { // Set all to refine excludeAllPatchFromRefineAll (landscape, listZone, false); // Setup the landscape landscape.setThreshold (0); landscape.setTileMaxSubdivision (order); // Refine it landscape.refineAll (CVector (0, 0, 0)); // Dump tesselated triangles std::vector leaves; landscape.getTessellationLeaves(leaves); // Number of leaves uint leavesCount=(uint)leaves.size(); // Reserve the array triangleArray.reserve (triangleArray.size()+leavesCount); // Scan each leaves for (uint leave=0; leaveVBase->EndPos, face->VLeft->EndPos, face->VRight->EndPos))); } // Setup the landscape landscape.setThreshold (1000); landscape.setTileMaxSubdivision (0); // Remove all triangles landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); landscape.refineAll (CVector (0, 0, 0)); } // *************************************************************************** void CZoneLighter::addTriangles (const IShape &shape, const CMatrix& modelMT, std::vector& triangleArray) { // Cast to CMesh const CMesh *mesh=dynamic_cast(&shape); // Cast to CMeshMultiLod const CMeshMultiLod *meshMulti=dynamic_cast(&shape); // Cast to CMeshMultiLod const CMeshMRM *meshMRM=dynamic_cast(&shape); // It is a mesh ? if (mesh) { // Add its triangles addTriangles (*mesh, mesh->getMeshGeom (), modelMT, triangleArray); } // It is a CMeshMultiLod ? else if (meshMulti) { // Get the first geommesh const IMeshGeom *meshGeom=&meshMulti->getMeshGeom (0); // Dynamic cast const CMeshGeom *geomMesh=dynamic_cast(meshGeom); if (geomMesh) { addTriangles (*meshMulti, *geomMesh, modelMT, triangleArray); } // Dynamic cast const CMeshMRMGeom *mrmGeomMesh=dynamic_cast(meshGeom); if (mrmGeomMesh) { addTriangles (*meshMulti, *mrmGeomMesh, modelMT, triangleArray); } } // It is a CMeshMultiLod ? else if (meshMRM) { // Get the first lod mesh geom addTriangles (*meshMRM, meshMRM->getMeshGeom (), modelMT, triangleArray); } } // *************************************************************************** void CZoneLighter::addTriangles (const CMeshBase &meshBase, const CMeshGeom &meshGeom, const CMatrix& modelMT, std::vector& triangleArray) { // Get the vertex buffer const CVertexBuffer &vb=meshGeom.getVertexBuffer(); CVertexBufferRead vba; vb.lock (vba); // For each matrix block uint numBlock=meshGeom.getNbMatrixBlock(); for (uint block=0; blocksupportSharing ()) { // Share name string name = texture->getShareName(); // Texture exist ? std::map::iterator ite = _Bitmaps.find (name); if (ite != _Bitmaps.end ()) { // Yes result = &(ite->second); } else { // No, add it ite = _Bitmaps.insert (std::map::value_type (name, CBitmap())).first; result = &(ite->second); // Generate the texture texture->generate (); // Convert to RGBA texture->convertToType (CBitmap::RGBA); // Copy it *result = *texture; // Release the texture texture->release (); } // Wrap flags clampU = texture->getWrapS () == ITexture::Clamp; clampV = texture->getWrapT () == ITexture::Clamp; } } // Get double sided flag doubleSided = material.getDoubleSided (); return true; } // *************************************************************************** void CZoneLighter::addTriangles (const CMeshBase &meshBase, const CMeshMRMGeom &meshGeom, const CMatrix& modelMT, std::vector& triangleArray) { // Get the vertex buffer const CVertexBuffer &vb=meshGeom.getVertexBuffer(); CVertexBufferRead vba; vb.lock (vba); // For each render pass uint numRenderPass=meshGeom.getNbRdrPass(0); for (uint pass=0; pass &listZone, bool exclude) { // For each zone for (uint zone=0; zonegetNumPatchs(); // For each patches for (uint patch=0; patchright, 1 for base<->left), tesselation edge vertex 0, tesselation edge vertex 1 const sint8 CZoneLighter::VertexThanCanBeSnappedOnABorder[8][4] = { {0, 0, 0, 9}, {1, 0, 9, 4}, {2, 0, 4, 10}, {3, 0, 10, 1}, {0, 1, 0, 11}, {4, 1, 11, 5}, {7, 1, 5, 12}, {9, 1, 12, 2}, }; // *************************************************************************** // lumel vertex ID, tesselation corner vertex const sint8 CZoneLighter::VertexThanCanBeSnappedOnACorner[3][2] = { {0, 0}, {3, 1}, {9, 2}, }; // *************************************************************************** void CZoneLighter::buildZoneInformation (CLandscape &landscape, const vector &listZone, const CLightDesc &lightDesc) { // Bool visit vector > visited; // Zone count uint zoneCount=(uint)listZone.size(); // Resize arries _Locator.resize (zoneCount); _Binded.resize (zoneCount); _BindInfo.resize (zoneCount); _BezierPatch.resize (zoneCount); // For each zone for (uint zone=0; zonegetNumPatchs(); // Insert zone id _ZoneId.insert (map::value_type (listZone[zone], zone)); // This is the zone to light ? if (listZone[zone]==_ZoneToLight) { // Resize the arraies _Lumels.resize(patchCount); // _LumelCorners.resize(patchCount); // _BezierPatch.resize(patchCount); _OversampleEdges.resize(patchCount); visited.resize(patchCount); } // Common arries _Locator[zone].resize(patchCount); _Binded[zone].resize(patchCount); _BindInfo[zone].resize(patchCount); _BezierPatch[zone].resize(patchCount); // For each patch uint patch; for (patch=0; patch(landscape.getZone(listZone[zone])))->getPatch (patch); // Progress bar progress ("Scan all patches", (float)patch/(float)patchCount); // Get pointer on arries vector &binded=_Binded[zone][patch]; vector &bindInfo=_BindInfo[zone][patch]; vector &locator=_Locator[zone][patch]; CBezierPatch &bezierPatch=_BezierPatch[zone][patch]; binded.resize (4, false); bindInfo.resize (4); locator.resize (4); // Contruct the patch bezierPatch=*pPatch->unpackIntoCache(); // Same zone ? if (listZone[zone]==_ZoneToLight) { // oversample this edge _OversampleEdges[patch].resize (4, false); } // *** Build bind info // *** Build neighboorhood information uint edge; for (edge=0; edge<4; edge++) { // Bond neighbor pPatch->getBindNeighbor (edge, bindInfo[edge]); // Patch binded if (bindInfo[edge].NPatchs>0) { // This edeg is binded binded[edge]=true; // Same zone ? if ((listZone[zone]==_ZoneToLight)&&(bindInfo[edge].Zone->getZoneId()!=_ZoneToLight)) { // oversample this edge _OversampleEdges[patch][edge]=true; } locator[edge].build (pPatch, edge, bindInfo[edge]); } else { if (listZone[zone]==_ZoneToLight) { // oversample this edge _OversampleEdges[patch][edge]=true; } } } // This is the zone to light ? if (listZone[zone]==_ZoneToLight) { // *** Resize lumel array for this patch // Get patch order uint orderS=pPatch->getOrderS(); uint orderT=pPatch->getOrderT(); // Number of lumels uint lumelCount = orderS*orderT*16; // Resize the lumel descriptor CLumelDescriptor descriptor; descriptor.Normal.set (0,0,0); descriptor.Position.set (0,0,0); descriptor.S=0; descriptor.T=0; _Lumels[patch].resize (lumelCount, descriptor); visited[patch].resize (lumelCount, 0); // _LumelCorners[patch].resize (lumelCornerCount); // *** Unexclude this patch // Exclude all the patches from refine all landscape.excludePatchFromRefineAll (listZone[zone], patch, false); } else { // Exclude all the patches from refine all landscape.excludePatchFromRefineAll (listZone[zone], patch, true); } } } // *** Now tesselate this zone to shadow casters accuracy // Setup the landscape landscape.setThreshold (0); landscape.setTileMaxSubdivision (0); // Refine all progress ("Refine landscape to shadow accuracy", 0.5f); landscape.refineAll (CVector (0, 0, 0)); // Get tesselated faces std::vector leaves; landscape.getTessellationLeaves(leaves); if (_WaterShapes.size() != 0) // any water shape in this zone ? { /// make a quad grid of each water shape makeQuadGridFromWaterShapes(landscape.getZone(_ZoneToLight)->getZoneBB().getAABBox()); /// check for each tile if it is above / below water computeTileFlagsForPositionTowardWater(lightDesc, leaves); } else { setTileFlagsToDefault(leaves); } // Id of this zone in the array uint zoneNumber=_ZoneId[_ZoneToLight]; // Scan each leaves uint leavesCount=(uint)leaves.size(); uint leave; for (leave=0; leavePatch->getZone()->getZoneId()==_ZoneToLight) { // Get a patch pointer const CPatch* pPatch=face->Patch; // Get order uint orderS=pPatch->getOrderS(); uint orderT=pPatch->getOrderT(); // *** Base Coordinates CVector pos[15]; pos[0]=face->VBase->EndPos; // p0 pos[1]=face->VRight->EndPos; pos[2]=face->VLeft->EndPos; // p2 pos[3]=(pos[1]+pos[2])/2; pos[4]=(pos[0]+pos[1])/2; // p4 pos[5]=(pos[0]+pos[2])/2; pos[6]=(pos[0]+pos[3])/2; // p6 pos[7]=(pos[2]+pos[3])/2; pos[8]=(pos[1]+pos[3])/2; // p8 pos[9]=(pos[0]+pos[4])/2; pos[10]=(pos[1]+pos[4])/2; // p10 pos[11]=(pos[0]+pos[5])/2; pos[12]=(pos[2]+pos[5])/2; // p12 pos[13]=(pos[3]+pos[5])/2; pos[14]=(pos[3]+pos[4])/2; // p14 float s0=face->PVBase.getS(); float s1=face->PVRight.getS(); float s2=face->PVLeft.getS(); float s3=(s1+s2)/2; float s4=(s0+s1)/2; float s5=(s0+s2)/2; float s6=(s4+s5)/2; float s7=(s2+s3)/2; float s8=(s1+s3)/2; float t0=face->PVBase.getT(); float t1=face->PVRight.getT(); float t2=face->PVLeft.getT(); float t3=(t1+t2)/2; float t4=(t0+t1)/2; float t5=(t0+t2)/2; float t6=(t4+t5)/2; float t7=(t2+t3)/2; float t8=(t1+t3)/2; // *** Interpolated value CVector interpolatedP[10]= { (pos[0]+pos[6])/2, (pos[4]+pos[6])/2, (pos[4]+pos[8])/2, (pos[1]+pos[8])/2, (pos[5]+pos[6])/2, (pos[3]+pos[6])/2, (pos[3]+pos[8])/2, (pos[5]+pos[7])/2, (pos[3]+pos[7])/2, (pos[2]+pos[7])/2, }; // Does the border are snapped ? uint sBase = (uint)floor ((float)orderS * face->PVBase.getS() + 0.5); uint tBase = (uint)floor ((float)orderT * face->PVBase.getT() + 0.5); uint sLeft = (uint)floor ((float)orderS * face->PVLeft.getS() + 0.5); uint tLeft = (uint)floor ((float)orderT * face->PVLeft.getT() + 0.5); uint sRight = (uint)floor ((float)orderS * face->PVRight.getS() + 0.5); uint tRight = (uint)floor ((float)orderT * face->PVRight.getT() + 0.5); bool snapedLeft[2]= { (sBase == 0) && (sRight == 0), (sBase == 0) && (sLeft == 0), }; bool snapedRight[2]= { (sBase == orderS) && (sRight == orderS), (sBase == orderS) && (sLeft == orderS), }; bool snapedTop[2]= { (tBase == 0) && (tRight == 0), (tBase == 0) && (tLeft == 0), }; bool snapedBottom[2]= { (tBase == orderT) && (tRight == orderT), (tBase == orderT) && (tLeft == orderT), }; bool snapedBorder[2]= { snapedLeft[0]||snapedRight[0]||snapedTop[0]||snapedBottom[0], snapedLeft[1]||snapedRight[1]||snapedTop[1]||snapedBottom[1], }; bool snapedCorner[3]= { ((sBase == 0) && ((tBase == 0) || (tBase == orderT))) || ((sBase == orderS) && ((tBase == 0) || (tBase == orderT))), ((sRight == 0) && ((tRight == 0) || (tRight == orderT))) || ((sRight == orderS) && ((tRight == 0) || (tRight == orderT))), ((sLeft == 0) && ((tLeft == 0) || (tLeft == orderT))) || ((sLeft == orderS) && ((tLeft == 0) || (tLeft == orderT))), }; // Snap on the border uint i; for (i=0; i<8; i++) { // Snaped on left ? if (snapedBorder[VertexThanCanBeSnappedOnABorder[i][1]]) { // Compute the border vertex interpolatedP[VertexThanCanBeSnappedOnABorder[i][0]] = (pos[VertexThanCanBeSnappedOnABorder[i][2]] + pos[VertexThanCanBeSnappedOnABorder[i][3]])/2; } } // Snap on the corner for (i=0; i<3; i++) { // Snaped on a corner ? uint tesselCornerIndex = VertexThanCanBeSnappedOnACorner[i][1]; if ( snapedCorner[tesselCornerIndex] ) { // Compute the border vertex interpolatedP[VertexThanCanBeSnappedOnACorner[i][0]] = pos[tesselCornerIndex]; } } float interpolatedS[10]= { (s0+s6)/2, (s4+s6)/2, (s4+s8)/2, (s1+s8)/2, (s5+s6)/2, (s3+s6)/2, (s3+s8)/2, (s5+s7)/2, (s3+s7)/2, (s2+s7)/2, }; float interpolatedT[10]= { (t0+t6)/2, (t4+t6)/2, (t4+t8)/2, (t1+t8)/2, (t5+t6)/2, (t3+t6)/2, (t3+t8)/2, (t5+t7)/2, (t3+t7)/2, (t2+t7)/2, }; for (i=0; i<10; i++) { sint s=(sint)((float)orderS*4*interpolatedS[i]); sint t=(sint)((float)orderT*4*interpolatedT[i]); if ((s>=0)&&(s<(sint)orderS*4)&&(t>=0)&&(t<(sint)orderT*4)) { // Triangle index uint index=s+t*orderS*4; // Ge tthe patch id uint patchId=pPatch->getPatchId(); // Get lumel array vector &lumels=_Lumels[patchId]; // Visited visited[patchId][index]++; // Position lumels[index].Position+=interpolatedP[i]; } } } } // *** Now, finalise patch information for shadow source positions // For each patches uint patchCount=landscape.getZone(_ZoneToLight)->getNumPatchs(); uint patch; for (patch=0; patch(landscape.getZone(_ZoneToLight)))->getPatch (patch); uint orderS=pPatch->getOrderS(); uint orderT=pPatch->getOrderT(); // Get lumel array vector &lumels=_Lumels[patch]; // *** Average position // Renormalize nlassert (isPowerOf2 (orderS)); nlassert (isPowerOf2 (orderT)); uint lumelS=4<Patch->getZone()->getZoneId()==_ZoneToLight) { // Get a patch pointer const CPatch* pPatch=face->Patch; // Get order uint orderS=pPatch->getOrderS(); uint orderT=pPatch->getOrderT(); // Coordinates float fS=(face->PVBase.getS()+face->PVLeft.getS()+face->PVRight.getS())/3.f; float fT=(face->PVBase.getT()+face->PVLeft.getT()+face->PVRight.getT())/3.f; uint s=(uint)((float)orderS*4*fS); uint t=(uint)((float)orderT*4*fT); //nlassert (s>=0); nlassert (s=0); nlassert (tgetPatchId(); // Get lumel array vector &lumels=_Lumels[patchId]; // Visited visited[patchId][index]++; // Lumel s and t lumels[index].S+=fS; lumels[index].T+=fT; // Normal CPlane plane; plane.make (face->VBase->EndPos, face->VLeft->EndPos, face->VRight->EndPos); lumels[index].Normal+=plane.getNormal(); } } // *** Now, finalise patch information // For each patches patchCount=landscape.getZone(_ZoneToLight)->getNumPatchs(); for (patch=0; patch(landscape.getZone(_ZoneToLight)))->getPatch (patch); uint orderS=pPatch->getOrderS(); uint orderT=pPatch->getOrderT(); // Get lumel array vector &lumels=_Lumels[patch]; // *** Compute an interpolated normal // Get pointer on arries vector &binded=_Binded[zoneNumber][patch]; vector &locator=_Locator[zoneNumber][patch]; vector &bindInfo=_BindInfo[zoneNumber][patch]; CBezierPatch &bezierPatch=_BezierPatch[zoneNumber][patch]; // Renormalize nlassert (isPowerOf2 (orderS)); nlassert (isPowerOf2 (orderT)); uint powerS=getPowerOf2 (orderS); uint powerT=getPowerOf2 (orderT); uint lumelS=4< visitedLumels; getNormal (pPatch, startS, startT, locator, bindInfo, binded, visitedLumels, startS+0.5f-origineS, startT+0.5f-origineT, 0, bezierPatch); _GetNormalNormal.normalize (); normals[lumel][edge]=_GetNormalNormal; } // Smooth the corners #define BLUR_SIZE 4 for (uint i=1; i=lumelS-BLUR_SIZE) { sGood=true; // Average the two normals CVector average=normals[t][2]; average+=normals[t+1][2]; average/=2; // Blend float value=s+0.5f; sFactor=BLUR_SIZE-(lumelS-value); value=(lumelS-value)/BLUR_SIZE; value=easineasout(value); normalS=(normalS*value+average*(1-value)); normalS.normalize(); } if (t=lumelT-BLUR_SIZE) { tGood=true; // Average the two normals CVector average=normals[s][1]; average+=normals[s+1][1]; average/=2; // Blend float value=t+0.5f; tFactor=BLUR_SIZE-(lumelT-value); value=((lumelT)-value)/BLUR_SIZE; value=easineasout(value); normalT=(normalT*value+average*(1-value)); normalT.normalize(); } // The smooth normal CVector smoothNormal; if ((sGood)&&(tGood)) { if ((sFactor!=BLUR_SIZE)||(tFactor!=BLUR_SIZE)) smoothNormal=normalS*(BLUR_SIZE-tFactor)+normalT*(BLUR_SIZE-sFactor); else smoothNormal=normalS+normalT; } else if (sGood) smoothNormal=normalS; else smoothNormal=normalT; // Normalize it smoothNormal.normalize(); // The pure normal CVector purNormal=bezierPatch.evalNormal (((float)s+0.5f)/(float)lumelS, ((float)t+0.5f)/(float)lumelT); // Normalize the noisy normal lumels[lumelIndex].Normal.normalize(); // Final normal lumels[lumelIndex].Normal=lumels[lumelIndex].Normal-purNormal+smoothNormal; lumels[lumelIndex].Normal.normalize (); // *** Number of visit uint visitedCount=visited[patch][lumelIndex]; // Some lumel have not been found in tesselation //nlassert (visitedCount==2); // If visited, renormalise other values if (visitedCount) { // Normalise position lumels[lumelIndex].S/=(float)visitedCount; lumels[lumelIndex].T/=(float)visitedCount; } } } } // *************************************************************************** void CZoneLighter::computeTileFlagsOnly (CLandscape &landscape, CZone& output, uint zoneToLight, const CLightDesc& description, std::vector &listZone) { // Zone to light _ZoneToLight=zoneToLight; // Landscape _Landscape=&landscape; // Zone count uint zoneCount=(uint)listZone.size(); // For each zone for (uint zone=0; zonegetNumPatchs(); // Insert zone id _ZoneId.insert (map::value_type (listZone[zone], zone)); // For each patch uint patch; for (patch=0; patch leaves; landscape.getTessellationLeaves(leaves); // compute only the water states if (_WaterShapes.size() != 0) // any water shape in this zone ? { /// make a quad grid of each water shape makeQuadGridFromWaterShapes(landscape.getZone(_ZoneToLight)->getZoneBB().getAABBox()); /// check for each tile if it is above / below water computeTileFlagsForPositionTowardWater(description, leaves); } else { setTileFlagsToDefault(leaves); } /// verify that the zonew and the zonel (output) are compatible bool ok= true; CZone &zonew= *(landscape.getZone(zoneToLight)); if(zonew.getNumPatchs() == output.getNumPatchs()) { // verify for each patch that the tile array are same for(uint i=0;i<(uint)zonew.getNumPatchs();i++) { const CPatch &p0= *const_cast(zonew).getPatch(i); const CPatch &p1= *const_cast(output).getPatch(i); if( p0.getOrderS()!=p1.getOrderS() || p0.getOrderT()!=p1.getOrderT() ) { ok= false; break; } } } else ok= false; // can't copy tile flags if(!ok) throw Exception("The input zonew, and output zonel are too different: not same patchs!!"); /// copy the tiles flags from the zone to light to the output zone copyTileFlags(output, zonew); } // *************************************************************************** CZoneLighter::CLightDesc::CLightDesc () { SunDirection.set (1, 1, -1); GridSize=512; GridCellSize=4; HeightfieldSize=200; HeightfieldCellSize=20; SkyContribution=true; SkyIntensity=0.25; ZBufferLandscapeSize = DEFAULT_ZBUFFER_LANDSCAPE_SIZE; ZBufferObjectSize = DEFAULT_ZBUFFER_OBJECT_SIZE; SoftShadowJitter = DEFAULT_JITTER; SunDistance = DEFAULT_SUN_DISTANCE; SunFOV = (float)DEFAULT_SUN_FOV; SunCenter = DEFAULT_SUN_CENTER; SunRadius = DEFAULT_SUN_RADIUS; SoftShadowSamplesSqrt = DEFAULT_SUN_SRQT_SAMPLES; } // *************************************************************************** void CZoneLighter::addLightableShape(IShape *shape, const NLMISC::CMatrix& MT) { CShapeInfo lsi; lsi.MT = MT; lsi.Shape = shape; _LightableShapes.push_back(lsi); } // *************************************************************************** bool CZoneLighter::isLightableShape(IShape &shape) { /// for now, the only shape that we lit are water shapes if (dynamic_cast(&shape) != NULL) { // check that this water surface has a diffuse map that is a CTextureFile (we must be able to save it !) CWaterShape *ws = static_cast(&shape); const ITexture *tex = ws->getColorMap(); if (dynamic_cast(tex) != NULL) { return ws->isLightMappingEnabled(); } } return false; } // *************************************************************************** void CZoneLighter::lightShapes(uint zoneID, const CLightDesc& description) { /// compute light for the lightable shapes in the given zone if (_LightableShapes.size() == 0) return; uint numShapePerThread = 1 + ((uint)_LightableShapes.size() / _ProcessCount); uint currShapeIndex = 0; uint process = 0; _ProcessExited = 0; _NumLightableShapesProcessed = 0; progress("Processing lightable shapes", 0); for (uint k = 0; k < _LightableShapes.size(); ++k, ++process) { uint lastShapeIndex = currShapeIndex + numShapePerThread; lastShapeIndex = std::min((uint)_LightableShapes.size(), lastShapeIndex); IThread *pThread = IThread::create (new CCalcLightableShapeRunnable(process, this, &description, &_LightableShapes, currShapeIndex, lastShapeIndex)); pThread->start(); currShapeIndex = lastShapeIndex; } /// wait for other process while (_ProcessExited != _ProcessCount) { nlSleep (10); } } // *************************************************************************** void CZoneLighter::processLightableShapeCalc (uint process, TShapeVect *shapesToLit, uint firstShape, uint lastShape, const CLightDesc& description) { // for each lightable shape for (uint k = firstShape; k < lastShape; ++k) { nlassert(isLightableShape(* (*shapesToLit)[k].Shape)); // make sure it is a lightable shape lightSingleShape((*shapesToLit)[k], description, process); } } // *************************************************************************** void CZoneLighter::lightSingleShape(CShapeInfo &si, const CLightDesc& description, uint cpu) { /// we compute the lighting for one single shape if (dynamic_cast(si.Shape)) { lightWater(* static_cast(si.Shape), si.MT, description, cpu); } ++_NumLightableShapesProcessed; progress("Processing lightable shapes", (float) _NumLightableShapesProcessed / _LightableShapes.size()); return; } // *************************************************************************** // utility function to get the directory of a fileName static std::string getDir (const std::string& path) { char tmpPath[512]; strcpy (tmpPath, path.c_str()); char* slash=strrchr (tmpPath, '/'); if (!slash) { slash=strrchr (tmpPath, '\\'); } if (!slash) return ""; slash++; *slash=0; return tmpPath; } // *************************************************************************** // utility function to get a file name fdrom a path static std::string getName (const std::string& path) { std::string dir=getDir (path); char tmpPath[512]; strcpy (tmpPath, path.c_str()); char *name=tmpPath; nlassert (dir.length()<=strlen(tmpPath)); name+=dir.length(); char* point=strrchr (name, '.'); if (point) *point=0; return name; } // *************************************************************************** // utility function to get the extension of a fileName static std::string getExt (const std::string& path) { std::string dir = getDir (path); std::string name = getName (path); char tmpPath[512]; strcpy (tmpPath, path.c_str()); char *ext=tmpPath; nlassert (dir.length()+name.length()<=strlen(tmpPath)); ext+=dir.length()+name.length(); return ext; } // *************************************************************************** void CZoneLighter::lightWater(CWaterShape &ws, const CMatrix &MT, const CLightDesc& description, uint cpu) { try { /// get the diffuse map CTextureFile *diffuseTex = NLMISC::safe_cast(ws.getColorMap()); std::string texFileName = CPath::lookup(diffuseTex->getFileName()); diffuseTex->generate(); const uint width = diffuseTex->getWidth(); const uint height = diffuseTex->getHeight(); /// build a matrix to convert from water space to uv space NLMISC::CMatrix worldSpaceToUVs; NLMISC::CVector2f col0, col1, pos; ws.getColorMapMat(col0, col1, pos); worldSpaceToUVs.setRot(NLMISC::CVector(col0.x * width, col0.y * height, 0), NLMISC::CVector(col1.x * width, col1.y * height, 0), NLMISC::CVector::K); worldSpaceToUVs.setPos(NLMISC::CVector(pos.x * width, pos.y * height, 0)); /// get min and max uvs NLMISC::CPolygon p; ws.getShapeInWorldSpace(p); float minU, maxU; float minV, maxV; NLMISC::CVector uvs = worldSpaceToUVs * p.Vertices[0]; minU = maxU = uvs.x; minV = maxV = uvs.y; for (uint k = 1; k < (uint) p.getNumVertices(); ++k) { uvs = worldSpaceToUVs * p.Vertices[k]; minU = std::min(uvs.x, minU); minV = std::min(uvs.y, minV); maxU = std::max(uvs.x, maxU); maxV = std::max(uvs.y, maxV); } sint iMinU = (sint) minU; sint iMaxU = (sint) maxU; sint iMinV = (sint) minV; sint iMaxV = (sint) maxV; NLMISC::clamp(iMinU, 0, (sint) width); NLMISC::clamp(iMaxU, 0, (sint) width); NLMISC::clamp(iMinV, 0, (sint) height); NLMISC::clamp(iMaxV, 0, (sint) height); // matrix to go from uv space to worldspace NLMISC::CMatrix UVSpaceToWorldSpace = worldSpaceToUVs.inverted(); CObjectVector &pixs8 = diffuseTex->getPixels(); NLMISC::CRGBA *rgbPixs = (NLMISC::CRGBA *) &pixs8[0]; /// raytrace each texel for (sint x = iMinU; x < iMaxU; ++x) { for (sint y = iMinV; y < iMaxV; ++y) { float factor; NLMISC::CVector pos = UVSpaceToWorldSpace * NLMISC::CVector( x + 0.5f, y + 0.5f, 0 ) + description.WaterShadowBias * NLMISC::CVector::K; if (description.Shadow) { factor = attenuation (pos, description); } else { factor = - NLMISC::CVector::K * description.SunDirection; } clamp(factor, 0.f, 1.f); factor = factor * description.WaterDiffuse + description.WaterAmbient; if (description.SkyContributionForWater) { factor += getSkyContribution(pos, NLMISC::CVector::K, description.SkyIntensity); } clamp(factor, 0.f, 1.f); uint intensity = (uint8) (255 * factor); NLMISC::CRGBA srcCol(intensity, intensity, intensity, 255); if (!description.ModulateWaterColor) { rgbPixs[x + y * width] = srcCol; } else { NLMISC::CRGBA &col = rgbPixs[x + y * width]; col.modulateFromColor(col, srcCol); } } } /// now, save the result if (getExt(texFileName) != ".tga") { nlwarning("Zone lighter : error when lighting a water surface : input bitmap is not a tga file"); } else { try { COFile of; of.open(texFileName); diffuseTex->writeTGA(of, 24); of.close(); } catch (const NLMISC::Exception &) { nlwarning("Zone lighter : while lighting a water shape, writing %s failed! ", texFileName.c_str()); } } } catch(const NLMISC::Exception &e) { nlwarning("Water shape lighting failed !"); nlwarning(e.what()); } } // *********************************************************** void CZoneLighter::addWaterShape(CWaterShape *shape, const NLMISC::CMatrix &MT) { /// make sure it hasn't been inserted twice CShapeInfo ci; ci.Shape = shape; ci.MT = MT; _WaterShapes.push_back(ci); } // *********************************************************** void CZoneLighter::makeQuadGridFromWaterShapes(NLMISC::CAABBox zoneBBox) { if (!_WaterShapes.size()) return; NLMISC::CAABBox tmpBox; /// the number of cells we want in the quad grid const uint numCells = 16; /// get the dimension float width = zoneBBox.getMax().x - zoneBBox.getMin().x; float height = zoneBBox.getMax().y - zoneBBox.getMin().y; float dim = std::max(width, height); /// init the quad grid _WaterShapeQuadGrid.create(numCells, dim / numCells); uint count = 0, totalCount = (uint)_WaterShapes.size(); /// now, insert all water shapes for (TShapeVect::iterator it = _WaterShapes.begin(); it != _WaterShapes.end(); ++it, ++count) { /// get the current shape bbox in the world it->Shape->getAABBox(tmpBox); NLMISC::CAABBox currBB = NLMISC::CAABBox::transformAABBox(it->MT, tmpBox); /// test if it intesect the zone bbox if (zoneBBox.intersect(currBB)) { _WaterShapeQuadGrid.insert(currBB.getMin(), currBB.getMax(), *it); } progress("Building quadtree from water surfaces", (float) count / totalCount); } /// free the vector of water shapes NLMISC::contReset(_WaterShapes); } //================================================================== /// a struct that helps us to know which tile we've processed struct CTileOfPatch { uint8 TileId; CPatch *Patch; CTileOfPatch(); CTileOfPatch(uint8 tileId, CPatch *patch) : TileId(tileId), Patch(patch) { } }; // *************************************************************************** // *************************************************************************** // Static point lights. // *************************************************************************** // *************************************************************************** // *************************************************************************** CZoneLighter::CPointLightRT::CPointLightRT() { RefCount= 0; } // *************************************************************************** bool CZoneLighter::CPointLightRT::testRaytrace(const CVector &v) { CVector dummy; if(!BSphere.include(v)) return false; // If Ambient light, just skip if(PointLight.getType()== CPointLight::AmbientLight) return false; // If SpotLight verify in angle radius. if(PointLight.getType()== CPointLight::SpotLight) { float att= PointLight.computeLinearAttenuation(v); if (att==0) return false; } // Select in the cubeGrid FaceCubeGrid.select(v); // For all faces selected while(!FaceCubeGrid.isEndSel()) { const CTriangle *tri= FaceCubeGrid.getSel(); // If intersect, the point is occluded. if( tri->Triangle.intersect(BSphere.Center, v, dummy, tri->getPlane()) ) return false; // next FaceCubeGrid.nextSel(); } // Ok the point is visilbe from the light return true; } // *************************************************************************** void CZoneLighter::addStaticPointLight(const CPointLightNamed &pln) { // build the plRT. CPointLightRT plRT; plRT.PointLight= pln; // compute plRT.OODeltaAttenuation plRT.OODeltaAttenuation= pln.getAttenuationEnd() - pln.getAttenuationBegin(); if(plRT.OODeltaAttenuation <=0 ) plRT.OODeltaAttenuation= 0; else plRT.OODeltaAttenuation= 1.0f / plRT.OODeltaAttenuation; // compute plRT.BSphere plRT.BSphere.Center= pln.getPosition(); plRT.BSphere.Radius= pln.getAttenuationEnd(); // NB: FaceCubeGrid will be computed during light() // add the plRT _StaticPointLights.push_back(plRT); } // *************************************************************************** void CZoneLighter::compilePointLightRT(uint gridSize, float gridCellSize, std::vector& obstacles, bool doShadow) { uint i; // Fill the quadGrid of Lights. // =========== _StaticPointLightQuadGrid.create(gridSize, gridCellSize); for(i=0; i<_StaticPointLights.size();i++) { CPointLightRT &plRT= _StaticPointLights[i]; // Compute the bbox of the light CAABBox bbox; bbox.setCenter(plRT.BSphere.Center); float hl= plRT.BSphere.Radius; bbox.setHalfSize(CVector(hl,hl,hl)); // Insert the pointLight in the quadGrid. _StaticPointLightQuadGrid.insert(bbox.getMin(), bbox.getMax(), &plRT); } // Append triangles to cubeGrid ?? if(doShadow) { // Point lights ? if (!_StaticPointLights.empty ()) { // For all obstacles, Fill a quadGrid. // =========== CQuadGrid obstacleGrid; obstacleGrid.create(gridSize, gridCellSize); uint size= (uint)obstacles.size(); for(i=0; i::CIterator itObstacle; itObstacle= obstacleGrid.begin(); while( itObstacle!=obstacleGrid.end() ) { CTriangle &tri= *(*itObstacle); // Triangle bounding box CAABBox triBbox; triBbox.setCenter (tri.Triangle.V0); triBbox.extend (tri.Triangle.V1); triBbox.extend (tri.Triangle.V2); // Triangle in the light if (triBbox.intersect (bbox)) { // Test BackFace culling. Only faces which are BackFace the point light are inserted. // This is to avoid AutoOccluding problems if( tri.getPlane() * plRT.BSphere.Center < 0) { // Insert the triangle in the CubeGrid plRT.FaceCubeGrid.insert( tri.Triangle, &tri); } } itObstacle++; } } // Compile the CubeGrid. plRT.FaceCubeGrid.compile(); // And Reset RefCount. plRT.RefCount= 0; } } } // else, just build empty grid else { for(i=0; i<_StaticPointLights.size();i++) { // progress progress ("Compute Influences of PointLights", 0.5f*i / (float)(_StaticPointLights.size()-1)); CPointLightRT &plRT= _StaticPointLights[i]; // Create a dummy empty cubeGrid => no rayTrace :) plRT.FaceCubeGrid.create(plRT.PointLight.getPosition(), 4); // Compile the CubeGrid. plRT.FaceCubeGrid.compile(); // And Reset RefCount. plRT.RefCount= 0; } } } // *************************************************************************** bool CZoneLighter::CPredPointLightToPoint::operator() (CPointLightRT *pla, CPointLightRT *plb) const { float ra= (pla->BSphere.Center - Point).norm(); float rb= (plb->BSphere.Center - Point).norm(); float infA= (pla->PointLight.getAttenuationEnd() - ra) * pla->OODeltaAttenuation; float infB= (plb->PointLight.getAttenuationEnd() - rb) * plb->OODeltaAttenuation; // return which light impact the most. // If same impact if(infA==infB) // return nearest return ra < rb; else // return better impact return infA > infB; } // *************************************************************************** void CZoneLighter::processZonePointLightRT(vector &listPointLight) { uint i; vector lightInfs; lightInfs.reserve(1024); // clear result list listPointLight.clear(); // zoneToLight CZone *zoneToLight= _Landscape->getZone(_ZoneToLight); if(!zoneToLight) return; // Build patchForPLs //=========== vector patchForPLs; patchForPLs.resize(_PatchInfo.size()); for(i=0; i(zoneToLight)->getPatch(i); uint x, y; for(y= 0; ycomputeVertex(s, t); // Use UnNoised normal from BezierPatch, because the lighting does not need to be so precise. CBezierPatch *bp= patch->unpackIntoCache(); normal= bp->evalNormal(s, t); // Compute Which light influences him. //--------- lightInfs.clear(); // Search possible lights around the position. _StaticPointLightQuadGrid.select(pos, pos); // For all of them, get the ones which touch this point. CQuadGrid::CIterator it= _StaticPointLightQuadGrid.begin(); while(it != _StaticPointLightQuadGrid.end()) { CPointLightRT *pl= *it; // a light influence a TLI only if this one is FrontFaced to the light !! if( ( pl->BSphere.Center - pos ) * normal > 0) { // Add 5cm else it fails in some case where ( pl->BSphere.Center - pos ) * normal is // nearly 0 and the point should be occluded. const float deltaY= 0.05f; CVector posToRT= pos + normal * deltaY; // Test if really in the radius of the light, if no occlusion, and if in SpotAngle if( pl->testRaytrace(posToRT) ) { // Ok, add the light to the lights which influence the TLI lightInfs.push_back(pl); } } // next it++; } // Choose the Best ones. //--------- CPredPointLightToPoint predPLTP; predPLTP.Point= pos; // sort. sort(lightInfs.begin(), lightInfs.end(), predPLTP); // truncate. lightInfs.resize( min((uint)lightInfs.size(), (uint)CTileLightInfluence::NumLightPerCorner) ); // For each of them, fill TLI //--------- CTileLightInfUnpack tli; uint lightInfId; for(lightInfId=0; lightInfIdBSphere.Center - pos; dir.normalize(); tli.LightFactor[lightInfId]= dir * normal; clamp(tli.LightFactor[lightInfId], 0.f, 1.f); // modulate by light attenuation. tli.LightFactor[lightInfId]*= pl->PointLight.computeLinearAttenuation(pos); // Inc RefCount of the light. pl->RefCount++; } // Reset any empty slot to NULL. for(; lightInfId 0) { // Must Copy it into Zone. listPointLight.push_back(plRT.PointLight); plRT.DstId= plId++; // If index >= 255, too many lights (NB: => because 255 is a NULL code). if(plId>=0xFF) { throw Exception("Too many Static Point Lights influence the zone!!"); } } } // For each patch, compress TLI in PatchInfo. for(i=0; iDstId; // Get Diffuse Factor. tliDst.setDiffuseLightFactor(lightId, (uint8)(tliSrc.LightFactor[lightId]*255)); } } } } } } // *********************************************************** // *********************************************************** // TileFlagsForPositionTowardWater // *********************************************************** // *********************************************************** //================================================================== /// for map insertion of CTileOfPatch structs static inline bool operator < (const CTileOfPatch &lhs, const CTileOfPatch &rhs) { return lhs.Patch == rhs.Patch ? lhs.TileId < rhs.TileId : lhs.Patch < rhs.Patch; }; /// A set of tiles from patch and their bbox typedef std::map TTileOfPatchMap; // *********************************************************** void CZoneLighter::computeTileFlagsForPositionTowardWater(const CLightDesc &lightDesc, std::vector &tessFaces ) { uint numTileAbove = 0; uint numTileBelow = 0; uint numTileIntersect = 0; /// the tiles that we have setupped so far... TTileOfPatchMap tiles; /////////////////////////////////////////// // First, build the bbox for all tiles // /////////////////////////////////////////// uint triCount = 0, totalTriCount = (uint)tessFaces.size(); nlinfo("Dealing with %d tessFaces", tessFaces.size()); for (std::vector::iterator it = tessFaces.begin(); it != tessFaces.end(); ++it, ++triCount) { /// does the face belong to the zone to light ? if ((*it)->Patch->getZone()->getZoneId() != _ZoneToLight) continue; /// if the tile flags say that micro vegetation is disabled, just skip that if ((*it)->Patch->Tiles[(*it)->TileId].getVegetableState() == CTileElement::VegetableDisabled) continue; CTileOfPatch top((*it)->TileId, (*it)->Patch); TTileOfPatchMap::iterator tileIt = tiles.find(top); /// test whether we've seen face(s) from this tile before if (tileIt == tiles.end()) // first time ? { /// build a bbox for this face NLMISC::CAABBox b; b.setMinMax((*it)->VBase->EndPos, (*it)->VLeft->EndPos); b.extend((*it)->VRight->EndPos); b.extend(b.getMax() + lightDesc.VegetableHeight * NLMISC::CVector::K); // adds vegetable height tiles[top] = b; } else // extends the bbox with the given face { NLMISC::CAABBox &b = tileIt->second; b.extend((*it)->VBase->EndPos); b.extend((*it)->VRight->EndPos); b.extend((*it)->VLeft->EndPos); } if ((triCount % 100) == 0) { progress("Building bbox from tiles", (float) triCount / totalTriCount); } } progress("Building bbox from tiles", 1.f); //////////////////////////////////////////////////// // Now, check each tile bbox against water shapes // //////////////////////////////////////////////////// NLMISC::CPolygon waterPoly; NLMISC::CPolygon2D tilePoly; tilePoly.Vertices.resize(4); uint tileCount = 0, totalTileCount = (uint)tiles.size(); for (TTileOfPatchMap::iterator tileIt = tiles.begin(); tileIt != tiles.end(); ++tileIt, ++tileCount) { const NLMISC::CVector v0 = tileIt->second.getMin(); const NLMISC::CVector v1 = tileIt->second.getMax(); /// build a top view from the bbox tilePoly.Vertices[0].set(v0.x, v0.y); tilePoly.Vertices[1].set(v1.x, v0.y); tilePoly.Vertices[2].set(v1.x, v1.y); tilePoly.Vertices[3].set(v0.x, v1.y); /// Select the candidate water shape from the quad grid _WaterShapeQuadGrid.clearSelection(); _WaterShapeQuadGrid.select(tileIt->second.getMin(), tileIt->second.getMax()); CTileElement &te = tileIt->first.Patch->Tiles[tileIt->first.TileId]; // alias to the current tile element /// test more accurate intersection for each water shape TWaterShapeQuadGrid::CIterator qgIt; for (qgIt = _WaterShapeQuadGrid.begin(); qgIt != _WaterShapeQuadGrid.end(); ++qgIt) { CWaterShape *waterShape= safe_cast((*qgIt).Shape); waterShape->getShapeInWorldSpace(waterPoly, (*qgIt).MT); NLMISC::CPolygon2D poly(waterPoly); if (poly.intersect(tilePoly)) // above or below a water surface ? { /// height of water float waterHeight = waterPoly.Vertices[0].z; if (v1.z < waterHeight) { // below te.setVegetableState(CTileElement::UnderWater); //nlassert(te.getVegetableState() == CTileElement::UnderWater); ++ numTileBelow; } else if (v0. z > waterHeight) { // above te.setVegetableState(CTileElement::AboveWater); //nlassert(te.getVegetableState() == CTileElement::AboveWater); ++ numTileAbove; } else { // intersect water te.setVegetableState(CTileElement::IntersectWater); //nlassert(te.getVegetableState() == CTileElement::IntersectWater); ++ numTileIntersect; } break; } } if (qgIt == _WaterShapeQuadGrid.end()) // no intersection found ? if yes it's above water { te.setVegetableState(CTileElement::AboveWater); //nlassert(te.getVegetableState() == CTileElement::AboveWater); ++ numTileAbove; } if ((tileCount % 50) == 0) { progress("Computing tile position towards water", (float) tileCount / totalTileCount); } } progress("Computing tile position towards water", 1.f); nlinfo(" %d tiles are above water.", numTileAbove); nlinfo(" %d tiles are below water.", numTileBelow); nlinfo(" %d tiles intersect water.", numTileIntersect); /// delete the quadgrid now NLMISC::contReset(_WaterShapeQuadGrid); } // *********************************************************** void CZoneLighter::setTileFlagsToDefault(std::vector &tessFaces) { /// We may setup a tile several time, but this doesn't matter here... for (std::vector::iterator it = tessFaces.begin(); it != tessFaces.end(); ++it) { if ((*it)->Patch->getZone()->getZoneId() != _ZoneToLight) continue; CTileElement &te = (*it)->Patch->Tiles[(*it)->TileId]; if (te.getVegetableState() != CTileElement::VegetableDisabled) { te.setVegetableState(CTileElement::AboveWater); } } } // *********************************************************** uint CZoneLighter::getAPatch (uint process) { // Accessor CSynchronized >::CAccessor access (&_PatchComputed); // Current index uint index = _LastPatchComputed[process]; uint firstIndex = index; nlassert(index < _PatchInfo.size()); if (access.value().size() == 0) // no more patches return 0xffffffff; while (access.value()[index]) { // Next patch index++; // Last patch ? if (index == _PatchInfo.size()) index = 0; // First ? if (firstIndex == index) // no more patches return 0xffffffff; } // Visited access.value()[index] = true; // Last index _LastPatchComputed[process] = index; _NumberOfPatchComputed++; // Return the index return index; } // *********************************************************** float CZoneLighter::attenuation (const CVector &pos, const CZoneLighter::CLightDesc &description) { // Clipped ? // *** Landscape attenuation // Current value float averageAttenuation = 0; float randomSum = 0; // For each sample uint sample; const uint samples = description.SoftShadowSamplesSqrt*description.SoftShadowSamplesSqrt; for (sample=0; sample