/* 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. */ #include "Engine/StdH.h" #include #include #include #include #include static CTerrain *_ptrTerrain = NULL; static FLOAT3D _vOrigin; // Origin of ray static FLOAT3D _vTarget; // Ray target static FLOAT _fMinHeight; // Min height that ray will pass through in tested quad static FLOAT _fMaxHeight; // Max height that ray will pass through in tested quad static BOOL _bHitInvisibleTris; // Does ray hits invisible triangles static FLOAT3D _vHitExact; // hit point static FLOATplane3D _plHitPlane; // hit plane // TEMP static CStaticStackArray _avRCVertices; static CStaticStackArray _aiRCIndices; static FLOAT3D _vHitBegin; static FLOAT3D _vHitEnd; static FLOAT _fDistance; // Test ray agains one quad on terrain (if it's visible) static FLOAT HitCheckQuad(const PIX ix, const PIX iz) { FLOAT fDistance = UpperLimit(0.0f); // if quad is outside terrain if(ix<0 || iz<0 || ix>= (_ptrTerrain->tr_pixHeightMapWidth-1) || iz >= (_ptrTerrain->tr_pixHeightMapHeight-1)) { return fDistance; } ASSERT(ix>=0 && iz>=0); ASSERT(ix<(_ptrTerrain->tr_pixHeightMapWidth-1) && iz<(_ptrTerrain->tr_pixHeightMapHeight-1)); const PIX pixMapWidth = _ptrTerrain->tr_pixHeightMapWidth; const INDEX ctVertices = _avRCVertices.Count(); // TEMP UWORD *puwHeight = &_ptrTerrain->tr_auwHeightMap[ix + iz*pixMapWidth]; UBYTE *pubMask = &_ptrTerrain->tr_aubEdgeMap[ix + iz*pixMapWidth]; GFXVertex *pvx = _avRCVertices.Push(4); GFXVertex *pavVertices = &_avRCVertices[0]; // Add four vertices pvx[0].x = (ix+0) * _ptrTerrain->tr_vStretch(1); pvx[0].y = puwHeight[0] * _ptrTerrain->tr_vStretch(2); pvx[0].z = (iz+0) * _ptrTerrain->tr_vStretch(3); pvx[0].shade = pubMask[0]; pvx[1].x = (ix+1) * _ptrTerrain->tr_vStretch(1); pvx[1].y = puwHeight[1] * _ptrTerrain->tr_vStretch(2); pvx[1].z = (iz+0) * _ptrTerrain->tr_vStretch(3); pvx[1].shade = pubMask[1]; pvx[2].x = (ix+0) * _ptrTerrain->tr_vStretch(1); pvx[2].y = puwHeight[pixMapWidth] * _ptrTerrain->tr_vStretch(2); pvx[2].z = (iz+1) * _ptrTerrain->tr_vStretch(3); pvx[2].shade = pubMask[pixMapWidth]; pvx[3].x = (ix+1) * _ptrTerrain->tr_vStretch(1); pvx[3].y = puwHeight[pixMapWidth+1] * _ptrTerrain->tr_vStretch(2); pvx[3].z = (iz+1) * _ptrTerrain->tr_vStretch(3); pvx[3].shade = pubMask[pixMapWidth+1]; BOOL bFacing = (ix + iz*pixMapWidth)&1; INDEX ctIndices=0; // Add one quad if(bFacing) { // if at least one point of triangle is above min height and bellow max height of ray and traingle is visible if((pvx[0].y>=_fMinHeight || pvx[2].y>=_fMinHeight || pvx[1].y>=_fMinHeight) && (pvx[0].y<=_fMaxHeight || pvx[2].y<=_fMinHeight || pvx[1].y<=_fMinHeight) && ((pvx[0].shade + pvx[2].shade + pvx[1].shade == 255*3) | _bHitInvisibleTris)) { // Add this triangle INDEX_T *pind = _aiRCIndices.Push(3); pind[0] = ctVertices+0; pind[1] = ctVertices+2; pind[2] = ctVertices+1; ctIndices+=3; } // if at least one point of triangle is above min height and bellow max height of ray and traingle is visible if((pvx[1].y>=_fMinHeight || pvx[2].y>=_fMinHeight || pvx[3].y>=_fMinHeight) && (pvx[1].y<=_fMaxHeight || pvx[2].y<=_fMaxHeight || pvx[3].y<=_fMaxHeight) && ((pvx[1].shade + pvx[2].shade + pvx[3].shade == 255*3) | _bHitInvisibleTris)) { // Add this triangle INDEX_T *pind = _aiRCIndices.Push(3); pind[0] = ctVertices+1; pind[1] = ctVertices+2; pind[2] = ctVertices+3; ctIndices+=3; } } else { // if at least one point of triangle is above min height and bellow max height of ray and traingle is visible if((pvx[2].y>=_fMinHeight || pvx[3].y>=_fMinHeight || pvx[0].y>=_fMinHeight) && (pvx[2].y<=_fMaxHeight || pvx[3].y<=_fMaxHeight || pvx[0].y<=_fMaxHeight) && ((pvx[2].shade + pvx[3].shade + pvx[0].shade == 255*3) | _bHitInvisibleTris)) { // Add this triangle INDEX_T *pind = _aiRCIndices.Push(3); pind[0] = ctVertices+2; pind[1] = ctVertices+3; pind[2] = ctVertices+0; ctIndices+=3; } // if at least one point of triangle is above min height and bellow max height of ray and traingle is visible if((pvx[0].y>=_fMinHeight || pvx[3].y>=_fMinHeight || pvx[1].y>=_fMinHeight) && (pvx[0].y<=_fMaxHeight || pvx[3].y<=_fMaxHeight || pvx[1].y<=_fMaxHeight) && ((pvx[0].shade + pvx[3].shade + pvx[1].shade == 255*3) | _bHitInvisibleTris)) { // Add this triangle INDEX_T *pind = _aiRCIndices.Push(3); pind[0] = ctVertices+0; pind[1] = ctVertices+3; pind[2] = ctVertices+1; ctIndices+=3; } } if(ctIndices==0) { return fDistance; } INDEX_T *paiIndices = &_aiRCIndices[_aiRCIndices.Count() - ctIndices]; // for each triangle for(INDEX iTri=0;iTri=0 && fDistance0>=fDistance1) { // calculate fraction of line before intersection FLOAT fFraction = fDistance0/(fDistance0-fDistance1); // calculate intersection coordinate FLOAT3D vHitPoint = _vOrigin+(_vTarget-_vOrigin)*fFraction; // calculate intersection distance FLOAT fHitDistance = (vHitPoint-_vOrigin).Length(); // if the hit point can not be new closest candidate if (fHitDistance>fDistance) { // skip this triangle continue; } // find major axes of the polygon plane INDEX iMajorAxis1, iMajorAxis2; GetMajorAxesForPlane(plTriPlane, iMajorAxis1, iMajorAxis2); // create an intersector CIntersector isIntersector(vHitPoint(iMajorAxis1), vHitPoint(iMajorAxis2)); // check intersections for all three edges of the polygon isIntersector.AddEdge( vx0(iMajorAxis1), vx0(iMajorAxis2), vx1(iMajorAxis1), vx1(iMajorAxis2)); isIntersector.AddEdge( vx1(iMajorAxis1), vx1(iMajorAxis2), vx2(iMajorAxis1), vx2(iMajorAxis2)); isIntersector.AddEdge( vx2(iMajorAxis1), vx2(iMajorAxis2), vx0(iMajorAxis1), vx0(iMajorAxis2)); // if the polygon is intersected by the ray, and it is the closest intersection so far if (isIntersector.IsIntersecting() && (fHitDistance < fDistance)) { // remember hit coordinates fDistance = fHitDistance; _vHitExact = vHitPoint; _plHitPlane = plTriPlane; } } } return fDistance; } #pragma message(">> Remove defined NUMDIM, RIGHT, LEFT ...") #define NUMDIM 3 #define RIGHT 0 #define LEFT 1 #define MIDDLE 2 // Check if ray hits aabbox and return coords where ray enter and exit the box static BOOL HitAABBox(const FLOAT3D &vOrigin, const FLOAT3D &vTarget, FLOAT3D &vHitBegin, FLOAT3D &vHitEnd, const FLOATaabbox3D &bbox) { const FLOAT3D vDir = (vTarget - vOrigin).Normalize(); const FLOAT3D vMin = bbox.minvect; const FLOAT3D vMax = bbox.maxvect; FLOAT3D vBeginCandidatePlane; FLOAT3D vEndCandidatePlane; FLOAT3D vBeginTDistance; FLOAT3D vEndTDistance; INDEX iOriginSide[3]; BOOL bOriginInside = TRUE; INDEX i; // Find candidate planes for(i=1;i<4;i++) { // Check begining of ray if(vOrigin(i) < vMin(i)) { vBeginCandidatePlane(i) = vMin(i); vEndCandidatePlane(i) = vMax(i); bOriginInside = FALSE; iOriginSide[i-1] = LEFT; } else if(vOrigin(i) > vMax(i)) { vBeginCandidatePlane(i) = vMax(i); vEndCandidatePlane(i) = vMin(i); bOriginInside = FALSE; iOriginSide[i-1] = RIGHT; } else { iOriginSide[i-1] = MIDDLE; if(vDir(i)>0.0f) { vEndCandidatePlane(i) = vMax(i); } else { vEndCandidatePlane(i) = vMin(i); } } } // Calculate T distances to candidate planes for(i=1;i<4;i++) { if(iOriginSide[i-1]!=MIDDLE && vDir(i)!=0.0f) { vBeginTDistance(i) = (vBeginCandidatePlane(i)-vOrigin(i)) / vDir(i); } else { vBeginTDistance(i) = -1.0f; } if(vDir(i)!=0.0f) { vEndTDistance(i) = (vEndCandidatePlane(i)-vOrigin(i)) / vDir(i); } else { vEndTDistance(i) = -1.0f; } } // Get largest of the T distances for final choice of intersection INDEX iBeginMaxT = 1; INDEX iEndMinT = 1; for(i=2;i<4;i++) { if(vBeginTDistance(i) > vBeginTDistance(iBeginMaxT)) { iBeginMaxT = i; } if(vEndTDistance(i)>=0.0f && (vEndTDistance(iEndMinT)<0.0f || vEndTDistance(i) < vEndTDistance(iEndMinT)) ) { iEndMinT = i; } } // if origin inside box if(bOriginInside) { // Begining of ray is origin point vHitBegin = vOrigin; // else } else { // Check final candidate actually inside box if(vBeginTDistance(iBeginMaxT)<0.0f) { return FALSE; } if(vEndTDistance(iEndMinT)<0.0f) { return FALSE; } // Calculate point where ray enter box for(i=1;i<4;i++) { if(iBeginMaxT != i) { vHitBegin(i) = vOrigin(i) + vBeginTDistance(iBeginMaxT) * vDir(i); if(vHitBegin(i) < vMin(i) || vHitBegin(i) > vMax(i)) { return FALSE; } } else { vHitBegin(i) = vBeginCandidatePlane(i); } } } // Caclulate point where ray exit box for(i=1;i<4;i++) { if(iEndMinT != i) { vHitEnd(i) = vOrigin(i) + vEndTDistance(iEndMinT) * vDir(i); if(vHitEnd(i) < vMin(i) || vHitEnd(i) > vMax(i)) { // no ray exit point !? ASSERT(FALSE); } } else { vHitEnd(i) = vEndCandidatePlane(i); } } return TRUE; } // Test all quads in ray direction and return exact hit location static FLOAT GetExactHitLocation(CTerrain *ptrTerrain, const FLOAT3D &vHitBegin, const FLOAT3D &vHitEnd, const FLOAT fOldDistance) { // set global vars _ptrTerrain = ptrTerrain; _vOrigin = vHitBegin; _vTarget = vHitEnd; // TEMP _avRCVertices.PopAll(); _aiRCIndices.PopAll(); const FLOAT fX0 = vHitBegin(1) / ptrTerrain->tr_vStretch(1); const FLOAT fY0 = vHitBegin(3) / ptrTerrain->tr_vStretch(3); const FLOAT fH0 = vHitBegin(2);// / ptrTerrain->tr_vStretch(2); const FLOAT fX1 = vHitEnd(1) / ptrTerrain->tr_vStretch(1); const FLOAT fY1 = vHitEnd(3) / ptrTerrain->tr_vStretch(3); const FLOAT fH1 = vHitEnd(2);// / ptrTerrain->tr_vStretch(2); FLOAT fDeltaX = Abs(fX1-fX0); FLOAT fDeltaY = Abs(fY1-fY0); FLOAT fIterator; if(fDeltaX>fDeltaY) { fIterator = fDeltaX; } else { fIterator = fDeltaY; } if(fIterator==0) { fIterator = 0.01f; } const FLOAT fStepX = (fX1-fX0) / fIterator; const FLOAT fStepY = (fY1-fY0) / fIterator; const FLOAT fStepH = (fH1-fH0) / fIterator; const FLOAT fEpsilonH = Abs(fStepH); FLOAT fX; FLOAT fY; FLOAT fH; // calculate prestep if(fDeltaX>fDeltaY) { if(fX0fDeltaY) { // check left quad fDistance1 = HitCheckQuad(pixX-1,pixY); // else } else { // check upper quad fDistance1 = HitCheckQuad(pixX,pixY-1); } // find closer of two quads if(fDistance1GetAllTerrainBBox(bboxAll); extern INDEX ter_bTempFreezeCast; static FLOAT3D _vFrozenStart; static FLOAT3D _vFrozenEnd; if(ter_bTempFreezeCast) { vStart = _vFrozenStart; vEnd = _vFrozenEnd; } else { _vFrozenStart = vStart; _vFrozenEnd = vEnd; } // if ray hits terrain box if(HitAABBox(vStart,vEnd,vHitBegin,vHitEnd,bboxAll)) { // if begin and end are at same pos if(vHitBegin==vHitEnd) { // move end hit vHitBegin(2)+=0.1f; vHitEnd(2)-=0.1f; } _vHitBegin = vHitBegin; _vHitEnd = vHitEnd; // find exact hit location on terrain fDistance = GetExactHitLocation(ptrTerrain,vHitBegin,vHitEnd,fOldDistance); fDistance += (vStart-vHitBegin).Length(); } _fDistance = fDistance; return fDistance; } FLOAT TestRayCastHit(CTerrain *ptrTerrain, const FLOATmatrix3D &mRotation, const FLOAT3D &vPosition, const FLOAT3D &vOrigin, const FLOAT3D &vTarget,const FLOAT fOldDistance, const BOOL bHitInvisibleTris, FLOATplane3D &plHitPlane, FLOAT3D &vHitPoint) { ASSERT(ptrTerrain!=NULL); ASSERT(ptrTerrain->tr_penEntity!=NULL); CEntity *pen = ptrTerrain->tr_penEntity; // casting ray FLOAT fDistance = TestRayCastHit(ptrTerrain, mRotation, vPosition, vOrigin, vTarget, fOldDistance, bHitInvisibleTris); // convert hit point to absulute point vHitPoint = (_vHitExact * pen->en_mRotation) + pen->en_plPlacement.pl_PositionVector; plHitPlane = _plHitPlane; return fDistance; } #include #include void ShowRayPath(CDrawPort *pdp) { return; INDEX ctVertices = _avRCVertices.Count(); INDEX ctIndices = _aiRCIndices.Count(); if(ctVertices>0 && ctIndices>0) { gfxDisableTexture(); gfxDisableBlend(); gfxEnableDepthBias(); gfxPolygonMode(GFX_LINE); gfxSetVertexArray(&_avRCVertices[0],_avRCVertices.Count()); gfxSetConstantColor(0xFFFFFFFF); gfxDrawElements(_aiRCIndices.Count(),&_aiRCIndices[0]); gfxDisableDepthBias(); gfxPolygonMode(GFX_FILL); } gfxEnableDepthBias(); gfxDisableDepthTest(); pdp->DrawPoint3D(_vHitBegin,0x00FF00FF,8); pdp->DrawPoint3D(_vHitEnd,0xFF0000FF,8); pdp->DrawPoint3D(_vHitExact,0x00FFFF,8); pdp->DrawLine3D(_vHitBegin,_vHitEnd,0xFFFF00FF); pdp->DrawLine3D(FLOAT3D(_vHitBegin(1),_vHitEnd(2),_vHitBegin(3)),_vHitEnd,0xFF0000FF); gfxEnableDepthTest(); gfxDisableDepthBias(); /* extern void gfxDrawWireBox(FLOATaabbox3D &bbox, COLOR col); if(_ptrTerrain!=NULL) { FLOATaabbox3D bboxAll; _ptrTerrain->GetAllTerrainBBox(bboxAll); gfxDrawWireBox(bboxAll,0xFFFF00FF); } pdp->SetFont( _pfdConsoleFont); pdp->SetTextAspect( 1.0f); pdp->SetOrtho(); pdp->PutText(CTString(0,"%g",_fDistance),0,0,0xFFFFFFFF); */ }