// 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/patch.h" #include "nel/3d/tessellation.h" #include "nel/3d/bezier_patch.h" #include "nel/3d/zone.h" #include "nel/3d/landscape.h" #include "nel/misc/vector.h" #include "nel/misc/common.h" #include "nel/3d/patchuv_locator.h" #include "nel/3d/vegetable_manager.h" #include "nel/misc/fast_floor.h" #include "nel/3d/light_influence_interpolator.h" #include "nel/3d/patchdlm_context.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** // Precalc table used to decompress shadow map // NB: if you want to change thoses values, see unpackLumelBlock, cause hardcoded. static const uint NL3DDecompressLumelFactor0Case0[8]= { 7, 0, 6, 5, 4, 3, 2, 1 }; static const uint NL3DDecompressLumelFactor1Case0[8]= { 0, 7, 1, 2, 3, 4, 5, 6 }; static const uint NL3DDecompressLumelFactor0Case1[6]= { 5, 0, 4, 3, 2, 1, }; static const uint NL3DDecompressLumelFactor1Case1[6]= { 0, 5, 1, 2, 3, 4, }; // *************************************************************************** void CPatch::unpackLumelBlock (uint8 *dest, const uint8 *src) { // Take the two alpha values uint alpha0=src[0]; uint alpha1=src[1]; // precompute the 8 possible values, for each possible code. // ------------------ uint8 values[8]; // Case 0 if (alpha0>alpha1) { // unrolled, and hardcoded for faster compute values[0]= alpha0; values[1]= alpha1; values[2]= (uint8) ( (alpha0*219 + alpha1*37 ) >>8 ) ; // 6*256/7 values[3]= (uint8) ( (alpha0*183 + alpha1*73 ) >>8 ) ; // 5*256/7 values[4]= (uint8) ( (alpha0*146 + alpha1*110) >>8 ) ; // 4*256/7 values[5]= (uint8) ( (alpha0*110 + alpha1*146) >>8 ) ; // 3*256/7 values[6]= (uint8) ( (alpha0*73 + alpha1*183) >>8 ) ; // 2*256/7 values[7]= (uint8) ( (alpha0*37 + alpha1*219) >>8 ) ; // 1*256/7 } // Case 1 else { // unrolled, and hardcoded for faster compute values[0]= alpha0; values[1]= alpha1; values[2]= (uint8) ( (alpha0*205 + alpha1*51 ) >>8 ) ; // 4*256/5 values[3]= (uint8) ( (alpha0*154 + alpha1*102) >>8 ) ; // 3*256/5 values[4]= (uint8) ( (alpha0*102 + alpha1*154) >>8 ) ; // 2*256/5 values[5]= (uint8) ( (alpha0*51 + alpha1*205) >>8 ) ; // 1*256/5 values[6]= 0; values[7]= 255; } // For each pixel, set the value according to the code // ------------------ uint block8Pixs[2]; // Split the 48 bits data in 2 24 bits pass. block8Pixs[0]= ((uint)src[2]<<16) + ((uint)src[3]<<8) + ((uint)src[4]) ; block8Pixs[1]= ((uint)src[5]<<16) + ((uint)src[6]<<8) + ((uint)src[7]) ; // write all lumels for(uint i=0; i<2; i++) { uint blockPix= block8Pixs[i]; // parse the 8 pixs, and write seq to dest for(uint n=8; n>0; n--, dest++) { uint code= (blockPix>>21)&0x7; // read LUT, and store *dest= values[code]; // shift the block blockPix<<= 3; } } } // *************************************************************************** inline uint8 getUnpackLumelBlock (const uint8 *src, uint pixel) { // Offset of the bit pixel*=3; uint offset=(pixel>>3)+2; uint bits=pixel&7; // Uncompress 16 codes uint code; // Get the code if (bits<=5) code=(src[offset]>>(5-bits))&0x7; else code= ( (src[offset]<<(bits-5)) | (src[offset+1]>>(13-bits)) )&0x7; // Case 0 if (src[0]>src[1]) { // Decompress the data return (uint8)((NL3DDecompressLumelFactor0Case0[code]*src[0]+NL3DDecompressLumelFactor1Case0[code]*src[1])/7); } // Case 1 else { // Decompress the data if (code<6) return (uint8)((NL3DDecompressLumelFactor0Case1[code]*src[0]+NL3DDecompressLumelFactor1Case1[code]*src[1])/5); else if (code==6) return 0; else return 255; } } // *************************************************************************** void CPatch::unpackShadowMap (uint8 *pLumelDest) { // Input of compresed data uint8 *compressedData=&CompressedLumels[0]; // Number of lumel by lines uint lumelCount=OrderS*NL_LUMEL_BY_TILE; // Number of block in a line nlassert ((lumelCount&0x3)==0); uint numLumelBlock=lumelCount>>2; // Number of line uint lineCount=OrderT*NL_LUMEL_BY_TILE; // Number of block line nlassert ((lineCount&0x3)==0); uint numLineBlock=lineCount>>2; // Destination lumel block size uint lumelDestBlockSize=4; // Destination lumel line block size uint lumelDestLineBlockSize=lumelCount*lumelDestBlockSize; // Each line block for (uint lineBlock=0; lineBlockalpha1) // Case 0 value[i]=(NL3DDecompressLumelFactor0Case0[i]*alpha0+NL3DDecompressLumelFactor1Case0[i]*alpha1)/7; else { if (i<6) value[i]=(NL3DDecompressLumelFactor0Case1[i]*alpha0+NL3DDecompressLumelFactor1Case1[i]*alpha1)/5; else if (i==6) value[i]=0; else value[i]=255; } } // Store alpha value dest[0]=alpha0; dest[1]=alpha1; // Clear dest codes for (i=0; i<6; i++) { // Clear the code dest[2+i]=0; } // For each original select the best uint codeOffset=2; sint codeShift=5; for (i=0; i<16; i++) { // Best dist and code uint bestDist=10000; uint8 bestCode=0; // Calc distance for (uint code=0; code<8; code++) { // Distance from original value uint dist=abs ((sint)(source[i])-(sint)(value[code])); // The best ? if (dist=0) dest[codeOffset]|=bestCode<>(-codeShift); dest[codeOffset+1]|=bestCode<<(8+codeShift); } // Next shift codeShift-=3; if (codeShift<=-3) { codeOffset++; codeShift+=8; } } } // *************************************************************************** void CPatch::getTileTileColors(uint ts, uint tt, CRGBA corners[4]) { for(sint i=0;i<4;i++) { CTileColor &tcol= TileColors[ (tt+(i>>1))*(OrderS+1) + (ts+(i&1)) ]; CRGBA &col= corners[i]; col.set565 (tcol.Color565); } } // *************************************************************************** // bilinear at center of the pixels. x E [0, 3], y E [0, 3]. inline void bilinearColor(CRGBA corners[4], uint x, uint y, uint &R, uint &G, uint &B) { // Fast bilinear and modulate. // hardcoded for 4 pixels. nlassert(NL_LUMEL_BY_TILE==4); // expand to be on center of pixel=> 1,3,5 or 7. x= (x<<1)+1; y= (y<<1)+1; uint x1= 8-x; uint y1= 8-y; // compute weight factors. uint xy= x*y; uint x1y= x1*y; uint xy1= x*y1; uint x1y1= x1*y1; // bilinear // pix left top. R = corners[0].R * x1y1; G = corners[0].G * x1y1; B = corners[0].B * x1y1; // pix right top. R+= corners[1].R * xy1; G+= corners[1].G * xy1; B+= corners[1].B * xy1; // pix left bottom. R+= corners[2].R * x1y; G+= corners[2].G * x1y; B+= corners[2].B * x1y; // pix right bottom. R+= corners[3].R * xy; G+= corners[3].G * xy; B+= corners[3].B * xy; } // *************************************************************************** // bilinear at center of the pixels. x E [0, 3], y E [0, 3]. inline void bilinearColorAndModulate(CRGBA corners[4], uint x, uint y, CRGBA &res) { uint R, G, B; bilinearColor(corners, x, y, R, G, B); // modulate with input. R *= res.R; G *= res.G; B *= res.B; // R,G,B are on 14 bits res.R = R >> 14; res.G = G >> 14; res.B = B >> 14; } // *************************************************************************** // bilinear at center of the pixels. x E [0, 3], y E [0, 3]. inline void bilinearColorAndModulatex2(CRGBA corners[4], uint x, uint y, CRGBA &res) { uint R, G, B; bilinearColor(corners, x, y, R, G, B); // modulate with input. R *= res.R; G *= res.G; B *= res.B; // result not >> 14 but >> 13 and clamp from 0 to 255 R = R >> 13; G = G >> 13; B = B >> 13; res.R = min (R, 255U); res.G = min (G, 255U); res.B = min (B, 255U); } // *************************************************************************** // bilinear at center of the pixels. x E [0, 3], y E [0, 3]. inline void bilinearColorDiv2AndAdd(CRGBA corners[4], uint x, uint y, CRGBA &res) { uint R,G,B; bilinearColor(corners, x, y, R, G, B); // add with input. Resulting TLI must be on 7 bits. R= (R>>7) + res.R; G= (G>>7) + res.G; B= (B>>7) + res.B; R= min(R, 255U); G= min(G, 255U); B= min(B, 255U); // result. res.R= R; res.G= G; res.B= B; } // *************************************************************************** // bilinear at center of the pixels. x E [0, 3], y E [0, 3]. inline void bilinearColorAndAdd(CRGBA corners[4], uint x, uint y, CRGBA &res) { uint R,G,B; bilinearColor(corners, x, y, R, G, B); // add with input. Resulting TLI must be on 7 bits. R= (R>>6) + res.R; G= (G>>6) + res.G; B= (B>>6) + res.B; R= min(R, 255U); G= min(G, 255U); B= min(B, 255U); // result. res.R= R; res.G= G; res.B= B; } // *************************************************************************** void CPatch::modulateTileLightmapWithTileColors(uint ts, uint tt, CRGBA *dest, uint stride) { // Get the tileColors around this tile CRGBA corners[4]; getTileTileColors(ts, tt, corners); // For all lumel, bilinear. uint x, y; for(y=0; ygetAutomaticLightDir(); c= max(c, 0.f); sint ic; #if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM) && defined(NL_USE_FASTFLOOR) // FastFloor using fistp. Don't care convention. float fc= c*256; _asm { fld fc fistp ic } #else ic= (sint)floor(c*256); #endif clamp(ic, 0, 255); // ambiant/diffuse lighting. *dest= getLandscape()->getStaticLight()[ic]; } // *************************************************************************** void CPatch::getTileLumelmapPrecomputed(uint ts, uint tt, uint8 *dest, uint stride) { // Unpack the lumels uint8 buffer[NL_LUMEL_BY_TILE*NL_LUMEL_BY_TILE]; unpackLumelBlock (buffer, &(CompressedLumels[(ts + (tt*OrderS))*NL_BLOCK_LUMEL_COMPRESSED_SIZE])); // Retrun it uint x, y; for(y=0; ygetStaticLight (); // Unpack the lumels uint8 buffer[NL_LUMEL_BY_TILE*NL_LUMEL_BY_TILE]; unpackLumelBlock (buffer, &(CompressedLumels[(ts + (tt*OrderS))*NL_BLOCK_LUMEL_COMPRESSED_SIZE])); // Retrun it uint x, y; for(y=0; ygetStaticLight (); // Witch corner to start ? uint pixel=NL3DPixelStartLumel[edge]; uint delta=NL3DDeltaLumel[edge]; // For each lumels const uint8 *src=&(CompressedLumels[(ts + (tt*OrderS))*NL_BLOCK_LUMEL_COMPRESSED_SIZE]); uint x; if (inverse) { uint inverseStride=stride*(4-1); for(x=0; x<4; x++) { // lumel dest[inverseStride-x*stride]=colorTable[getUnpackLumelBlock (src, pixel)]; pixel+=delta; } } else { for(x=0; x<4; x++) { // lumel dest[x*stride]=colorTable[getUnpackLumelBlock (src, pixel)]; pixel+=delta; } } } // *************************************************************************** void CPatch::computeTileLightmapPixelPrecomputed(uint ts, uint tt, uint s, uint t, CRGBA *dest) { // Lumel table const CRGBA* colorTable=getLandscape ()->getStaticLight (); // Return the lumel *dest=colorTable[getUnpackLumelBlock (&(CompressedLumels[(ts + (tt*OrderS))*NL_BLOCK_LUMEL_COMPRESSED_SIZE]), s+(t<<2))]; } // *************************************************************************** void CPatch::computeTileLightmap(uint ts, uint tt, CRGBA *dest, uint stride) { if(getLandscape()->getAutomaticLighting()) computeTileLightmapAutomatic(ts, tt, dest, stride); else { computeTileLightmapPrecomputed(ts, tt, dest, stride); // Add the inlufence of TLI. addTileLightmapWithTLI(ts, tt, dest, stride); } // modulate dest with tileColors (at center of lumels). modulateTileLightmapWithTileColors(ts, tt, dest, stride); } // *************************************************************************** void CPatch::computeTileLightmapEdge(uint ts, uint tt, uint edge, CRGBA *dest, uint stride, bool inverse) { if(getLandscape()->getAutomaticLighting()) computeTileLightmapEdgeAutomatic(ts, tt, edge, dest, stride, inverse); else { computeTileLightmapEdgePrecomputed(ts, tt, edge, dest, stride, inverse); // Add the inlufence of TLI. addTileLightmapEdgeWithTLI(ts, tt, edge, dest, stride, inverse); } // modulate dest with tileColors (at center of lumels). modulateTileLightmapEdgeWithTileColors(ts, tt, edge, dest, stride, inverse); } // *************************************************************************** void CPatch::computeTileLightmapPixel(uint ts, uint tt, uint s, uint t, CRGBA *dest) { if(getLandscape()->getAutomaticLighting()) computeTileLightmapPixelAutomatic(ts, tt, s, t, dest); else { computeTileLightmapPixelPrecomputed(ts, tt, s, t, dest); // Add the inlufence of TLI. addTileLightmapPixelWithTLI(ts, tt, s, t, dest); } // modulate dest with tileColors (at center of lumels). modulateTileLightmapPixelWithTileColors(ts, tt, s, t, dest); } // *************************************************************************** void CPatch::computeTileLightmapPixelAroundCorner(const CVector2f &stIn, CRGBA *dest, bool lookAround) { bool mustLookOnNeighbor= false; // Get the Uv, in [0,Order?*NL_LUMEL_BY_TILE] basis (ie lumel basis). sint u, v; u= (sint)floor(stIn.x*NL_LUMEL_BY_TILE); v= (sint)floor(stIn.y*NL_LUMEL_BY_TILE); // if allowed, try to go on neighbor patch. if(lookAround) { // try to know if we must go on a neighbor patch (maybe false with bind X/1). if( u<0 || u>=OrderS*NL_LUMEL_BY_TILE || v<0 || v>=OrderT*NL_LUMEL_BY_TILE) mustLookOnNeighbor= true; } // If we must get (if possible) the pixel in the current patch, do it. if(!mustLookOnNeighbor) { // if out this patch, abort. if( u<0 || u>=OrderS*NL_LUMEL_BY_TILE || v<0 || v>=OrderT*NL_LUMEL_BY_TILE) return; else { // get this pixel. computeTileLightmapPixel(u>>NL_LUMEL_BY_TILE_SHIFT, v>>NL_LUMEL_BY_TILE_SHIFT, u&(NL_LUMEL_BY_TILE-1), v&(NL_LUMEL_BY_TILE-1), dest); } } // else get from the best neighbor patch. else { // choose against which edge we must find the pixel. uint edge=0; if(u<0) edge=0; else if(v>=OrderT*NL_LUMEL_BY_TILE) edge=1; else if(u>=OrderS*NL_LUMEL_BY_TILE) edge=2; else if(v<0) edge=3; // retrieve info on neighbor. CBindInfo bindInfo; getBindNeighbor(edge, bindInfo); // if neighbor present. if(bindInfo.Zone) { CVector2f stOut; CPatch *patchOut; uint patchId; // Ok, search uv on this patch. CPatchUVLocator uvLocator; uvLocator.build(this, edge, bindInfo); patchId= uvLocator.selectPatch(stIn); uvLocator.locateUV(stIn, patchId, patchOut, stOut); // retry only one time, so at next call, must find the data IN htis patch (else abort). patchOut->computeTileLightmapPixelAroundCorner(stOut, dest, false); } } } // *************************************************************************** void CPatch::computeNearBlockLightmap(uint uts, uint utt, CRGBA *lightText) { sint ts= uts; sint tt= utt; // hardcoded for 10x10. nlassert(NL_TILE_LIGHTMAP_SIZE==10); CRGBA *dest; uint edge; uint corner; // Compute center of the TessBlock: the 2x2 tiles. //================= // compute tile 0,0 of the tessBlock. must decal of 1 pixel. dest= lightText+NL_TILE_LIGHTMAP_SIZE+1; computeTileLightmap(ts, tt, dest, NL_TILE_LIGHTMAP_SIZE); // compute tile 1,0 of the tessBlock. must decal of 1 pixel. dest= lightText + NL_LUMEL_BY_TILE + NL_TILE_LIGHTMAP_SIZE+1 ; computeTileLightmap(ts+1, tt, dest, NL_TILE_LIGHTMAP_SIZE); // compute tile 0,1 of the tessBlock. must decal of 1 pixel. dest= lightText + NL_LUMEL_BY_TILE*NL_TILE_LIGHTMAP_SIZE + NL_TILE_LIGHTMAP_SIZE+1 ; computeTileLightmap(ts, tt+1, dest, NL_TILE_LIGHTMAP_SIZE); // compute tile 1,1 of the tessBlock. must decal of 1 pixel. dest= lightText + NL_LUMEL_BY_TILE*NL_TILE_LIGHTMAP_SIZE + NL_LUMEL_BY_TILE + NL_TILE_LIGHTMAP_SIZE+1 ; computeTileLightmap(ts+1, tt+1, dest, NL_TILE_LIGHTMAP_SIZE); // Compute edges of the TessBlock. //================= bool edgeBorder[4]; // where are we on a border of a patch?? edgeBorder[0]= ( ts==0 ); edgeBorder[1]= ( tt == OrderT-2 ); edgeBorder[2]= ( ts == OrderS-2 ); edgeBorder[3]= ( tt==0 ); // For all edges. for(edge=0; edge<4; edge++) { // compute dest info. //============== // Are we on a vertical edge or horizontal edge?? uint stride= (edge&1)==0? NL_TILE_LIGHTMAP_SIZE : 1; // must compute on which tile we must find info. sint decalS=0; sint decalT=0; // and must compute ptr, where we store the result of the edge. switch(edge) { case 0: decalS=-1; dest= lightText + 0 + NL_TILE_LIGHTMAP_SIZE; break; case 1: decalT= 2; dest= lightText + 1 + (NL_TILE_LIGHTMAP_SIZE-1)*NL_TILE_LIGHTMAP_SIZE; break; case 2: decalS= 2; dest= lightText + (NL_TILE_LIGHTMAP_SIZE-1) + NL_TILE_LIGHTMAP_SIZE; break; case 3: decalT=-1; dest= lightText + 1; break; }; // compute the second tile dest info. CRGBA *dest2; sint decalS2; sint decalT2; // if vertical edge. if((edge&1)==0) { // Next Y tile. dest2= dest + NL_LUMEL_BY_TILE*NL_TILE_LIGHTMAP_SIZE; decalS2= decalS; decalT2= decalT+1; } else { // Next X tile. dest2= dest + NL_LUMEL_BY_TILE; decalS2= decalS+1; decalT2= decalT; } // If we are not on a border of a patch, just compute on the interior of the patch. //============== if(!edgeBorder[edge]) { // find the result on the mirrored border of us. First tile. computeTileLightmapEdge(ts+decalS, tt+decalT, (edge+2)&3, dest, stride, false); // find the result on the mirrored border of us. Second Tile. computeTileLightmapEdge(ts+decalS2, tt+decalT2, (edge+2)&3, dest2, stride, false); } // else, slightly complicated, must find the result on neighbor patch(s). //============== else { CPatchUVLocator uvLocator; CBindInfo bindInfo; bindInfo.Zone= NULL; // if smoothed edge, search the neighbor. if(getSmoothFlag(edge)) { // Build the bindInfo against this edge. getBindNeighbor(edge, bindInfo); // if ok, build the uv info against this edge. if(bindInfo.Zone) { uvLocator.build(this, edge, bindInfo); // if there is not same tile order across the edge, invalidate the smooth. // This is rare, so don't bother. if(!uvLocator.sameEdgeOrder()) bindInfo.Zone= NULL; } } // Fast reject: if no neighbor, or if not smoothed, or if edge order pb, just copy from my interior. if(!bindInfo.Zone) { CRGBA *src=0; switch(edge) { case 0: src= dest + 1; break; case 1: src= dest - NL_TILE_LIGHTMAP_SIZE; break; case 2: src= dest - 1; break; case 3: src= dest + NL_TILE_LIGHTMAP_SIZE; break; }; // fill the NL_LUMEL_BY_TILE*2 (8) pixels. for(uint n=NL_LUMEL_BY_TILE*2; n>0; n--, src+=stride, dest+=stride) *dest= *src; } // else, ok, get from neighbor. else { CVector2f stIn, stOut; CPatch *patchOut; uint patchId; uint edgeOut; bool inverse; // First Tile. //========= // to remove floor pbs, take the center of the wanted tile. stIn.set(ts+decalS + 0.5f, tt+decalT + 0.5f); patchId= uvLocator.selectPatch(stIn); uvLocator.locateUV(stIn, patchId, patchOut, stOut); // must find what edge on neighbor to compute, and if we must inverse (swap) result. // easy: the edge of the tile is the edge of the patch where we are binded. edgeOut= bindInfo.Edge[patchId]; // edge0 is oriented in T increasing order. edge1 is oriented in S increasing order. // edge2 is oriented in T decreasing order. edge3 is oriented in S decreasing order. // inverse is true if same sens on both edges (because of mirroring, sens should be different). inverse= (edge>>1)==(edgeOut>>1); // compute the lightmap on the edge of the neighbor. patchOut->computeTileLightmapEdge((sint)floor(stOut.x), (sint)floor(stOut.y), edgeOut, dest, stride, inverse); // Second Tile. //========= // same reasoning. stIn.set(ts+decalS2 + 0.5f, tt+decalT2 + 0.5f); patchId= uvLocator.selectPatch(stIn); uvLocator.locateUV(stIn, patchId, patchOut, stOut); edgeOut= bindInfo.Edge[patchId]; inverse= (edge>>1)==(edgeOut>>1); patchOut->computeTileLightmapEdge((sint)floor(stOut.x), (sint)floor(stOut.y), edgeOut, dest2, stride, inverse); } } } // Compute corners of the TessBlock. //================= bool cornerOnPatchEdge[4]; bool cornerOnPatchCorner[4]; // where are we on a edge border of a patch?? cornerOnPatchEdge[0]= edgeBorder[3] != edgeBorder[0]; cornerOnPatchEdge[1]= edgeBorder[0] != edgeBorder[1]; cornerOnPatchEdge[2]= edgeBorder[1] != edgeBorder[2]; cornerOnPatchEdge[3]= edgeBorder[2] != edgeBorder[3]; // where are we on a corner border of a patch?? cornerOnPatchCorner[0]= edgeBorder[3] && edgeBorder[0]; cornerOnPatchCorner[1]= edgeBorder[0] && edgeBorder[1]; cornerOnPatchCorner[2]= edgeBorder[1] && edgeBorder[2]; cornerOnPatchCorner[3]= edgeBorder[2] && edgeBorder[3]; // For all corners. for(corner=0; corner<4; corner++) { // compute dest info. //============== // must compute on which tile we must find info. sint decalS=0; sint decalT=0; // and must compute ptr, where we store the result of the corner. switch(corner) { case 0: decalS=-1; decalT=-1; dest= lightText + 0 + 0; break; case 1: decalS=-1; decalT= 2; dest= lightText + 0 + (NL_TILE_LIGHTMAP_SIZE-1)*NL_TILE_LIGHTMAP_SIZE; break; case 2: decalS= 2; decalT= 2; dest= lightText + (NL_TILE_LIGHTMAP_SIZE-1) + (NL_TILE_LIGHTMAP_SIZE-1)*NL_TILE_LIGHTMAP_SIZE; break; case 3: decalS= 2; decalT=-1; dest= lightText + (NL_TILE_LIGHTMAP_SIZE-1) + 0; break; }; // If we are not on a border of a patch, just compute on the interior of the patch. //============== // if the corner is IN the patch. if(!cornerOnPatchCorner[corner] && !cornerOnPatchEdge[corner]) { // what pixel to read. uint subS, subT; if(decalS==-1) subS= NL_LUMEL_BY_TILE-1; else subS= 0; if(decalT==-1) subT= NL_LUMEL_BY_TILE-1; else subT= 0; // find the result on the corner of the neighbor tile. computeTileLightmapPixel(ts+decalS, tt+decalT, subS, subT, dest); } else { // By default, fill the corner with our interior corner. Because other methods may fail. CRGBA *src=0; switch(corner) { case 0: src= dest + 1 + NL_TILE_LIGHTMAP_SIZE; break; case 1: src= dest + 1 - NL_TILE_LIGHTMAP_SIZE; break; case 2: src= dest - 1 - NL_TILE_LIGHTMAP_SIZE; break; case 3: src= dest - 1 + NL_TILE_LIGHTMAP_SIZE; break; }; // fill the pixel. *dest= *src; // get the coordinate of the corner, in our [0,Order] basis. get it at the center of the pixel. CBindInfo bindInfo; CPatchUVLocator uvLocator; CVector2f stIn, stOut; CPatch *patchOut; uint patchId; float decX, decY; static const float lumelSize= 1.f/NL_LUMEL_BY_TILE; static const float semiLumelSize= 0.5f*lumelSize; if(decalS==-1) decX= - semiLumelSize; else decX= 2+ semiLumelSize; if(decalT==-1) decY= - semiLumelSize; else decY= 2+ semiLumelSize; stIn.set( ts+decX, tt+decY); // if the corner is on One edge only of the patch. if(cornerOnPatchEdge[corner]) { // find the edge where to read this corner: hard edge after or before this corner. if(edgeBorder[corner]) edge= corner; else edge= (corner+4-1) & 3; // if this edge is smoothed, find on neighbor. if(getSmoothFlag(edge)) { // retrieve neigbhor info. getBindNeighbor(edge, bindInfo); // if neighbor present. if(bindInfo.Zone) { // Ok, search uv on this patch. uvLocator.build(this, edge, bindInfo); patchId= uvLocator.selectPatch(stIn); uvLocator.locateUV(stIn, patchId, patchOut, stOut); // Get the Uv, in [0,Order?*NL_LUMEL_BY_TILE] basis (ie lumel basis), and get from neighbor patch sint u, v; u= (sint)floor(stOut.x*NL_LUMEL_BY_TILE); v= (sint)floor(stOut.y*NL_LUMEL_BY_TILE); patchOut->computeTileLightmapPixel(u>>NL_LUMEL_BY_TILE_SHIFT, v>>NL_LUMEL_BY_TILE_SHIFT, u&(NL_LUMEL_BY_TILE-1), v&(NL_LUMEL_BY_TILE-1), dest); } } // else we must still smooth with our lumel on this patch, so get it from neighbor on edge. else { // first, clamp to our patch (recenter on the previous pixel) if(stIn.x<0) stIn.x+= lumelSize; else if(stIn.x>OrderS) stIn.x-= lumelSize; else if(stIn.y<0) stIn.y+= lumelSize; else if(stIn.y>OrderT) stIn.y-= lumelSize; // Get the Uv, in [0,Order?*NL_LUMEL_BY_TILE] basis (ie lumel basis), and get from this patch sint u, v; u= (sint)floor(stIn.x*NL_LUMEL_BY_TILE); v= (sint)floor(stIn.y*NL_LUMEL_BY_TILE); computeTileLightmapPixel(u>>NL_LUMEL_BY_TILE_SHIFT, v>>NL_LUMEL_BY_TILE_SHIFT, u&(NL_LUMEL_BY_TILE-1), v&(NL_LUMEL_BY_TILE-1), dest); } } // else it is on a corner of the patch. else { // if the corner of the patch (same as tile corner) is smoothed, find on neighbor if(getCornerSmoothFlag(corner)) { // retrieve neigbhor info. NB: use edgeId=corner, (corner X is the start of the edge X)it works. getBindNeighbor(corner, bindInfo); // if neighbor present. if(bindInfo.Zone) { // Ok, search uv on this patch. uvLocator.build(this, corner, bindInfo); patchId= uvLocator.selectPatch(stIn); uvLocator.locateUV(stIn, patchId, patchOut, stOut); // same reasoning as in computeDisplaceCornerSmooth(), must find the pixel on the neighbor // of our neighbor. But the current corner may be a corner on a bind X/1. All is managed by doing // this way. patchOut->computeTileLightmapPixelAroundCorner(stOut, dest, true); } } } } } } // *************************************************************************** void CPatch::getTileLightMap(uint ts, uint tt, CPatchRdrPass *&rdrpass) { // TessBlocks must have been allocated. nlassert(TessBlocks.size()!=0); // get what tessBlock to use. uint numtb, numtm; computeTbTm(numtb, numtm, ts, tt); CTessBlock &tessBlock= TessBlocks[numtb]; // If the lightmap Id has not been computed, compute it. if(tessBlock.LightMapRefCount==0) { // Compute the lightmap texture, with help of TileColors, with neighboring info etc... CRGBA lightText[NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE]; computeNearBlockLightmap(ts&(~1), tt&(~1), lightText); // Create a rdrPass with this texture, donlod to Driver etc... tessBlock.LightMapId= Zone->Landscape->getTileLightMap(lightText, rdrpass); // store this rdrpass ptr. tessBlock.LightMapRdrPass= rdrpass; } // We are using this 2x2 tiles lightmap. tessBlock.LightMapRefCount++; // get the rdrpass ptr of the tessBlock lightmap rdrpass= tessBlock.LightMapRdrPass; } // *************************************************************************** void CPatch::getTileLightMapUvInfo(uint ts, uint tt, CVector &uvScaleBias) { // TessBlocks must have been allocated. nlassert(TessBlocks.size()!=0); // get what tessBlock to use. uint numtb, numtm; computeTbTm(numtb, numtm, ts, tt); CTessBlock &tessBlock= TessBlocks[numtb]; // Get the uvScaleBias for the tile 0,0 of the block. Zone->Landscape->getTileLightMapUvInfo(tessBlock.LightMapId, uvScaleBias); // Must increment the bias, for the good tile in the 2x2 block Lightmap. uint tsDec= ts & 1; uint ttDec= tt & 1; uvScaleBias.x+= tsDec * uvScaleBias.z; uvScaleBias.y+= ttDec * uvScaleBias.z; } // *************************************************************************** void CPatch::releaseTileLightMap(uint ts, uint tt) { // TessBlocks must have been allocated. nlassert(TessBlocks.size()!=0); // get what tessBlock to use. uint numtb, numtm; computeTbTm(numtb, numtm, ts, tt); CTessBlock &tessBlock= TessBlocks[numtb]; // If no more tileMaterial use this lightmap, release it. nlassert(tessBlock.LightMapRefCount>0); tessBlock.LightMapRefCount--; if(tessBlock.LightMapRefCount==0) { Zone->Landscape->releaseTileLightMap(tessBlock.LightMapId); } } // *************************************************************************** void CPatch::packShadowMap (const uint8 *pLumelSrc) { // Number of lumel by lines uint lumelCount=OrderS*NL_LUMEL_BY_TILE; // Number of block in a line nlassert ((lumelCount&0x3)==0); uint numLumelBlock=lumelCount>>2; // Number of line uint lineCount=OrderT*NL_LUMEL_BY_TILE; // Number of block line nlassert ((lineCount&0x3)==0); uint numLineBlock=lineCount>>2; // Resize the compressed buffer CompressedLumels.resize (numLumelBlock*numLineBlock*NL_BLOCK_LUMEL_COMPRESSED_SIZE); // Input of compresed data uint8 *compressedData=&CompressedLumels[0]; // Each line block for (uint lineBlock=0; lineBlockalphaMax) alphaMax=originalBlock[i]; } // *** Try to compress by 2 methods // Blcok uncompressed uint8 uncompressedBlock[4*4]; // Pack the block packLumelBlock (compressedData, originalBlock, alphaMin, alphaMax); // Unpack the block unpackLumelBlock (uncompressedBlock, compressedData); // Eval error uint firstMethod=evalLumelBlock (originalBlock, uncompressedBlock, NL_LUMEL_BY_TILE, NL_LUMEL_BY_TILE); // second compression uint8 secondCompressedBlock[NL_BLOCK_LUMEL_COMPRESSED_SIZE]; packLumelBlock (secondCompressedBlock, originalBlock, alphaMax, alphaMin); // Unpack the block unpackLumelBlock (uncompressedBlock, secondCompressedBlock); // Eval error uint secondMethod=evalLumelBlock (originalBlock, uncompressedBlock, NL_LUMEL_BY_TILE, NL_LUMEL_BY_TILE); // Second best ? if (secondMethod>2; // Number of line uint lineCount=OrderT*NL_LUMEL_BY_TILE; // Number of block line nlassert ((lineCount&0x3)==0); uint numLineBlock=lineCount>>2; // Size of the lumel array uint size=numLineBlock*numLumelBlock*8; // 4 bits per lumel CompressedLumels.resize (size); // No line have shadows. memset (&CompressedLumels[0], 0, size); } // *************************************************************************** // *************************************************************************** // Functions (C/ASM). // *************************************************************************** // *************************************************************************** // *************************************************************************** #define a00 tex[0] #define a10 tex[1] #define a20 tex[2] #define a30 tex[3] #define a40 tex[4] #define a01 tex[5] #define a11 tex[6] #define a21 tex[7] #define a31 tex[8] #define a41 tex[9] #define a02 tex[10] #define a12 tex[11] #define a22 tex[12] #define a32 tex[13] #define a42 tex[14] #define a03 tex[15] #define a13 tex[16] #define a23 tex[17] #define a33 tex[18] #define a43 tex[19] #define a04 tex[20] #define a14 tex[21] #define a24 tex[22] #define a34 tex[23] #define a44 tex[24] void NL3D_bilinearTileLightMap(CRGBA *tex) { // Fast bilinear of a 5x5 tile. // Corners must be set. // Later: pass it to ASM. // Fill first column 0 and column 4. a02.avg2(a00, a04); a01.avg2(a00, a02); a03.avg2(a02, a04); a42.avg2(a40, a44); a41.avg2(a40, a42); a43.avg2(a42, a44); // Fill Line 0. a20.avg2(a00, a40); a10.avg2(a00, a20); a30.avg2(a20, a40); // Fill Line 1. a21.avg2(a01, a41); a11.avg2(a01, a21); a31.avg2(a21, a41); // Fill Line 2. a22.avg2(a02, a42); a12.avg2(a02, a22); a32.avg2(a22, a42); // Fill Line 3. a23.avg2(a03, a43); a13.avg2(a03, a23); a33.avg2(a23, a43); // Fill Line 4. a24.avg2(a04, a44); a14.avg2(a04, a24); a34.avg2(a24, a44); } // *************************************************************************** // *************************************************************************** // Lightmap get interface. // *************************************************************************** // *************************************************************************** // *************************************************************************** uint8 CPatch::getLumel(const CUV &uv) const { // compute tile coord and lumel coord. sint ts, tt; // get in lumel coord. sint w= (OrderS<>=8; tt= NLMISC::OptFastFloor(uv.V* (h<<8) + 128); tt>>=8; clamp(ts, 0, w-1); clamp(tt, 0, h-1); // get the lumel uint8 ret; getTileLumelmapPixelPrecomputed(ts>>NL_LUMEL_BY_TILE_SHIFT, tt>>NL_LUMEL_BY_TILE_SHIFT, ts&(NL_LUMEL_BY_TILE-1), tt&(NL_LUMEL_BY_TILE-1), ret); return ret; } // *************************************************************************** // *************************************************************************** // TileLightInfluences // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::resetTileLightInfluences() { // Fill default. TileLightInfluences.resize((OrderS/2 +1) * (OrderT/2 +1)); // Disable All light influence on all points for(uint i=0;i &pointLightList) const { /* WARNING !! only CPointLightNamed must be added here (used for convenience by CPatch::generateTileVegetable() ) */ // Compute TLI coord for BiLinear. sint x,y; // There is (OrderS/2+1) * (OrderT/2+1) tileLightInfluences (TLI). sint w= (OrderS>>1); sint h= (OrderT>>1); sint wTLI= w+1; // fastFloor: use a precision of 256 to avoid doing OptFastFloorBegin. x= NLMISC::OptFastFloor(uv.U * (w<<8)); y= NLMISC::OptFastFloor(uv.V * (h<<8)); clamp(x, 0, w<<8); clamp(y, 0, h<<8); // compute the TLI coord, and the subCoord for bilinear. sint xTLI,yTLI, xSub, ySub; xTLI= x>>8; clamp(xTLI, 0, w-1); yTLI= y>>8; clamp(yTLI, 0, h-1); // Hence, xSub and ySub range is [0, 256]. xSub= x - (xTLI<<8); ySub= y - (yTLI<<8); // Use a CLightInfluenceInterpolator to biLinear light influence CLightInfluenceInterpolator interp; // Must support only 2 light per TLI. nlassert(CTileLightInfluence::NumLightPerCorner==2); nlassert(CLightInfluenceInterpolator::NumLightPerCorner==2); // Get ref on array of PointLightNamed. CPointLightNamed *zonePointLights= NULL; if( getZone()->_PointLightArray.getPointLights().size() >0 ) { // const_cast, because will only change _IdInfluence, and // also because CLightingManager will call appendLightedModel() zonePointLights= const_cast(&(getZone()->_PointLightArray.getPointLights()[0])); } // For 4 corners. for(y=0;y<2;y++) { for(x=0;x<2;x++) { // get ref on TLI, and on corner. const CTileLightInfluence &tli= TileLightInfluences[ (yTLI+y)*wTLI + xTLI+x ]; CLightInfluenceInterpolator::CCorner &corner= interp.Corners[y*2 + x]; // For all lights uint lid; for(lid= 0; lid_PointLightArray.getPointLights().size() >0 ) { const CPointLightNamed *zonePointLights; zonePointLights= (&(getZone()->_PointLightArray.getPointLights()[0])); uint wTLI= (OrderS>>1)+1; const CTileLightInfluence &tli= TileLightInfluences[ y*wTLI + x]; for(uint lid=0;lidgetPointLightDiffuseMaterial() ); // modulate with precomputed diffuse factor lightCol.modulateFromuiRGBOnly(lightCol, tli.getDiffuseLightFactor(lid) ); // add to the corner ret.addRGBOnly(ret, lightCol); } } return ret; } // *************************************************************************** void CPatch::getCurrentTileTLIColors(uint ts, uint tt, NLMISC::CRGBA corners[4]) { // Get ref on array of PointLightNamed. if( getZone()->_PointLightArray.getPointLights().size() >0 ) { // get coord of the tessBlock uint tbs= ts>>1; uint tbt= tt>>1; // get tile id local to tessBlock. uint tls= ts-(tbs<<1); uint tlt= tt-(tbt<<1); // For each corner of the tessBlock, compute lighting with pointLights. CRGBA tbCorners[4]; for(uint y=0;y<2;y++) { for(uint x=0;x<2;x++) { CRGBA &cornerCol= tbCorners[y*2+x]; cornerCol= getCurrentTLIColor(tbs+x, tbt+y); } } // Then biLinear to tile Level (tessBlock==2x2 tiles). CRGBA tbEdges[4]; CRGBA tbMiddle; // left. tbEdges[0].avg2RGBOnly(tbCorners[0], tbCorners[2]); tbEdges[0].A = 255; // bottom tbEdges[1].avg2RGBOnly(tbCorners[2], tbCorners[3]); tbEdges[1].A = 255; // right tbEdges[2].avg2RGBOnly(tbCorners[1], tbCorners[3]); tbEdges[2].A = 255; // up tbEdges[3].avg2RGBOnly(tbCorners[0], tbCorners[1]); tbEdges[3].A = 255; // middle. tbMiddle.avg2RGBOnly(tbEdges[0], tbEdges[2]); tbMiddle.A = 255; // just copy result according to tile pos in tessBlock. if(tlt==0) { if(tls==0) { corners[0]= tbCorners[0]; corners[1]= tbEdges[3]; corners[2]= tbEdges[0]; corners[3]= tbMiddle; } else { corners[0]= tbEdges[3]; corners[1]= tbCorners[1]; corners[2]= tbMiddle; corners[3]= tbEdges[2]; } } else { if(tls==0) { corners[0]= tbEdges[0]; corners[1]= tbMiddle; corners[2]= tbCorners[2]; corners[3]= tbEdges[1]; } else { corners[0]= tbMiddle; corners[1]= tbEdges[2]; corners[2]= tbEdges[1]; corners[3]= tbCorners[3]; } } } else { // Just fill with 0s. corners[0]= CRGBA::Black; corners[1]= CRGBA::Black; corners[2]= CRGBA::Black; corners[3]= CRGBA::Black; } } // *************************************************************************** void CPatch::addTileLightmapWithTLI(uint ts, uint tt, NLMISC::CRGBA *dest, uint stride) { // compute colors ar corners of the tile. CRGBA corners[4]; getCurrentTileTLIColors(ts, tt, corners); // Bilinear accross the tile, and add to dest. uint x, y; for(y=0; y>1)+1; uint hTLI= (OrderT>>1)+1; // colros at corners of tiles size. uint wTC= OrderS+1; uint wTCx2= wTC*2; uint hTC= OrderT+1; uint x, y; // Compute TLI colors at each corner of each TessBlocks. //============= for(y=0;yR >>= 1; dst->G >>= 1; dst->B >>= 1; // skip 2 tiles corners. dst++; dst++; } } // Compute TLI colors at each corner of each Tiles. //============= // Compute corner at middle of vertical TessBlock edges. for(y=0;yavg2RGBOnly(*dst, *(dst + wTCx2) ); // skip 2 tiles corners. dst++; dst++; } } // Compute corner at middle of horizontal TessBlock edges, and at middle of TessBlock. for(y=0;yavg2RGBOnly(*dst, *(dst+2)); // skip 2 tiles corners. dst++; dst++; } } } // *************************************************************************** // *************************************************************************** // UpdateLighting. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::linkBeforeNearUL(CPatch *patchNext) { nlassert(patchNext); // first, unlink others from me. NB: works even if _ULNearPrec==_ULNearNext==this. _ULNearNext->_ULNearPrec= _ULNearPrec; _ULNearPrec->_ULNearNext= _ULNearNext; // link to igNext. _ULNearNext= patchNext; _ULNearPrec= patchNext->_ULNearPrec; // link others to me. _ULNearNext->_ULNearPrec= this; _ULNearPrec->_ULNearNext= this; } // *************************************************************************** void CPatch::unlinkNearUL() { // first, unlink others from me. NB: works even if _ULNearPrec==_ULNearNext==this. _ULNearNext->_ULNearPrec= _ULNearPrec; _ULNearPrec->_ULNearNext= _ULNearNext; // reset _ULNearPrec= this; _ULNearNext= this; } // *************************************************************************** uint CPatch::updateTessBlockLighting(uint numTb) { // TessBlocks must have been allocated and tessBlockId must be ok. nlassert(numTb>1; uint ts= numTb&(tbWidth-1); uint tt= numTb/tbWidth; // expand to tile coordinate. ts*= 2; tt*= 2; // get what tessBlock to use. CTessBlock &tessBlock= TessBlocks[numTb]; // If the lightmap Id has not been computed, quit if(tessBlock.LightMapRefCount==0) return 0; else { // Recompute the lightmap texture, with help of TileColors, with neighboring info etc... CRGBA lightText[NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE]; computeNearBlockLightmap(ts&(~1), tt&(~1), lightText); // donlod this texture to Driver etc... Zone->Landscape->refillTileLightMap(tessBlock.LightMapId, lightText); // return number of pixels computed. return NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE; } } // *************************************************************************** // *************************************************************************** // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::addRefDLMContext() { // the patch must be compiled. nlassert(Zone); // if 0, create the context. if(_DLMContextRefCount==0) { nlassert(_DLMContext==NULL); _DLMContext= new CPatchDLMContext; // init now the context. _DLMContext->generate(this, getLandscape()->getTextureDLM(), getLandscape()->getPatchDLMContextList()); // If the patch is visible, it may have Far Vertices created, // hence, we must refill them with good DLM Uvs. if(!isRenderClipped()) { // setup DLM Uv with new _DLMContext fillVBFarsDLMUvOnly(); } } // incRef. _DLMContextRefCount++; } // *************************************************************************** void CPatch::decRefDLMContext(uint count) { // the patch must be compiled. nlassert(Zone); nlassert(_DLMContextRefCount>0); // dec Ref. _DLMContextRefCount-= count; nlassert(_DLMContextRefCount>=0); // If 0, delete the context. if(_DLMContextRefCount==0) { delete _DLMContext; _DLMContext= NULL; // If the patch is visible, it may have Far Vertices created, // hence, we must reset their DLM Uvs (to point to black pixel) if(!isRenderClipped()) { // setup DLM Uv with new _DLMContext fillVBFarsDLMUvOnly(); } } } // *************************************************************************** void CPatch::beginDLMLighting() { nlassert(_DLMContext); // Must bkup prec pointLightCount in OldPointLightCount, and reset CurPointLightCount _DLMContext->OldPointLightCount= _DLMContext->CurPointLightCount; _DLMContext->CurPointLightCount= 0; // clear lighting, only if patch is visible if(!isRenderClipped()) // NB: no-op if src is already full black. _DLMContext->clearLighting(); } // *************************************************************************** void CPatch::processDLMLight(CPatchDLMPointLight &pl) { // add reference to currentLight, creating DLMContext if needed addRefDLMContext(); // add curLight counter _DLMContext->CurPointLightCount++; // compute lighting, only if patch is visible if(!isRenderClipped()) _DLMContext->addPointLightInfluence(pl); } // *************************************************************************** void CPatch::endDLMLighting() { nlassert(_DLMContext); // delete reference from old pointLight influences, at prec render() pass. _DLMContext may be deleted here, // if no more lights use it, and if the patch is not in Near. decRefDLMContext(_DLMContext->OldPointLightCount); } } // NL3D