khanat-opennel-code/code/ryzom/client/src/landscape_poly_drawer.cpp

724 lines
22 KiB
C++
Raw Normal View History

2012-04-23 17:30:57 +00:00
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// 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 "stdpch.h"
#include "landscape_poly_drawer.h"
// 3D Interfaces
#include "nel/3d/u_driver.h"
#include "nel/3d/u_scene.h"
#include "nel/3d/u_landscape.h"
// 3d
#include "nel/3d/zone.h"
#include "nel/3d/driver_user.h"
// client
#include "decal.h"
using namespace NLMISC;
using namespace NL3D;
using namespace std;
// EXTERN
extern UDriver *Driver;
extern UScene *Scene;
extern UMaterial GenericMat;
extern ULandscape *Landscape;
//-----------------------------------------------------------------------------------------------------------
//---------------------------------------- CInitStencil -----------------------------------------------------
//-----------------------------------------------------------------------------------------------------------
void CInitStencil::init()
{
// set ILandscapePolyDrawingCallback attribute of current scene
Scene->setLandscapePolyDrawingCallback(this);
}
//-----------------------------------------------------------------------------------------------------------
void CInitStencil::beginPolyDrawing()
{
// The eighth bit will be written with a 1 during next render (landscape)
Driver->stencilOp(UDriver::keep, UDriver::keep, UDriver::replace);
}
//-----------------------------------------------------------------------------------------------------------
void CInitStencil::endPolyDrawing()
{
// The eighth bit will be written with a 0 during next render (veget,...)
Driver->stencilOp(UDriver::keep, UDriver::keep, UDriver::zero);
}
//-----------------------------------------------------------------------------------------------------------
//---------------------------------- CLandscapePolyDrawer ---------------------------------------------------
//-----------------------------------------------------------------------------------------------------------
CLandscapePolyDrawer::CLandscapePolyDrawer()
{
// set ILandscapePolyDrawingCallback attribute of current scene to modify stencil operation
// from NEL classes with callbacks.
_InitStencil = new CInitStencil();
_InitStencil->init();
// shadow rectangle
_Shadow.V0 = CVector(0.0, 0.0, 0.0);
_Shadow.V1 = CVector(1.0, 0.0, 0.0);
_Shadow.V2 = CVector(1.0, 1.0, 0.0);
_Shadow.V3 = CVector(0.0, 1.0, 0.0);
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::initLandscapePolyDrawingCallback()
{
// set ILandscapePolyDrawingCallback attribute of current scene to modify stencil operation
// from NEL classes with callbacks.
nlassert(_InitStencil);
_InitStencil->init();
}
//-----------------------------------------------------------------------------------------------------------
CLandscapePolyDrawer::~CLandscapePolyDrawer()
{
delete _InitStencil;
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::addPoly(const NLMISC::CPolygon2D &poly,
const NLMISC::CRGBA & color, const NLMISC::CAABBox & bBox)
{
nlassert(!poly.Vertices.empty());
_Polygons.push_back(poly);
_PolyColor.push_back(color);
_BBoxes.push_back(bBox);
buildShadowVolume((uint)_Polygons.size());
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::beginRenderLandscapePolyPart()
{
// activation of stencil test
Driver->enableStencilTest(true);
Driver->stencilFunc(UDriver::always, 128, 0xff);
// the eighth bit will be written with a 0 during next render to mark stencil buffer
// parts which will support Shadow Volume algorithm (landscape).
// When stencil operation will be modify with "replace" operation, the eighth bit will be written
// with a 1 (vegeation, shadow...)
Driver->stencilOp(UDriver::keep, UDriver::keep, UDriver::zero);
}
//-----------------------------------------------------------------------------------------------------------
inline void createFace(CVector * face, const vector<CVector> & vertices, uint v1,
uint v2, uint v3, uint v4)
{
face[0] = vertices[v1];
face[1] = vertices[v2];
face[2] = vertices[v3];
face[3] = vertices[v4];
}
void CLandscapePolyDrawer::setScissor(uint polyId)
{
// vector of bounding box vertices
const CAABBox & bBox = _BBoxes[polyId];
const CVector& center = bBox.getCenter();
const CVector& halfSize = bBox.getHalfSize();
vector<CVector> vertices(8);
vertices[0] = CVector(center.x-halfSize.x, center.y+halfSize.y, center.z+halfSize.z);
vertices[1] = CVector(center.x+halfSize.x, center.y+halfSize.y, center.z+halfSize.z);
vertices[2] = CVector(center.x+halfSize.x, center.y-halfSize.y, center.z+halfSize.z);
vertices[3] = CVector(center.x-halfSize.x, center.y-halfSize.y, center.z+halfSize.z);
vertices[4] = CVector(center.x-halfSize.x, center.y+halfSize.y, center.z-halfSize.z);
vertices[5] = CVector(center.x+halfSize.x, center.y+halfSize.y, center.z-halfSize.z);
vertices[6] = CVector(center.x+halfSize.x, center.y-halfSize.y, center.z-halfSize.z);
vertices[7] = CVector(center.x-halfSize.x, center.y-halfSize.y, center.z-halfSize.z);
// transform each point to obtain vertices coordinates in camera location
CMatrix transformMatrix = Scene->getCam().getMatrix();
transformMatrix.invert();
for(uint i=0; i<8; i++)
{
CVector & point = vertices[i];
point = transformMatrix.mulPoint(point);
}
// clip bounding box faces
CVector faces[6][4];
createFace(faces[0], vertices, 3, 2, 1, 0);
createFace(faces[1], vertices, 7, 6, 5, 4);
createFace(faces[2], vertices, 0, 1, 5, 4);
createFace(faces[3], vertices, 7, 6, 2, 3);
createFace(faces[4], vertices, 2, 6, 5, 1);
createFace(faces[5], vertices, 7, 3, 0, 4);
// "near" plane of current frustum
CPlane nearPlane;
nearPlane.make(CVector(0, 1, 0), CVector(0, Driver->getFrustum().Near, 0));
CVector out[10];
_FiniteFrustum = Driver->getFrustum();
CVector2f rectMax(0,0), rectMin(0,0);
// for each face, we research its intersection points with "near" plane (if exist)
for(uint f=0; f<6; f++)
{
uint outNb = nearPlane.clipPolygonFront(faces[f], out, 4);
// if intersection points exist, we project them in windows coordinates to recover
// scissor rectangle
if(outNb!=0)
{
for(uint v=0; v<outNb; v++)
{
CVector & point = out[v];
//project
point = _FiniteFrustum.project(point);
//clamp
NLMISC::clamp(point.x, 0, 1);
NLMISC::clamp(point.y, 0, 1);
// search for bounding rectangle of scissor test
if(f==0 && v==0)
{
rectMax = rectMin = point;
}
else
{
rectMax.x = std::max(rectMax.x, point.x);
rectMax.y = std::max(rectMax.y, point.y);
rectMin.x = std::min(rectMin.x, point.x);
rectMin.y = std::min(rectMin.y, point.y);
}
}
}
}
CScissor scissor(rectMin.x, rectMin.y, rectMax.x - rectMin.x, rectMax.y - rectMin.y);
Driver->setScissor(scissor);
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::renderLandscapePolyPart()
{
uint i;
UCamera cam = Scene->getCam();
Driver->setMatrixMode3D(cam);
CViewport oldViewPort = Driver->getViewport();
Driver->setViewport(Scene->getViewport());
// get initial values of depth range
float depthRangeMin;
Driver->getDepthRange(depthRangeMin, _MaxDepthRange);
// get original (finite) frustum
_FiniteFrustum = Driver->getFrustum();
// we can write in stencil buffer only the last 7 bits.
// The eighth bit mustn't be written because it indicates if a part of stencil buffer
// can support shadow volume algorithm.
Driver->stencilMask(0x7f);
for(i=1; i<=_Polygons.size(); i++)
{
// calculate and set scissor rectangle to optimize algorithm.
setScissor(i-1);
// disable color buffer update
Driver->setColorMask(false, false, false, false);
Scene->enableLightingSystem(false);
// disable Z buffer update
GenericMat.setZWrite(false);
// ZFAIL algorithm
{
// test and write only if eighth bit is a 1 (landscape).
Driver->stencilFunc(UDriver::notequal, 0, 0x80);
// display of shadow volumes in two passes
GenericMat.setDoubleSided(false);
// render the shadow volume back faces and increment the stencil buffer
// when the depth test fails
Driver->setCullMode(UDriver::CW);
Driver->stencilOp(UDriver::keep, UDriver::incr, UDriver::keep);
drawShadowVolume(i, true);
// render the shadow volume front faces and decrement the stencil buffer
// when the depth test fails
Driver->setCullMode(UDriver::CCW);
Driver->stencilOp(UDriver::keep, UDriver::decr, UDriver::keep);
drawShadowVolume(i, false);
GenericMat.setDoubleSided(true);
}
// enable color and Z Buffer updates
Driver->setColorMask(true, true, true, true);
GenericMat.setZWrite(true);
// enable light
Scene->enableLightingSystem(true);
// render of the polygon : render of a rectangle whith polygon color.
// It's renderer only if stencil buffer is different to 0 on seven last bits
Driver->stencilFunc(UDriver::notequal, 0, 0x7f);
// if stencil buffer is different to 0 (polygon), value must be
// replace by 0 (seven last bits).
Driver->stencilOp(UDriver::keep, UDriver::keep, UDriver::replace);
drawPolygon(i);
// reset neutral values
Driver->stencilOp(UDriver::keep, UDriver::keep, UDriver::keep);
Driver->setScissor(CScissor(0, 0, 1, 1));
}
// render decals on landscape only
Driver->stencilFunc(UDriver::equal, 0x80, 0x80);
// call to render the decals just before the projected polygons on landscape
CDecalRenderList::getInstance().renderAllDecals();
// disable stencil test
Driver->enableStencilTest(false);
Driver->stencilMask(0xff);
Driver->stencilFunc(UDriver::always, 0, 0xff);
// reset viewport
Driver->setViewport(oldViewPort);
}
//-----------------------------------------------------------------------------------------------------------
void
CLandscapePolyDrawer::infiniteFrustum()
{
// setup new values of depth range
Driver->setDepthRange(0.0, 1.0);
// znear and zfar initial values
float znear, zfar;
znear = _FiniteFrustum.Near;
zfar = _FiniteFrustum.Far;
// initial projection matrix
CMatrix frustumMatrix = Driver->getFrustumMatrix();
// epsilon depends from OpenGL or Direct3D use
double factor = Driver->getClipSpaceZMin() == -1.f ? 2 : 1;
double epsilon = factor*(1 - _MaxDepthRange*(zfar/(zfar - znear)));
// update of coefficients to obtain "robust" infinite frustum,
// and join depth range used in main scen display
// OpenGL
if(factor == 2)
{
frustumMatrix.setCoefficient((float)(epsilon-1), 2, 2);
frustumMatrix.setCoefficient((float)(znear*(epsilon-2)), 2, 3);
}
// Direct3D
else
{
frustumMatrix.setCoefficient((float)(1-epsilon), 2, 2);
frustumMatrix.setCoefficient((float)(znear*(epsilon-1)), 3, 2);
}
// initial projection matrix
Driver->setFrustumMatrix(frustumMatrix);
}
//-----------------------------------------------------------------------------------------------------------
void
CLandscapePolyDrawer::finiteFrustum()
{
// depth range
Driver->setDepthRange(0.0, _MaxDepthRange);
// projection matrix
Driver->setFrustum(_FiniteFrustum);
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::buildShadowVolume(uint poly)
{
uint i, v1, v2, v3, v4;
CVertexBuffer vb;
CIndexBuffer ib;
CVector2f barycenter(0, 0);
const CPolygon2D & polygon = _Polygons[poly-1];
uint verticesNb = (uint)polygon.Vertices.size();
// barycenter polygon
for(i=0; i<verticesNb; i++)
{
barycenter += polygon.Vertices[i];
}
barycenter = barycenter/((float)verticesNb);
_Barycenters.push_back(barycenter);
// vertex buffer initialization.
// vertices coordinates aren't calculated immediately but in drawShadowVolume method,
// because they are calculated in camera location.
vb.setVertexFormat(CVertexBuffer::PositionFlag);
vb.setNumVertices(2*verticesNb + 2);
{
CVertexBufferReadWrite vba;
vb.lock(vba);
for(i=0; i<2*verticesNb + 2; i++)
{
vba.setVertexCoord(i, CVector(0.0, 0.0, 0.0));
}
}
_PolyVB.push_back(vb);
// index buffer initialization.
int index = 0;
ib.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
ib.setNumIndexes(12*verticesNb);
{
CIndexBufferReadWrite iba;
ib.lock (iba);
// near cap
for(v1=1; v1<=verticesNb; v1++)
{
v2 = v1+1;
if(v2 == verticesNb+1)
{
v2 = 1;
}
iba.setTri(index, v1, v2, 0);
index += 3;
}
// far cap
for(v1=verticesNb+2; v1<=2*verticesNb+1; v1++)
{
v2 = v1+1;
if(v2 == 2*verticesNb+2)
{
v2 = verticesNb+2;
}
iba.setTri(index, v2, v1, verticesNb+1);
index += 3;
}
// surrounding faces
for(v1=1; v1<=verticesNb; v1++)
{
v3 = v1+verticesNb+1;
v2 = v1+1;
if(v2 == verticesNb+1)
{
v2 = 1;
}
v4 = v3+1;
if(v4 == 2*verticesNb+2)
{
v4 = verticesNb+2;
}
// first triangle
iba.setTri(index, v2, v1, v3);
index += 3;
// second triangle
iba.setTri(index, v2, v3, v4);
index += 3;
}
}
_PolyIB.push_back(ib);
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::drawShadowVolume(uint poly, bool firstPass)
{
if(firstPass)
{
// render shadow volume in camera location, in order to minimize
// polygon coordinates
CVertexBuffer & vb = _PolyVB[poly-1];
const CPolygon2D & polygon = _Polygons[poly-1];
const CVector2f & barycenter = _Barycenters[poly-1];
uint verticesNb = (uint)polygon.Vertices.size();
uint i;
CVector2f vertex;
CVector * vertexVB = NULL;
const CVector cameraPos = Scene->getCam().getPos();
float height = 2000.0;
{
CVertexBufferReadWrite vba;
vb.lock(vba);
// top vertices
vertexVB = vba.getVertexCoordPointer(0);
*vertexVB = CVector(barycenter.x, barycenter.y, height) - cameraPos;
for(i=0; i<verticesNb; i++)
{
vertex = polygon.Vertices[i];
vertexVB = vba.getVertexCoordPointer(i+1);
*vertexVB = CVector(vertex.x, vertex.y, height) - cameraPos;
}
// bottom vertices
vertexVB = vba.getVertexCoordPointer(verticesNb+1);
*vertexVB = CVector(barycenter.x, barycenter.y, -height) - cameraPos;
for(i=0; i<verticesNb; i++)
{
vertex = polygon.Vertices[i];
vertexVB = vba.getVertexCoordPointer(i+verticesNb+2);
*vertexVB = CVector(vertex.x, vertex.y, -height) - cameraPos;
}
}
// new matrix model/view to reposition in camera position
Driver->setModelMatrix(CMatrix::Identity);
CMatrix viewMatrix = Driver->getViewMatrix();
_OldViewMatrix = viewMatrix;
viewMatrix.setPos(CVector::Null);
Driver->setViewMatrix(viewMatrix);
// before drawing shadow volume, we setup an infinite frustum
// and a [0, 1] depth range
infiniteFrustum();
}
// render Vertex Buffer
((CDriverUser*)Driver)->getDriver()->activeVertexBuffer(_PolyVB[poly-1]);
((CDriverUser*)Driver)->getDriver()->activeIndexBuffer(_PolyIB[poly-1]);
((CDriverUser*)Driver)->getDriver()->renderTriangles(
*GenericMat.getObjectPtr(), 0, 4*(uint32)_Polygons[poly-1].Vertices.size());
if(!firstPass)
{
// we recover initial (finite) projection matrix
finiteFrustum();
// setup old model/view matrix
Driver->setViewMatrix(_OldViewMatrix);
}
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::drawPolygon(uint poly)
{
GenericMat.setZWrite(false);
const CRGBA & currentColor = GenericMat.getColor();
// activation of transparency
GenericMat.setBlend(true);
GenericMat.setBlendFunc(UMaterial::srcalpha, UMaterial::invsrcalpha);
GenericMat.getObjectPtr()->setOpacity(128);
GenericMat.getObjectPtr()->setColor(_PolyColor[poly-1]);
// draw a half transparent rectangle which covers all scissor rectangle
Driver->setMatrixMode2D11();
Driver->drawQuad(_Shadow, GenericMat);
UCamera cam = Scene->getCam();
Driver->setMatrixMode3D(cam);
// reset old material of scene
GenericMat.setBlend(false);
GenericMat.setColor(currentColor);
GenericMat.setZWrite(true);
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::deletePolygons()
{
uint i;
_Polygons.clear();
_Barycenters.clear();
_PolyColor.clear();
_BBoxes.clear();
for(i=0; i<_PolyVB.size(); i++)
{
_PolyVB[i].deleteAllVertices();
}
_PolyVB.clear();
for(i=0; i<_PolyIB.size(); i++)
{
_PolyIB[i].deleteAllIndexes();
}
_PolyIB.clear();
}
//-----------------------------------------------------------------------------------------------------------
void CLandscapePolyDrawer::computeBBoxFromPolygon(const NLMISC::CPolygon2D &poly2D,
NLMISC::CAABBox &destBBox)
{
if (poly2D.Vertices.empty())
{
destBBox.setMinMax(CVector::Null, CVector::Null);
return;
}
// search for min and max dimensions of polygon on x and y axes.
CVector2f point = poly2D.Vertices[0];
CVector2f rectMax(point), rectMin(point);
for(uint i=1; i<poly2D.Vertices.size(); i++)
{
point = poly2D.Vertices[i];
rectMax.x = std::max(rectMax.x, point.x);
rectMax.y = std::max(rectMax.y, point.y);
rectMin.x = std::min(rectMin.x, point.x);
rectMin.y = std::min(rectMin.y, point.y);
}
// init bounding box
float w = rectMax.x - rectMin.x, h = rectMax.y - rectMin.y;
destBBox.setCenter(CVector(rectMin.x + w/2 , rectMin.y + h/2, 0));
destBBox.setHalfSize(CVector(w/2 , h/2, 0));
// search for zones list on which the polygon will be "projected".
uint zoneDim = 160;
sint32 xmin = ((uint)(rectMin.x/zoneDim))*zoneDim;
sint32 xmax = ((uint)(rectMax.x/zoneDim))*zoneDim;
sint32 ymin = (((uint)(rectMin.y/zoneDim)))*zoneDim;
sint32 ymax = (((uint)(rectMax.y/zoneDim)))*zoneDim;
std::list<uint16> zoneIds;
for(sint32 x=xmin; x<=xmax; x+=zoneDim)
{
for(sint32 y=ymin; y<=ymax; y+=zoneDim)
{
float xcount = (float)(x/zoneDim);
float ycount = (float)(-y/zoneDim) + 1;
uint16 zoneId = (uint16) ((ycount-1)*256+xcount);
zoneIds.push_back(zoneId);
}
}
// search for min and max dimensions on z axis.
bool firstExtend = true;
std::list<uint16>::iterator it;
for(it=zoneIds.begin(); it!=zoneIds.end(); it++)
{
if (Landscape)
{
const CZone* zone = Landscape->getZone(*it);
if(zone)
{
// For each zone, we traverse its patchs and check intersection between our polygon
// bounding rectangle on (x, y) plane and the bounding sphere patch .
// If intersection, we use the bounding box of the patch to extend the polygon boudning box
// on z axis.
// NB : The patch bounding box isn't used immediately because it's more expensive
// to obtain than bounding sphere.
sint numPatchs = zone->getNumPatchs();
for(sint i=0; i<numPatchs; i++)
{
const CBSphere& bSphere = zone->getPatchBSphere(i);
const CVector2f sphereMax = CVector2f(bSphere.Center.x + bSphere.Radius,
bSphere.Center.y + bSphere.Radius);
const CVector2f sphereMin = CVector2f(bSphere.Center.x - bSphere.Radius,
bSphere.Center.y - bSphere.Radius);
// intersection beetween patch bounding sphere and polygon bounding rectangle
if(sphereMin.x<rectMax.x && sphereMax.x>rectMin.x && sphereMin.y<rectMax.y
&& sphereMax.y>rectMin.y)
{
const CPatch* patch = zone->getPatch(i);
const CAABBox & patchBBox = patch->buildBBox();
const CVector& center = patchBBox.getCenter();
const CVector& halfSize = patchBBox.getHalfSize();
const CVector2f patchMax = CVector2f(center.x + halfSize.x, center.y + halfSize.y);
const CVector2f patchMin = CVector2f(center.x - halfSize.x, center.y - halfSize.y);
// intersection beetween patch bounding box and polygon bounding rectangle
if(patchMin.x<rectMax.x && patchMax.x>rectMin.x && patchMin.y<rectMax.y
&& patchMax.y>rectMin.y)
{
if(firstExtend)
{
destBBox.setCenter(CVector(destBBox.getCenter().x, destBBox.getCenter().y,
center.z));
destBBox.setHalfSize(CVector(destBBox.getHalfSize().x, destBBox.getHalfSize().y,
halfSize.z));
firstExtend = false;
}
else
{
float zmin=destBBox.getCenter().z-destBBox.getHalfSize().z;
float zmax=destBBox.getCenter().z+destBBox.getHalfSize().z;
if(center.z-halfSize.z < zmin)
zmin = center.z-halfSize.z;
if(center.z+halfSize.z > zmax)
zmax = center.z+halfSize.z;
destBBox.setCenter(CVector(destBBox.getCenter().x, destBBox.getCenter().y,
zmin + (zmax-zmin)/2));
destBBox.setHalfSize(CVector(destBBox.getHalfSize().x, destBBox.getHalfSize().y,
(zmax-zmin)/2));
}
}
}
}
}
}
}
}
//-----------------------------------------------------------------------------------------------------------