Serious-Engine/Sources/Engine/Rendering/RendClip.cpp
2016-03-11 15:57:17 +02:00

519 lines
17 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
/////////////////////////////////////////////////////////////////////
// Clipping functions
// check if a polygon is to be visible
__forceinline ULONG CRenderer::GetPolygonVisibility(const CBrushPolygon &bpo)
{
// get transformed polygon's plane
CWorkingPlane *pwplPolygonPlane = bpo.bpo_pbplPlane->bpl_pwplWorking;
CWorkingPlane wplReverse;
BOOL bInvertPolygon = FALSE;
// if the polygon should be inverted or double sided
if((re_bRenderingShadows
&&!re_bDirectionalShadows
&&re_ubLightIllumination!=0
&&bpo.bpo_bppProperties.bpp_ubIlluminationType==re_ubLightIllumination)
|| (re_pbrCurrent->br_pfsFieldSettings!=NULL && !pwplPolygonPlane->wpl_bVisible)
) {
bInvertPolygon = TRUE;
}
if (bInvertPolygon) {
// make temporary inverted polygon plane
pwplPolygonPlane = &wplReverse;
pwplPolygonPlane->wpl_plView = -bpo.bpo_pbplPlane->bpl_pwplWorking->wpl_plView;
pwplPolygonPlane->wpl_bVisible =
re_pbrCurrent->br_prProjection->IsViewerPlaneVisible(pwplPolygonPlane->wpl_plView);
}
// if the poly is double-sided and detail
if( !re_bRenderingShadows && (bpo.bpo_ulFlags&BPOF_DOUBLESIDED) && (bpo.bpo_ulFlags&BPOF_DETAILPOLYGON)) {
// it's definately visible
return PDF_POLYGONVISIBLE;
}
// if the plane is invisible
if (!pwplPolygonPlane->wpl_bVisible) {
// polygon is invisible
return 0;
}
// if the polygon is invisible
if ((bpo.bpo_ulFlags&BPOF_INVISIBLE)
||(re_bRenderingShadows && (bpo.bpo_ulFlags&BPOF_DOESNOTCASTSHADOW))) {
// skip it
return 0;
}
ULONG ulDirection = PDF_POLYGONVISIBLE;
BOOL bProjectionInverted = re_prProjection->pr_bInverted;
if (bProjectionInverted && !bInvertPolygon) {
ulDirection |= PDF_FLIPEDGESPRE;
} else if (!bProjectionInverted && bInvertPolygon){
ulDirection |= PDF_FLIPEDGESPOST;
}
// else, polygon is visible
return ulDirection;
}
// check if polygon is outside viewfrustum
__forceinline BOOL CRenderer::IsPolygonCulled(const CBrushPolygon &bpo)
{
CBrushSector &bsc = *bpo.bpo_pbscSector;
// setup initial mask
ULONG ulMask = 0xFFFFFFFF;
// for each vertex
INDEX ctVtx = bpo.bpo_apbvxTriangleVertices.Count();
{for(INDEX i=0; i<ctVtx; i++) {
CBrushVertex *pbvx = bpo.bpo_apbvxTriangleVertices[i];
INDEX ivx = bsc.bsc_abvxVertices.Index(pbvx);
// get the outcodes for that vertex
ULONG ulCode = re_avvxViewVertices[bsc.bsc_ivvx0+ivx].vvx_ulOutcode;
// and them to the mask
ulMask &= ulCode;
}}
// if any bit in the mask is still set, it means that all points are out
// wtr to that plane
return ulMask;
}
// find which portals should be rendered as portals or as pretenders
void CRenderer::FindPretenders(void)
{
re_pbscCurrent->bsc_ispo0 = re_aspoScreenPolygons.Count();
// for all polygons in sector
FOREACHINSTATICARRAY(re_pbscCurrent->bsc_abpoPolygons, CBrushPolygon, itpo) {
CBrushPolygon &bpo = *itpo;
// initially not rendered as portal
bpo.bpo_ulFlags&=~BPOF_RENDERASPORTAL;
// if it is portal,
if (bpo.bpo_ulFlags&BPOF_PORTAL) {
// initially rendered as portal
bpo.bpo_ulFlags|=BPOF_RENDERASPORTAL;
// if could be a pretender
if (bpo.bpo_bppProperties.bpp_uwPretenderDistance!=0) {
// get distance at which it is a pretender
FLOAT fPretenderDistance = bpo.bpo_bppProperties.bpp_uwPretenderDistance;
// for each vertex in the polygon
INDEX ctVtx = bpo.bpo_apbvxTriangleVertices.Count();
CBrushSector &bsc = *bpo.bpo_pbscSector;
{for(INDEX i=0; i<ctVtx; i++) {
CBrushVertex *pbvx = bpo.bpo_apbvxTriangleVertices[i];
INDEX ivx = bsc.bsc_abvxVertices.Index(pbvx);
// get distance of the vertex from the view plane
FLOAT fx = re_avvxViewVertices[bsc.bsc_ivvx0+ivx].vvx_vView(1);
FLOAT fy = re_avvxViewVertices[bsc.bsc_ivvx0+ivx].vvx_vView(2);
FLOAT fz = re_avvxViewVertices[bsc.bsc_ivvx0+ivx].vvx_vView(3);
FLOAT fD = fx*fx+fy*fy+fz*fz;
// if nearer than allowed pretender distance
if (fD<fPretenderDistance*fPretenderDistance) {
// this polygon is not a pretender
goto nextpolygon;
}
}}
// if all vertices passed the check, mark as pretender
bpo.bpo_ulFlags&=~BPOF_RENDERASPORTAL;
}
}
nextpolygon:;
}
}
// make screen polygons for nondetail polygons in current sector
void CRenderer::MakeNonDetailScreenPolygons(void)
{
_pfRenderProfile.StartTimer(CRenderProfile::PTI_MAKENONDETAILSCREENPOLYGONS);
re_pbscCurrent->bsc_ispo0 = re_aspoScreenPolygons.Count();
// detail polygons are not skipped if rendering shadows
const ULONG ulDetailMask = re_bRenderingShadows ? 0 : BPOF_DETAILPOLYGON;
// for all polygons in sector
FOREACHINSTATICARRAY(re_pbscCurrent->bsc_abpoPolygons, CBrushPolygon, itpo) {
CBrushPolygon &bpo = *itpo;
// if polygon does not contribute to the visibility determination
if ( (bpo.bpo_ulFlags&ulDetailMask)
&&!(bpo.bpo_ulFlags&BPOF_RENDERASPORTAL)) {
// skip it
continue;
}
// no screen polygon by default
bpo.bpo_pspoScreenPolygon = NULL;
// skip if the polygon is not visible
ASSERT( !IsPolygonCulled(bpo)); // cannot be culled yet!
const ULONG ulVisible = GetPolygonVisibility(bpo);
if( ulVisible==0) continue;
_sfStats.IncrementCounter(CStatForm::SCI_POLYGONS);
_pfRenderProfile.IncrementCounter(CRenderProfile::PCI_NONDETAILPOLYGONS);
// make screen polygon for the polygon
CScreenPolygon &spo = *MakeScreenPolygon(bpo);
// add its edges
MakeInitialPolygonEdges(bpo, spo, ulVisible);
}
// remember number of polygons in sector
re_pbscCurrent->bsc_ctspo = re_aspoScreenPolygons.Count()-re_pbscCurrent->bsc_ispo0;
_pfRenderProfile.StopTimer(CRenderProfile::PTI_MAKENONDETAILSCREENPOLYGONS);
}
// make screen polygons for detail polygons in current sector
void CRenderer::MakeDetailScreenPolygons(void)
{
// if rendering shadows or not rendering detail polygons
if (re_bRenderingShadows
||!wld_bRenderDetailPolygons) {
// do nothing
return;
}
_pfRenderProfile.StartTimer(CRenderProfile::PTI_MAKEDETAILSCREENPOLYGONS);
// for all polygons in sector
FOREACHINSTATICARRAY(re_pbscCurrent->bsc_abpoPolygons, CBrushPolygon, itpo) {
CBrushPolygon &bpo = *itpo;
// if polygon is not detail
if (!(bpo.bpo_ulFlags&BPOF_DETAILPOLYGON)
|| (bpo.bpo_ulFlags&BPOF_RENDERASPORTAL)) {
// skip it
continue;
}
// no screen polygon by default
bpo.bpo_pspoScreenPolygon = NULL;
// skip if the polygon is not visible
if( GetPolygonVisibility(bpo)==0) continue;
// skip if outside the frustum
if( (re_pbscCurrent->bsc_ulFlags&BSCF_NEEDSCLIPPING) && IsPolygonCulled(bpo)) continue;
_sfStats.IncrementCounter(CStatForm::SCI_DETAILPOLYGONS);
_pfRenderProfile.IncrementCounter(CRenderProfile::PCI_DETAILPOLYGONS);
// make screen polygon for the polygon
CScreenPolygon &spo = *MakeScreenPolygon(bpo);
// if it is portal
if (spo.IsPortal()) {
// pass it immediately
PassPortal(spo);
} else {
// add polygon to scene polygons for rendering
AddPolygonToScene(&spo);
}
}
_pfRenderProfile.StopTimer(CRenderProfile::PTI_MAKEDETAILSCREENPOLYGONS);
}
// make initial edges for a polygon
void CRenderer::MakeInitialPolygonEdges(CBrushPolygon &bpo, CScreenPolygon &spo, BOOL ulDirection)
{
// get number of edges
INDEX ctEdges = bpo.bpo_abpePolygonEdges.Count();
spo.spo_ubDirectionFlags = ulDirection&PDF_FLIPEDGESPOST;
BOOL bInvert = (ulDirection&PDF_FLIPEDGESPRE)!=0;
// remember edge vertex start and count
spo.spo_ctEdgeVx = ctEdges*2;
spo.spo_iEdgeVx0 = re_aiEdgeVxClipSrc.Count();
// create edge vertices
INDEX *ai = re_aiEdgeVxClipSrc.Push(ctEdges*2);
// for each edge
for (INDEX iEdge=0; iEdge<ctEdges; iEdge++) {
// set the two vertices
CBrushPolygonEdge &bpe = bpo.bpo_abpePolygonEdges[iEdge];
CWorkingEdge &wed = *bpe.bpe_pbedEdge->bed_pwedWorking;
if (bpe.bpe_bReverse^bInvert) {
ai[iEdge*2+0] = wed.wed_iwvx1+re_iViewVx0;
ai[iEdge*2+1] = wed.wed_iwvx0+re_iViewVx0;
} else {
ai[iEdge*2+0] = wed.wed_iwvx0+re_iViewVx0;
ai[iEdge*2+1] = wed.wed_iwvx1+re_iViewVx0;
}
}
}
// make final edges for all polygons in current sector
void CRenderer::MakeFinalPolygonEdges(void)
{
_pfRenderProfile.StartTimer(CRenderProfile::PTI_MAKEFINALPOLYGONEDGES);
// for each polygon
INDEX ispo0 = re_pbscCurrent->bsc_ispo0;
INDEX ispoTop = re_pbscCurrent->bsc_ispo0+re_pbscCurrent->bsc_ctspo;
for(INDEX ispo = ispo0; ispo<ispoTop; ispo++) {
CScreenPolygon &spo = re_aspoScreenPolygons[ispo];
// if polygon has no edges
if (spo.spo_ctEdgeVx==0) {
// skip it
continue;
}
INDEX iEdgeVx0New = re_aiEdgeVxMain.Count();
INDEX *piVertices = re_aiEdgeVxMain.Push(spo.spo_ctEdgeVx);
// for each vertex
INDEX ivxTop = spo.spo_iEdgeVx0+spo.spo_ctEdgeVx;
for(INDEX ivx=spo.spo_iEdgeVx0; ivx<ivxTop; ivx++) {
// copy to final array
*piVertices++ = re_aiEdgeVxClipSrc[ivx];
}
// remember new edge vertex positions
spo.spo_iEdgeVx0 = iEdgeVx0New;
}
// clear temporary arrays
re_aiEdgeVxClipSrc.PopAll();
re_aiEdgeVxClipDst.PopAll();
_pfRenderProfile.StopTimer(CRenderProfile::PTI_MAKEFINALPOLYGONEDGES);
}
// clip all polygons to one clip plane
void CRenderer::ClipToOnePlane(const FLOATplane3D &plView)
{
// remember clip plane
re_plClip = plView;
// no need for clipping if no vertices are outside
ASSERT( re_pbscCurrent->bsc_ulFlags&BSCF_NEEDSCLIPPING);
if( !MakeOutcodes()) return;
// for each polygon
INDEX ispo0 = re_pbscCurrent->bsc_ispo0;
INDEX ispoTop = re_pbscCurrent->bsc_ispo0+re_pbscCurrent->bsc_ctspo;
for(INDEX ispo = ispo0; ispo<ispoTop; ispo++) {
// clip to the plane
ClipOnePolygon(re_aspoScreenPolygons[ispo]);
}
// swap edge buffers
Swap(re_aiEdgeVxClipSrc.sa_Count , re_aiEdgeVxClipDst.sa_Count );
Swap(re_aiEdgeVxClipSrc.sa_Array , re_aiEdgeVxClipDst.sa_Array );
Swap(re_aiEdgeVxClipSrc.sa_UsedCount, re_aiEdgeVxClipDst.sa_UsedCount);
re_aiEdgeVxClipDst.PopAll();
}
// clip all polygons to all clip planes of a projection
void CRenderer::ClipToAllPlanes(CAnyProjection3D &pr)
{
_pfRenderProfile.StartTimer(CRenderProfile::PTI_CLIPTOALLPLANES);
// clip to up/down/left/right clip planes
FLOATplane3D pl;
pl = pr->pr_plClipU; pl.Offset(-0.001f); ClipToOnePlane(pl);
pl = pr->pr_plClipD; pl.Offset(-0.001f); ClipToOnePlane(pl);
pl = pr->pr_plClipL; pl.Offset(-0.001f); ClipToOnePlane(pl);
pl = pr->pr_plClipR; pl.Offset(-0.001f); ClipToOnePlane(pl);
// clip to near clip plane
ClipToOnePlane(FLOATplane3D(FLOAT3D(0,0,-1), pr->pr_NearClipDistance));
// clip to far clip plane if existing
if (pr->pr_FarClipDistance>0) {
ClipToOnePlane(FLOATplane3D(FLOAT3D(0,0,1), -pr->pr_FarClipDistance));
}
// if projection is mirrored or warped
if (pr->pr_bMirror||pr->pr_bWarp) {
// clip to mirror plane
ClipToOnePlane(pr->pr_plMirrorView);
}
_pfRenderProfile.StopTimer(CRenderProfile::PTI_CLIPTOALLPLANES);
}
// make outcodes for current clip plane for all active vertices
__forceinline BOOL CRenderer::MakeOutcodes(void)
{
SLONG slMask = 0;
// for each active view vertex
INDEX iVxTop = re_avvxViewVertices.Count();
for(INDEX ivx = re_iViewVx0; ivx<iVxTop; ivx++) {
CViewVertex &vvx = re_avvxViewVertices[ivx];
// calculate the distance
vvx.vvx_fD = re_plClip.PointDistance(vvx.vvx_vView);
// calculate the outcode
const ULONG ulOutCode = (*(SLONG*)&vvx.vvx_fD) & 0x80000000;
// add to the outcode of the vertex
vvx.vvx_ulOutcode = (vvx.vvx_ulOutcode>>1)|ulOutCode;
// add to mask
slMask|=ulOutCode;
}
// if any was negative, return true -- needs clipping
return slMask;
}
// clip one polygon to current clip plane
void CRenderer::ClipOnePolygon(CScreenPolygon &spo)
{
//
// NOTE: There is one ugly problem with this loop.
// I don't know any better way to fix it, so I have commented it.
// If the vertex references (vvx0 and vvx1) are taken _before_ pushing the vvxNew,
// it can cause access violation, because pushing can move array in memory.
// Therefore, it is important to take the references _after_ the Push() call.
INDEX iEdgeVx0New = re_aiEdgeVxClipDst.Count();
// for each edge
INDEX ivxTop = spo.spo_iEdgeVx0+spo.spo_ctEdgeVx;
for(INDEX ivx=spo.spo_iEdgeVx0; ivx<ivxTop; ivx+=2) {
INDEX ivx0 = re_aiEdgeVxClipSrc[ivx+0];
INDEX ivx1 = re_aiEdgeVxClipSrc[ivx+1];
// get vertices
FLOAT fD0 = re_avvxViewVertices[ivx0].vvx_fD;
FLOAT fD1 = re_avvxViewVertices[ivx1].vvx_fD;
if (fD0<=0) {
// if both are back
if (fD1<=0) {
// no screen edge remains
continue;
// if first is back, second front
} else {
// make new vertex
INDEX ivxNew = re_avvxViewVertices.Count();
CViewVertex &vvxNew = re_avvxViewVertices.Push();
CViewVertex &vvx0 = re_avvxViewVertices[ivx0]; // must do this after push, see note above!
CViewVertex &vvx1 = re_avvxViewVertices[ivx1]; // must do this after push, see note above!
// clip first
FLOAT fDivisor = 1.0f/(fD0-fD1);
FLOAT fFactor = fD0*fDivisor;
vvxNew.vvx_vView(1) = vvx0.vvx_vView(1)-(vvx0.vvx_vView(1)-vvx1.vvx_vView(1))*fFactor;
vvxNew.vvx_vView(2) = vvx0.vvx_vView(2)-(vvx0.vvx_vView(2)-vvx1.vvx_vView(2))*fFactor;
vvxNew.vvx_vView(3) = vvx0.vvx_vView(3)-(vvx0.vvx_vView(3)-vvx1.vvx_vView(3))*fFactor;
// remember new edge
re_aiEdgeVxClipDst.Push() = ivxNew;
re_aiEdgeVxClipDst.Push() = ivx1;
// add new vertex to clip buffer
re_aiClipBuffer.Push() = ivxNew;
}
} else {
// if first is front, second back
if ((SLONG&)fD1<=0) {
// make new vertex
INDEX ivxNew = re_avvxViewVertices.Count();
CViewVertex &vvxNew = re_avvxViewVertices.Push();
CViewVertex &vvx0 = re_avvxViewVertices[ivx0]; // must do this after push, see note above!
CViewVertex &vvx1 = re_avvxViewVertices[ivx1]; // must do this after push, see note above!
// clip second
FLOAT fDivisor = 1.0f/(fD0-fD1);
FLOAT fFactor = fD1*fDivisor;
vvxNew.vvx_vView(1) = vvx1.vvx_vView(1)-(vvx0.vvx_vView(1)-vvx1.vvx_vView(1))*fFactor;
vvxNew.vvx_vView(2) = vvx1.vvx_vView(2)-(vvx0.vvx_vView(2)-vvx1.vvx_vView(2))*fFactor;
vvxNew.vvx_vView(3) = vvx1.vvx_vView(3)-(vvx0.vvx_vView(3)-vvx1.vvx_vView(3))*fFactor;
// remember new edge
re_aiEdgeVxClipDst.Push() = ivx0;
re_aiEdgeVxClipDst.Push() = ivxNew;
// add new vertex to clip buffer
re_aiClipBuffer.Push() = ivxNew;
// if both are front
} else {
// just copy the edge
re_aiEdgeVxClipDst.Push() = ivx0;
re_aiEdgeVxClipDst.Push() = ivx1;
}
}
}
// if there is anything in clip buffer
if (re_aiClipBuffer.Count()>0) {
// generate clip edges
GenerateClipEdges(spo);
}
// remember new edge vertex positions
spo.spo_ctEdgeVx = re_aiEdgeVxClipDst.Count()-iEdgeVx0New;
spo.spo_iEdgeVx0 = iEdgeVx0New;
}
/*
* Compare two vertices for quick-sort.
*/
static UBYTE *_aVertices=NULL;
static int qsort_CompareVertices_plus( const void *ppvVertex0, const void *ppvVertex1)
{
INDEX ivx0 = *(const INDEX*)ppvVertex0;
INDEX ivx1 = *(const INDEX*)ppvVertex1;
FLOAT f0 = *(FLOAT*)(_aVertices+ivx0*sizeof(CViewVertex));
FLOAT f1 = *(FLOAT*)(_aVertices+ivx1*sizeof(CViewVertex));
if (f0<f1) return -1;
else if (f0>f1) return +1;
else return 0;
}
static int qsort_CompareVertices_minus( const void *ppvVertex0, const void *ppvVertex1)
{
INDEX ivx0 = *(const INDEX*)ppvVertex0;
INDEX ivx1 = *(const INDEX*)ppvVertex1;
FLOAT f0 = *(FLOAT*)(_aVertices+ivx0*sizeof(CViewVertex));
FLOAT f1 = *(FLOAT*)(_aVertices+ivx1*sizeof(CViewVertex));
if (f0<f1) return +1;
else if (f0>f1) return -1;
else return 0;
}
// generate clip edges for one polygon
void CRenderer::GenerateClipEdges(CScreenPolygon &spo)
{
ASSERT(re_aiClipBuffer.Count()>0);
FLOATplane3D &plPolygonPlane = spo.spo_pbpoBrushPolygon->bpo_pbplPlane->bpl_pwplWorking->wpl_plView;
// calculate the clip buffer direction in 3d as:
// clip_normal_vector x polygon_normal_vector
FLOAT3D vClipDir = ((FLOAT3D &)re_plClip)*((FLOAT3D &)plPolygonPlane);
// get max axis
INDEX iMaxAxis = 1;
FLOAT fMaxAbs = Abs(vClipDir(1));
if (Abs(vClipDir(2))>fMaxAbs) {
iMaxAxis = 2;
fMaxAbs = Abs(vClipDir(2));
}
if (Abs(vClipDir(3))>fMaxAbs) {
iMaxAxis = 3;
fMaxAbs = Abs(vClipDir(3));
}
_aVertices = (UBYTE*) &re_avvxViewVertices[0].vvx_vView(iMaxAxis);
INDEX *aIndices = &re_aiClipBuffer[0];
INDEX ctIndices = re_aiClipBuffer.Count();
// there must be even number of vertices in the buffer
ASSERT(ctIndices%2 == 0);
// if the sign of axis is negative
if (vClipDir(iMaxAxis)<0) {
// sort them inversely
qsort(aIndices, ctIndices, sizeof(INDEX), qsort_CompareVertices_minus);
// if it is negative
} else {
// sort them normally
qsort(aIndices, ctIndices, sizeof(INDEX), qsort_CompareVertices_plus);
}
// for each two vertices
for(INDEX iClippedVertex=0; iClippedVertex<ctIndices; iClippedVertex+=2) {
// add the edge
re_aiEdgeVxClipDst.Push() = aIndices[iClippedVertex+0];
re_aiEdgeVxClipDst.Push() = aIndices[iClippedVertex+1];
}
// clear the clip buffer
re_aiClipBuffer.PopAll();
}