// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "std3d.h"
#include "nel/3d/vegetable.h"
#include "nel/misc/common.h"
#include "nel/3d/vegetable_manager.h"
#include "nel/misc/fast_floor.h"
using namespace std;
using namespace NLMISC;
namespace NL3D
{
// ***************************************************************************
// Generate random value, but seed is spacial. Take a high frequency, so it gets more the aspect of random.
static NLMISC::CNoiseValue RandomGenerator(0,1, 7.68f);
// ***************************************************************************
CVegetable::CVegetable()
{
// Ground style density.
setAngleGround(0);
// Density not maximised.
MaxDensity= -1;
// No scale.
Sxy.Abs= Sz.Abs= 1;
Sxy.Rand= Sz.Rand= 0;
// No rotation.
Rx.Abs= Ry.Abs= Rz.Abs= 0;
Rx.Rand= Ry.Rand= Rz.Rand= 0;
// No BendFactor.
BendFactor.Abs= 1;
BendFactor.Rand= 0;
BendFrequencyFactor= 1;
// Appear at 0.
DistType= 0;
_Manager= NULL;
}
// ***************************************************************************
void CVegetable::setAngleGround(float cosAngleMin)
{
_AngleType= AngleGround;
_CosAngleMin= cosAngleMin;
// We must be at densityFactor==1, when cosAngle==1, keeping the same formula.
_CosAngleMax= 1 + (1-cosAngleMin);
// precalc
_CosAngleMiddle= (_CosAngleMin + _CosAngleMax)/2;
_OOCosAngleDist= _CosAngleMax - _CosAngleMiddle;
if(_OOCosAngleDist)
_OOCosAngleDist= 1.0f / _OOCosAngleDist;
}
// ***************************************************************************
void CVegetable::setAngleCeiling(float cosAngleMax)
{
_AngleType= AngleCeiling;
_CosAngleMax= cosAngleMax;
// We must be at densityFactor==1, when cosAngle==-1, keeping the same formula.
_CosAngleMin= -1 - (cosAngleMax-(-1));
// precalc
_CosAngleMiddle= (_CosAngleMin + _CosAngleMax)/2;
_OOCosAngleDist= _CosAngleMax - _CosAngleMiddle;
if(_OOCosAngleDist)
_OOCosAngleDist= 1.0f / _OOCosAngleDist;
}
// ***************************************************************************
void CVegetable::setAngleWall(float cosAngleMin, float cosAngleMax)
{
_AngleType= AngleWall;
_CosAngleMin= cosAngleMin;
_CosAngleMax= cosAngleMax;
// precalc
_CosAngleMiddle= (_CosAngleMin + _CosAngleMax)/2;
_OOCosAngleDist= _CosAngleMax - _CosAngleMiddle;
if(_OOCosAngleDist)
_OOCosAngleDist= 1.0f / _OOCosAngleDist;
}
// ***************************************************************************
void CVegetable::registerToManager(CVegetableManager *manager)
{
nlassert(manager);
_Manager= manager;
_VegetableShape= _Manager->getVegetableShape(ShapeName);
}
// ***************************************************************************
void CVegetable::generateGroupEx(float nbInst, const CVector &posInWorld, const CVector &surfaceNormal, uint vegetSeed, std::vector &instances) const
{
nlassert(_Manager);
// Density modulation.
//===================
// compute cos of angle between surfaceNormal and K(0,0,1).
float cosAngle= surfaceNormal.z;
// compute angleFactor density. Use a quadratic, because f'(_CosAngleMiddle)==0.
float angleFact= 1 - sqr((cosAngle - _CosAngleMiddle) * _OOCosAngleDist);
angleFact= max(0.f, angleFact);
// modulate density with angleFactor.
nbInst*= angleFact;
// modulate result by Global Manager density
nbInst*= _Manager->getGlobalDensity();
// Now, 0<=nbInst<+oo. If we have 0.1, it means that we have 10% chance to spawn an instance.
// So add a "random" value (with help of a noise with High frequency)
// if nbInst==0, we should never have any instance (which may arise if evalOneLevelRandom()==1).
// hence the 0.99f* which ensure that we do nbInst+= [0..1[.
nbInst+= 0.99f * RandomGenerator.evalOneLevelRandom(posInWorld);
// and then get only the integral part.
sint nbInstances= NLMISC::OptFastFloor(nbInst);
nbInstances= max(0, nbInstances);
// resize the instances
instances.resize(nbInstances);
// Position generation.
//===================
// For now, generate them randomly.
static CVector2f dSeed(0.513f, 0.267f); // random values.
CVector seed= posInWorld;
seed.z+= vegetSeed * 0.723f; // 0.723f is a random value.
for(sint i=0; i &instances) const
{
// number of instances to generate
float dens= Density.eval(posInWorld);
if(MaxDensity >= 0)
dens= min(dens, MaxDensity);
float nbInst= area * dens;
// modulate by normal and generate them.
generateGroupEx(nbInst, posInWorld, surfaceNormal, vegetSeed, instances);
}
// ***************************************************************************
void CVegetable::generateGroupBiLinear(const CVector &posInWorld, const CVector posInWorldBorder[4], const CVector &surfaceNormal, float area, uint vegetSeed, std::vector &instances) const
{
sint i;
const float evenDistribFact= 12.25f; // an arbitrary value to have a higher frequency for random.
// compute how many instances to generate on borders of the patch
// ==================
float edgeDensity[4];
for(i=0; i<4; i++)
{
// Get number of instances generated on edges
edgeDensity[i]= area * Density.eval(posInWorldBorder[i]);
if(MaxDensity >= 0)
edgeDensity[i]= min(edgeDensity[i], area * MaxDensity);
edgeDensity[i]= max(0.f, edgeDensity[i]);
}
// Average on center of the patch for each direction.
float edgeDensityCenterX;
float edgeDensityCenterY;
edgeDensityCenterX= 0.5f * (edgeDensity[0] + edgeDensity[1]);
edgeDensityCenterY= 0.5f * (edgeDensity[2] + edgeDensity[3]);
// Average for all the patch
float nbInstAverage= 0.5f * (edgeDensityCenterX + edgeDensityCenterY);
// generate instances on the patch
// ==================
generateGroupEx(nbInstAverage, posInWorld, surfaceNormal, vegetSeed, instances);
// move instances x/y to follow edge repartition
// ==================
// If on a direction, both edges are 0 density, then must do a special formula
bool middleX= edgeDensityCenterX<=1;
bool middleY= edgeDensityCenterY<=1;
float OOEdgeDCX=0.0;
float OOEdgeDCY=0.0;
if(!middleX) OOEdgeDCX= 1.0f / edgeDensityCenterX;
if(!middleY) OOEdgeDCY= 1.0f / edgeDensityCenterY;
// for all instances
for(i=0; i<(sint)instances.size(); i++)
{
float x= instances[i].x;
float y= instances[i].y;
// a seed for random.
CVector randSeed(x*evenDistribFact, y*evenDistribFact, 0);
// X change.
if(middleX)
{
// instances are grouped at middle. this is the bijection of easeInEaseOut
x= x+x - easeInEaseOut(x);
x= x+x - easeInEaseOut(x);
instances[i].x= x;
}
else
{
// Swap X, randomly. swap more on border
// evaluate the density in X direction we have at this point.
float densX= edgeDensity[0]*(1-x) + edgeDensity[1]* x ;
// If on the side of the lowest density
if(densX < edgeDensityCenterX)
{
// may swap the position
float rdSwap= (densX * OOEdgeDCX );
// (densX * OOEdgeDCX) E [0..1[. The more it is near 0, the more is has chance to be swapped.
rdSwap+= RandomGenerator.evalOneLevelRandom( randSeed );
if(rdSwap<1)
instances[i].x= 1 - instances[i].x;
}
}
// Y change.
if(middleY)
{
// instances are grouped at middle. this is the bijection of easeInEaseOut
y= y+y - easeInEaseOut(y);
y= y+y - easeInEaseOut(y);
instances[i].y= y;
}
else
{
// Swap Y, randomly. swap more on border
// evaluate the density in Y direction we have at this point.
float densY= edgeDensity[2]*(1-y) + edgeDensity[3]* y ;
// If on the side of the lowest density
if(densY < edgeDensityCenterY)
{
// may swap the position
float rdSwap= (densY * OOEdgeDCY);
// (densY * OOEdgeDCY) E [0..1[. The more it is near 0, the more is has chance to be swapped.
rdSwap+= RandomGenerator.evalOneLevelRandom( randSeed );
if(rdSwap<1)
instances[i].y= 1 - instances[i].y;
}
}
}
}
// ***************************************************************************
void CVegetable::reserveIgAddInstances(CVegetableInstanceGroupReserve &vegetIgReserve, TVegetableWater vegetWaterState, uint numInstances) const
{
nlassert(_Manager);
if (_VegetableShape)
_Manager->reserveIgAddInstances(vegetIgReserve, _VegetableShape, (CVegetableManager::TVegetableWater)vegetWaterState, numInstances);
}
// ***************************************************************************
void CVegetable::generateInstance(CVegetableInstanceGroup *ig, const NLMISC::CMatrix &posInWorld,
const NLMISC::CRGBAF &modulateAmbientColor, const NLMISC::CRGBAF &modulateDiffuseColor, float blendDistMax,
TVegetableWater vegetWaterState, CVegetableUV8 dlmUV) const
{
nlassert(_Manager);
CVector seed= posInWorld.getPos();
// Generate Matrix.
// ===============
// Generate a random Scale / Rotation matrix.
CMatrix randomMat;
// setup rotation
CVector rot;
rot.x= Rx.eval(seed);
rot.y= Ry.eval(seed);
rot.z= Rz.eval(seed);
randomMat.setRot(rot, CMatrix::ZXY);
// scale.
if(Sxy.Abs!=0 || Sxy.Rand!=0 || Sz.Abs!=0 || Sz.Rand!=0)
{
CVector scale;
scale.x= scale.y= Sxy.eval(seed);
scale.z= Sz.eval(seed);
randomMat.scale(scale);
}
// Final Matrix.
CMatrix finalMatrix;
finalMatrix= posInWorld * randomMat;
// Generate Color and factor
// ===============
CRGBAF materialColor(1,1,1,1);
// evaluate gradients. If none, color not modified.
Color.eval(seed, materialColor);
// modulate with user
CRGBAF ambient, diffuse;
if(_VegetableShape && _VegetableShape->Lighted)
{
ambient= modulateAmbientColor * materialColor;
diffuse= modulateDiffuseColor * materialColor;
}
else
{
ambient= materialColor;
diffuse= materialColor;
}
// Generate a bendFactor
float bendFactor= BendFactor.eval(seed);
// Generate a bendPhase
float bendPhase= BendPhase.eval(seed);
// Append to the vegetableManager
// ===============
if (_VegetableShape)
{
_Manager->addInstance(ig, _VegetableShape, finalMatrix, ambient, diffuse,
bendFactor, bendPhase, BendFrequencyFactor, blendDistMax,
(CVegetableManager::TVegetableWater)vegetWaterState, dlmUV);
}
}
// ***************************************************************************
void CVegetable::serial(NLMISC::IStream &f)
{
/*
Version 1:
- add BendFrequencyFactor
Version 0:
- base version
*/
sint ver= f.serialVersion(1);
f.serial(ShapeName);
f.serial(Density);
f.serial(MaxDensity);
f.serial(_CosAngleMin, _CosAngleMax, _CosAngleMiddle, _OOCosAngleDist);
f.serialEnum(_AngleType);
f.serial(Sxy, Sz);
f.serial(Rx, Ry, Rz);
f.serial(BendFactor);
f.serial(BendPhase);
f.serial(Color);
f.serial(DistType);
if(ver>=1)
f.serial(BendFrequencyFactor);
else
BendFrequencyFactor= 1;
}
} // NL3D