etqw-sdk/source/idlib/geometry/TraceSurface.cpp

403 lines
12 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
//===============================================================
//
// sdTraceSurface
//
//===============================================================
/*
====================
sdTraceSurface::sdTraceSurface
====================
*/
sdTraceSurface::sdTraceSurface( const idDrawVert *verts, const int numVerts, const vertIndex_t *indexes, const int numIndexes, const int hashBinsPerAxis ) :
verts( verts ),
numVerts( numVerts ),
indexes( indexes ),
numIndexes( numIndexes ) {
binsPerAxis = idMath::FloorPowerOfTwo( hashBinsPerAxis );
binLinks = NULL;
// find the bounding volume for the mesh
SIMDProcessor->MinMax( _bounds.GetMins(), _bounds.GetMaxs(), verts, numVerts );
_bounds.ExpandSelf( 64.f );
// create face planes
facePlanes = (idPlane*)Mem_AllocAligned( (numIndexes / 3) * sizeof(idPlane), ALIGN_16 );
SIMDProcessor->DeriveTriPlanes( facePlanes, verts, numVerts, indexes, numIndexes );
CreateHash();
}
/*
====================
sdTraceSurface::~sdTraceSurface
====================
*/
sdTraceSurface::~sdTraceSurface() {
int i, j;
if ( binLinks ) {
for ( i = 0; i < binsPerAxis; i++ ) {
for ( j = 0; j < binsPerAxis; j++ ) {
Mem_Free( binLinks[i][j] );
}
Mem_Free( binLinks[i] );
}
Mem_Free( binLinks );
}
for ( i = 0; i < numLinkBlocks; i++ ) {
Mem_Free( linkBlocks[i] );
}
Mem_FreeAligned( facePlanes );
}
/*
====================
sdTraceSurface::RayIntersection
====================
*/
bool sdTraceSurface::RayIntersection( const idVec3& start, const idVec3& end, idDrawVert& dv ) const {
idVec3 p;
int linkNum;
unsigned int i;
unsigned int maxTraceBins;
int hashBin[3];
int separatorAxis;
float faceDist, traceDist;
float separatorDist;
idVec3 normal, invNormal;
// clear the result in case nothing is hit
dv.Clear();
if ( numLinkBlocks == 0 ) {
return false;
}
// get trace normal and normalize
normal = end - start;
faceDist = traceDist = normal.Normalize();
// inverse normal
invNormal[0] = ( normal[0] != 0.0f ) ? 1.0f / normal[0] : 1.0f;
invNormal[1] = ( normal[1] != 0.0f ) ? 1.0f / normal[1] : 1.0f;
invNormal[2] = ( normal[2] != 0.0f ) ? 1.0f / normal[2] : 1.0f;
// maximum number of hash bins the trace can go through
maxTraceBins = 1 + idMath::Ftoi( traceDist * (
fabs( normal[0] ) * invBinSize[0] +
fabs( normal[1] ) * invBinSize[1] +
fabs( normal[2] ) * invBinSize[2] ) );
// get the first hash bin for the trace
p = start - _bounds[0];
hashBin[0] = idMath::Ftoi( idMath::Floor( p[0] * invBinSize[0] ) );
hashBin[1] = idMath::Ftoi( idMath::Floor( p[1] * invBinSize[1] ) );
hashBin[2] = idMath::Ftoi( idMath::Floor( p[2] * invBinSize[2] ) );
separatorAxis = 0;
separatorDist = 0.0f;
bool insideHash = false;
for ( i = 0; i < maxTraceBins; i++, GetNextHashBin( start, normal, hashBin, separatorAxis, separatorDist ) ) {
// if this bin is outside the valid hash space
if ( ( hashBin[0] | hashBin[1] | hashBin[2] ) & ~( binsPerAxis - 1 ) ) {
if ( insideHash ) {
break; // left valid hash space
}
continue; // not yet in valid hash space
}
insideHash = true;
// if a triangle was hit before the plane that separates this hash bin from the previous hash bin
if ( faceDist < traceDist && faceDist < separatorDist * invNormal[separatorAxis] ) {
break;
}
const triLink_t *link;
// test all the triangles in this hash bin
for ( linkNum = binLinks[hashBin[0]][hashBin[1]][hashBin[2]]; linkNum != -1; linkNum = link->nextLink ) {
link = &linkBlocks[ linkNum / MAX_LINKS_PER_BLOCK ][ linkNum % MAX_LINKS_PER_BLOCK ];
faceDist = TraceToMeshFace( link->faceNum, start, normal, faceDist, traceDist, dv );
}
}
// FIXME: SD_USE_DRAWVERT_SIZE_32
#if defined( SD_USE_DRAWVERT_SIZE_32 )
idVec3 n = dv.GetNormal();
n.Normalize();
dv.SetNormal( n );
#else
dv._normal.Normalize();
#endif
return ( faceDist < traceDist );
}
/*
====================
sdTraceSurface::CreateHash
====================
*/
void sdTraceSurface::CreateHash() {
int i, j, k, l;
idBounds triBounds;
int iBounds[2][3];
int maxLinks, numLinks;
numLinkBlocks = 0;
// divide each axis as needed
for ( i = 0; i < 3; i++ ) {
binSize[i] = ( _bounds[1][i] - _bounds[0][i] ) / binsPerAxis;
if ( binSize[i] <= 0.0f ) {
common->Warning( "CreateTriHash: bad bounds: (%f %f %f) to (%f %f %f)",
_bounds[0][0], _bounds[0][1], _bounds[0][2],
_bounds[1][0], _bounds[1][1], _bounds[1][2] );
return;
}
invBinSize[i] = 1.0f / binSize[i];
}
// a -1 link number terminated the link chain
binLinks = (int ***) Mem_Alloc( binsPerAxis * sizeof( int ** ) );
for ( i = 0; i < binsPerAxis; i++ ) {
binLinks[i] = (int **) Mem_Alloc( binsPerAxis * sizeof( int * ) );
for ( j = 0; j < binsPerAxis; j++ ) {
binLinks[i][j] = (int *) Mem_Alloc( binsPerAxis * sizeof( int ) );
memset( binLinks[i][j], -1, binsPerAxis * sizeof( int ) );
}
}
numLinks = 0;
linkBlocks[numLinkBlocks] = (triLink_t *)Mem_Alloc( MAX_LINKS_PER_BLOCK * sizeof( triLink_t ) );
numLinkBlocks++;
maxLinks = numLinkBlocks * MAX_LINKS_PER_BLOCK;
// for each triangle, place a triLink in each bin that might reference it
for ( i = 0; i < numIndexes; i += 3 ) {
// determine which hash bins the triangle will need to be in
triBounds.Clear();
for ( j = 0; j < 3; j++ ) {
triBounds.AddPoint( verts[ indexes[i+j] ].xyz );
}
for ( j = 0; j < 3; j++ ) {
iBounds[0][j] = idMath::Ftoi( ( triBounds[0][j] - _bounds[0][j] - 0.001f ) * invBinSize[j] );
if ( iBounds[0][j] < 0 ) {
iBounds[0][j] = 0;
} else if ( iBounds[0][j] >= binsPerAxis ) {
iBounds[0][j] = binsPerAxis - 1;
}
iBounds[1][j] = idMath::Ftoi( ( triBounds[1][j] - _bounds[0][j] + 0.001f ) * invBinSize[j] );
if ( iBounds[1][j] < 0 ) {
iBounds[1][j] = 0;
} else if ( iBounds[1][j] >= binsPerAxis ) {
iBounds[1][j] = binsPerAxis - 1;
}
}
// add the links
for ( j = iBounds[0][0]; j <= iBounds[1][0]; j++ ) {
for ( k = iBounds[0][1]; k <= iBounds[1][1]; k++ ) {
for ( l = iBounds[0][2]; l <= iBounds[1][2]; l++ ) {
if ( numLinks == maxLinks ) {
linkBlocks[numLinkBlocks] = (triLink_t *)Mem_Alloc( MAX_LINKS_PER_BLOCK * sizeof( triLink_t ) );
numLinkBlocks++;
maxLinks = numLinkBlocks * MAX_LINKS_PER_BLOCK;
}
triLink_t *link = &linkBlocks[ numLinks / MAX_LINKS_PER_BLOCK ][ numLinks % MAX_LINKS_PER_BLOCK ];
link->faceNum = i / 3;
link->nextLink = binLinks[j][k][l];
binLinks[j][k][l] = numLinks;
numLinks++;
}
}
}
}
}
/*
====================
sdTraceSurface::GetNextHashBin
====================
*/
ID_INLINE void sdTraceSurface::GetNextHashBin( const idVec3 &point, const idVec3 &normal, int hashBin[3], int &separatorAxis, float &separatorDist ) const {
int n[3];
int b[3];
int axis;
float d[3];
idVec3 dir;
// get the normal sign bits
n[0] = FLOATSIGNBITSET( normal.x );
n[1] = FLOATSIGNBITSET( normal.y );
n[2] = FLOATSIGNBITSET( normal.z );
// get the direction from the ray start point to the hash bin corner the normal is pointing at
dir.x = _bounds[0][0] + ( hashBin[0] + ( n[0] ^ 1 ) ) * binSize[0] - point[0];
dir.y = _bounds[0][1] + ( hashBin[1] + ( n[1] ^ 1 ) ) * binSize[1] - point[1];
dir.z = _bounds[0][2] + ( hashBin[2] + ( n[2] ^ 1 ) ) * binSize[2] - point[2];
// determine at which side the ray passes each edge coming from the has bin corner
d[0] = dir.y * normal.x - dir.x * normal.y; // x-y plane: edge parallel to z-axis
d[1] = dir.z * normal.x - dir.x * normal.z; // x-z plane: edge parallel to y-axis
d[2] = dir.z * normal.y - dir.y * normal.z; // y-z plane: edge parallel to x-axis
b[0] = FLOATSIGNBITSET( d[0] ) ^ n[0] ^ n[1];
b[1] = FLOATSIGNBITSET( d[1] ) ^ n[0] ^ n[2];
b[2] = FLOATSIGNBITSET( d[2] ) ^ n[1] ^ n[2];
// determine the hash bin boundary plane the ray goes through based on which side the ray passes each of the edges
axis = ( b[0] & ( b[2] ^ 1 ) ) | ( ( b[1] & b[2] ) << 1 ); // 0 = x, 1 = y, 2 = z
// increment/decrement the axis which separates this hash bin from the next
hashBin[axis] += 1 - ( n[axis] << 1 );
// save the separator axis and separator plane distance relative to ray start point
separatorAxis = axis;
separatorDist = dir[axis];
}
/*
====================
sdTraceSurface::TraceToMeshFace
====================
*/
const float TRIANGLE_NORMAL_EPSILON = 0.0001f;
const float TRIANGLE_EDGE_EPSILON = 0.001f;
ID_INLINE float sdTraceSurface::TraceToMeshFace( const int faceNum, const idVec3& point, const idVec3 &normal, const float faceDist, const float traceDist, idDrawVert& dv ) const {
int i;
float dist;
const idPlane* plane;
idVec3 edge;
float d, t;
idVec3 dir[3];
float baseArea;
float bary[3];
idVec3 testVert;
plane = facePlanes + faceNum;
// only test against planes facing the opposite direction as our trace normal
d = -( plane->Normal() * normal );
if ( d <= TRIANGLE_NORMAL_EPSILON ) {
return faceDist;
}
// get the distance to the plane
dist = plane->Distance( point );
if ( dist >= traceDist * d ) {
return faceDist;
}
const vertIndex_t* index = indexes + faceNum * 3;
const idDrawVert &v0 = verts[ index[ 0 ] ];
const idDrawVert &v1 = verts[ index[ 1 ] ];
const idDrawVert &v2 = verts[ index[ 2 ] ];
// if normal is inside all edge planes, this face is hit
dir[0] = v0.xyz - point;
dir[1] = v1.xyz - point;
edge = dir[0].Cross( dir[1] );
t = normal * edge;
if ( t < -TRIANGLE_EDGE_EPSILON ) {
return faceDist;
}
dir[2] = v2.xyz - point;
edge = dir[1].Cross( dir[2] );
t = normal * edge;
if ( t < -TRIANGLE_EDGE_EPSILON ) {
return faceDist;
}
edge = dir[2].Cross( dir[0] );
t = normal * edge;
if ( t < -TRIANGLE_EDGE_EPSILON ) {
return faceDist;
}
// find the point of impact on the triangle plane
dist /= d;
testVert = point + dist * normal;
// calculate barycentric coordinates of the impact point on the high poly triangle
baseArea = 1.0f / idWinding::TriangleArea( v0.xyz, v1.xyz, v2.xyz );
bary[0] = idWinding::TriangleArea( testVert, v1.xyz, v2.xyz ) * baseArea;
bary[1] = idWinding::TriangleArea( v0.xyz, testVert, v2.xyz ) * baseArea;
bary[2] = idWinding::TriangleArea( v0.xyz, v1.xyz, testVert ) * baseArea;
if ( bary[0] + bary[1] + bary[2] > 1.1f ) {
return faceDist;
}
dv.xyz = testVert;
// triangularly interpolate the texture coordinates, normals and colors to the sample point
// FIXME: SD_USE_DRAWVERT_SIZE_32
for ( i = 0; i < 2; i++ ) {
dv._st[ i ] = bary[0] * v0._st[i] + bary[1] * v1._st[i] + bary[2] * v2._st[i];
}
#if defined( SD_USE_DRAWVERT_SIZE_32 )
idVec3 n;
idVec3 n0 = v0.GetNormal();
idVec3 n1 = v1.GetNormal();
idVec3 n2 = v2.GetNormal();
for ( i = 0; i < 3; i++ ) {
n[ i ] = bary[0] * n0[i] + bary[1] * n1[i] + bary[2] * n2[i];
}
n.Normalize();
dv.SetNormal( n );
idVec3 tgnt;
idVec3 tgnt0 = v0.GetTangent();
idVec3 tgnt1 = v1.GetTangent();
idVec3 tgnt2 = v2.GetTangent();
for ( i = 0; i < 3; i++ ) {
tgnt[ i ] = bary[0] * tgnt0[i] + bary[1] * tgnt1[i] + bary[2] * tgnt2[i];
}
tgnt.Normalize();
dv.SetTangent( tgnt );
float s0 = v0.GetBiTangentSign();
float s1 = v1.GetBiTangentSign();
float s2 = v2.GetBiTangentSign();
dv.SetBiTangentSign( bary[0] * s0 + bary[1] * s1 + bary[2] * s2 );
#else
for ( i = 0; i < 3; i++ ) {
dv._normal[ i ] = bary[0] * v0._normal[i] + bary[1] * v1._normal[i] + bary[2] * v2._normal[i];
}
dv._normal.Normalize();
for ( i = 0; i < 4; i++ ) {
dv._tangent[i] = bary[0] * v0._tangent[i] + bary[1] * v1._tangent[i] + bary[2] * v2._tangent[i];
}
#endif
for ( i = 0; i < 4; i++ ) {
dv.color[i] = idMath::ClampInt( 0, 255, idMath::FtoiFast( bary[0] * v0.color[i] + bary[1] * v1.color[i] + bary[2] * v2.color[i] ) );
}
return dist;
}