khanat-opennel-code/code/nel/src/3d/patchdlm_context.cpp

1268 lines
33 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
#include "std3d.h"
#include "nel/3d/patchdlm_context.h"
#include "nel/3d/patch.h"
#include "nel/3d/bezier_patch.h"
#include "nel/3d/point_light.h"
#include "nel/3d/texture_dlm.h"
#include "nel/misc/fast_floor.h"
#include "nel/3d/tile_far_bank.h"
#include "nel/3d/landscape.h"
#include "nel/misc/system_info.h"
#include "nel/misc/fast_mem.h"
using namespace std;
using namespace NLMISC;
namespace NL3D
{
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CPatchDLMPointLight::compile(const CPointLight &pl, NLMISC::CRGBA landDiffMat, float maxAttEnd)
{
nlassert(maxAttEnd>0);
// copy color
R= (float) (( pl.getDiffuse().R*(landDiffMat.R+1) ) >>8);
G= (float) (( pl.getDiffuse().G*(landDiffMat.G+1) ) >>8);
B= (float) (( pl.getDiffuse().B*(landDiffMat.B+1) ) >>8);
// Copy Spot/Pos/Dir.
IsSpot= pl.getType() == CPointLight::SpotLight;
Pos= pl.getPosition();
Dir= pl.getSpotDirection();
// compute spot params
if(IsSpot)
{
CosMax= cosf(pl.getSpotAngleBegin());
CosMin= cosf(pl.getSpotAngleEnd());
}
else
{
// with tesse Values, we have always (cosSpot-CosMin) * OOCosDelta > 1.0f
CosMax= -1;
CosMin= -2;
}
OOCosDelta= 1.f / (CosMax-CosMin);
// compute att params
AttMax= pl.getAttenuationEnd();
AttMin= pl.getAttenuationBegin();
// infinite pointLight?
if(AttMax==0)
{
AttMax= maxAttEnd;
AttMin= maxAttEnd*0.99f;
}
// To big pointLigt?
else if(AttMax>maxAttEnd)
{
AttMax= maxAttEnd;
AttMin= min(AttMin, maxAttEnd*0.99f);
}
// compile distance
OOAttDelta= 1.f / (AttMin-AttMax);
// Compute bounding sphere.
// If not a spot or if angleMin>Pi/2
if(!IsSpot || CosMin<0)
{
// Take sphere of pointlight sphere
BSphere.Center= Pos;
BSphere.Radius= AttMax;
// The bbox englobe the sphere.
BBox.setCenter(Pos);
BBox.setHalfSize(CVector(AttMax, AttMax, AttMax));
}
else
{
// Compute BSphere.
//==============
// compute sinus of AngleMin
float sinMin= sqrtf(1-sqr(CosMin));
// Test 2 centers: Center of radius along Dir: Pos+Dir*AttMax/2, and intersection of end cone with line (Pos,Dir)
// Don't know why but I think they are sufficiently good :)
// See below for computing of those centers.
/* compute radius of each sphere by taking max of 3 distances: distance to spotLight center, distance
to spotLight forward extremity, and distance to spotLight circle interstion Cone/Sphere. (named DCCS)
NB: Do the compute with radius=1 at first, then multiply later.
*/
float radius1= 0.5f; // =max(0.5, 0.5); max distance to spot center and extremity center :)
// for distance DCCS, this is the hypothenuse of (cosMin-0.5) + sinMin.
float dccs= sqrtf( sqr(CosMin-0.5f) + sqr(sinMin));
// take the bigger.
radius1= max(radius1, dccs );
// Same reasoning for center2.
float radius2= max(CosMin, 1-CosMin); // max distance to spot center and extremity center :)
// for distance DCCS, it is simply sinMin!!
dccs= sinMin;
// take the bigger.
radius2= max(radius2, dccs );
// Then take the center which gives the smaller sphere
if(radius1<radius2)
{
BSphere.Center= Pos + (Dir*0.5f*AttMax);
// radius1 E [0,1], must take real size.
BSphere.Radius= radius1 * AttMax;
}
else
{
BSphere.Center= Pos + (Dir*CosMin*AttMax);
// radius2 E [0,1], must take real size.
BSphere.Radius= radius2 * AttMax;
}
// Compute BBox.
//==============
// just take bbox of the sphere, even if not optimal.
BBox.setCenter(BSphere.Center);
float rad= BSphere.Radius;
BBox.setHalfSize( CVector(rad, rad, rad) );
}
}
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CPatchDLMContext::CPatchDLMContext()
{
_Patch= NULL;
_DLMTexture= NULL;
_DLMContextList= NULL;
OldPointLightCount= 0;
CurPointLightCount= 0;
// By default there is crash in textures
_IsSrcTextureFullBlack= false;
_IsDstTextureFullBlack= false;
}
// ***************************************************************************
CPatchDLMContext::~CPatchDLMContext()
{
// release the lightmap in the texture
if(_DLMTexture)
{
_DLMTexture->releaseLightMap(TextPosX, TextPosY);
}
// exit
_Patch= NULL;
_DLMTexture= NULL;
// remove it from list.
if(_DLMContextList)
_DLMContextList->remove(this);
}
// ***************************************************************************
#ifdef NL_DLM_TILE_RES
// if tileRes defined, still start to clip at tessBlockLevel.
#define NL_DLM_CLIP_FACTOR 2
#else
// start to clip at tessBlockLevel (same as dlm map precision)
#define NL_DLM_CLIP_FACTOR 1
#endif
#define NL_DLM_CLIP_NUM_LEVEL 3
// ***************************************************************************
bool CPatchDLMContext::generate(CPatch *patch, CTextureDLM *textureDLM, CPatchDLMContextList *ctxList)
{
nlassert(patch);
nlassert(textureDLM);
nlassert(ctxList);
// keep info on patch/landscape.
_Patch= patch;
_DLMTexture= textureDLM;
// append to the list.
_DLMContextList= ctxList;
_DLMContextList->append(this);
// Get Texture Size info;
#ifdef NL_DLM_TILE_RES
// get coord at cornes of tiles
Width= (_Patch->getOrderS())+1;
Height= (_Patch->getOrderT())+1;
#else
// get coord at cornes of tessBlocks
Width= (_Patch->getOrderS()/2)+1;
Height= (_Patch->getOrderT()/2)+1;
#endif
// Allocate space in texture
if(!_DLMTexture->createLightMap(Width, Height, TextPosX, TextPosY))
{
// Mark as not allocated.
// NB: the context still work with NULL _DLMTexture, but do nothing (excpetionnal case)
_DLMTexture= NULL;
}
// If the lightmap is correclty allocated in the global texture, compute UVBias.
if(_DLMTexture)
{
// Compute patch UV matrix from pixels. Must map to center of pixels.
DLMUScale= (float)(Width-1) / (float)_DLMTexture->getWidth();
DLMVScale= (float)(Height-1) / (float)_DLMTexture->getHeight();
DLMUBias= ((float)TextPosX+0.5f) / (float)_DLMTexture->getWidth();
DLMVBias= ((float)TextPosY+0.5f) / (float)_DLMTexture->getHeight();
}
else
{
// Build UVBias such that the UVs point to Black
// NB: TextureDLM ensure that point (MaxX,MaxY) of texture is black.
DLMUScale= 0;
DLMVScale= 0;
DLMUBias= 1;
DLMVBias= 1;
}
// TestYoyo: to see lightmap usage in the big texture
/*DLMUScale= _Patch->getOrderS();
DLMVScale= _Patch->getOrderT();
DLMUBias= 0;
DLMVBias= 0;*/
// Bound 8bits UV for Vegetable. This is to ensure vegetable Dlm UVs won't peek in neighbor lightmaps.
sint tmpU, tmpV;
// Bound U minimum
tmpU= (sint)ceil ( (DLMUBias) * 255 );
clamp(tmpU, 0, 255);
MinU8= tmpU;
// Bound U maximum
tmpU= (sint)floor( (DLMUBias+DLMUScale) * 255 );
clamp(tmpU, (sint)MinU8, 255);
MaxU8= tmpU;
// Bound V minimum
tmpV= (sint)ceil ( (DLMVBias) * 255 );
clamp(tmpV, 0, 255);
MinV8= tmpV;
// Bound V maximum
tmpV= (sint)floor( (DLMVBias+DLMVScale) * 255 );
clamp(tmpV, (sint)MinV8, 255);
MaxV8= tmpV;
// Allocate RAM Lightmap
_LightMap.resize(Width*Height);
// generate Vertices: pos and normals
_Vertices.resize(Width*Height);
float s, t;
float ds= 1.0f / (Width-1);
float dt= 1.0f / (Height-1);
// eval all the patch.
t= 0;
uint x,y;
for(y=0; y<Height; y++, t+=dt)
{
s= 0;
for(x=0; x<Width; x++, s+=ds)
{
CVertex &vert= _Vertices[y*Width+x];
// NB: use the bezier patch, and don't take Noise into account, for speed reason.
CBezierPatch *bpatch= _Patch->unpackIntoCache();
// Eval pos.
vert.Pos= bpatch->eval(s, t);
// Eval Normal.
vert.Normal= bpatch->evalNormal(s, t);
}
}
// Build bounding Spheres QuadTree
//============
// Size of the cluster array (at level 0)
uint bsx, bsy;
#ifdef NL_DLM_TILE_RES
// level 0 is at tile level.
bsx= max(1, (_Patch->getOrderS())/NL_DLM_CLIP_FACTOR );
bsy= max(1, (_Patch->getOrderT())/NL_DLM_CLIP_FACTOR );
#else
// level 0 is at tessBlock level.
bsx= max(1, (_Patch->getOrderS()/2)/NL_DLM_CLIP_FACTOR );
bsy= max(1, (_Patch->getOrderT()/2)/NL_DLM_CLIP_FACTOR );
#endif
// resize bboxes for level 0.
static vector<CAABBox> tmpBBoxes[NL_DLM_CLIP_NUM_LEVEL];
tmpBBoxes[0].resize(bsx * bsy);
// Extend all leaves clusters BBoxes with patch coordinates
for(y=0;y<bsy;y++)
{
// For Y, compute how many patch Positions used to extend bbox.
uint beginY= y*NL_DLM_CLIP_FACTOR;
uint endY= min( (y+1)*NL_DLM_CLIP_FACTOR+1, Height);
for(x=0;x<bsx;x++)
{
// For X, compute how many patch Positions used to extend bbox.
uint beginX= x*NL_DLM_CLIP_FACTOR;
uint endX= min((x+1)*NL_DLM_CLIP_FACTOR+1, Width);
// Build a bbox.
CAABBox bbox;
bbox.setCenter(_Vertices[beginY*Width + beginX].Pos);
for(uint yi= beginY; yi<endY; yi++)
{
for(uint xi= beginX; xi<endX; xi++)
{
bbox.extend(_Vertices[yi*Width + xi].Pos);
}
}
// Set the BBox info.
tmpBBoxes[0][y*bsx + x]= bbox;
}
}
// build parent BSpheres for quadTree hierarchy
uint curLevel= 0;
uint nextLevel= 1;
uint nextBsx= max(1U, bsx/2);
uint nextBsy= max(1U, bsy/2);
// the number of cluster Sons, and descendants this cluster level owns.
uint tmpClusterNumToSkip[NL_DLM_CLIP_NUM_LEVEL];
// width for this cluster level.
uint tmpClusterWidth[NL_DLM_CLIP_NUM_LEVEL];
// Number of sons per line/column
uint tmpClusterWSon[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterHSon[NL_DLM_CLIP_NUM_LEVEL];
// Fill level 0 info
tmpClusterNumToSkip[0]= 0;
tmpClusterWidth[0]= bsx;
tmpClusterWSon[0]= 0;
tmpClusterHSon[0]= 0;
uint finalClusterSize= bsx * bsy;
// If the next level has 1x1 cases, it is not useful (since same sphere as entire Patch)
while(nextBsx * nextBsy > 1 && nextLevel<NL_DLM_CLIP_NUM_LEVEL )
{
finalClusterSize+= nextBsx * nextBsy;
uint wSon= (bsx/nextBsx);
uint hSon= (bsy/nextBsy);
// compute cluster level info.
tmpClusterWidth[nextLevel]= nextBsx;
tmpClusterWSon[nextLevel]= wSon;
tmpClusterHSon[nextLevel]= hSon;
// NB: level 0 has 0 sons to skip, hence level1 must skip (1+0)*4= 4 (wSon==hSon==2)
// level2 must skip (1+4)*4= 20 (wSon==hSon==2)
tmpClusterNumToSkip[nextLevel]= (1+tmpClusterNumToSkip[curLevel]) * wSon * hSon;
// alloc bboxes.
tmpBBoxes[nextLevel].resize(nextBsx * nextBsy);
// For all cluster of upper level, build bb, as union of finers clusters
for(y=0;y<nextBsy;y++)
{
for(x=0;x<nextBsx;x++)
{
// compute coordinate in curLevel tmpBBoxes to look
uint x2= x*wSon;
uint y2= y*hSon;
// Build a bbox for 4 (or 2) children clusters
if(wSon>1 && hSon>1)
{
CAABBox bbox1;
CAABBox bbox2;
bbox1= CAABBox::computeAABBoxUnion(
tmpBBoxes[curLevel][y2*bsx + x2], tmpBBoxes[curLevel][y2*bsx + x2+1]);
bbox2= CAABBox::computeAABBoxUnion(
tmpBBoxes[curLevel][(y2+1)*bsx + x2], tmpBBoxes[curLevel][(y2+1)*bsx + x2+1]);
// final father bbox.
tmpBBoxes[nextLevel][y*nextBsx + x]= CAABBox::computeAABBoxUnion(bbox1, bbox2);
}
else if(wSon==1)
{
CAABBox bbox1;
bbox1= CAABBox::computeAABBoxUnion(
tmpBBoxes[curLevel][y2*bsx + x2], tmpBBoxes[curLevel][(y2+1)*bsx + x2]);
// final father bbox.
tmpBBoxes[nextLevel][y*nextBsx + x]= bbox1;
}
else if(hSon==1)
{
CAABBox bbox1;
bbox1= CAABBox::computeAABBoxUnion(
tmpBBoxes[curLevel][y2*bsx + x2], tmpBBoxes[curLevel][y2*bsx + x2+1]);
// final father bbox.
tmpBBoxes[nextLevel][y*nextBsx + x]= bbox1;
}
else
// impossible...
nlstop;
}
}
// upper level.
bsx= nextBsx;
bsy= nextBsy;
nextBsx= max(1U, nextBsx/2);
nextBsy= max(1U, nextBsy/2);
curLevel++;
nextLevel++;
}
// Resize clusters with size according to all levels
_Clusters.resize(finalClusterSize);
uint iDstCluster= 0;
// Fill cluster hierarchy, in _Clusters.
uint numLevels= nextLevel;
// NB: the principle is recursive, but it is "iterated", with a stack-like: tmpClusterX and tmpClusterY;
uint tmpClusterX[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterY[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterXMin[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterYMin[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterXMax[NL_DLM_CLIP_NUM_LEVEL];
uint tmpClusterYMax[NL_DLM_CLIP_NUM_LEVEL];
// we start at curLevel (the highest Level), and we must fill all the squares of this level
tmpClusterX[curLevel]= 0;
tmpClusterY[curLevel]= 0;
tmpClusterXMin[curLevel]= 0;
tmpClusterYMin[curLevel]= 0;
tmpClusterXMax[curLevel]= bsx;
tmpClusterYMax[curLevel]= bsy;
// while the "root" level is not pop
while(curLevel < numLevels)
{
// If we ended with this level (all lines done).
if(tmpClusterY[curLevel] >= tmpClusterYMax[curLevel])
{
// Ok, finished with this level, pop up.
curLevel++;
// skip.
continue;
}
nlassert(iDstCluster<_Clusters.size());
// get the bbox from current position.
CAABBox bbox= tmpBBoxes[curLevel][ tmpClusterY[curLevel] * tmpClusterWidth[curLevel] + tmpClusterX[curLevel] ];
// Fill _Clusters for this square.
_Clusters[iDstCluster].BSphere.Center= bbox.getCenter();
_Clusters[iDstCluster].BSphere.Radius= bbox.getRadius();
// If leaf level, fill special info
if(curLevel == 0)
{
_Clusters[iDstCluster].NSkips= 0;
_Clusters[iDstCluster].X= tmpClusterX[0];
_Clusters[iDstCluster].Y= tmpClusterY[0];
}
// else, set total number of sons to skips if "invisible"
else
_Clusters[iDstCluster].NSkips= tmpClusterNumToSkip[curLevel];
// next dst cluster
iDstCluster ++;
// If not Leaf level, recurs. First pass, use curLevel params (tmpClusterX...)
if(curLevel > 0)
{
// compute info for next level.
tmpClusterXMin[curLevel-1]= tmpClusterX[curLevel] * tmpClusterWSon[curLevel];
tmpClusterYMin[curLevel-1]= tmpClusterY[curLevel] * tmpClusterHSon[curLevel];
tmpClusterXMax[curLevel-1]= (tmpClusterX[curLevel]+1) * tmpClusterWSon[curLevel];
tmpClusterYMax[curLevel-1]= (tmpClusterY[curLevel]+1) * tmpClusterHSon[curLevel];
// begin iteration of child level
tmpClusterX[curLevel-1]= tmpClusterXMin[curLevel-1];
tmpClusterY[curLevel-1]= tmpClusterYMin[curLevel-1];
}
// next square for this level
tmpClusterX[curLevel]++;
// if ended for X.
if(tmpClusterX[curLevel] >= tmpClusterXMax[curLevel])
{
// reset X.
tmpClusterX[curLevel]= tmpClusterXMin[curLevel];
// next line.
tmpClusterY[curLevel]++;
}
// If not Leaf level, recurs. Second pass, after tmpClusterX and tmpClusterY of curLevel are changed
if(curLevel > 0)
{
// descend in hierarchy. (recurs)
curLevel--;
}
}
// All dst clusters must have been filled
nlassert(iDstCluster == _Clusters.size());
// PreProcess Patch TileColors.
//============
// Verify that a CTileColor is nothing more than a 565 color.
nlassert(sizeof(CTileColor)==sizeof(uint16));
#ifndef NL_DLM_TILE_RES
// retrieve patch tileColor pointer.
nlassert(_Patch->TileColors.size()>0);
CTileColor *tileColor= &_Patch->TileColors[0];
// skip 1 tiles colors per column and per row
uint wTileColor= _Patch->getOrderS()+1;
CTileColor *tcOrigin= tileColor;
// alloc _LowResTileColors at same resolution than lightmap
_LowResTileColors.resize(Width*Height);
uint16 *dstLRtc= &_LowResTileColors[0];
// For all lines of dst.
for(y=0;y<Height;y++)
{
// tileColor start of line.
tileColor= tcOrigin + y*2* wTileColor;
sint npix= Width;
// for all pixels at corner of tessBlock.
for(;npix>0; npix--, tileColor+=2, dstLRtc++)
{
*dstLRtc= tileColor->Color565;
}
}
#endif
// compute the TextureFar used for Far dynamic lightmaping.
//============
// NB: simpler to compute it at generate() time, even if not necessarly needed for near
computeTextureFar();
// fill texture with Black
//============
clearLighting();
return true;
}
// ***************************************************************************
void CPatchDLMContext::clearLighting()
{
// If the srcTexture is not already black.
if(!_IsSrcTextureFullBlack)
{
// Reset Lightmap with black.
uint count= _LightMap.size();
if(count>0)
{
memset(&_LightMap[0], 0, count * sizeof(CRGBA));
}
// Now the src lightmap is fully black
_IsSrcTextureFullBlack= true;
}
}
// ***************************************************************************
// TestYoyo: I thought this code was better, but actually, this is not the case
/*
static float NL3D_Val1= 1.f;
inline void __stdcall fastClamp01(float &x)
{
__asm
{
mov esi, x
mov eax, [esi]
// clamp to 0.
cmp eax, 0x80000001 // set carry if sign bit is set.
sbb ecx, ecx // if attDist is negative, ecx==0 , else 0xFFFFFFFF.
and eax, ecx // if attDist is negative, eax=0, else unchanged
// clamp eax to 1 (NB: now we are sure eax>=0).
cmp eax, NL3D_Val1 // set carry if < Val1.
sbb ecx, ecx // if < Val1, ecx==0xFFFFFFFF, else 0.
and eax, ecx // if < Val1, ecx= eax, else ecx=0
not ecx
and ecx, NL3D_Val1 // if > Val1, ecx== Val1, else ecx= 0.
add eax, ecx // finally, eax= val clamped to 1.
// store.
mov [esi], eax
}
}*/
// faster to do a simple clamp ???
inline void fastClamp01(float &x)
{
clamp(x, 0.f, 1.f);
}
// ***************************************************************************
void CPatchDLMContext::addPointLightInfluence(const CPatchDLMPointLight &pl)
{
uint nverts= _Vertices.size();
nlassert(nverts==_LightMap.size());
if(nverts==0)
return;
CVertex *vert= &_Vertices[0];
// precise clip: parse the quadTree of sphere
//================
uint i, x,y;
uint startX, startY, endX, endY;
startX= 0xFFFFFFFF;
startY= 0xFFFFFFFF;
endX= 0;
endY= 0;
for(i=0;i<_Clusters.size();)
{
// If the sphere intersect pl,
if(_Clusters[i].BSphere.intersect(pl.BSphere) )
{
// if this cluster is a leaf, extend start/end
if(_Clusters[i].NSkips==0)
{
x= _Clusters[i].X;
y= _Clusters[i].Y;
startX= min(startX, x);
startY= min(startY, y);
endX= max(endX, x+1);
endY= max(endY, y+1);
}
// go to next cluster (a brother, a parent or a son)
i++;
}
else
{
// if this cluster is a leaf, just go to next cluster (a parent or a brother)
if(_Clusters[i].NSkips==0)
i++;
// else, go to next brother or parent (NSkips say how to go)
else
i+= _Clusters[i].NSkips;
}
}
// if never intersect, just quit.
if(startX==0xFFFFFFFF)
return;
// get vertices in array to process.
startX*=NL_DLM_CLIP_FACTOR;
startY*=NL_DLM_CLIP_FACTOR;
endX= min(endX*NL_DLM_CLIP_FACTOR+1, Width);
endY= min(endY*NL_DLM_CLIP_FACTOR+1, Height);
// TestYoyo only.
//extern uint YOYO_LandDLCount;
//YOYO_LandDLCount+= (endX - startX) * (endY - startY);
// process all vertices
//================
float r,g,b;
CRGBA *dst= &_LightMap[0];
CVertex *originVert= vert;
CRGBA *originDst= dst;
// TestYoyo: finally, precache does not seems to impact final result.
// precache loading, for better cache use. NB: precache the entire line, ignoring clip result.
// Precache only if interesting.
//if( (endX - startX)*4>=Width && (endY-startY)>=2)
//{
//vert= originVert + startY*Width;
//dst= originDst + startY*Width;
//uint nPixelLine= (endY-startY)*Width;
//CFastMem::precacheBest(vert, nPixelLine * sizeof(CVertex));
//CFastMem::precacheBest(dst, nPixelLine * sizeof(CRGBA));
//}
// Start 24 precision, for faster compute.
OptFastFloorBegin24();
// If the pointLight is a spot, compute is more complex/slower
if(pl.IsSpot)
{
for(y=startY; y<endY; y++)
{
nverts= endX - startX;
vert= originVert + startX + y*Width;
dst= originDst + startX + y*Width;
for(;nverts>0; nverts--, vert++, dst++)
{
CVector dirToP= vert->Pos - pl.Pos;
float dist= dirToP.norm();
dirToP/= dist;
// compute cos for pl. attenuation
float cosSpot= dirToP * pl.Dir;
float attSpot= (cosSpot-pl.CosMin) * pl.OOCosDelta;
fastClamp01(attSpot);
// distance attenuation
float attDist= (dist-pl.AttMax) * pl.OOAttDelta;
fastClamp01(attDist);
// compute diffuse lighting
float diff= -(vert->Normal * dirToP);
fastClamp01(diff);
// compute colors.
diff*= attSpot * attDist;
r= pl.R*diff;
g= pl.G*diff;
b= pl.B*diff;
CRGBA col;
#ifdef NL_OS_MAC
// OptFastFloor24 should compiles but it generates an internal compiler error
col.R= (uint8)floor(r);
col.G= (uint8)floor(g);
col.B= (uint8)floor(b);
#else
// we need to do the 0xff mask or run time type check can break here because sometime r g b are > 255
col.R= uint8(OptFastFloor24(r) & 0xff);
col.G= uint8(OptFastFloor24(g) & 0xff);
col.B= uint8(OptFastFloor24(b) & 0xff);
#endif
// add to map.
#if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM)
// Fast AddClamp.
__asm
{
mov esi, dst
mov al, [esi]dst.R
add al, col.R
sbb cl, cl
or al, cl
mov [esi]dst.R, al
mov al, [esi]dst.G
add al, col.G
sbb cl, cl
or al, cl
mov [esi]dst.G, al
mov al, [esi]dst.B
add al, col.B
sbb cl, cl
or al, cl
mov [esi]dst.B, al
}
#else
// add and clamp to map.
dst->addRGBOnly(*dst, col);
#endif
}
}
}
// else, pointLight with no Spot cone attenuation
else
{
// TestYoyo
//extern void YOYO_startDLMItCount();
//YOYO_startDLMItCount();
// Compute lightmap pixels of interest
for(y=startY; y<endY; y++)
{
nverts= endX - startX;
vert= originVert + startX + y*Width;
dst= originDst + startX + y*Width;
for(;nverts>0; nverts--, vert++, dst++)
{
CVector dirToP= vert->Pos - pl.Pos;
float dist= dirToP.norm();
float OODist= 1.0f / dist;
dirToP*= OODist;
// distance attenuation
float attDist= (dist-pl.AttMax) * pl.OOAttDelta;
fastClamp01(attDist);
// compute diffuse lighting
float diff= -(vert->Normal * dirToP);
fastClamp01(diff);
// compute colors.
diff*= attDist;
r= pl.R*diff;
g= pl.G*diff;
b= pl.B*diff;
CRGBA col;
#ifdef NL_OS_MAC
// OptFastFloor24 should compiles but it generates an internal compiler error
col.R= (uint8)floor(r);
col.G= (uint8)floor(g);
col.B= (uint8)floor(b);
#else
// we need to do the 0xff mask or run time type check can break here because sometime r g b are > 255
col.R= uint8(OptFastFloor24(r) & 0xff);
col.G= uint8(OptFastFloor24(g) & 0xff);
col.B= uint8(OptFastFloor24(b) & 0xff);
#endif
// add to map.
#if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM)
// Fast AddClamp.
__asm
{
mov esi, dst
mov al, [esi]dst.R
add al, col.R
sbb cl, cl
or al, cl
mov [esi]dst.R, al
mov al, [esi]dst.G
add al, col.G
sbb cl, cl
or al, cl
mov [esi]dst.G, al
mov al, [esi]dst.B
add al, col.B
sbb cl, cl
or al, cl
mov [esi]dst.B, al
}
#else
// add and clamp to map.
dst->addRGBOnly(*dst, col);
#endif
}
}
// TestYoyo
//extern void YOYO_endDLMItCount();
//YOYO_endDLMItCount();
}
// Stop 24 bit precision
OptFastFloorEnd24();
// Src texture is modified, hence it can't be black.
//==============
_IsSrcTextureFullBlack= false;
}
// ***************************************************************************
void CPatchDLMContext::compileLighting(TCompileType compType, CRGBA modulateCte)
{
// If srcTexture is full black, and if dst texture is already full black too, don't need to update dst texture
if(! (_IsSrcTextureFullBlack && _IsDstTextureFullBlack) )
{
// if lightMap allocated
if(_LightMap.size()>0 && _DLMTexture)
{
// If the srcTexture is full black (ie no pointLight influence touch it),
if(_IsSrcTextureFullBlack)
{
// reset the texture to full black.
_DLMTexture->fillRect(TextPosX, TextPosY, Width, Height, 0);
}
// else the srcTexture is not full black (ie some pointLight influence touch it),
else
{
// if must modulate with tileColor
if(compType == ModulateTileColor)
{
// a vector can't have negative size
//nlassert(_Patch->TileColors.size()>=0);
#ifdef NL_DLM_TILE_RES
// retrieve userColor pointer.
uint16 *tileColor= (uint16*)(&_Patch->TileColors[0]);
#else
uint16 *tileColor= (uint16*)(&_LowResTileColors[0]);
#endif
// modulate and fill dest.
_DLMTexture->modulateAndfillRect565(TextPosX, TextPosY, Width, Height, &_LightMap[0], tileColor);
}
// else if must modulate with textureFar
else if(compType == ModulateTextureFar)
{
// modulate and fill dest.
_DLMTexture->modulateAndfillRect8888(TextPosX, TextPosY, Width, Height, &_LightMap[0], &_TextureFar[0]);
}
// else if must modulate with constante
else if(compType == ModulateConstant)
{
// modulate and fill dest.
_DLMTexture->modulateConstantAndfillRect(TextPosX, TextPosY, Width, Height, &_LightMap[0], modulateCte);
}
// else, no Modulate.
else
{
// just copy lightmap to texture
_DLMTexture->copyRect(TextPosX, TextPosY, Width, Height, &_LightMap[0]);
}
}
}
// copy full black state
_IsDstTextureFullBlack= _IsSrcTextureFullBlack;
}
}
// ***************************************************************************
uint CPatchDLMContext::getMemorySize() const
{
uint size= sizeof(CPatchDLMContext);
size+= _Vertices.size() * sizeof(CVertex);
size+= _LightMap.size() * sizeof(CRGBA);
size+= _Clusters.size() * sizeof(CCluster);
size+= _TextureFar.size() * sizeof(CRGBA);
#ifndef NL_DLM_TILE_RES
size+= _LowResTileColors.size() * sizeof(uint16);
#endif
return size;
}
// ***************************************************************************
void CPatchDLMContext::computeTextureFar()
{
// First compute Far at order1 Level (ie 2x2 pixels per tiles).
//==================
static vector<CRGBA> tileFars;
// Get the FarBank from landscape.
CTileFarBank &farBank= _Patch->getLandscape()->TileFarBank;
// size of the texture.
uint os= _Patch->getOrderS();
uint ot= _Patch->getOrderT();
// resize tmp texture. keep a border of 1 pixel around this texture (for average with border)
uint tfWidth= os*2+2;
uint tfHeight= ot*2+2;
uint tfSize= tfWidth * tfHeight;
tileFars.resize(tfSize);
CRGBA *dst= &tileFars[0];
// default: fill dst with black (for possible non-existing tiles).
memset(dst, 0, tfSize*sizeof(CRGBA));
// For all tiles.
uint x, y;
for(y=0; y<ot; y++)
{
for(x=0;x<os;x++)
{
// get the tile from patch.
CTileElement &tileElm= _Patch->Tiles[y*os + x];
// For all layers
for(uint l=0; l<3;l++)
{
uint16 tileId= tileElm.Tile[0];
if (tileId!=NL_TILE_ELM_LAYER_EMPTY)
{
// Get the read only pointer on the far tile
const CTileFarBank::CTileFar* pTile= farBank.getTile (tileId);
// if exist.
if(pTile && pTile->isFill (CTileFarBank::diffuse))
{
// get tile element information.
sint nRot= tileElm.getTileOrient(l);
bool is256x256;
uint8 uvOff;
tileElm.getTile256Info(is256x256, uvOff);
// compute src pixel
const CRGBA *srcPixel= pTile->getPixels(CTileFarBank::diffuse, CTileFarBank::order1);
// compute src info, for this tile rot and 256x256 context.
sint srcDeltaX = 0;
sint srcDeltaY = 0;
srcPixel= computeTileFarSrcDeltas(nRot, is256x256, uvOff, srcPixel, srcDeltaX, srcDeltaY);
// compute dst coordinate. start writing at pixel (1,1)
CRGBA *dstPixel= dst + (y*2+1)*tfWidth + x*2+1;
if(l==0)
{
// copy the tile content to the texture.
copyTileToTexture(srcPixel, srcDeltaX, srcDeltaY, dstPixel, tfWidth);
}
else
{
// blend the tile content to the texture.
blendTileToTexture(srcPixel, srcDeltaX, srcDeltaY, dstPixel, tfWidth);
}
}
else
// go to next tile.
break;
}
else
// go to next tile.
break;
}
}
}
/* copy borders pixels from border of current patch
NB: this is not correct, but visually sufficient.
To look on neighbor would be more complex.
*/
// copy lines up and down.
y= tfHeight-1;
for(x=1;x<tfWidth-1;x++)
{
// copy line 0 from line 1.
dst[0*tfWidth + x]= dst[1*tfWidth + x];
// copy last line from last line-1.
dst[y*tfWidth + x]= dst[(y-1)*tfWidth + x];
}
// copy column left and right
x= tfWidth-1;
for(y=1;y<tfHeight-1;y++)
{
// copy column 0 from column 1.
dst[y*tfWidth + 0]= dst[y*tfWidth + 1];
// copy last column from last column-1.
dst[y*tfWidth + x]= dst[y*tfWidth + x-1];
}
// copy 4 corners
x= tfWidth-1;
y= tfHeight-1;
// top-left corner
dst[0]= dst[1];
// top-right corner
dst[x]= dst[x-1];
// bottom-left corner
dst[y*tfWidth + 0]= dst[y*tfWidth + 1];
// bottom-right corner
dst[y*tfWidth + x]= dst[y*tfWidth + x-1];
// Average to DLM resolution (ie OrderS+1, OrderT+1)
//==================
// resize _TextureFar.
_TextureFar.resize(Width*Height);
CRGBA *src= &tileFars[0];
dst= &_TextureFar[0];
// for all pixels of dst texture.
for(y=0;y<Height;y++)
{
for(x=0;x<Width;x++, dst++)
{
// compute coordinate in tileFars.
uint x2, y2;
#ifdef NL_DLM_TILE_RES
x2= x * 2;
y2= y * 2;
#else
// easiest method: sample every 2 tiles.
x2= x * 4;
y2= y * 4;
#endif
// Average the 4 pixels around this tile corner
dst->avg4RGBOnly(src[y2*tfWidth + x2],
src[y2*tfWidth + x2+1],
src[(y2+1)*tfWidth + x2],
src[(y2+1)*tfWidth + x2+1]);
}
}
// Modulate result with TileColors.
//==================
// vector-size is always >= 0
//nlassert(_Patch->TileColors.size()>=0);
#ifdef NL_DLM_TILE_RES
// retrieve userColor pointer.
uint16 *tileColor= (uint16*)(&_Patch->TileColors[0]);
#else
uint16 *tileColor= (uint16*)(&_LowResTileColors[0]);
#endif
// For all pixels
dst= &_TextureFar[0];
for(sint n= Width*Height; n>0; n--, dst++, tileColor++)
{
uint16 tc= *tileColor;
// modulate R.
dst->R= ( (tc>>11) * dst->R)>>5;
// modulate G.
dst->G= (((tc>>5)&63) * dst->G)>>6;
// modulate B.
dst->B= ( (tc&31) * dst->B)>>5;
}
}
// ***************************************************************************
const CRGBA *CPatchDLMContext::computeTileFarSrcDeltas(sint nRot, bool is256x256, uint8 uvOff, const CRGBA *srcPixel, sint &srcDeltaX, sint &srcDeltaY)
{
// NB: code copied from CTextureFar::rebuildRectangle()
// The tileSize at order1 is 2.
uint tileSize= 2;
// Source size
sint sourceSize;
// Source offset (for 256)
uint sourceOffset=0;
// 256 ?
if (is256x256)
{
// On the left ?
if (uvOff&0x02)
sourceOffset+=tileSize;
// On the bottom ?
if ((uvOff==1)||(uvOff==2))
sourceOffset+=2*tileSize*tileSize;
// Yes, 256
sourceSize=tileSize<<1;
}
else
{
// No, 128
sourceSize=tileSize;
}
// Compute offset and deltas
switch (nRot)
{
case 0:
// Source pointers
srcPixel= srcPixel+sourceOffset;
// Source delta
srcDeltaX=1;
srcDeltaY=sourceSize;
break;
case 1:
{
// Source pointers
uint newOffset=sourceOffset+(tileSize-1);
srcPixel=srcPixel+newOffset;
// Source delta
srcDeltaX=sourceSize;
srcDeltaY=-1;
}
break;
case 2:
{
// Destination pointer
uint newOffset=sourceOffset+(tileSize-1)*sourceSize+tileSize-1;
srcPixel=srcPixel+newOffset;
// Source delta
srcDeltaX=-1;
srcDeltaY=-sourceSize;
}
break;
case 3:
{
// Destination pointer
uint newOffset=sourceOffset+(tileSize-1)*sourceSize;
srcPixel=srcPixel+newOffset;
// Source delta
srcDeltaX=-sourceSize;
srcDeltaY=1;
}
break;
}
return srcPixel;
}
// ***************************************************************************
void CPatchDLMContext::copyTileToTexture(const CRGBA *srcPixel, sint srcDeltaX, sint srcDeltaY, CRGBA *dstPixel, uint dstStride)
{
// copy the 2x2 tile to the texture.
// first line.
dstPixel[0]= srcPixel[0];
dstPixel[1]= srcPixel[srcDeltaX];
// second line.
dstPixel[0+dstStride]= srcPixel[srcDeltaY];
dstPixel[1+dstStride]= srcPixel[srcDeltaY+srcDeltaX];
}
// ***************************************************************************
void CPatchDLMContext::blendTileToTexture(const CRGBA *srcPixel, sint srcDeltaX, sint srcDeltaY, CRGBA *dstPixel, uint dstStride)
{
// blend the 2x2 tile with the texture.
CRGBA *dst;
CRGBA src;
// first line.
dst= &dstPixel[0]; src= srcPixel[0];
dst->blendFromuiRGBOnly(*dst, src, src.A);
dst= &dstPixel[1]; src= srcPixel[srcDeltaX];
dst->blendFromuiRGBOnly(*dst, src, src.A);
// second line.
dst= &dstPixel[0+dstStride]; src= srcPixel[srcDeltaY];
dst->blendFromuiRGBOnly(*dst, src, src.A);
dst= &dstPixel[1+dstStride]; src= srcPixel[srcDeltaY+srcDeltaX];
dst->blendFromuiRGBOnly(*dst, src, src.A);
}
} // NL3D