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

2613 lines
84 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/vegetable_manager.h"
#include "nel/3d/driver.h"
#include "nel/3d/texture_file.h"
#include "nel/misc/fast_floor.h"
#include "nel/3d/vegetable_quadrant.h"
#include "nel/3d/dru.h"
#include "nel/3d/radix_sort.h"
#include "nel/3d/scene.h"
#include "nel/3d/vegetable_blend_layer_model.h"
#include "nel/3d/vegetable_light_ex.h"
#include "nel/misc/hierarchical_timer.h"
#include <algorithm>
using namespace std;
using namespace NLMISC;
namespace NL3D
{
#define NL3D_VEGETABLE_CLIP_BLOCK_BLOCKSIZE 16
#define NL3D_VEGETABLE_SORT_BLOCK_BLOCKSIZE 64
#define NL3D_VEGETABLE_INSTANCE_GROUP_BLOCKSIZE 128
// ***************************************************************************
CVegetableManager::CVegetableManager(uint maxVertexVbHardUnlit, uint maxVertexVbHardLighted,
uint nbBlendLayers, float blendLayerDistMax) :
_ClipBlockMemory(NL3D_VEGETABLE_CLIP_BLOCK_BLOCKSIZE),
_SortBlockMemory(NL3D_VEGETABLE_SORT_BLOCK_BLOCKSIZE),
_InstanceGroupMemory(NL3D_VEGETABLE_INSTANCE_GROUP_BLOCKSIZE),
_GlobalDensity(1.f),
_NumZSortBlendLayers(nbBlendLayers), _ZSortLayerDistMax(blendLayerDistMax),
_ZSortScene(NULL)
{
uint i;
// Init all the allocators
nlassert((uint)(CVegetableVBAllocator::VBTypeCount) == 2);
_VBHardAllocator[CVegetableVBAllocator::VBTypeLighted].init( CVegetableVBAllocator::VBTypeLighted, maxVertexVbHardLighted );
_VBHardAllocator[CVegetableVBAllocator::VBTypeUnlit].init( CVegetableVBAllocator::VBTypeUnlit, maxVertexVbHardUnlit );
// Init soft one, with no vbHard vertices.
_VBSoftAllocator[CVegetableVBAllocator::VBTypeLighted].init( CVegetableVBAllocator::VBTypeLighted, 0 );
_VBSoftAllocator[CVegetableVBAllocator::VBTypeUnlit].init( CVegetableVBAllocator::VBTypeUnlit, 0 );
// NB Vertex programs are initilized during the first call to update driver.
// setup the material. Unlit (doesn't matter, lighting in VP) Alpha Test.
_VegetableMaterial.initUnlit();
_VegetableMaterial.setAlphaTest(true);
_VegetableMaterial.setBlendFunc(CMaterial::srcalpha, CMaterial::invsrcalpha);
// default light.
_DirectionalLight= (CVector(0,1, -1)).normed();
_GlobalAmbient.set(64, 64, 64, 255);
_GlobalDiffuse.set(150, 150, 150, 255);
// Wind.
_WindDirection.set(1,0,0);
_WindFrequency= 1;
_WindPower= 1;
_WindBendMin= 0;
_Time= 0;
_WindPrecRenderTime= 0;
_WindAnimTime= 0;
// Init CosTable.
for(i=0; i<NL3D_VEGETABLE_VP_LUT_SIZE; i++)
{
_CosTable[i]= (float)cos( i*2*Pi / NL3D_VEGETABLE_VP_LUT_SIZE );
}
// init to NULL _ZSortModelLayers.
_NumZSortBlendLayers= max(1U, _NumZSortBlendLayers);
_ZSortModelLayers.resize(_NumZSortBlendLayers, NULL);
_ZSortModelLayersUW.resize(_NumZSortBlendLayers, NULL);
// UL
_ULFrequency= 0;
_ULNVerticesToUpdate=0;
_ULNTotalVertices= 0;
_ULRootIg= NULL;
_ULCurrentIgRdrPass= 0;
_ULCurrentIgInstance= 0;
_ULPrecTime= 0;
_ULPrecTimeInit= false;
_ULTime= 0;
// Misc.
_NumVegetableFaceRendered= 0;
for (uint k = 0; k < NL3D_VEGETABLE_NRDRPASS; ++k)
{
_VertexProgram[k][0] = NULL;
_VertexProgram[k][1] = NULL;
}
}
// ***************************************************************************
CVegetableManager::~CVegetableManager()
{
// delete All VP
for(sint i=0; i <NL3D_VEGETABLE_NRDRPASS; i++)
{
delete _VertexProgram[i][0];
delete _VertexProgram[i][1];
_VertexProgram[i][0] = NULL;
_VertexProgram[i][1] = NULL;
}
// delete ZSort models.
if(_ZSortScene)
{
// remove models from scene.
for(uint i= 0; i<_NumZSortBlendLayers; i++)
{
_ZSortScene->deleteModel(_ZSortModelLayers[i]);
_ZSortModelLayers[i]= NULL;
_ZSortScene->deleteModel(_ZSortModelLayersUW[i]);
_ZSortModelLayersUW[i]= NULL;
}
_ZSortScene= NULL;
}
}
// ***************************************************************************
void CVegetableManager::createVegetableBlendLayersModels(CScene *scene)
{
// setup scene
nlassert(scene);
_ZSortScene= scene;
// create the layers models.
for(uint i=0;i<_NumZSortBlendLayers; i++)
{
// assert not already done.
nlassert(_ZSortModelLayers[i]==NULL);
nlassert(_ZSortModelLayersUW[i]==NULL);
_ZSortModelLayers[i]= (CVegetableBlendLayerModel*)scene->createModel(VegetableBlendLayerModelId);
_ZSortModelLayersUW[i]= (CVegetableBlendLayerModel*)scene->createModel(VegetableBlendLayerModelId);
// init owner.
_ZSortModelLayers[i]->VegetableManager= this;
_ZSortModelLayersUW[i]->VegetableManager= this;
// Set UnderWater layer for _ZSortModelLayersUW
_ZSortModelLayersUW[i]->setOrderingLayer(2);
}
}
// ***************************************************************************
CVegetableVBAllocator &CVegetableManager::getVBAllocatorForRdrPassAndVBHardMode(uint rdrPass, uint vbHardMode)
{
// If software VB
if(vbHardMode==0)
{
if(rdrPass == NL3D_VEGETABLE_RDRPASS_LIGHTED)
return _VBSoftAllocator[CVegetableVBAllocator::VBTypeLighted];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED)
return _VBSoftAllocator[CVegetableVBAllocator::VBTypeLighted];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT)
return _VBSoftAllocator[CVegetableVBAllocator::VBTypeUnlit];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED)
return _VBSoftAllocator[CVegetableVBAllocator::VBTypeUnlit];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
return _VBSoftAllocator[CVegetableVBAllocator::VBTypeUnlit];
}
// If hard VB
else
{
if(rdrPass == NL3D_VEGETABLE_RDRPASS_LIGHTED)
return _VBHardAllocator[CVegetableVBAllocator::VBTypeLighted];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED)
return _VBHardAllocator[CVegetableVBAllocator::VBTypeLighted];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT)
return _VBHardAllocator[CVegetableVBAllocator::VBTypeUnlit];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED)
return _VBHardAllocator[CVegetableVBAllocator::VBTypeUnlit];
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
return _VBHardAllocator[CVegetableVBAllocator::VBTypeUnlit];
}
// abnormal case
nlstop;
// To avoid warning;
return _VBSoftAllocator[0];
}
// ***************************************************************************
// ***************************************************************************
// Vertex Program.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
/*
Vegetable, without bend for now.
Inputs
--------
v[0] == Pos to Center of the vegetable in world space.
v[10] == Center of the vegetable in world space.
v[2] == Normal (present if lighted only)
v[3] == Color (if unlit) or DiffuseColor (if lighted)
v[4] == SecondaryColor (==ambient if Lighted, and use only Alpha part for DLM if Unlit)
v[8] == Tex0 (xy)
v[9] == BendInfo (xyz) = {BendWeight/2, BendPhase, BendFrequencyFactor}
NB: /2 because compute a quaternion
Changes: If unlit, then small changes:
v[0] == Pos to center, with v[0].w == BendWeight * v[0].norm()
v[9] == BendInfo/BlendInfo (xyzw) = {v[0].norm(), BendPhase, BendFrequencyFactor, BlendDist}
NB: v[9].w. is used only in Unlit+2Sided+AlphaBlend. But prefer do this for gestion purpose:
to have only one VBAllocator for all modes.
NB: Color and Secondary color Alpha Part contains Dynamic LightMap UV, (in 8 bits).
Constant:
--------
Setuped at beginning of CVegetableManager::render()
c[0..3]= ModelViewProjection Matrix.
c[6]= Fog vector.
c[8]= {0, 1, 0.5, 2}
c[9]= unit world space Directionnal light.
c[10]= camera pos in world space.
c[11]= {1/DistBlendTransition}
NB: DiffuseColor and AmbientColor of vertex must have been pre-multiplied by lightColor
// Bend:
c[16]= quaternion axis. w==1, and z must be 0
c[17]= { timeAnim , WindPower, WindPower*(1-WindBendMin)/2, 0 }
c[18]= High order Taylor cos coefficient: { -1/2, 1/24, -1/720, 1/40320 }
c[19]= Low order Taylor cos coefficient: { 1, -1/2, 1/24, -1/720 }
c[20]= Low order Taylor sin coefficient: { 1, -1/6, 1/120, -1/5040 }
c[21]= Special constant vector for quatToMatrix: { 0, 1, -1, 0 }
c[22]= {0.5, Pi, 2*Pi, 1/(2*Pi)}
c[23]= {64, 0, 0, 0} (size of the LUT)
// Bend Lut:
c[32..95] 64 Lut entries for cos-like animation
Fog Note:
-----------
Fog should be disabled, because not computed (for speed consideration and becasue micro-vegetation should never
be in Fog).
Speed Note:
-----------
Max program length (lighted/2Sided) is:
29 (bend-quaternion) +
16 (rotNormal + bend + lit 2Sided) +
5 (proj + tex)
2 (Dynamic lightmap copy)
51
Normal program length (unlit/2Sided/No Alpha Blend) is:
12 (bend-delta) +
1 (unlit) +
5 (proj + tex)
2 (Dynamic lightmap copy)
20
AlphaBlend program length (unlit/2Sided/Alpha Blend) is:
12 (bend-delta) +
1 (unlit) +
5 (Alpha Blend)
5 (proj + tex)
2 (Dynamic lightmap copy)
26
*/
// ***********************
/*
Fast (but less accurate) Bend program:
Result: bend pos into R5,
*/
// ***********************
const char* NL3D_FastBendProgram=
"!!VP1.0 \n\
# compute time of animation: time*freqfactor + phase. \n\
MAD R0.x, c[17].x, v[9].z, v[9].y; # R0.x= time of animation \n\
\n\
# animation: use the 64 LUT entries \n\
EXP R0.y, R0.x; # fract part=> R0.y= [0,1[ \n\
MUL R0, R0.y, c[23].xyyy; # R0.x= [0,64[ \n\
ARL A0.x, R0.x; # A0.x= index in the LUT \n\
EXP R0.y, R0.x; # R0.y= R0.x-A0.x= fp (fract part) \n\
# lookup and lerp in one it: R0= value + fp * dv. \n\
MAD R0.xy, R0.y, c[A0.x+32].zwww, c[A0.x+32].xyww; \n\
\n\
# The direction and power of the wind is encoded in the LUT. \n\
# Scale now by vertex BendFactor (stored in v[0].w) \n\
MAD R5, R0, v[0].w, v[0].xyzw; \n\
# compute 1/norm, and multiply by original norm stored in v[9].x \n\
DP3 R0.x, R5, R5; \n\
RSQ R0.x, R0.x; \n\
MUL R0.x, R0.x, v[9].x; \n\
# mul by this factor, and add to center \n\
MAD R5, R0.xxxw, R5, v[10]; \n\
\n\
# make local to camera pos. Important for ZBuffer precision \n\
ADD R5, R5, -c[10]; \n\
";
// Test
/*const char* NL3D_FastBendProgram=
"!!VP1.0 \n\
# compute time of animation: time + phase. \n\
ADD R0.x, c[17].x, v[9].y; # R0.x= time of animation \n\
\n\
# animation: f(x)= cos(x). compute a high precision cosinus \n\
EXP R0.y, R0.x; # fract part=> R0.y= [0,1] <=> [-Pi, Pi] \n\
MAD R0.x, R0.y, c[22].z, -c[22].y; # R0.x= a= [-Pi, Pi] \n\
# R0 must get a2, a4, a6, a8 \n\
MUL R0.x, R0.x, R0.x; # R0.x= a2 \n\
MUL R0.y, R0.x, R0.x; # R0= a2, a4 \n\
MUL R0.zw, R0.y, R0.xxxy; # R0= a2, a4, a6, a8 \n\
# Taylor serie: cos(x)= 1 - (1/2) a2 + (1/24) a4 - (1/720) a6 + (1/40320) a8. \n\
DP4 R0.x, R0, c[18]; # R0.x= cos(x) - 1. \n\
\n\
\n\
# original norm \n\
DP3 R2.x, v[0], v[0]; \n\
RSQ R2.y, R2.x; \n\
MUL R2.x, R2.x, R2.y; \n\
# norm, mul by factor, and add to relpos \n\
ADD R1.x, R0.x, c[8].w; \n\
MUL R0.x, v[9].x, R2.x; \n\
MUL R1, R1, R0.x; \n\
ADD R5.xyz, R1, v[0]; \n\
# mod norm \n\
DP3 R0.x, R5, R5; \n\
RSQ R0.x, R0.x; \n\
MUL R0.x, R0.x, R2.x; \n\
MAD R5, R0.x, R5, v[10]; \n\
";*/
// ***********************
/*
Bend start program:
Result: bend pos into R5, and R7,R8,R9 is the rotation matrix for possible normal lighting.
*/
// ***********************
// Splitted in 2 parts because of the 2048 char limit
const char* NL3D_BendProgramP0=
"!!VP1.0 \n\
# compute time of animation: time*freqfactor + phase. \n\
MAD R0.x, c[17].x, v[9].z, v[9].y; # R0.x= time of animation \n\
\n\
# animation: f(x)= cos(x). compute a high precision cosinus \n\
EXP R0.y, R0.x; # fract part=> R0.y= [0,1] <=> [-Pi, Pi] \n\
MAD R0.x, R0.y, c[22].z, -c[22].y; # R0.x= a= [-Pi, Pi] \n\
# R0 must get a2, a4, a6, a8 \n\
MUL R0.x, R0.x, R0.x; # R0.x= a2 \n\
MUL R0.y, R0.x, R0.x; # R0= a2, a4 \n\
MUL R0.zw, R0.y, R0.xxxy; # R0= a2, a4, a6, a8 \n\
# Taylor serie: cos(x)= 1 - (1/2) a2 + (1/24) a4 - (1/720) a6 + (1/40320) a8. \n\
DP4 R0.x, R0, c[18]; # R0.x= cos(x) - 1. \n\
\n\
# R0.x= [-2, 0]. And we want a result in BendWeight/2*WindPower*[WindBendMin, 1] \n\
MAD R0.x, R0.x, c[17].z, c[17].y; # R0.x= WindPower*[WindBendMin, 1] \n\
MUL R0.x, R0.x, v[9].x; # R0.x= angle= BendWeight/2*WindPower*[WindBendMin, 1] \n\
\n\
# compute good precision sinus and cosinus, in R0.xy. \n\
# suppose that BendWeightMax/2== 2Pi/3 => do not need to fmod() nor \n\
# to have high order taylor serie \n\
DST R1.xy, R0.x, R0.x; # R1= 1, a2 \n\
MUL R1.z, R1.y, R1.y; # R1= 1, a2, a4 \n\
MUL R1.w, R1.y, R1.z; # R1= 1, a2, a4, a6 (cos serie) \n\
MUL R2, R1, R0.x; # R2= a, a3, a5, a7 (sin serie) \n\
DP4 R0.x, R1, c[19]; # R0.x= cos(a) \n\
DP4 R0.y, R2, c[20]; # R0.y= sin(a) \n\
";
const char* NL3D_BendProgramP1=
" \n\
# build our quaternion \n\
# multiply the angleAxis by sin(a) / cos(a), where a is actually a/2 \n\
# remind: c[16].z== angleAxis.z== 0 \n\
MUL R0, c[16], R0.yyyx; # R0= quaternion.xyzw \n\
\n\
# build our matrix from this quaternion, into R7,R8,R9 \n\
# Quaternion TO matrix 3x3 in 7 ope, with quat.z==0 \n\
MUL R1, R0, c[8].w; # R1= quat2= 2*quat == 2*x, 2*y, 0, 2*w \n\
MUL R2, R1, R0.x; # R2= quatX= xx, xy, 0, wx \n\
MUL R3, R1, R0.y; # R3= quatY= xy, yy, 0, wy \n\
# NB: c[21]= {0, 1, -1, 0} \n\
# Write to w, then w = 0, this avoid an unitialized component \n\
MAD R7.xyzw, c[21].zyyw, R3.yxww, c[21].yxxw; \n\
# R7.x= a11 = 1.0f - (yy) \n\
# R7.y= a12 = xy \n\
# R7.z= a13 = wy \n\
# NB: c[21]= {0, 1, -1, 0} \n\
# Write to w, then w = 0, this avoid an unitialized component \n\
MAD R8.xyzw, c[21].yzzw, R2.yxww, c[21].xyxw; \n\
# R8.x= a21 = xy \n\
# R8.y= a22 = 1.0f - (xx) \n\
# R8.z= a23 = - wx \n\
# NB: c[21]= {0, 1, -1, 0} \n\
# Write to w, then w = 0, this avoid an unitialized component \n\
ADD R9.xyzw, R2.zwxw, R3.wzyw; # a31= 0+wy, a32= wx+0, a33= xx + yy, because z==0 \n\
MAD R9.xyzw, R9.xyzw, c[21].zyzw, c[21].xxyw; \n\
# R9.x= a31 = - wy \n\
# R9.y= a32 = wx \n\
# R9.z= a33 = 1.0f - (xx + yy) \n\
# transform pos \n\
DP3 R5.x, R7, v[0]; \n\
DP3 R5.y, R8, v[0]; \n\
DP3 R5.z, R9, v[0]; # R5= bended relative pos to center. \n\
#temp, to optimize \n\
MOV R5.w, c[21].w; \n\
# add pos to center pos. \n\
ADD R5, R5, v[10]; # R5= world pos. R5.w= R5.w+v[10].w= 0+1= 1 \n\
# make local to camera pos. Important for ZBuffer precision \n\
ADD R5, R5, -c[10]; \n\
";
// Concat the 2 strings
const string NL3D_BendProgram= string(NL3D_BendProgramP0) + string(NL3D_BendProgramP1);
// ***********************
/*
Lighted start program:
bend pos and normal, normalize and lit
*/
// ***********************
// Common start program.
const char* NL3D_LightedStartVegetableProgram=
" \n\
# bend Pos into R5. Now do it for normal \n\
DP3 R0.x, R7, v[2]; \n\
DP3 R0.y, R8, v[2]; \n\
DP3 R0.z, R9, v[2]; # R0= matRot * normal. \n\
# Do the rot 2 times for normal (works fine) \n\
DP3 R6.x, R7, R0; \n\
DP3 R6.y, R8, R0; \n\
DP3 R6.z, R9, R0; # R6= bended normal. \n\
\n\
# Normalize normal, and dot product, into R0.x \n\
# w hasn't been written \n\
DP3 R0.x, R6.xyzz, R6.xyzz; # R0.x= R6.sqrnorm() \n\
RSQ R0.x, R0.x; # R0.x= 1/norm() \n\
MUL R6, R6.xyzz, R0.x; # R6= R6.normed() \n\
DP3 R0.x, R6, c[9]; \n\
\n\
#FrontFacing \n\
MAX R0.y, -R0.x, c[8].x; # R0.y= diffFactor= max(0, -R6*LightDir) \n\
MUL R1.xyz, R0.y, v[3]; # R7= diffFactor*DiffuseColor \n\
ADD o[COL0].xyz, R1, v[4]; # col0.RGB= AmbientColor + diffFactor*DiffuseColor \n\
MOV o[COL0].w, c[8].y; \n\
";
// ***********************
/*
Unlit start program:
bend pos into R5, and copy color(s)
*/
// ***********************
// Unlit no alpha blend.
const char* NL3D_UnlitVegetableProgram=
" MOV o[COL0].xyz, v[3]; # col.RGBA= vertex color \n\
\n\
MOV o[COL0].w, c[8].y; \n\
";
// Unlit with AlphaBlend.
const char* NL3D_UnlitAlphaBlendVegetableProgram=
" MOV o[COL0].xyz, v[3]; # col.RGBA= vertex color \n\
\n\
#Blend transition. NB: in R5, we already have the position relative to the camera \n\
DP3 R0.x, R5, R5; # R0.x= sqr(dist to viewer). \n\
RSQ R0.y, R0.x; \n\
MUL R0.x, R0.x, R0.y; # R0.x= dist to viewer \n\
# setup alpha Blending. Distance of appartition is encoded in the vertex. \n\
MAD o[COL0].w, R0.x, c[11].x, v[9].w; \n\
";
// ***********************
/*
Common end of program: project, texture. Take pos from R5
*/
// ***********************
const char* NL3D_CommonEndVegetableProgram=
" # compute in Projection space \n\
DP4 o[HPOS].x, c[0], R5; \n\
DP4 o[HPOS].y, c[1], R5; \n\
DP4 o[HPOS].z, c[2], R5; \n\
DP4 o[HPOS].w, c[3], R5; \n\
# copy Dynamic lightmap UV in stage0, from colors Alpha part. \n\
MAD o[TEX0].xzw, v[3].w, c[8].yxxx, c[8].xxxy; \n\
MOV o[TEX0].y, v[4].w; \n\
# copy diffuse texture uv to stage 1. \n\
MOV o[TEX1], v[8]; \n\
";
// fogged version
const char* NL3D_VegetableProgramFog =
" DP4 o[FOGC].x, c[6], R5; \n\
";
// ***********************
/*
Speed test VP, No bend,no lighting.
*/
// ***********************
const char* NL3D_SimpleStartVegetableProgram=
"!!VP1.0 \n\
# compute in Projection space \n\
MAD R5, v[0], c[8].yyyx, c[8].xxxy; \n\
ADD R5.xyz, R5, v[10]; \n\
# make local to camera pos \n\
ADD R5, R5, -c[10]; \n\
MOV o[COL0].xyz, v[3]; # col.RGBA= vertex color \n\
";
// ***************************************************************************
void CVegetableManager::initVertexProgram(uint vpType, bool fogEnabled)
{
nlassert(_LastDriver); // update driver should have been called at least once !
// Init the Vertex Program.
string vpgram;
// start always with Bend.
if( vpType==NL3D_VEGETABLE_RDRPASS_LIGHTED || vpType==NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED )
vpgram= NL3D_BendProgram;
else
vpgram= NL3D_FastBendProgram;
// combine the VP according to Type
switch(vpType)
{
case NL3D_VEGETABLE_RDRPASS_LIGHTED:
case NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED:
vpgram+= string(NL3D_LightedStartVegetableProgram);
break;
case NL3D_VEGETABLE_RDRPASS_UNLIT:
case NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED:
vpgram+= string(NL3D_UnlitVegetableProgram);
break;
case NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT:
vpgram+= string(NL3D_UnlitAlphaBlendVegetableProgram);
break;
}
// common end of VP
vpgram+= string(NL3D_CommonEndVegetableProgram);
if (fogEnabled)
{
vpgram+= string(NL3D_VegetableProgramFog);
}
vpgram+="\nEND\n";
// create VP.
_VertexProgram[vpType][fogEnabled ? 1 : 0] = new CVertexProgram(vpgram.c_str());
}
// ***************************************************************************
// ***************************************************************************
// Instanciation
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CVegetableClipBlock *CVegetableManager::createClipBlock()
{
// create a clipblock
CVegetableClipBlock *ret;
ret= _ClipBlockMemory.allocate();
// append to list.
_EmptyClipBlockList.append(ret);
return ret;
}
// ***************************************************************************
void CVegetableManager::deleteClipBlock(CVegetableClipBlock *clipBlock)
{
if(!clipBlock)
return;
// verify no more sortBlocks in this clipblock
nlassert(clipBlock->_SortBlockList.size() == 0);
// unlink from _EmptyClipBlockList, because _InstanceGroupList.size() == 0 ...
_EmptyClipBlockList.remove(clipBlock);
// delete
_ClipBlockMemory.free(clipBlock);
}
// ***************************************************************************
CVegetableSortBlock *CVegetableManager::createSortBlock(CVegetableClipBlock *clipBlock, const CVector &center, float radius)
{
nlassert(clipBlock);
// create a clipblock
CVegetableSortBlock *ret;
ret= _SortBlockMemory.allocate();
ret->_Owner= clipBlock;
ret->_Center= center;
ret->_Radius= radius;
// append to list.
clipBlock->_SortBlockList.append(ret);
return ret;
}
// ***************************************************************************
void CVegetableManager::deleteSortBlock(CVegetableSortBlock *sortBlock)
{
if(!sortBlock)
return;
// verify no more IGs in this sortblock
nlassert(sortBlock->_InstanceGroupList.size() == 0);
// unlink from clipBlock
sortBlock->_Owner->_SortBlockList.remove(sortBlock);
// delete
_SortBlockMemory.free(sortBlock);
}
// ***************************************************************************
CVegetableInstanceGroup *CVegetableManager::createIg(CVegetableSortBlock *sortBlock)
{
nlassert(sortBlock);
CVegetableClipBlock *clipBlock= sortBlock->_Owner;
// create an IG
CVegetableInstanceGroup *ret;
ret= _InstanceGroupMemory.allocate();
ret->_SortOwner= sortBlock;
ret->_ClipOwner= clipBlock;
// if the clipBlock is empty, change list, because won't be no more.
if(clipBlock->_NumIgs==0)
{
// remove from empty list
_EmptyClipBlockList.remove(clipBlock);
// and append to not empty one.
_ClipBlockList.append(clipBlock);
}
// inc the number of igs appended to the clipBlock.
clipBlock->_NumIgs++;
// link ig to sortBlock.
sortBlock->_InstanceGroupList.append(ret);
// Special Init: The ZSort rdrPass must start with the same HardMode than SortBlock.
ret->_RdrPass[NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT].HardMode= sortBlock->ZSortHardMode;
return ret;
}
// ***************************************************************************
void CVegetableManager::deleteIg(CVegetableInstanceGroup *ig)
{
if(!ig)
return;
// update lighting mgt: no more vertices.
// -----------
// If I delete the ig which is the current root
if(_ULRootIg == ig)
{
// switch to next
_ULRootIg= ig->_ULNext;
// if still the same, it means that the circular list is now empty
if(_ULRootIg == ig)
_ULRootIg= NULL;
// Reset UL instance info.
_ULCurrentIgRdrPass= 0;
_ULCurrentIgInstance= 0;
}
// remove UL vertex count of the deleted ig
_ULNTotalVertices-= ig->_ULNumVertices;
// unlink the ig for lighting update.
ig->unlinkUL();
// For all render pass of this instance, delete his vertices
// -----------
for(sint rdrPass=0; rdrPass < NL3D_VEGETABLE_NRDRPASS; rdrPass++)
{
// rdrPass
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPass];
// which allocator?
CVegetableVBAllocator &vbAllocator= getVBAllocatorForRdrPassAndVBHardMode(rdrPass, vegetRdrPass.HardMode);
// For all vertices of this rdrPass, delete it
sint numVertices;
numVertices= vegetRdrPass.Vertices.size();
// all vertices must have been setuped.
nlassert((uint)numVertices == vegetRdrPass.NVertices);
for(sint i=0; i<numVertices;i++)
{
vbAllocator.deleteVertex(vegetRdrPass.Vertices[i]);
}
vegetRdrPass.Vertices.clear();
}
CVegetableClipBlock *clipBlock= ig->_ClipOwner;
CVegetableSortBlock *sortBlock= ig->_SortOwner;
// If I have got some faces in ZSort rdrPass
if(ig->_HasZSortPassInstances)
// after my deletion, the sortBlock must be updated.
sortBlock->_Dirty= true;
// unlink from sortBlock, and delete.
sortBlock->_InstanceGroupList.remove(ig);
_InstanceGroupMemory.free(ig);
// decRef the clipBlock
clipBlock->_NumIgs--;
// if the clipBlock is now empty, change list
if(clipBlock->_NumIgs==0)
{
// remove from normal list
_ClipBlockList.remove(clipBlock);
// and append to empty list.
_EmptyClipBlockList.append(clipBlock);
}
}
// ***************************************************************************
CVegetableShape *CVegetableManager::getVegetableShape(const std::string &shape)
{
ItShapeMap it= _ShapeMap.find(shape);
// if found
if(it != _ShapeMap.end())
return &it->second;
// else insert
{
// insert.
CVegetableShape *ret;
it= ( _ShapeMap.insert(make_pair(shape, CVegetableShape()) ) ).first;
ret= &it->second;
// fill.
try
{
if( !ret->loadShape(shape) )
{
// Warning
nlwarning ("CVegetableManager::getVegetableShape could not load shape file '%s'", shape.c_str ());
// Remove from map
_ShapeMap.erase (shape);
// Return NULL
ret = NULL;
}
}
catch (Exception &e)
{
// Warning
nlwarning ("CVegetableManager::getVegetableShape error while loading shape file '%s' : '%s'", shape.c_str (), e.what ());
// Remove from map
_ShapeMap.erase (shape);
// Return NULL
ret = NULL;
}
return ret;
}
}
// ***************************************************************************
uint CVegetableManager::getRdrPassInfoForShape(CVegetableShape *shape, TVegetableWater vegetWaterState,
bool &instanceLighted, bool &instanceDoubleSided, bool &instanceZSort,
bool &destLighted, bool &precomputeLighting)
{
instanceLighted= shape->Lighted;
instanceDoubleSided= shape->DoubleSided;
// Disable ZSorting when we intersect water.
instanceZSort= shape->AlphaBlend && vegetWaterState!=IntersectWater;
destLighted= instanceLighted && !shape->PreComputeLighting;
precomputeLighting= instanceLighted && shape->PreComputeLighting;
// get correct rdrPass
uint rdrPass;
// get according to lighted / doubleSided state
if(destLighted)
{
if(instanceDoubleSided)
rdrPass= NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED;
else
rdrPass= NL3D_VEGETABLE_RDRPASS_LIGHTED;
}
else
{
if(instanceDoubleSided)
{
if(instanceZSort)
rdrPass= NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT;
else
rdrPass= NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED;
}
else
rdrPass= NL3D_VEGETABLE_RDRPASS_UNLIT;
}
return rdrPass;
}
// ***************************************************************************
void CVegetableManager::reserveIgAddInstances(CVegetableInstanceGroupReserve &vegetIgReserve, CVegetableShape *shape, TVegetableWater vegetWaterState, uint numInstances)
{
bool instanceLighted;
bool instanceDoubleSided;
bool instanceZSort;
bool destLighted;
bool precomputeLighting;
// get correct rdrPass / info
uint rdrPass;
rdrPass= getRdrPassInfoForShape(shape, vegetWaterState, instanceLighted, instanceDoubleSided,
instanceZSort, destLighted, precomputeLighting);
// veget rdrPass
CVegetableInstanceGroupReserve::CVegetableRdrPass &vegetRdrPass= vegetIgReserve._RdrPass[rdrPass];
// Reserve space in the rdrPass.
vegetRdrPass.NVertices+= numInstances * shape->VB.getNumVertices();
vegetRdrPass.NTriangles+= numInstances * (uint)shape->TriangleIndices.size()/3;
// if the instances are lighted, reserve space for lighting updates
if(instanceLighted)
vegetRdrPass.NLightedInstances+= numInstances;
}
// ***************************************************************************
void CVegetableManager::reserveIgCompile(CVegetableInstanceGroup *ig, const CVegetableInstanceGroupReserve &vegetIgReserve)
{
uint rdrPass;
// Check.
//===========
// For all rdrPass of the ig, check empty
for(rdrPass= 0; rdrPass<NL3D_VEGETABLE_NRDRPASS; rdrPass++)
{
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPass];
nlassert(vegetRdrPass.TriangleIndices.getNumIndexes()==0);
nlassert(vegetRdrPass.TriangleLocalIndices.size()==0);
nlassert(vegetRdrPass.Vertices.size()==0);
nlassert(vegetRdrPass.LightedInstances.size()==0);
}
// Do the same for all quadrants of the zsort rdrPass.
nlassert(ig->_TriangleQuadrantOrderArray.size()==0);
nlassert(ig->_TriangleQuadrantOrderNumTriangles==0);
// Reserve.
//===========
// For all rdrPass of the ig, reserve.
for(rdrPass= 0; rdrPass<NL3D_VEGETABLE_NRDRPASS; rdrPass++)
{
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPass];
uint numVertices= vegetIgReserve._RdrPass[rdrPass].NVertices;
uint numTris= vegetIgReserve._RdrPass[rdrPass].NTriangles;
uint numLightedInstances= vegetIgReserve._RdrPass[rdrPass].NLightedInstances;
// reserve triangles indices and vertices for this rdrPass.
vegetRdrPass.TriangleIndices.setFormat(vegetRdrPass.HardMode ? CIndexBuffer::Indices16 : CIndexBuffer::Indices32);
vegetRdrPass.TriangleIndices.setNumIndexes(numTris*3);
vegetRdrPass.TriangleLocalIndices.resize(numTris*3);
vegetRdrPass.Vertices.resize(numVertices);
// reserve ligthedinstances space.
vegetRdrPass.LightedInstances.resize(numLightedInstances);
}
// Reserve space for the zsort rdrPass sorting.
uint numZSortTris= vegetIgReserve._RdrPass[NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT].NTriangles;
// allocate sufficient space for all quadrants (1 alloc for all quadrants).
ig->_TriangleQuadrantOrderArray.resize(numZSortTris * NL3D_VEGETABLE_NUM_QUADRANT);
// And init ptrs.
if(numZSortTris>0)
{
sint16 *start= ig->_TriangleQuadrantOrderArray.getPtr();
// init ptr to each qaudrant
for(uint i=0; i<NL3D_VEGETABLE_NUM_QUADRANT; i++)
{
ig->_TriangleQuadrantOrders[i]= start + i*numZSortTris;
}
}
}
// ***************************************************************************
inline void computeVegetVertexLighting(const CVector &rotNormal,
const CVector &sunDir, CRGBA primaryRGBA, CRGBA secondaryRGBA,
CVegetableLightEx &vegetLex, CRGBA diffusePL[2], CRGBA *dst)
{
float dpSun;
float dpPL[2];
CRGBA col;
CRGBA resColor;
// compute front-facing coloring.
{
// Compute Sun Light.
dpSun= rotNormal*sunDir;
float f= max(0.f, -dpSun);
col.modulateFromuiRGBOnly(primaryRGBA, NLMISC::OptFastFloor(f*256));
// Add it with ambient
resColor.addRGBOnly(col, secondaryRGBA);
// Add influence of 2 lights only. (unrolled for better BTB use)
// Compute Light 0 ?
if(vegetLex.NumLights>=1)
{
dpPL[0]= rotNormal*vegetLex.Direction[0];
f= max(0.f, -dpPL[0]);
col.modulateFromuiRGBOnly(diffusePL[0], NLMISC::OptFastFloor(f*256));
resColor.addRGBOnly(col, resColor);
// Compute Light 1 ?
if(vegetLex.NumLights>=2)
{
dpPL[1]= rotNormal*vegetLex.Direction[1];
f= max(0.f, -dpPL[1]);
col.modulateFromuiRGBOnly(diffusePL[1], NLMISC::OptFastFloor(f*256));
resColor.addRGBOnly(col, resColor);
}
}
// Keep correct U of Dynamic Lightmap UV encoded in primaryRGBA Alpha part.
resColor.A= primaryRGBA.A;
// copy to dest
*dst= resColor;
}
}
// ***************************************************************************
inline void computeVegetVertexLightingForceBestSided(const CVector &rotNormal,
const CVector &sunDir, CRGBA primaryRGBA, CRGBA secondaryRGBA,
CVegetableLightEx &vegetLex, CRGBA diffusePL[2], CRGBA *dst)
{
float dpSun;
float dpPL[2];
CRGBA col;
CRGBA resColor;
// compute best-facing coloring.
{
// Compute Sun Light.
dpSun= rotNormal*sunDir;
// ForceBestSided: take the absolute value (max of -val,val)
float f= (float)fabs(dpSun);
col.modulateFromuiRGBOnly(primaryRGBA, NLMISC::OptFastFloor(f*256));
// Add it with ambient
resColor.addRGBOnly(col, secondaryRGBA);
// Add influence of 2 lights only. (unrolled for better BTB use)
// Compute Light 0 ?
if(vegetLex.NumLights>=1)
{
dpPL[0]= rotNormal*vegetLex.Direction[0];
// ForceBestSided: take the absolute value (max of -val,val)
f= (float)fabs(dpPL[0]);
col.modulateFromuiRGBOnly(diffusePL[0], NLMISC::OptFastFloor(f*256));
resColor.addRGBOnly(col, resColor);
// Compute Light 1 ?
if(vegetLex.NumLights>=2)
{
dpPL[1]= rotNormal*vegetLex.Direction[1];
f= (float)fabs(dpPL[1]);
col.modulateFromuiRGBOnly(diffusePL[1], NLMISC::OptFastFloor(f*256));
resColor.addRGBOnly(col, resColor);
}
}
// Keep correct U of Dynamic Lightmap UV encoded in primaryRGBA Alpha part.
resColor.A= primaryRGBA.A;
// copy to dest
*dst= resColor;
}
}
// ***************************************************************************
void CVegetableManager::addInstance(CVegetableInstanceGroup *ig,
CVegetableShape *shape, const NLMISC::CMatrix &mat,
const NLMISC::CRGBAF &ambientColor, const NLMISC::CRGBAF &diffuseColor,
float bendFactor, float bendPhase, float bendFreqFactor, float blendDistMax,
TVegetableWater vegetWaterState, CVegetableUV8 dlmUV)
{
sint i;
// Some setup.
//--------------------
bool instanceLighted;
bool instanceDoubleSided;
bool instanceZSort;
bool destLighted;
bool precomputeLighting;
// get correct rdrPass / info
uint rdrPass;
rdrPass= getRdrPassInfoForShape(shape, vegetWaterState, instanceLighted, instanceDoubleSided,
instanceZSort, destLighted, precomputeLighting);
// bestSided Precompute lighting or not??
bool bestSidedPrecomputeLighting= precomputeLighting && shape->BestSidedPreComputeLighting;
// veget rdrPass
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPass];
// color.
// setup using OptFastFloor.
CRGBA ambientRGBA, diffuseRGBA;
CRGBA primaryRGBA, secondaryRGBA;
// diffuseColor
diffuseRGBA.R= (uint8)NLMISC::OptFastFloor(diffuseColor.R*255);
diffuseRGBA.G= (uint8)NLMISC::OptFastFloor(diffuseColor.G*255);
diffuseRGBA.B= (uint8)NLMISC::OptFastFloor(diffuseColor.B*255);
diffuseRGBA.A= 255;
// ambientColor
ambientRGBA.R= (uint8)NLMISC::OptFastFloor(ambientColor.R*255);
ambientRGBA.G= (uint8)NLMISC::OptFastFloor(ambientColor.G*255);
ambientRGBA.B= (uint8)NLMISC::OptFastFloor(ambientColor.B*255);
ambientRGBA.A= 255;
// For Lighted, modulate with global light.
if(instanceLighted)
{
primaryRGBA.modulateFromColorRGBOnly(diffuseRGBA, _GlobalDiffuse);
secondaryRGBA.modulateFromColorRGBOnly(ambientRGBA, _GlobalAmbient);
}
// if the instance is not lighted, then don't take care of lighting
else
{
primaryRGBA.R= diffuseRGBA.R;
primaryRGBA.G= diffuseRGBA.G;
primaryRGBA.B= diffuseRGBA.B;
// may not be useful (2Sided lighting no more supported)
secondaryRGBA= primaryRGBA;
}
// Copy Dynamic Lightmap UV in Alpha part (save memory for an extra cost of 1 VP instruction)
primaryRGBA.A= dlmUV.U;
secondaryRGBA.A= dlmUV.V;
// get ref on the vegetLex.
CVegetableLightEx &vegetLex= ig->VegetableLightEx;
// Color of pointLights modulated by diffuse.
CRGBA diffusePL[2];
diffusePL[0] = CRGBA::Black;
diffusePL[1] = CRGBA::Black;
if(vegetLex.NumLights>=1)
{
diffusePL[0].modulateFromColorRGBOnly(diffuseRGBA, vegetLex.Color[0]);
if(vegetLex.NumLights>=2)
{
diffusePL[1].modulateFromColorRGBOnly(diffuseRGBA, vegetLex.Color[1]);
}
}
// normalize bendFreqFactor
bendFreqFactor*= NL3D_VEGETABLE_FREQUENCY_FACTOR_PREC;
bendFreqFactor= (float)floor(bendFreqFactor + 0.5f);
bendFreqFactor/= NL3D_VEGETABLE_FREQUENCY_FACTOR_PREC;
// Get allocator, and manage VBhard overriding.
//--------------------
CVegetableVBAllocator *allocator;
// if still in Sfot mode, keep it.
if(!vegetRdrPass.HardMode)
{
// get the soft allocator.
allocator= &getVBAllocatorForRdrPassAndVBHardMode(rdrPass, 0);
}
else
{
// Get VB allocator Hard for this rdrPass
allocator= &getVBAllocatorForRdrPassAndVBHardMode(rdrPass, 1);
// Test if the instance don't add too many vertices for this VBHard
if(allocator->exceedMaxVertexInBufferHard(shape->VB.getNumVertices()))
{
// if exceed, then must pass ALL the IG in software mode. vertices/faces are correclty updated.
// special: if rdrPass is the ZSort one,
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
{
nlassert(ig->_SortOwner->ZSortHardMode);
// must do it on ALL igs of the sortBlock, for less VBuffer mode switching.
CVegetableInstanceGroup *pIg= ig->_SortOwner->_InstanceGroupList.begin();
while(pIg)
{
// let's pass them in software mode.
swapIgRdrPassHardMode(pIg, rdrPass);
// next
pIg= (CVegetableInstanceGroup*)pIg->Next;
}
// Then all The sortBlock is in SoftMode.
ig->_SortOwner->ZSortHardMode= false;
}
else
{
// just do it on this Ig (can mix hardMode in a SortBlock for normal rdrPass)
swapIgRdrPassHardMode(ig, rdrPass);
}
// now, we can use the software only Allocator to append our instance
allocator= &getVBAllocatorForRdrPassAndVBHardMode(rdrPass, 0);
}
}
// get correct dstVB
const CVertexBuffer &dstVBInfo= allocator->getSoftwareVertexBuffer();
// Transform vertices to a vegetable instance, and enlarge clipBlock
//--------------------
// compute matrix to multiply normals, ie (M-1)t
CMatrix normalMat;
// need just rotation scale matrix.
normalMat.setRot(mat);
normalMat.invert();
normalMat.transpose();
// compute Instance position
CVector instancePos;
mat.getPos(instancePos);
// At least, the bbox of the clipBlock must include the center of the shape.
ig->_ClipOwner->extendSphere(instancePos);
// Vertex/triangle Info.
uint numNewVertices= shape->VB.getNumVertices();
uint numNewTris= (uint)shape->TriangleIndices.size()/3;
uint numNewIndices= (uint)shape->TriangleIndices.size();
// src info.
uint srcNormalOff= (instanceLighted? shape->VB.getNormalOff() : 0);
uint srcTex0Off= shape->VB.getTexCoordOff(0);
uint srcTex1Off= shape->VB.getTexCoordOff(1);
// dst info
uint dstNormalOff= (destLighted? dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_NORMAL) : 0);
uint dstColor0Off= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_COLOR0);
uint dstColor1Off= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_COLOR1);
uint dstTex0Off= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_TEX0);
uint dstBendOff= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_BENDINFO);
uint dstCenterOff= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_CENTER);
// For D3D, If the VertexBuffer is in BGRA mode
if(allocator->isBGRA())
{
// then swap only the B and R (no cpu cycle added per vertex)
primaryRGBA.swapBR();
secondaryRGBA.swapBR();
diffusePL[0].swapBR();
diffusePL[1].swapBR();
}
// Useful for !destLighted only.
CVector deltaPos;
float deltaPosNorm=0.0;
// Useful for ZSORT rdrPass, the worldVertices.
static vector<CVector> worldVertices;
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
{
worldVertices.resize(numNewVertices);
}
CVertexBufferRead vba;
shape->VB.lock (vba);
// For all vertices of shape, transform and store manager indices in temp shape.
for(i=0; i<(sint)numNewVertices;i++)
{
// allocate a Vertex
uint vid= allocator->allocateVertex();
CVertexBufferReadWrite vbaOut;
allocator->getSoftwareVertexBuffer ().lock(vbaOut);
// store in tmp shape.
shape->InstanceVertices[i]= vid;
// Fill this vertex.
const uint8 *srcPtr= (uint8*)vba.getVertexCoordPointer(i);
uint8 *dstPtr= (uint8*)vbaOut.getVertexCoordPointer(vid);
// Get bendWeight for this vertex.
float vertexBendWeight= ((CUV*)(srcPtr + srcTex1Off))->U * bendFactor;
// Pos.
//-------
// Separate Center and relative pos.
CVector relPos= mat.mulVector(*(CVector*)srcPtr); // mulVector, because translation in v[center]
// compute bendCenterPos
CVector bendCenterPos;
if(shape->BendCenterMode == CVegetableShapeBuild::BendCenterNull)
bendCenterPos= CVector::Null;
else
{
CVector v= *(CVector*)srcPtr;
v.z= 0;
bendCenterPos= mat.mulVector(v); // mulVector, because translation in v[center]
}
// copy
deltaPos= relPos-bendCenterPos;
*(CVector*)dstPtr= deltaPos;
*(CVector*)(dstPtr + dstCenterOff)= instancePos + bendCenterPos;
// if !destLighted, then VP is different
if(!destLighted)
{
deltaPosNorm= deltaPos.norm();
// copy bendWeight in v.w
CVectorH *vh= (CVectorH*)dstPtr;
// Mul by deltaPosNorm, to draw an arc circle.
vh->w= vertexBendWeight * deltaPosNorm;
}
// Enlarge the clipBlock of the IG.
// Since small shape, enlarge with each vertices. simpler and maybe faster.
// TODO_VEGET: bend and clipping ...
ig->_ClipOwner->extendBBoxOnly(instancePos + relPos);
// prepare for ZSort
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
{
worldVertices[i]= instancePos + relPos;
}
// Color-ligthing.
//-------
if(!precomputeLighting)
{
// just copy the primary color (means diffuse part if lighted)
*(CRGBA*)(dstPtr + dstColor0Off)= primaryRGBA;
// normal and secondary color
if(destLighted)
{
// normal
*(CVector*)(dstPtr + dstNormalOff)= normalMat.mulVector( *(CVector*)(srcPtr + srcNormalOff) );
}
// If destLighted, secondaryRGBA is the ambient
// else secondaryRGBA is used only for Alpha (DLM uv.v).
*(CRGBA*)(dstPtr + dstColor1Off)= secondaryRGBA;
}
else
{
nlassert(!destLighted);
// compute normal.
CVector rotNormal= normalMat.mulVector( *(CVector*)(srcPtr + srcNormalOff) );
// must normalize() because scale is possible.
rotNormal.normalize();
// Do the compute.
if(!bestSidedPrecomputeLighting)
{
computeVegetVertexLighting(rotNormal,
_DirectionalLight, primaryRGBA, secondaryRGBA,
vegetLex, diffusePL, (CRGBA*)(dstPtr + dstColor0Off) );
}
else
{
computeVegetVertexLightingForceBestSided(rotNormal,
_DirectionalLight, primaryRGBA, secondaryRGBA,
vegetLex, diffusePL, (CRGBA*)(dstPtr + dstColor0Off) );
}
// copy secondaryRGBA, used only for Alpha (DLM uv.v).
*(CRGBA*)(dstPtr + dstColor1Off)= secondaryRGBA;
}
// Texture.
//-------
*(CUV*)(dstPtr + dstTex0Off)= *(CUV*)(srcPtr + srcTex0Off);
// Bend.
//-------
CVector *dstBendPtr= (CVector*)(dstPtr + dstBendOff);
// setup bend Phase.
dstBendPtr->y= bendPhase;
// setup bend Weight.
// if !destLighted, then VP is different, vertexBendWeight is stored in v[0].w
if(destLighted)
dstBendPtr->x= vertexBendWeight;
else
// the VP need the norm of relPos in v[9].x
dstBendPtr->x= deltaPosNorm;
// setup bendFreqFactor
dstBendPtr->z= bendFreqFactor;
/// If AlphaBlend / ZSort rdrPass, then setup AlphaBlend computing.
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
{
// get ptr on v[9].w NB: in Unlit mode, it has 4 components.
CVectorH *dstBendPtr= (CVectorH*)(dstPtr + dstBendOff);
// setup the constant of linear formula:
// Alpha= -1/blendTransDist * dist + blendDistMax/blendTransDist
dstBendPtr->w= blendDistMax/NL3D_VEGETABLE_BLOCK_BLEND_TRANSITION_DIST;
}
// fill the vertex in AGP.
//-------
allocator->flushVertex(vid);
}
// must recompute the sphere according to the bbox.
ig->_ClipOwner->updateSphere();
// If ZSort, compute Triangle Centers and Orders for quadrant
//--------------------
if(rdrPass==NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
{
// inform the SB that it must be updated.
ig->_SortOwner->_Dirty= true;
// For deletion, inform the ig that it has instances which impact the SB.
ig->_HasZSortPassInstances= true;
// change UnderWater falg of the SB
if(vegetWaterState == AboveWater)
ig->_SortOwner->_UnderWater= false;
else if(vegetWaterState == UnderWater)
ig->_SortOwner->_UnderWater= true;
// static to avoid reallocation
static vector<CVector> triangleCenters;
triangleCenters.resize(numNewTris);
// compute triangle centers
for(uint i=0; i<numNewTris; i++)
{
// get index in shape.
uint v0= shape->TriangleIndices[i*3+0];
uint v1= shape->TriangleIndices[i*3+1];
uint v2= shape->TriangleIndices[i*3+2];
// get world coord.
const CVector &vert0= worldVertices[v0];
const CVector &vert1= worldVertices[v1];
const CVector &vert2= worldVertices[v2];
// compute center
triangleCenters[i]= (vert0 + vert1 + vert2) / 3;
// relative to center of the sortBlock (for sint16 compression)
triangleCenters[i]-= ig->_SortOwner->_Center;
}
// resize the array. Actually only modify the number of triangles really setuped.
uint offTri= ig->_TriangleQuadrantOrderNumTriangles;
ig->_TriangleQuadrantOrderNumTriangles+= numNewTris;
// verify user has correclty used reserveIg system.
nlassert(ig->_TriangleQuadrantOrderNumTriangles * NL3D_VEGETABLE_NUM_QUADRANT <= ig->_TriangleQuadrantOrderArray.size());
// compute distance for each quadrant. Since we are not sure of the sortBlockSize, mul with a (big: 16) security.
// NB: for landscape practical usage, this left us with more than 1mm precision.
float distFactor=32768/(16*ig->_SortOwner->_Radius);
for(uint quadId=0; quadId<NL3D_VEGETABLE_NUM_QUADRANT; quadId++)
{
const CVector &quadDir= CVegetableQuadrant::Dirs[quadId];
// For all tris.
for(uint i=0; i<numNewTris; i++)
{
// compute the distance with orientation of the quadrant. (DotProduct)
float dist= triangleCenters[i] * quadDir;
// compress to sint16.
ig->_TriangleQuadrantOrders[quadId][offTri + i]= (sint16)NLMISC::OptFastFloor(dist*distFactor);
}
}
}
// Append list of indices and list of triangles to the IG
//--------------------
// TODO_VEGET_OPTIM: system reallocation of array is very bad...
// compute dest start idx.
uint offVertex= vegetRdrPass.NVertices;
uint offTri= vegetRdrPass.NTriangles;
uint offTriIdx= offTri*3;
// verify user has correclty used reserveIg system.
nlassert(offVertex + numNewVertices <= vegetRdrPass.Vertices.size());
nlassert(offTriIdx + numNewIndices <= vegetRdrPass.TriangleIndices.getNumIndexes());
nlassert(offTriIdx + numNewIndices <= vegetRdrPass.TriangleLocalIndices.size());
// insert list of vertices to delete in ig vertices.
vegetRdrPass.Vertices.copy(offVertex, offVertex+numNewVertices, &shape->InstanceVertices[0]);
// insert array of triangles in ig.
// for all indices, fill IG
CIndexBufferReadWrite ibaWrite;
vegetRdrPass.TriangleIndices.lock (ibaWrite);
if (vegetRdrPass.TriangleIndices.getFormat() == CIndexBuffer::Indices16)
{
uint16 *ptr = (uint16 *) ibaWrite.getPtr();
for(i=0; i<(sint)numNewIndices; i++)
{
// get the index of the vertex in the shape
uint vid= shape->TriangleIndices[i];
// re-direction, using InstanceVertices;
#ifdef NL_DEBUG
nlassert(shape->InstanceVertices[vid] <= 0xffff);
#endif
ptr[offTriIdx + i]= (uint16) shape->InstanceVertices[vid];
// local re-direction: adding vertexOffset.
vegetRdrPass.TriangleLocalIndices[offTriIdx + i]= offVertex + vid;
}
}
else
{
uint32 *ptr = (uint32 *) ibaWrite.getPtr();
for(i=0; i<(sint)numNewIndices; i++)
{
// get the index of the vertex in the shape
uint vid= shape->TriangleIndices[i];
// re-direction, using InstanceVertices;
ptr[offTriIdx + i]= shape->InstanceVertices[vid];
// local re-direction: adding vertexOffset.
vegetRdrPass.TriangleLocalIndices[offTriIdx + i]= offVertex + vid;
}
}
// new triangle and vertex size.
vegetRdrPass.NTriangles+= numNewTris;
vegetRdrPass.NVertices+= numNewVertices;
// if lighted, must add a lightedInstance for lighting update.
//--------------------
if(instanceLighted)
{
// first, update Ig.
ig->_ULNumVertices+= numNewVertices;
// and update the vegetable manager.
_ULNTotalVertices+= numNewVertices;
// link at the end of the circular list: link before the current root.
if(_ULRootIg==NULL)
_ULRootIg= ig;
else
ig->linkBeforeUL(_ULRootIg);
// check good use of reserveIg.
nlassert(vegetRdrPass.NLightedInstances < vegetRdrPass.LightedInstances.size());
// Fill instance info
CVegetableInstanceGroup::CVegetableLightedInstance &vli=
vegetRdrPass.LightedInstances[vegetRdrPass.NLightedInstances];
vli.Shape= shape;
vli.NormalMat= normalMat;
// copy colors unmodulated by global light.
vli.MatAmbient= ambientRGBA;
vli.MatDiffuse= diffuseRGBA;
// store dynamic lightmap UV
vli.DlmUV= dlmUV;
// where vertices of this instances are wrote in the VegetRdrPass
vli.StartIdInRdrPass= offVertex;
// Inc size setuped.
vegetRdrPass.NLightedInstances++;
}
}
// ***************************************************************************
void CVegetableManager::swapIgRdrPassHardMode(CVegetableInstanceGroup *ig, uint rdrPass)
{
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPass];
// the allocator where vertices come from
CVegetableVBAllocator &srcAllocator= getVBAllocatorForRdrPassAndVBHardMode(rdrPass, vegetRdrPass.HardMode);
// the allocator where vertices will go
CVegetableVBAllocator &dstAllocator= getVBAllocatorForRdrPassAndVBHardMode(rdrPass, !vegetRdrPass.HardMode);
// vertex size
uint vbSize= srcAllocator.getSoftwareVertexBuffer().getVertexSize();
nlassert(vbSize == dstAllocator.getSoftwareVertexBuffer().getVertexSize());
CVertexBufferRead vbaIn;
srcAllocator.getSoftwareVertexBuffer ().lock(vbaIn);
// for all vertices of the IG, change of VBAllocator
uint i;
// Do it only for current Vertices setuped!!! because a swapIgRdrPassHardMode awlays arise when the ig is
// in construcion.
// Hence here, we may have vegetRdrPass.NVertices < vegetRdrPass.Vertices.size() !!!
for(i=0;i<vegetRdrPass.NVertices;i++)
{
// get idx in src allocator.
uint srcId= vegetRdrPass.Vertices[i];
// allocate a vertex in the dst allocator.
uint dstId= dstAllocator.allocateVertex();
CVertexBufferReadWrite vbaOut;
dstAllocator.getSoftwareVertexBuffer ().lock(vbaOut);
// copy from VBsoft of src to dst.
const void *vbSrc= vbaIn.getVertexCoordPointer(srcId);
void *vbDst= vbaOut.getVertexCoordPointer(dstId);
memcpy(vbDst, vbSrc, vbSize);
// release src vertex.
srcAllocator.deleteVertex(srcId);
// and copy new dest id in Vertices array.
vegetRdrPass.Vertices[i]= dstId;
// and flush this vertex into VBHard (if dst is aVBHard).
dstAllocator.flushVertex(dstId);
}
// For all triangles, bind correct triangles.
nlassert(vegetRdrPass.TriangleIndices.getNumIndexes() == vegetRdrPass.TriangleLocalIndices.size());
// Do it only for current Triangles setuped!!! same reason as vertices
// For all setuped triangles indices
CIndexBufferReadWrite ibaWrite;
// For hard mode, uses faster 16 bit indices because the VB is not bigger than 65K
vegetRdrPass.TriangleIndices.setFormat(vegetRdrPass.HardMode ? CIndexBuffer::Indices32 : CIndexBuffer::Indices16); // NB : this is not an error here : vegetRdrPass.HardMode has not been inverted yet
vegetRdrPass.TriangleIndices.lock (ibaWrite);
if (ibaWrite.getFormat() == CIndexBuffer::Indices16)
{
uint16 *ptr = (uint16 *) ibaWrite.getPtr();
for(i=0;i<vegetRdrPass.NTriangles*3;i++)
{
// get the index in Vertices.
uint localVid= vegetRdrPass.TriangleLocalIndices[i];
// get the index in new VBufffer (dstAllocator), and copy to TriangleIndices
ptr[i]= (uint16) vegetRdrPass.Vertices[localVid];
}
}
else
{
uint32 *ptr = (uint32 *) ibaWrite.getPtr();
for(i=0;i<vegetRdrPass.NTriangles*3;i++)
{
// get the index in Vertices.
uint localVid= vegetRdrPass.TriangleLocalIndices[i];
// get the index in new VBufffer (dstAllocator), and copy to TriangleIndices
ptr[i]= (uint32) vegetRdrPass.Vertices[localVid];
}
}
// Since change is made, flag the IG rdrpass
vegetRdrPass.HardMode= !vegetRdrPass.HardMode;
}
// ***************************************************************************
void CVegetableManager::setGlobalDensity(float density)
{
clamp(density, 0.f, 1.f);
_GlobalDensity= density;
}
// ***************************************************************************
// ***************************************************************************
// Render
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
bool CVegetableManager::doubleSidedRdrPass(uint rdrPass)
{
nlassert(rdrPass<NL3D_VEGETABLE_NRDRPASS);
return (rdrPass == NL3D_VEGETABLE_RDRPASS_LIGHTED_2SIDED) ||
(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED) ||
(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT);
}
// ***************************************************************************
void CVegetableManager::updateDriver(IDriver *driver)
{
// update all driver
uint i;
for(i=0; i <CVegetableVBAllocator::VBTypeCount; i++)
{
_VBHardAllocator[i].updateDriver(driver);
_VBSoftAllocator[i].updateDriver(driver);
}
// if driver changed, recreate vertex programs
if (driver != _LastDriver)
{
_LastDriver = driver;
for(i=0; i <NL3D_VEGETABLE_NRDRPASS; i++)
{
// both fog & no fog
initVertexProgram(i, true);
initVertexProgram(i, false);
}
}
}
// ***************************************************************************
void CVegetableManager::loadTexture(const string &texName)
{
// setup a CTextureFile (smartPtr-ized).
ITexture *tex= new CTextureFile(texName);
loadTexture(tex);
// setup good params.
tex->setFilterMode(ITexture::Linear, ITexture::LinearMipMapLinear);
tex->setWrapS(ITexture::Clamp);
tex->setWrapT(ITexture::Clamp);
}
// ***************************************************************************
void CVegetableManager::loadTexture(ITexture *itex)
{
// setup a ITexture (smartPtr-ized).
// Store in stage1, for dynamicLightmaping
_VegetableMaterial.setTexture(1, itex);
}
// ***************************************************************************
void CVegetableManager::setDirectionalLight(const CRGBA &ambient, const CRGBA &diffuse, const CVector &light)
{
_DirectionalLight= light;
_DirectionalLight.normalize();
// Setup ambient/Diffuse.
_GlobalAmbient= ambient;
_GlobalDiffuse= diffuse;
}
// ***************************************************************************
void CVegetableManager::lockBuffers()
{
// lock all buffers
for(uint i=0; i <CVegetableVBAllocator::VBTypeCount; i++)
{
_VBHardAllocator[i].lockBuffer();
_VBSoftAllocator[i].lockBuffer();
}
}
// ***************************************************************************
void CVegetableManager::unlockBuffers()
{
// unlock all buffers
for(uint i=0; i <CVegetableVBAllocator::VBTypeCount; i++)
{
_VBHardAllocator[i].unlockBuffer();
_VBSoftAllocator[i].unlockBuffer();
}
}
// ***************************************************************************
class CSortVSB
{
public:
CVegetableSortBlock *Sb;
CSortVSB() : Sb(NULL) {}
CSortVSB(CVegetableSortBlock *sb) : Sb(sb) {}
// for sort()
bool operator<(const CSortVSB &o) const
{
return Sb->_SortKey>o.Sb->_SortKey;
}
};
// ***************************************************************************
void CVegetableManager::setupVertexProgramConstants(IDriver *driver)
{
// Standard
// setup VertexProgram constants.
// c[0..3] take the ModelViewProjection Matrix. After setupModelMatrix();
driver->setConstantMatrix(0, IDriver::ModelViewProjection, IDriver::Identity);
// c[6] take the Fog vector. After setupModelMatrix();
driver->setConstantFog(6);
// c[8] take useful constants.
driver->setConstant(8, 0, 1, 0.5f, 2);
// c[9] take normalized directional light
driver->setConstant(9, _DirectionalLight);
// c[10] take pos of camera
driver->setConstant(10, _ViewCenter);
// c[11] take factor for Blend formula
driver->setConstant(11, -1.f/NL3D_VEGETABLE_BLOCK_BLEND_TRANSITION_DIST, 0, 0, 0);
// Bend.
// c[16]= quaternion axis. w==1, and z must be 0
driver->setConstant( 16, _AngleAxis.x, _AngleAxis.y, _AngleAxis.z, 1);
// c[17]= {timeAnim, WindPower, WindPower*(1-WindBendMin)/2, 0)}
driver->setConstant( 17, (float)_WindAnimTime, _WindPower, _WindPower*(1-_WindBendMin)/2, 0 );
// c[18]= High order Taylor cos coefficient: { -1/2, 1/24, -1/720, 1/40320 }
driver->setConstant( 18, -1/2.f, 1/24.f, -1/720.f, 1/40320.f );
// c[19]= Low order Taylor cos coefficient: { 1, -1/2, 1/24, -1/720 }
driver->setConstant( 19, 1, -1/2.f, 1/24.f, -1/720.f );
// c[20]= Low order Taylor sin coefficient: { 1, -1/6, 1/120, -1/5040 }
driver->setConstant( 20, 1, -1/6.f, 1/120.f, -1/5040.f );
// c[21]= Special constant vector for quatToMatrix: { 0, 1, -1, 0 }
driver->setConstant( 21, 0.f, 1.f, -1.f, 0.f);
// c[22]= {0.5f, Pi, 2*Pi, 1/(2*Pi)}
driver->setConstant( 22, 0.5f, (float)Pi, (float)(2*Pi), (float)(1/(2*Pi)) );
// c[23]= {NL3D_VEGETABLE_VP_LUT_SIZE, 0, 0, 0}. NL3D_VEGETABLE_VP_LUT_SIZE==64.
driver->setConstant( 23, NL3D_VEGETABLE_VP_LUT_SIZE, 0.f, 0.f, 0.f );
// Fill constant. Start at 32.
for(uint i=0; i<NL3D_VEGETABLE_VP_LUT_SIZE; i++)
{
CVector2f cur= _WindTable[i];
CVector2f delta= _WindDeltaTable[i];
driver->setConstant( 32+i, cur.x, cur.y, delta.x, delta.y );
}
}
// ***************************************************************************
void CVegetableManager::render(const CVector &viewCenter, const CVector &frontVector, const std::vector<CPlane> &pyramid,
ITexture *textureDLM, IDriver *driver)
{
H_AUTO( NL3D_Vegetable_Render );
CVegetableClipBlock *rootToRender= NULL;
// get normalized front vector.
CVector frontVectorNormed= frontVector.normed();
// For Speed debug only.
/*extern bool YOYO_ATTest;
if(YOYO_ATTest)
return;
*/
// Clip.
//--------------------
// For all current not empty clipBlocks, clip against pyramid, and insert visibles in list.
CVegetableClipBlock *ptrClipBlock= _ClipBlockList.begin();
while(ptrClipBlock)
{
// if the clipBlock is visible and not empty
if(ptrClipBlock->clip(pyramid))
{
// insert into visible list.
ptrClipBlock->_RenderNext= rootToRender;
rootToRender= ptrClipBlock;
}
// next
ptrClipBlock= (CVegetableClipBlock*)ptrClipBlock->Next;
}
// If no clip block visible, just skip!!
if(rootToRender==NULL)
return;
// Prepare Render
//--------------------
// profile.
CPrimitiveProfile ppIn, ppOut;
driver->profileRenderedPrimitives(ppIn, ppOut);
uint precNTriRdr= ppOut.NTriangles;
// Disable Fog.
bool bkupFog;
bkupFog= driver->fogEnabled();
bool fogged = bkupFog && driver->getFogStart() < _ZSortLayerDistMax;
driver->enableFog(fogged);
// Used by setupVertexProgramConstants(). The center of camera.
// Used for AlphaBlending, and for ZBuffer precision problems.
_ViewCenter= viewCenter;
// The manager is identity in essence. But for ZBuffer improvements, must set it as close
// to the camera. In the VertexProgram, _ViewCenter is substracted from bent vertex pos. So take it as position.
_ManagerMatrix.identity();
_ManagerMatrix.setPos(_ViewCenter);
// set model matrix to the manager matrix.
driver->setupModelMatrix(_ManagerMatrix);
// set the driver for all allocators
updateDriver(driver);
// Compute Bend Anim.
// AnimFrequency factor.
// Doing it incrementally allow change of of frequency each frame with good results.
_WindAnimTime+= (_Time - _WindPrecRenderTime)*_WindFrequency;
_WindAnimTime= fmod((float)_WindAnimTime, (float)NL3D_VEGETABLE_FREQUENCY_FACTOR_PREC);
// NB: Leave timeBend (_WindAnimTime) as a time (ie [0..1]), because VP do a "EXP time".
// For incremental computing.
_WindPrecRenderTime= _Time;
// compute the angleAxis corresponding to direction
// perform a 90deg rotation to get correct angleAxis
_AngleAxis.set(-_WindDirection.y,_WindDirection.x,0);
// Fill LUT WindTable.
uint i;
for(i=0; i<NL3D_VEGETABLE_VP_LUT_SIZE; i++)
{
/* NB: this formula works quite well, because vertex BendFactor is expressed in Radian/2.
And since animFactor==(_CosTable[i] + 1) E [0..2], we have here an arc-circle computing:
dmove= Radius * AngleRadian/2 * animFactor. So at max of animFactor (ie 2), we have:
dmove= Radius * AngleRadian, which is by definition an arc-circle computing...
And so this approximate the Bend-quaternion Vertex Program.
*/
float windForce= (_CosTable[(i+32)%64] + 1);
// Modify with _WindPower / _WindBendMin.
windForce= _WindBendMin*2 + windForce * (1-_WindBendMin);
windForce*= _WindPower;
// Compute direction of the wind, and multiply by windForce.
_WindTable[i]= CVector2f(_WindDirection.x, _WindDirection.y) * windForce;
}
// compute delta
for(i=0; i<NL3D_VEGETABLE_VP_LUT_SIZE; i++)
{
CVector2f cur= _WindTable[i];
CVector2f delta= _WindTable[ (i+1)%NL3D_VEGETABLE_VP_LUT_SIZE ] - cur;
_WindDeltaTable[i]= delta;
}
// setup VP constants.
setupVertexProgramConstants(driver);
// Setup TexEnvs for Dynamic lightmapping
//--------------------
// if the dynamic lightmap is provided
if(textureDLM)
{
// stage0 RGB is Diffuse + DLM.
_VegetableMaterial.setTexture(0, textureDLM);
_VegetableMaterial.texEnvOpRGB(0, CMaterial::Add);
_VegetableMaterial.texEnvArg0RGB(0, CMaterial::Texture, CMaterial::SrcColor);
_VegetableMaterial.texEnvArg1RGB(0, CMaterial::Diffuse, CMaterial::SrcColor);
// stage1 RGB is Previous * Texture
_VegetableMaterial.texEnvOpRGB(1, CMaterial::Modulate);
_VegetableMaterial.texEnvArg0RGB(1, CMaterial::Texture, CMaterial::SrcColor);
_VegetableMaterial.texEnvArg1RGB(1, CMaterial::Previous, CMaterial::SrcColor);
}
else
{
// reset stage0 (to skip it)
_VegetableMaterial.setTexture(0, NULL);
// stage1 RGB is Diffuse * Texture
_VegetableMaterial.texEnvOpRGB(1, CMaterial::Modulate);
_VegetableMaterial.texEnvArg0RGB(1, CMaterial::Texture, CMaterial::SrcColor);
_VegetableMaterial.texEnvArg1RGB(1, CMaterial::Diffuse, CMaterial::SrcColor);
}
// stage1 Alpha is always "Modulate texture with diffuse Alpha"
_VegetableMaterial.texEnvOpAlpha(1, CMaterial::Modulate);
_VegetableMaterial.texEnvArg0Alpha(1, CMaterial::Texture, CMaterial::SrcAlpha);
_VegetableMaterial.texEnvArg1Alpha(1, CMaterial::Diffuse, CMaterial::SrcAlpha);
// Render !ZSORT pass
//--------------------
// setup material (may have change because of ZSORT / alphaBlend pass)
_VegetableMaterial.setBlend(false);
_VegetableMaterial.setZWrite(true);
_VegetableMaterial.setAlphaTestThreshold(0.5f);
/*
Prefer sort with Soft / Hard first.
Also, Prefer do VBsoft last, for better GPU //ism with Landscape.
*/
// For both allocators: Hard(1) then Soft(0)
for(sint vbHardMode= 1; vbHardMode>=0; vbHardMode--)
{
// For all renderPass.
for(sint rdrPass=0; rdrPass < NL3D_VEGETABLE_NRDRPASS; rdrPass++)
{
// skip ZSORT rdrPass, done after.
if(rdrPass == NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT)
continue;
// which allocator?
CVegetableVBAllocator &vbAllocator= getVBAllocatorForRdrPassAndVBHardMode(rdrPass, vbHardMode);
// Do the pass only if there is some vertices to draw.
if(vbAllocator.getNumUserVerticesAllocated()>0)
{
// additional setup to the material
bool doubleSided= doubleSidedRdrPass(rdrPass);
// set the 2Sided flag in the material
_VegetableMaterial.setDoubleSided( doubleSided );
// Activate the unique material.
driver->setupMaterial(_VegetableMaterial);
// activate Vertex program first.
//nlinfo("\nSTARTVP\n%s\nENDVP\n", _VertexProgram[rdrPass]->getProgram().c_str());
nlverify(driver->activeVertexProgram(_VertexProgram[rdrPass][fogged ? 1 : 0]));
// Activate the good VBuffer
vbAllocator.activate();
// For all visibles clipBlock, render their instance groups.
ptrClipBlock= rootToRender;
while(ptrClipBlock)
{
// For all sortBlock of the clipBlock
CVegetableSortBlock *ptrSortBlock= ptrClipBlock->_SortBlockList.begin();
while(ptrSortBlock)
{
// For all igs of the sortBlock
CVegetableInstanceGroup *ptrIg= ptrSortBlock->_InstanceGroupList.begin();
while(ptrIg)
{
// rdrPass
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ptrIg->_RdrPass[rdrPass];
// if this rdrPass is in same HardMode as we process now.
if( (vegetRdrPass.HardMode && vbHardMode==1) || (!vegetRdrPass.HardMode && vbHardMode==0) )
{
// Ok, Render the faces.
if(vegetRdrPass.NTriangles)
{
driver->activeIndexBuffer(vegetRdrPass.TriangleIndices);
#ifdef NL_DEBUG
if (vegetRdrPass.HardMode)
{
nlassert(vegetRdrPass.TriangleIndices.getFormat() == CIndexBuffer::Indices16);
}
else
{
nlassert(vegetRdrPass.TriangleIndices.getFormat() == CIndexBuffer::Indices32);
}
#endif
driver->renderSimpleTriangles(0,
vegetRdrPass.NTriangles);
}
}
// next ig.
ptrIg= (CVegetableInstanceGroup*)ptrIg->Next;
}
// next sortBlock
ptrSortBlock= (CVegetableSortBlock *)(ptrSortBlock->Next);
}
// next clipBlock to render
ptrClipBlock= ptrClipBlock->_RenderNext;
}
}
}
}
// Render ZSort pass.
//--------------------
// Debug Quadrants.
/*static vector<CVector> p0DebugLines;
static vector<CVector> p1DebugLines;
p0DebugLines.clear();
p1DebugLines.clear();*/
// For all Blend model Layers, clear Sort Block list and setup.
for(i=0; i<_NumZSortBlendLayers;i++)
{
// must have been created.
nlassert(_ZSortModelLayers[i]);
nlassert(_ZSortModelLayersUW[i]);
// NB: don't refresh list, it is done in CVegetableBlendLayerModel.
// We must do it here, because if vegetableManger::render() is no more called (eg: disabled),
// then the models must do nothing.
// To get layers correclty sorted from fornt to back, must init their pos
// because it is the renderTraversal which sort them.
// compute distance to camera of this layer.
float layerZ= i * _ZSortLayerDistMax / _NumZSortBlendLayers;
// compute position of this layer.
CVector pos= viewCenter + frontVector * layerZ;
// special setup in the layer.
_ZSortModelLayers[i]->setWorldPos(pos);
_ZSortModelLayersUW[i]->setWorldPos(pos);
}
// If some vertices in arrays for ZSort rdrPass
if( getVBAllocatorForRdrPassAndVBHardMode(NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT, 0).getNumUserVerticesAllocated()>0 ||
getVBAllocatorForRdrPassAndVBHardMode(NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT, 1).getNumUserVerticesAllocated()>0 )
{
uint rdrPass= NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT;
// sort
//-------------
// Array for sorting. (static to avoid reallocation)
static vector<CSortVSB> sortVegetSbs;
sortVegetSbs.clear();
// For all visibles clipBlock
ptrClipBlock= rootToRender;
while(ptrClipBlock)
{
// For all sortBlock, prepare to sort them
CVegetableSortBlock *ptrSortBlock= ptrClipBlock->_SortBlockList.begin();
while(ptrSortBlock)
{
// if the sortBlock has some sorted faces to render
if(ptrSortBlock->_NTriangles != 0)
{
// Compute Distance to Viewer.
/* NB: compute radial distance (with norm()) instead of linear distance
(DotProduct with front vector) get less "ZSort poping".
*/
CVector dirToSb= ptrSortBlock->_Center - viewCenter;
float distToViewer= dirToSb.norm();
// SortKey change if the center is behind the camera.
if(dirToSb * frontVectorNormed<0)
{
ptrSortBlock->_SortKey= - distToViewer;
}
else
{
ptrSortBlock->_SortKey= distToViewer;
}
// Choose the quadrant for this sortBlock
sint bestDirIdx= 0;
float bestDirVal= -FLT_MAX;
// If too near, must take the frontVector as key, to get better sort.
// use ptrSortBlock->_SortKey to get correct negative values.
if(ptrSortBlock->_SortKey < ptrSortBlock->_Radius)
{
dirToSb= frontVectorNormed;
}
// NB: no need to normalize dirToSb, because need only to sort with DP
// choose the good list of triangles according to quadrant.
for(uint dirIdx=0; dirIdx<NL3D_VEGETABLE_NUM_QUADRANT; dirIdx++)
{
float dirVal= CVegetableQuadrant::Dirs[dirIdx] * dirToSb;
if(dirVal>bestDirVal)
{
bestDirVal= dirVal;
bestDirIdx= dirIdx;
}
}
// set the result.
ptrSortBlock->_QuadrantId= bestDirIdx;
// insert in list to sort.
sortVegetSbs.push_back(CSortVSB(ptrSortBlock));
// Debug Quadrants
/*p0DebugLines.push_back(ptrSortBlock->_Center);
p1DebugLines.push_back(ptrSortBlock->_Center + CVegetableQuadrant::Dirs[bestDirIdx]);*/
}
// next sortBlock
ptrSortBlock= (CVegetableSortBlock *)(ptrSortBlock->Next);
}
// next clipBlock to render
ptrClipBlock= ptrClipBlock->_RenderNext;
}
// sort!
// QSort. (I tried, better than radix sort, guckk!!)
sort(sortVegetSbs.begin(), sortVegetSbs.end());
// setup material for this rdrPass. NB: rendered after (in LayerModels).
//-------------
bool doubleSided= doubleSidedRdrPass(rdrPass);
// set the 2Sided flag in the material
_VegetableMaterial.setDoubleSided( doubleSided );
// setup the unique material.
_VegetableMaterial.setBlend(true);
_VegetableMaterial.setZWrite(false);
// leave AlphaTest but still kick low alpha values (for fillRate performance)
_VegetableMaterial.setAlphaTestThreshold(0.1f);
// order them in Layers.
//-------------
// render from back to front, to keep correct Z order in a single layer.
for(uint i=0; i<sortVegetSbs.size();i++)
{
CVegetableSortBlock *ptrSortBlock= sortVegetSbs[i].Sb;
float z= ptrSortBlock->_SortKey;
// compute in which layer must store this SB.
z= z*_NumZSortBlendLayers / _ZSortLayerDistMax;
// Avoid a floor(), using an OptFastFloor, but without the OptFastFloorBegin() End() group.
// => avoid the imprecision with such a trick; *256, then divide the integer by 256.
sint layer= NLMISC::OptFastFloor(z*256) >> 8;
clamp(layer, 0, (sint)_NumZSortBlendLayers-1);
// Range in correct layer, according to water ordering
if(ptrSortBlock->_UnderWater)
// range in the correct layermodel (NB: keep the same layer internal order).
_ZSortModelLayersUW[layer]->SortBlocks.push_back(ptrSortBlock);
else
_ZSortModelLayers[layer]->SortBlocks.push_back(ptrSortBlock);
}
}
// Quit
//--------------------
// disable VertexProgram.
driver->activeVertexProgram(NULL);
// restore Fog.
driver->enableFog(bkupFog);
// Debug Quadrants
/*for(uint l=0; l<p0DebugLines.size();l++)
{
CVector dv= CVector::K;
CDRU::drawLine(p0DebugLines[l]+dv, p1DebugLines[l]+dv, CRGBA(255,0,0), *driver);
}*/
// profile: compute number of triangles rendered with vegetable manager.
driver->profileRenderedPrimitives(ppIn, ppOut);
_NumVegetableFaceRendered= ppOut.NTriangles-precNTriRdr;
}
// ***************************************************************************
void CVegetableManager::setupRenderStateForBlendLayerModel(IDriver *driver)
{
// Setup Global.
//=============
// disable fog, for faster VP.
_BkupFog= driver->fogEnabled();
static volatile bool testDist = true;
bool fogged = _BkupFog && driver->getFogStart() < _ZSortLayerDistMax;
driver->enableFog(fogged);
// set model matrix to the manager matrix.
driver->setupModelMatrix(_ManagerMatrix);
// setup VP constants.
setupVertexProgramConstants(driver);
// Setup RdrPass.
//=============
uint rdrPass= NL3D_VEGETABLE_RDRPASS_UNLIT_2SIDED_ZSORT;
// Activate the unique material (correclty setuped for AlphaBlend in render()).
driver->setupMaterial(_VegetableMaterial);
// activate Vertex program first.
//nlinfo("\nSTARTVP\n%s\nENDVP\n", _VertexProgram[rdrPass]->getProgram().c_str());
nlverify(driver->activeVertexProgram(_VertexProgram[rdrPass][fogged ? 1 : 0]));
if (fogged)
{
driver->setConstantFog(6);
}
}
// ***************************************************************************
void CVegetableManager::resetNumVegetableFaceRendered()
{
_NumVegetableFaceRendered= 0;
}
// ***************************************************************************
uint CVegetableManager::getNumVegetableFaceRendered() const
{
return _NumVegetableFaceRendered;
}
// ***************************************************************************
void CVegetableManager::exitRenderStateForBlendLayerModel(IDriver *driver)
{
// disable VertexProgram.
driver->activeVertexProgram(NULL);
// restore Fog.
driver->enableFog(_BkupFog);
}
// ***************************************************************************
void CVegetableManager::setWind(const CVector &windDir, float windFreq, float windPower, float windBendMin)
{
// Keep only XY component of the Wind direction (because VP only support z==0 quaternions).
_WindDirection= windDir;
_WindDirection.z= 0;
_WindDirection.normalize();
// copy setup
_WindFrequency= windFreq;
_WindPower= windPower;
_WindBendMin= windBendMin;
clamp(_WindBendMin, 0, 1);
}
// ***************************************************************************
void CVegetableManager::setTime(double time)
{
// copy time
_Time= time;
}
// ***************************************************************************
// ***************************************************************************
// Lighting part.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CVegetableManager::setUpdateLightingTime(double time)
{
_ULTime= time;
}
// ***************************************************************************
void CVegetableManager::updateLighting()
{
// first time in this method??
if(!_ULPrecTimeInit)
{
_ULPrecTimeInit= true;
_ULPrecTime= _ULTime;
}
// compute delta time from last update.
float dt= float(_ULTime - _ULPrecTime);
_ULPrecTime= _ULTime;
// compute number of vertices to update.
_ULNVerticesToUpdate+= dt*_ULFrequency * _ULNTotalVertices;
// maximize, so at max, it computes all Igs, just one time.
_ULNVerticesToUpdate= min(_ULNVerticesToUpdate, (float)_ULNTotalVertices);
// go.
doUpdateLighting();
}
// ***************************************************************************
void CVegetableManager::updateLightingAll()
{
// maximize, so at max, it computes all Igs
_ULNVerticesToUpdate= (float)_ULNTotalVertices;
// go.
doUpdateLighting();
}
// ***************************************************************************
void CVegetableManager::doUpdateLighting()
{
// while there is still some vertices to update.
while(_ULNVerticesToUpdate > 0 && _ULRootIg)
{
// update the current ig. if all updated, skip to next one.
if(updateLightingIGPart())
{
// next
_ULRootIg= _ULRootIg->_ULNext;
}
}
// Now, _ULNVerticesToUpdate should be <=0. (most of the time < 0)
}
// ***************************************************************************
void CVegetableManager::setUpdateLightingFrequency(float freq)
{
freq= max(freq, 0.f);
_ULFrequency= freq;
}
// ***************************************************************************
bool CVegetableManager::updateLightingIGPart()
{
nlassert(_ULRootIg);
// First, update lighting info global to the ig, ie update current
// colros of the PointLights which influence the ig.
_ULRootIg->VegetableLightEx.computeCurrentColors();
// while there is some vertices to update
while(_ULNVerticesToUpdate>0)
{
// if all rdrPass of the ig are processed.
if(_ULCurrentIgRdrPass>= NL3D_VEGETABLE_NRDRPASS)
{
// All this Ig is updated.
_ULCurrentIgRdrPass= 0;
_ULCurrentIgInstance= 0;
// skip to next Ig.
return true;
}
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= _ULRootIg->_RdrPass[_ULCurrentIgRdrPass];
// if all instances are processed for this pass (especially if size()==0 !!)
if(_ULCurrentIgInstance>= vegetRdrPass.LightedInstances.size())
{
// skip to the next rdrPass.
_ULCurrentIgRdrPass++;
_ULCurrentIgInstance= 0;
continue;
}
// Process this instance.
_ULNVerticesToUpdate-= updateInstanceLighting(_ULRootIg, _ULCurrentIgRdrPass, _ULCurrentIgInstance);
// next instance.
_ULCurrentIgInstance++;
// if all instances are processed for this pass
if(_ULCurrentIgInstance>= vegetRdrPass.LightedInstances.size())
{
// skip to the next rdrPass.
_ULCurrentIgRdrPass++;
_ULCurrentIgInstance= 0;
}
}
// If all rdrPass of the ig are processed.
if(_ULCurrentIgRdrPass>= NL3D_VEGETABLE_NRDRPASS)
{
// All this Ig is updated.
_ULCurrentIgRdrPass= 0;
_ULCurrentIgInstance= 0;
// skip to next Ig.
return true;
}
else
{
// The Ig is not entirely updated.
return false;
}
}
// ***************************************************************************
uint CVegetableManager::updateInstanceLighting(CVegetableInstanceGroup *ig, uint rdrPassId, uint instanceId)
{
nlassert(ig);
// get the rdrPass.
nlassert(rdrPassId<NL3D_VEGETABLE_NRDRPASS);
CVegetableInstanceGroup::CVegetableRdrPass &vegetRdrPass= ig->_RdrPass[rdrPassId];
// get the lighted instance.
nlassert(instanceId<vegetRdrPass.LightedInstances.size());
CVegetableInstanceGroup::CVegetableLightedInstance &vegetLI= vegetRdrPass.LightedInstances[instanceId];
// get the shape
CVegetableShape *shape= vegetLI.Shape;
// it must be lighted.
nlassert(shape->Lighted);
bool instanceLighted= true;
// get ref on the vegetLex.
CVegetableLightEx &vegetLex= ig->VegetableLightEx;
// Color of pointLights modulated by diffuse.
CRGBA diffusePL[2];
diffusePL[0] = CRGBA::Black;
diffusePL[1] = CRGBA::Black;
if(vegetLex.NumLights>=1)
{
diffusePL[0].modulateFromColorRGBOnly(vegetLI.MatDiffuse, vegetLex.Color[0]);
if(vegetLex.NumLights>=2)
{
diffusePL[1].modulateFromColorRGBOnly(vegetLI.MatDiffuse, vegetLex.Color[1]);
}
}
// Recompute lighting
//===========
// setup for this instance.
//---------
// Precompute lighting or not??
bool precomputeLighting= instanceLighted && shape->PreComputeLighting;
// bestSided Precompute lighting or not??
bool bestSidedPrecomputeLighting= precomputeLighting && shape->BestSidedPreComputeLighting;
// destLighted?
bool destLighted= instanceLighted && !shape->PreComputeLighting;
// Diffuse and ambient, modulated by current GlobalAmbient and GlobalDiffuse.
CRGBA primaryRGBA, secondaryRGBA;
primaryRGBA.modulateFromColorRGBOnly(vegetLI.MatDiffuse, _GlobalDiffuse);
secondaryRGBA.modulateFromColorRGBOnly(vegetLI.MatAmbient, _GlobalAmbient);
// get normal matrix
CMatrix &normalMat= vegetLI.NormalMat;
// array of vertex id to update
uint32 *ptrVid= vegetRdrPass.Vertices.getPtr() + vegetLI.StartIdInRdrPass;
uint numVertices= (uint)shape->InstanceVertices.size();
// Copy Dynamic Lightmap UV in Alpha part (save memory for an extra cost of 1 VP instruction)
primaryRGBA.A= vegetLI.DlmUV.U;
secondaryRGBA.A= vegetLI.DlmUV.V;
// get VertexBuffer info.
CVegetableVBAllocator *allocator;
allocator= &getVBAllocatorForRdrPassAndVBHardMode(rdrPassId, vegetRdrPass.HardMode);
const CVertexBuffer &dstVBInfo= allocator->getSoftwareVertexBuffer();
uint srcNormalOff= (instanceLighted? shape->VB.getNormalOff() : 0);
uint dstColor0Off= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_COLOR0);
uint dstColor1Off= dstVBInfo.getValueOffEx(NL3D_VEGETABLE_VPPOS_COLOR1);
// For D3D, If the VertexBuffer is in BGRA mode
if(allocator->isBGRA())
{
// then swap only the B and R (no cpu cycle added per vertex)
primaryRGBA.swapBR();
secondaryRGBA.swapBR();
diffusePL[0].swapBR();
diffusePL[1].swapBR();
}
CVertexBufferRead vba;
shape->VB.lock (vba);
CVertexBufferReadWrite vbaOut;
allocator->getSoftwareVertexBuffer ().lock(vbaOut);
// For all vertices, recompute lighting.
//---------
for(sint i=0; i<(sint)numVertices;i++)
{
// get the Vertex in the VB.
uint vid= ptrVid[i];
// store in tmp shape.
shape->InstanceVertices[i]= vid;
// Fill this vertex.
const uint8 *srcPtr= (const uint8*)vba.getVertexCoordPointer(i);
uint8 *dstPtr= (uint8*)vbaOut.getVertexCoordPointer(vid);
// if !precomputeLighting (means destLighted...)
if(!precomputeLighting)
{
// just copy the primary and secondary color
*(CRGBA*)(dstPtr + dstColor0Off)= primaryRGBA;
*(CRGBA*)(dstPtr + dstColor1Off)= secondaryRGBA;
}
else
{
nlassert(!destLighted);
// compute normal.
CVector rotNormal= normalMat.mulVector( *(CVector*)(srcPtr + srcNormalOff) );
// must normalize() because scale is possible.
rotNormal.normalize();
// Do the compute.
if(!bestSidedPrecomputeLighting)
{
computeVegetVertexLighting(rotNormal,
_DirectionalLight, primaryRGBA, secondaryRGBA,
vegetLex, diffusePL, (CRGBA*)(dstPtr + dstColor0Off) );
}
else
{
computeVegetVertexLightingForceBestSided(rotNormal,
_DirectionalLight, primaryRGBA, secondaryRGBA,
vegetLex, diffusePL, (CRGBA*)(dstPtr + dstColor0Off) );
}
}
// flust the vertex in AGP.
allocator->flushVertex(vid);
}
// numVertices vertices are updated
return numVertices;
}
} // NL3D