mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-12-25 15:14:51 +01:00
840 lines
27 KiB
C++
840 lines
27 KiB
C++
/* 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 "stdh.h"
|
|
|
|
#include <Engine/Base/Console.h>
|
|
#include <Engine/World/World.h>
|
|
#include <Engine/Rendering/Render.h>
|
|
#include <Engine/World/WorldRayCasting.h>
|
|
#include <Engine/Base/ListIterator.inl>
|
|
#include <Engine/Templates/DynamicContainer.cpp>
|
|
#include <Engine/Templates/DynamicArray.cpp>
|
|
#include <Engine/Brushes/Brush.h>
|
|
#include <Engine/Templates/StaticArray.cpp>
|
|
#include <Engine/Models/ModelObject.h>
|
|
#include <Engine/Math/Clipping.inl>
|
|
#include <Engine/Entities/EntityCollision.h>
|
|
#include <Engine/Math/Geometry.inl>
|
|
#include <Engine/Network/Network.h>
|
|
#include <Engine/Ska/Render.h>
|
|
#include <Engine/Terrain/Terrain.h>
|
|
#include <Engine/Terrain/TerrainRayCasting.h>
|
|
|
|
#include <Engine/Base/Statistics_internal.h>
|
|
#include <Engine/Templates/StaticStackArray.cpp>
|
|
|
|
#define EPSILON (0.1f)
|
|
|
|
class CActiveSector {
|
|
public:
|
|
CBrushSector *as_pbsc;
|
|
void Clear(void) {};
|
|
};
|
|
|
|
static CStaticStackArray<CActiveSector> _aas;
|
|
CListHead _lhTestedTerrains; // list of tested terrains
|
|
|
|
// calculate origin position from ray placement
|
|
static inline FLOAT3D CalculateRayOrigin(const CPlacement3D &plRay)
|
|
{
|
|
// origin is the position from the placement
|
|
return plRay.pl_PositionVector;
|
|
}
|
|
// calculate target position from ray placement
|
|
static inline FLOAT3D CalculateRayTarget(const CPlacement3D &plRay)
|
|
{
|
|
// calculate direction of the ray
|
|
FLOAT3D vDirection;
|
|
AnglesToDirectionVector(plRay.pl_OrientationAngle, vDirection);
|
|
// make target be from the origin in that direction
|
|
return plRay.pl_PositionVector+vDirection;
|
|
}
|
|
|
|
/*
|
|
* Internal construction helper.
|
|
*/
|
|
void CCastRay::Init(CEntity *penOrigin, const FLOAT3D &vOrigin, const FLOAT3D &vTarget)
|
|
{
|
|
ClearSectorList();
|
|
cr_penOrigin = penOrigin;
|
|
cr_vOrigin = vOrigin;
|
|
cr_vTarget = vTarget;
|
|
cr_bAllowOverHit = FALSE;
|
|
cr_pbpoIgnore = NULL;
|
|
cr_penIgnore = NULL;
|
|
|
|
cr_bHitPortals = FALSE;
|
|
cr_bHitTranslucentPortals = TRUE;
|
|
cr_ttHitModels = TT_SIMPLE;
|
|
cr_bHitFields = FALSE;
|
|
cr_bPhysical = FALSE;
|
|
cr_bHitBrushes = TRUE;
|
|
cr_bHitTerrainInvisibleTris = FALSE;
|
|
cr_fTestR = 0;
|
|
|
|
cr_bFindBone = TRUE;
|
|
cr_iBoneHit = -1;
|
|
|
|
cl_plRay.pl_PositionVector = vOrigin;
|
|
DirectionVectorToAngles((vTarget-vOrigin).Normalize(), cl_plRay.pl_OrientationAngle);
|
|
}
|
|
|
|
/*
|
|
* Constructor.
|
|
*/
|
|
CCastRay::CCastRay(CEntity *penOrigin, const CPlacement3D &plOrigin)
|
|
{
|
|
Init(penOrigin, CalculateRayOrigin(plOrigin), CalculateRayTarget(plOrigin));
|
|
// mark last found hit point in infinity
|
|
cr_fHitDistance = UpperLimit(0.0f);
|
|
}
|
|
CCastRay::CCastRay(CEntity *penOrigin, const CPlacement3D &plOrigin, FLOAT fMaxTestDistance)
|
|
{
|
|
Init(penOrigin, CalculateRayOrigin(plOrigin), CalculateRayTarget(plOrigin));
|
|
// mark last found hit point just as far away as we wan't to test
|
|
cr_fHitDistance = fMaxTestDistance;
|
|
}
|
|
CCastRay::CCastRay(CEntity *penOrigin, const FLOAT3D &vOrigin, const FLOAT3D &vTarget)
|
|
{
|
|
Init(penOrigin, vOrigin, vTarget);
|
|
// mark last found hit point just a bit behind the target
|
|
cr_fHitDistance = (cr_vTarget-cr_vOrigin).Length() + EPSILON;
|
|
}
|
|
|
|
CCastRay::~CCastRay(void)
|
|
{
|
|
ClearSectorList();
|
|
}
|
|
|
|
void CCastRay::ClearSectorList(void)
|
|
{
|
|
// for each active sector
|
|
for(INDEX ias=0; ias<_aas.Count(); ias++) {
|
|
// mark it as inactive
|
|
_aas[ias].as_pbsc->bsc_ulFlags&=~BSCF_RAYTESTED;
|
|
}
|
|
_aas.PopAll();
|
|
}
|
|
|
|
/*
|
|
* Test if a ray hits sphere.
|
|
*/
|
|
inline static BOOL RayHitsSphere(
|
|
const FLOAT3D &vStart,
|
|
const FLOAT3D &vEnd,
|
|
const FLOAT3D &vSphereCenter,
|
|
const FLOAT fSphereRadius,
|
|
FLOAT &fDistance)
|
|
{
|
|
const FLOAT3D vSphereCenterToStart = vStart - vSphereCenter;
|
|
const FLOAT3D vStartToEnd = vEnd - vStart;
|
|
// calculate discriminant for intersection parameters
|
|
const FLOAT fP = ((vStartToEnd%vSphereCenterToStart)/(vStartToEnd%vStartToEnd));
|
|
const FLOAT fQ = (((vSphereCenterToStart%vSphereCenterToStart)
|
|
- (fSphereRadius*fSphereRadius))/(vStartToEnd%vStartToEnd));
|
|
const FLOAT fD = fP*fP-fQ;
|
|
// if it is less than zero
|
|
if (fD<0) {
|
|
// no collision will occur
|
|
return FALSE;
|
|
}
|
|
// calculate intersection parameters
|
|
const FLOAT fSqrtD = sqrt(fD);
|
|
const FLOAT fLambda1 = -fP+fSqrtD;
|
|
const FLOAT fLambda2 = -fP-fSqrtD;
|
|
// use lower one
|
|
const FLOAT fMinLambda = Min(fLambda1, fLambda2);
|
|
// calculate distance from parameter
|
|
fDistance = fMinLambda*vStartToEnd.Length();
|
|
return TRUE;
|
|
}
|
|
|
|
void CCastRay::TestModelSimple(CEntity *penModel, CModelObject &mo)
|
|
{
|
|
// get model's bounding box for current frame
|
|
FLOATaabbox3D boxModel;
|
|
mo.GetCurrentFrameBBox(boxModel);
|
|
boxModel.StretchByVector(mo.mo_Stretch);
|
|
// get center and radius of the bounding sphere in absolute space
|
|
FLOAT fSphereRadius = boxModel.Size().Length()/2.0f;
|
|
FLOAT3D vSphereCenter = boxModel.Center();
|
|
vSphereCenter*=penModel->en_mRotation;
|
|
vSphereCenter+=penModel->en_plPlacement.pl_PositionVector;
|
|
|
|
// if the ray doesn't hit the sphere
|
|
FLOAT fSphereHitDistance;
|
|
if (!RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vSphereCenter, fSphereRadius+cr_fTestR, fSphereHitDistance) ) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
// if the ray hits the sphere closer than closest found hit point yet
|
|
if (fSphereHitDistance<cr_fHitDistance && fSphereHitDistance>0.0f) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fSphereHitDistance;
|
|
cr_penHit = penModel;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
}
|
|
}
|
|
|
|
void CCastRay::TestModelCollisionBox(CEntity *penModel)
|
|
{
|
|
// if no collision box
|
|
CCollisionInfo *pci = penModel->en_pciCollisionInfo;
|
|
if (pci==NULL) {
|
|
// don't test
|
|
return;
|
|
}
|
|
|
|
// get model's collision bounding box
|
|
FLOATaabbox3D &boxModel = pci->ci_boxCurrent;
|
|
FLOAT fSphereRadius = boxModel.Size().Length()/2.0f;
|
|
FLOAT3D vSphereCenter = boxModel.Center();
|
|
|
|
// if the ray doesn't hit the sphere
|
|
FLOAT fSphereHitDistance;
|
|
if (!RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vSphereCenter, fSphereRadius+cr_fTestR, fSphereHitDistance) ) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
// get entity collision spheres
|
|
CStaticArray<CMovingSphere> &ams = pci->ci_absSpheres;
|
|
// get entity position
|
|
const FLOAT3D &vPosition = penModel->en_plPlacement.pl_PositionVector;
|
|
const FLOATmatrix3D &mRotation = penModel->en_mRotation;
|
|
|
|
// for each sphere
|
|
FOREACHINSTATICARRAY(ams, CMovingSphere, itms) {
|
|
// project its center to absolute space
|
|
FLOAT3D vCenter = itms->ms_vCenter*mRotation + vPosition;
|
|
// if the ray hits the sphere closer than closest found hit point yet
|
|
FLOAT fOneSphereHitDistance;
|
|
if (RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vCenter, itms->ms_fR+cr_fTestR, fOneSphereHitDistance) &&
|
|
fOneSphereHitDistance<cr_fHitDistance && fOneSphereHitDistance>-cr_fTestR) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fOneSphereHitDistance;
|
|
cr_penHit = penModel;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCastRay::TestModelFull(CEntity *penModel, CModelObject &mo)
|
|
{
|
|
// NOTE: this contains an ugly hack to simulate good trivial rejection
|
|
// for models that have attachments that extend far off the base entity.
|
|
// it is used only in wed, so it should not be a big problem.
|
|
|
|
// get model's bounding box for all frames and expand it a lot
|
|
FLOATaabbox3D boxModel;
|
|
mo.GetAllFramesBBox(boxModel);
|
|
boxModel.StretchByVector(mo.mo_Stretch*5.0f);
|
|
// get center and radius of the bounding sphere in absolute space
|
|
FLOAT fSphereRadius = boxModel.Size().Length()/2.0f;
|
|
FLOAT3D vSphereCenter = boxModel.Center();
|
|
vSphereCenter*=penModel->en_mRotation;
|
|
vSphereCenter+=penModel->en_plPlacement.pl_PositionVector;
|
|
|
|
// if the ray doesn't hit the sphere
|
|
FLOAT fSphereHitDistance;
|
|
if (!RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vSphereCenter, fSphereRadius+cr_fTestR, fSphereHitDistance) ) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
FLOAT fHitDistance;
|
|
// if the ray hits the model closer than closest found hit point yet
|
|
if (mo.PolygonHit(cl_plRay, penModel->en_plPlacement, 0/*iCurrentMip*/,
|
|
fHitDistance)!=NULL
|
|
&& fHitDistance<cr_fHitDistance) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fHitDistance;
|
|
cr_penHit = penModel;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test against a model entity.
|
|
*/
|
|
void CCastRay::TestModel(CEntity *penModel)
|
|
{
|
|
// if origin is predictor, and the model is predicted
|
|
if (cr_penOrigin!=NULL && cr_penOrigin->IsPredictor() && penModel->IsPredicted()) {
|
|
// don't test it
|
|
return;
|
|
}
|
|
|
|
// if hidden model
|
|
if( penModel->en_ulFlags&ENF_HIDDEN)
|
|
{
|
|
// don't test
|
|
return;
|
|
}
|
|
|
|
// get its model
|
|
CModelObject *pmoModel;
|
|
if (penModel->en_RenderType!=CEntity::RT_BRUSH
|
|
&& penModel->en_RenderType != CEntity::RT_FIELDBRUSH) {
|
|
pmoModel=penModel->en_pmoModelObject;
|
|
} else {
|
|
// empty brushes are also tested as models
|
|
pmoModel=_wrpWorldRenderPrefs.GetEmptyBrushModel();
|
|
}
|
|
// if there is no valid model
|
|
if (pmoModel==NULL) {
|
|
// don't test it
|
|
return;
|
|
}
|
|
CModelObject &mo = *pmoModel;
|
|
|
|
// if simple testing, or no testing (used when testing empty brushes)
|
|
if (cr_ttHitModels==TT_SIMPLE || cr_ttHitModels==TT_NONE) {
|
|
TestModelSimple(penModel, mo);
|
|
// if collision box testing
|
|
} else if (cr_ttHitModels==TT_COLLISIONBOX) {
|
|
TestModelCollisionBox(penModel);
|
|
// if full testing
|
|
} else if (cr_ttHitModels==TT_FULL || cr_ttHitModels==TT_FULLSEETHROUGH) {
|
|
TestModelFull(penModel, mo);
|
|
// must be no other testing
|
|
} else {
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test against a ska model
|
|
*/
|
|
void CCastRay::TestSkaModel(CEntity *penModel)
|
|
{
|
|
// if origin is predictor, and the model is predicted
|
|
if (cr_penOrigin!=NULL && cr_penOrigin->IsPredictor() && penModel->IsPredicted()) {
|
|
// don't test it
|
|
return;
|
|
}
|
|
|
|
// if hidden model
|
|
if( penModel->en_ulFlags&ENF_HIDDEN)
|
|
{
|
|
// don't test
|
|
return;
|
|
}
|
|
|
|
CModelInstance &mi = *penModel->GetModelInstance();
|
|
// if simple testing, or no testing (used when testing empty brushes)
|
|
if (cr_ttHitModels==TT_SIMPLE || cr_ttHitModels==TT_NONE) {
|
|
TestSkaModelSimple(penModel, mi);
|
|
// if collision box testing
|
|
} else if (cr_ttHitModels==TT_COLLISIONBOX) {
|
|
TestModelCollisionBox(penModel);
|
|
// if full testing
|
|
} else if (cr_ttHitModels==TT_FULL || cr_ttHitModels==TT_FULLSEETHROUGH) {
|
|
TestSkaModelFull(penModel, mi);
|
|
// must be no other testing
|
|
} else {
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
void CCastRay::TestSkaModelSimple(CEntity *penModel, CModelInstance &mi)
|
|
{
|
|
FLOATaabbox3D boxModel;
|
|
mi.GetCurrentColisionBox(boxModel);
|
|
boxModel.StretchByVector(mi.mi_vStretch);
|
|
// get center and radius of the bounding sphere in absolute space
|
|
FLOAT fSphereRadius = boxModel.Size().Length()/2.0f;
|
|
FLOAT3D vSphereCenter = boxModel.Center();
|
|
vSphereCenter*=penModel->en_mRotation;
|
|
vSphereCenter+=penModel->en_plPlacement.pl_PositionVector;
|
|
|
|
// if the ray doesn't hit the sphere
|
|
FLOAT fSphereHitDistance;
|
|
if (!RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vSphereCenter, fSphereRadius+cr_fTestR, fSphereHitDistance) ) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
// if the ray hits the sphere closer than closest found hit point yet
|
|
if (fSphereHitDistance<cr_fHitDistance && fSphereHitDistance>0.0f) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fSphereHitDistance;
|
|
cr_penHit = penModel;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
}
|
|
}
|
|
|
|
void CCastRay::TestSkaModelFull(CEntity *penModel, CModelInstance &mi)
|
|
{
|
|
FLOATaabbox3D boxModel;
|
|
mi.GetAllFramesBBox(boxModel);
|
|
boxModel.StretchByVector(mi.mi_vStretch);
|
|
// get center and radius of the bounding sphere in absolute space
|
|
FLOAT fSphereRadius = boxModel.Size().Length()/2.0f;
|
|
FLOAT3D vSphereCenter = boxModel.Center();
|
|
vSphereCenter*=penModel->en_mRotation;
|
|
vSphereCenter+=penModel->en_plPlacement.pl_PositionVector;
|
|
|
|
// if the ray doesn't hit the sphere
|
|
FLOAT fSphereHitDistance;
|
|
if (!RayHitsSphere(cr_vOrigin, cr_vTarget,
|
|
vSphereCenter, fSphereRadius+cr_fTestR, fSphereHitDistance) ) {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
// if the ray hits the sphere closer than closest found hit point yet
|
|
if (fSphereHitDistance<cr_fHitDistance && fSphereHitDistance>0.0f) {
|
|
FLOAT fTriangleHitDistance;
|
|
// set the current entity as new hit target
|
|
// cr_fHitDistance=fSphereHitDistance;
|
|
// cr_penHit = penModel;
|
|
// cr_pbscBrushSector = NULL;
|
|
// cr_pbpoBrushPolygon = NULL;
|
|
|
|
INDEX iBoneID = -1;
|
|
if (cr_bFindBone) {
|
|
fTriangleHitDistance = RM_TestRayCastHit(mi,penModel->en_mRotation,penModel->en_plPlacement.pl_PositionVector,cr_vOrigin,cr_vTarget,cr_fHitDistance,&iBoneID);
|
|
} else {
|
|
fTriangleHitDistance = RM_TestRayCastHit(mi,penModel->en_mRotation,penModel->en_plPlacement.pl_PositionVector,cr_vOrigin,cr_vTarget,cr_fHitDistance,NULL);
|
|
}
|
|
|
|
if (fTriangleHitDistance<cr_fHitDistance && fTriangleHitDistance>0.0f) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fTriangleHitDistance;
|
|
cr_penHit = penModel;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
|
|
if (cr_bFindBone) {
|
|
cr_iBoneHit = iBoneID;
|
|
}
|
|
}
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
void CCastRay::TestTerrain(CEntity *penTerrain)
|
|
{
|
|
// if hidden model
|
|
if( penTerrain->en_ulFlags&ENF_HIDDEN) {
|
|
// don't test
|
|
return;
|
|
}
|
|
|
|
CTerrain *ptrTerrain = penTerrain->GetTerrain();
|
|
FLOAT fHitDistance = TestRayCastHit(ptrTerrain,penTerrain->en_mRotation, penTerrain->en_plPlacement.pl_PositionVector,
|
|
cr_vOrigin,cr_vTarget,cr_fHitDistance,cr_bHitTerrainInvisibleTris);
|
|
|
|
if (fHitDistance<cr_fHitDistance && fHitDistance>0.0f) {
|
|
// set the current entity as new hit target
|
|
cr_fHitDistance=fHitDistance;
|
|
cr_penHit = penTerrain;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_pbpoBrushPolygon = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test against a brush sector.
|
|
*/
|
|
void CCastRay::TestBrushSector(CBrushSector *pbscSector)
|
|
{
|
|
// if entity is hidden
|
|
if(pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity->en_ulFlags&ENF_HIDDEN)
|
|
{
|
|
// don't cast ray
|
|
return;
|
|
}
|
|
// for each polygon in the sector
|
|
FOREACHINSTATICARRAY(pbscSector->bsc_abpoPolygons, CBrushPolygon, itpoPolygon) {
|
|
CBrushPolygon &bpoPolygon = itpoPolygon.Current();
|
|
|
|
if (&bpoPolygon==cr_pbpoIgnore) {
|
|
continue;
|
|
}
|
|
|
|
ULONG ulFlags = bpoPolygon.bpo_ulFlags;
|
|
// if not testing recursively
|
|
if (cr_penOrigin==NULL) {
|
|
// if the polygon is portal
|
|
if (ulFlags&BPOF_PORTAL) {
|
|
// if it is translucent or selected
|
|
if (ulFlags&(BPOF_TRANSLUCENT|BPOF_TRANSPARENT|BPOF_SELECTED)) {
|
|
// if translucent portals should be passed through
|
|
if (!cr_bHitTranslucentPortals) {
|
|
// skip this polygon
|
|
continue;
|
|
}
|
|
// if it is not translucent
|
|
} else {
|
|
// if portals should be passed through
|
|
if (!cr_bHitPortals) {
|
|
// skip this polygon
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// if polygon is detail, and detail polygons are off
|
|
extern INDEX wld_bRenderDetailPolygons;
|
|
if ((ulFlags&BPOF_DETAILPOLYGON) && !wld_bRenderDetailPolygons) {
|
|
// skip this polygon
|
|
continue;
|
|
}
|
|
}
|
|
// get distances of ray points from the polygon plane
|
|
FLOAT fDistance0 = bpoPolygon.bpo_pbplPlane->bpl_plAbsolute.PointDistance(cr_vOrigin);
|
|
FLOAT fDistance1 = bpoPolygon.bpo_pbplPlane->bpl_plAbsolute.PointDistance(cr_vTarget);
|
|
|
|
// if the ray hits the polygon plane
|
|
if (fDistance0>=0 && fDistance0>=fDistance1) {
|
|
// calculate fraction of line before intersection
|
|
FLOAT fFraction = fDistance0/((fDistance0-fDistance1) + 0.0000001f/*correction*/);
|
|
// calculate intersection coordinate
|
|
FLOAT3D vHitPoint = cr_vOrigin+(cr_vTarget-cr_vOrigin)*fFraction;
|
|
// calculate intersection distance
|
|
FLOAT fHitDistance = (vHitPoint-cr_vOrigin).Length();
|
|
// if the hit point can not be new closest candidate
|
|
if (fHitDistance>cr_fHitDistance) {
|
|
// skip this polygon
|
|
continue;
|
|
}
|
|
|
|
// find major axes of the polygon plane
|
|
INDEX iMajorAxis1, iMajorAxis2;
|
|
GetMajorAxesForPlane(itpoPolygon->bpo_pbplPlane->bpl_plAbsolute, iMajorAxis1, iMajorAxis2);
|
|
|
|
// create an intersector
|
|
CIntersector isIntersector(vHitPoint(iMajorAxis1), vHitPoint(iMajorAxis2));
|
|
// for all edges in the polygon
|
|
FOREACHINSTATICARRAY(bpoPolygon.bpo_abpePolygonEdges, CBrushPolygonEdge,
|
|
itbpePolygonEdge) {
|
|
// get edge vertices (edge direction is irrelevant here!)
|
|
const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute;
|
|
const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute;
|
|
// pass the edge to the intersector
|
|
isIntersector.AddEdge(
|
|
vVertex0(iMajorAxis1), vVertex0(iMajorAxis2),
|
|
vVertex1(iMajorAxis1), vVertex1(iMajorAxis2));
|
|
}
|
|
// if the polygon is intersected by the ray
|
|
if (isIntersector.IsIntersecting()) {
|
|
// if it is portal and testing recusively
|
|
if ((ulFlags&cr_ulPassablePolygons) && (cr_penOrigin!=NULL)) {
|
|
// for each sector on the other side
|
|
{FOREACHDSTOFSRC(bpoPolygon.bpo_rsOtherSideSectors, CBrushSector, bsc_rdOtherSidePortals, pbsc)
|
|
// add the sector
|
|
AddSector(pbsc);
|
|
ENDFOR}
|
|
|
|
if( cr_bHitPortals && ulFlags&(BPOF_TRANSLUCENT|BPOF_TRANSPARENT) && !cr_bPhysical)
|
|
{
|
|
// remember hit coordinates
|
|
cr_fHitDistance=fHitDistance;
|
|
cr_penHit = pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
|
|
cr_pbscBrushSector = pbscSector;
|
|
cr_pbpoBrushPolygon = &bpoPolygon;
|
|
}
|
|
// if the ray just plainly hit it
|
|
} else {
|
|
// remember hit coordinates
|
|
cr_fHitDistance=fHitDistance;
|
|
cr_penHit = pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
|
|
cr_pbscBrushSector = pbscSector;
|
|
cr_pbpoBrushPolygon = &bpoPolygon;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add a sector if needed. */
|
|
inline void CCastRay::AddSector(CBrushSector *pbsc)
|
|
{
|
|
// if not already active and in first mip of its brush
|
|
if ( pbsc->bsc_pbmBrushMip->IsFirstMip()
|
|
&&!(pbsc->bsc_ulFlags&BSCF_RAYTESTED)) {
|
|
// add it to active sectors
|
|
_aas.Push().as_pbsc = pbsc;
|
|
pbsc->bsc_ulFlags|=BSCF_RAYTESTED;
|
|
}
|
|
}
|
|
/* Add all sectors of a brush. */
|
|
void CCastRay::AddAllSectorsOfBrush(CBrush3D *pbr)
|
|
{
|
|
// get relevant mip as if in manual mip brushing mode
|
|
CBrushMip *pbmMip = pbr->GetBrushMipByDistance(
|
|
_wrpWorldRenderPrefs.GetManualMipBrushingFactor());
|
|
|
|
// if it has no brush mip for that mip factor
|
|
if (pbmMip==NULL) {
|
|
// skip it
|
|
return;
|
|
}
|
|
// for each sector in the brush mip
|
|
FOREACHINDYNAMICARRAY(pbmMip->bm_abscSectors, CBrushSector, itbsc) {
|
|
// add the sector
|
|
AddSector(itbsc);
|
|
}
|
|
}
|
|
|
|
/* Add all sectors around given entity. */
|
|
void CCastRay::AddSectorsAroundEntity(CEntity *pen)
|
|
{
|
|
// for each zoning sector that this entity is in
|
|
{FOREACHSRCOFDST(pen->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
|
|
// if part of zoning brush
|
|
if (pbsc->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity->GetRenderType()!=CEntity::RT_BRUSH) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
// add the sector
|
|
AddSector(pbsc);
|
|
ENDFOR}
|
|
}
|
|
|
|
/* Test entire world against ray. */
|
|
void CCastRay::TestWholeWorld(CWorld *pwoWorld)
|
|
{
|
|
// for each entity in the world
|
|
{FOREACHINDYNAMICCONTAINER(pwoWorld->wo_cenEntities, CEntity, itenInWorld) {
|
|
// if it is the origin of the ray
|
|
if (itenInWorld==cr_penOrigin || itenInWorld==cr_penIgnore) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
|
|
// if it is a brush and testing against brushes is disabled
|
|
if( (itenInWorld->en_RenderType == CEntity::RT_BRUSH ||
|
|
itenInWorld->en_RenderType == CEntity::RT_FIELDBRUSH) &&
|
|
!cr_bHitBrushes) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
|
|
// if it is a model and testing against models is enabled
|
|
if(((itenInWorld->en_RenderType == CEntity::RT_MODEL
|
|
||(itenInWorld->en_RenderType == CEntity::RT_EDITORMODEL
|
|
&& _wrpWorldRenderPrefs.IsEditorModelsOn()))
|
|
&& cr_ttHitModels != TT_NONE)
|
|
// and if cast type is TT_FULL_SEETROUGH then model is not
|
|
// ENF_SEETROUGH
|
|
&& !((cr_ttHitModels == TT_FULLSEETHROUGH || cr_ttHitModels == TT_COLLISIONBOX) &&
|
|
(itenInWorld->en_ulFlags&ENF_SEETHROUGH))) {
|
|
// test it against the model entity
|
|
TestModel(itenInWorld);
|
|
// if it is a ska model
|
|
} else if(((itenInWorld->en_RenderType == CEntity::RT_SKAMODEL
|
|
||(itenInWorld->en_RenderType == CEntity::RT_SKAEDITORMODEL
|
|
&& _wrpWorldRenderPrefs.IsEditorModelsOn()))
|
|
&& cr_ttHitModels != TT_NONE)
|
|
// and if cast type is TT_FULL_SEETROUGH then model is not
|
|
// ENF_SEETROUGH
|
|
&& !((cr_ttHitModels == TT_FULLSEETHROUGH || cr_ttHitModels == TT_COLLISIONBOX) &&
|
|
(itenInWorld->en_ulFlags&ENF_SEETHROUGH))) {
|
|
TestSkaModel(itenInWorld);
|
|
} else if (itenInWorld->en_RenderType == CEntity::RT_TERRAIN) {
|
|
TestTerrain(itenInWorld);
|
|
// if it is a brush
|
|
} else if (itenInWorld->en_RenderType == CEntity::RT_BRUSH ||
|
|
(itenInWorld->en_RenderType == CEntity::RT_FIELDBRUSH
|
|
&&_wrpWorldRenderPrefs.IsFieldBrushesOn() && cr_bHitFields)) {
|
|
// get its brush
|
|
CBrush3D &brBrush = *itenInWorld->en_pbrBrush;
|
|
|
|
// get relevant mip as if in manual mip brushing mode
|
|
CBrushMip *pbmMip = brBrush.GetBrushMipByDistance(
|
|
_wrpWorldRenderPrefs.GetManualMipBrushingFactor());
|
|
|
|
// if it has no brush mip for that mip factor
|
|
if (pbmMip==NULL) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
|
|
// if it has zero sectors
|
|
if (pbmMip->bm_abscSectors.Count()==0){
|
|
// test it against the model entity
|
|
TestModel(itenInWorld);
|
|
|
|
// if it has some sectors
|
|
} else {
|
|
// for each sector in the brush mip
|
|
FOREACHINDYNAMICARRAY(pbmMip->bm_abscSectors, CBrushSector, itbsc) {
|
|
// if the sector is not hidden
|
|
if (!(itbsc->bsc_ulFlags & BSCF_HIDDEN)) {
|
|
// test the ray against the sector
|
|
TestBrushSector(itbsc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
|
|
/* Test active sectors recusively. */
|
|
void CCastRay::TestThroughSectors(void)
|
|
{
|
|
// for each active sector (sectors are added during iteration!)
|
|
for(INDEX ias=0; ias<_aas.Count(); ias++) {
|
|
CBrushSector *pbsc = _aas[ias].as_pbsc;
|
|
// test the ray against the sector
|
|
TestBrushSector(pbsc);
|
|
// for each entity in the sector
|
|
{FOREACHDSTOFSRC(pbsc->bsc_rsEntities, CEntity, en_rdSectors, pen)
|
|
// if it is the origin of the ray
|
|
if (pen==cr_penOrigin || pen==cr_penIgnore) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
// if it is a model and testing against models is enabled
|
|
if(((pen->en_RenderType == CEntity::RT_MODEL
|
|
||(pen->en_RenderType == CEntity::RT_EDITORMODEL
|
|
&& _wrpWorldRenderPrefs.IsEditorModelsOn()))
|
|
&& cr_ttHitModels != TT_NONE)
|
|
// and if cast type is TT_FULL_SEETROUGH then model is not
|
|
// ENF_SEETROUGH
|
|
&& !((cr_ttHitModels == TT_FULLSEETHROUGH || cr_ttHitModels == TT_COLLISIONBOX) &&
|
|
(pen->en_ulFlags&ENF_SEETHROUGH))) {
|
|
// test it against the model entity
|
|
TestModel(pen);
|
|
// if is is a ska model
|
|
} else if(((pen->en_RenderType == CEntity::RT_SKAMODEL
|
|
||(pen->en_RenderType == CEntity::RT_SKAEDITORMODEL
|
|
&& _wrpWorldRenderPrefs.IsEditorModelsOn()))
|
|
&& cr_ttHitModels != TT_NONE)
|
|
// and if cast type is TT_FULL_SEETROUGH then model is not
|
|
// ENF_SEETROUGH
|
|
&& !((cr_ttHitModels == TT_FULLSEETHROUGH || cr_ttHitModels == TT_COLLISIONBOX) &&
|
|
(pen->en_ulFlags&ENF_SEETHROUGH))) {
|
|
// test it against the ska model entity
|
|
TestSkaModel(pen);
|
|
// if it is a terrain
|
|
} else if( pen->en_RenderType == CEntity::RT_TERRAIN) {
|
|
CTerrain *ptrTerrain = pen->GetTerrain();
|
|
ASSERT(ptrTerrain!=NULL);
|
|
// if terrain hasn't allready been tested
|
|
if(!ptrTerrain->tr_lnInActiveTerrains.IsLinked()) {
|
|
// test it now and add it to list of tested terrains
|
|
TestTerrain(pen);
|
|
_lhTestedTerrains.AddTail(ptrTerrain->tr_lnInActiveTerrains);
|
|
}
|
|
// if it is a non-hidden brush
|
|
} else if ( (pen->en_RenderType == CEntity::RT_BRUSH) &&
|
|
!(pen->en_ulFlags&ENF_HIDDEN) ) {
|
|
// get its brush
|
|
CBrush3D &brBrush = *pen->en_pbrBrush;
|
|
// add all sectors in the brush
|
|
AddAllSectorsOfBrush(&brBrush);
|
|
}
|
|
ENDFOR}
|
|
}
|
|
|
|
// for all tested terrains
|
|
{FORDELETELIST(CTerrain, tr_lnInActiveTerrains, _lhTestedTerrains, ittr) {
|
|
// remove it from list
|
|
ittr->tr_lnInActiveTerrains.Remove();
|
|
}}
|
|
ASSERT(_lhTestedTerrains.IsEmpty());
|
|
}
|
|
|
|
/*
|
|
* Do the ray casting.
|
|
*/
|
|
void CCastRay::Cast(CWorld *pwoWorld)
|
|
{
|
|
// setup stat timers
|
|
const BOOL bMainLoopTimer = _sfStats.CheckTimer(CStatForm::STI_MAINLOOP);
|
|
if( bMainLoopTimer) _sfStats.StopTimer(CStatForm::STI_MAINLOOP);
|
|
_sfStats.StartTimer(CStatForm::STI_RAYCAST);
|
|
|
|
// initially no polygon is found
|
|
cr_pbpoBrushPolygon= NULL;
|
|
cr_pbscBrushSector = NULL;
|
|
cr_penHit = NULL;
|
|
if (cr_bPhysical) {
|
|
cr_ulPassablePolygons = BPOF_PASSABLE|BPOF_SHOOTTHRU;
|
|
} else {
|
|
cr_ulPassablePolygons = BPOF_PORTAL|BPOF_OCCLUDER;
|
|
}
|
|
|
|
// if origin entity is given
|
|
if (cr_penOrigin!=NULL) {
|
|
// if not continuing
|
|
if (_aas.Count()==0) {
|
|
// add all sectors around it
|
|
AddSectorsAroundEntity(cr_penOrigin);
|
|
}
|
|
// test all sectors recursively
|
|
TestThroughSectors();
|
|
// if there is no origin entity
|
|
} else {
|
|
// test entire world against ray
|
|
TestWholeWorld(pwoWorld);
|
|
}
|
|
|
|
// calculate the hit point from the hit distance
|
|
cr_vHit = cr_vOrigin + (cr_vTarget-cr_vOrigin).Normalize()*cr_fHitDistance;
|
|
|
|
// done with timing
|
|
_sfStats.StopTimer(CStatForm::STI_RAYCAST);
|
|
if( bMainLoopTimer) _sfStats.StartTimer(CStatForm::STI_MAINLOOP);
|
|
}
|
|
|
|
|
|
/*
|
|
* Continue cast.
|
|
*/
|
|
void CCastRay::ContinueCast(CWorld *pwoWorld)
|
|
{
|
|
cr_pbpoIgnore = cr_pbpoBrushPolygon;
|
|
if (cr_penHit->GetRenderType()==CEntity::RT_MODEL) {
|
|
cr_penIgnore = cr_penHit;
|
|
}
|
|
|
|
cr_vOrigin = cr_vHit;
|
|
cl_plRay.pl_PositionVector = cr_vOrigin;
|
|
cr_fHitDistance = (cr_vTarget-cr_vOrigin).Length() + EPSILON;
|
|
Cast(pwoWorld);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* Cast a ray and see what it hits.
|
|
*/
|
|
void CWorld::CastRay(CCastRay &crRay)
|
|
{
|
|
crRay.Cast(this);
|
|
}
|
|
/*
|
|
* Continue to cast already cast ray
|
|
*/
|
|
void CWorld::ContinueCast(CCastRay &crRay)
|
|
{
|
|
crRay.ContinueCast(this);
|
|
}
|