/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ ///////////////////////////////////////////////////////////////////// // 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(); }