/* 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; ibsc_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; ibsc_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; iEdgebed_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; ispobsc_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; ispopr_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>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; ivx0) { // 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 (f0f1) 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 (f0f1) 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