@ -250,7 +250,236 @@ unsigned char compute_faceVertex_light2(const world_t* world, const face_t* face
return ( unsigned char ) ( light / numSamples ) ;
return ( unsigned char ) ( light / numSamples ) ;
}
}
// Start with a hash set of all faces
// From the first face onward, group together faces into an interconnected surface based on angle between faces, using a flood-fill type of approach:
// - Add the first face to a surface face list
// - Take the next face from the surface face list (use index to go to next, don't remove it from the surface list)
// - For each face vertex, look up all other faces connected to that vertex
// - If the other face is not present in the original hash set, skip it (it was already added to a surface)
// - If the other face has an angle > 60 degrees with the current face, skip it (sharp edge)
// - Add the other face to the surface face list, remove it from the hash set
// - Repeat from step 'take the next face'
SurfaceList group_surfaces ( const world_t * world , const VertexFaces & vertexFaces )
{
SurfaceList surfaces ;
std : : unordered_set < const face_t * > faceSet ;
for ( int i = 0 ; i < world - > numFaces ; + + i )
faceSet . insert ( & world - > faces [ i ] ) ;
while ( ! faceSet . empty ( ) )
{
const face_t * firstFace = * faceSet . begin ( ) ;
faceSet . erase ( firstFace ) ;
std : : vector < const face_t * > surfaceFaces ;
surfaceFaces . push_back ( firstFace ) ;
for ( size_t faceIdx = 0 ; faceIdx < surfaceFaces . size ( ) ; + + faceIdx )
{
const face_t * thisFace = surfaceFaces [ faceIdx ] ;
const plane_t * thisPlane = & world - > planes [ thisFace - > plane_id ] ;
vec3_t thisNormal = thisFace - > side ? - thisPlane - > normal : thisPlane - > normal ;
for ( int edgeListIdx = 0 ; edgeListIdx < thisFace - > ledge_num ; + + edgeListIdx )
{
int edgeIdx = world - > edgeList [ thisFace - > ledge_id + edgeListIdx ] ;
const edge_t * edge = & world - > edges [ abs ( edgeIdx ) ] ;
for ( int v = 0 ; v < 1 ; + + v )
{
unsigned short vertIndex = * ( & edge - > vertex0 + v ) ;
const vertex_t * vertex = & world - > vertices [ vertIndex ] ;
auto vertexFaceIter = vertexFaces . find ( vertex ) ;
if ( vertexFaceIter = = vertexFaces . end ( ) )
{
printf ( " Couldn't find list of faces for vertex %d, weird... \n " , vertIndex ) ;
continue ;
}
for ( auto faceIter = vertexFaceIter - > second . begin ( ) ; faceIter ! = vertexFaceIter - > second . end ( ) ; + + faceIter )
{
const face_t * otherFace = * faceIter ;
if ( faceSet . find ( otherFace ) = = faceSet . end ( ) )
continue ; // Face has already been added to a surface, skip it
const plane_t * otherPlane = & world - > planes [ otherFace - > plane_id ] ;
vec3_t otherNormal = otherFace - > side ? - otherPlane - > normal : otherPlane - > normal ;
float dot = thisNormal . dotProduct ( otherNormal ) ;
if ( dot < 0.5f )
continue ; // Sharp edge, face belongs to a different surface
// Add face to this surface and make sure it won't be reconsidered for any other surfaces
surfaceFaces . push_back ( otherFace ) ;
faceSet . erase ( otherFace ) ;
}
}
}
}
Surface surface ;
surface . faces . insert ( surfaceFaces . begin ( ) , surfaceFaces . end ( ) ) ;
surfaces . push_back ( surface ) ;
}
return surfaces ;
}
// To sample lightmap at any arbitrary world position:
// - Find the surface that the face belongs to
// - Select all faces from the surface where the position lies in its plane and is inside polygon boundaries
// - Sample those faces at the specified position (sample_lightmap) and average
std : : vector < const face_t * > find_facesWithPoint ( const world_t * world , const face_t * refFace , const SurfaceList & surfaces , Vec3 point )
{
std : : vector < const face_t * > faces ;
// Find the surface that the face belongs to
for ( auto surfIter = surfaces . begin ( ) ; surfIter ! = surfaces . end ( ) ; + + surfIter )
{
if ( surfIter - > faces . find ( refFace ) = = surfIter - > faces . end ( ) )
continue ;
// Select all faces from the surface where the point lies in its plane and is inside polygon boundaries
for ( auto faceIter = surfIter - > faces . begin ( ) ; faceIter ! = surfIter - > faces . end ( ) ; + + faceIter )
{
const face_t * face = * faceIter ;
const plane_t * plane = & world - > planes [ face - > plane_id ] ;
// Check if the point lies on the face's plane
float pointPlaneDist = ( point . x * plane - > normal . x + point . y * plane - > normal . y + point . z * plane - > normal . z ) - plane - > dist ;
if ( fabs ( pointPlaneDist ) > FLT_EPSILON )
continue ;
// Simple case first: check if this point lies on one of the face's edges
bool onEdge = false ;
for ( int edgeListIdx = 0 ; edgeListIdx < face - > ledge_num ; + + edgeListIdx )
{
int edgeIdx = world - > edgeList [ face - > ledge_id + edgeListIdx ] ;
const edge_t * edge = & world - > edges [ abs ( edgeIdx ) ] ;
Vec3 vert0 = world - > vertices [ edge - > vertex0 ] . toVec ( ) ;
Vec3 vert1 = world - > vertices [ edge - > vertex1 ] . toVec ( ) ;
Vec3 dir0 = vert1 - vert0 ;
Vec3 dir1 = point - vert0 ;
float mag0 = dir0 . magnitude ( ) ;
float mag1 = dir1 . magnitude ( ) ;
if ( mag1 / mag0 < = 1.0f + FLT_EPSILON & & dir0 . dotProduct ( dir1 ) > = ( mag0 - FLT_EPSILON ) * ( mag1 - FLT_EPSILON ) )
onEdge = true ;
}
if ( onEdge )
faces . push_back ( face ) ;
}
break ;
}
return faces ;
}
// Further improvements:
// Further improvements:
// - Reconstruct connected surfaces through vertices and edges, so we can fully sample all adjacent lightmaps.
// - Reconstruct connected surfaces through vertices and edges, so we can fully sample all adjacent lightmaps.
// Right now we don't properly detect when one face has vertices halfway along the edge of an adjacent face.
// Right now we don't properly detect when one face has vertices halfway along the edge of an adjacent face.
// - Sample more lightmap points around each vertex to obtain a more representative average value
// - Sample more lightmap points around each vertex to obtain a more representative average value
unsigned char compute_faceVertex_light3 ( const world_t * world , const face_t * refFace , const SurfaceList & surfaces , const FaceBounds & faceBounds , Vec3 point )
{
auto faces = find_facesWithPoint ( world , refFace , surfaces , point ) ;
if ( faces . empty ( ) )
return 0 ;
unsigned int light = 0 ;
for ( auto faceIter = faces . begin ( ) ; faceIter ! = faces . end ( ) ; + + faceIter )
{
const face_t * face = * faceIter ;
light + = sample_lightmap ( world , face , faceBounds . find ( face ) - > second , point ) + ( 0xFF - face - > baselight ) ;
}
return ( unsigned char ) ( light / faces . size ( ) ) ;
}
// Find the list of all faces that contain the given point, i.e. the point lies on the face's plane and is contained within its polygon bounds
std : : vector < const face_t * > world_facesWithPoint ( const world_t * world , Vec3 point )
{
std : : vector < const face_t * > faces ;
for ( int faceIdx = 0 ; faceIdx < world - > numFaces ; + + faceIdx )
{
const face_t * face = & world - > faces [ faceIdx ] ;
const plane_t * plane = & world - > planes [ face - > plane_id ] ;
// Check if the point lies on the face's plane (it's not strictly necessary to do this, but this check makes the whole function a lot faster)
double pointPlaneDist = ( ( double ) point . x * plane - > normal . x + ( double ) point . y * plane - > normal . y + ( double ) point . z * plane - > normal . z ) - plane - > dist ;
if ( fabs ( pointPlaneDist ) > 0.0001 )
continue ;
// Check if the point is contained within the face's polygon
double angleSum = 0 ;
for ( int edgeListIdx = 0 ; edgeListIdx < face - > ledge_num ; + + edgeListIdx )
{
int edgeIdx = world - > edgeList [ face - > ledge_id + edgeListIdx ] ;
Vec3 v0 , v1 ;
if ( edgeIdx > 0 )
{
const edge_t * edge = & world - > edges [ edgeIdx ] ;
v0 = world - > vertices [ edge - > vertex0 ] . toVec ( ) ;
v1 = world - > vertices [ edge - > vertex1 ] . toVec ( ) ;
}
else
{
const edge_t * edge = & world - > edges [ - edgeIdx ] ;
v0 = world - > vertices [ edge - > vertex1 ] . toVec ( ) ;
v1 = world - > vertices [ edge - > vertex0 ] . toVec ( ) ;
}
Vec3 p0 = v0 - point ;
Vec3 p1 = v1 - point ;
double m0 = p0 . magnitude ( ) ;
double m1 = p1 . magnitude ( ) ;
if ( ( m0 * m1 ) < = 0.0001 )
{
faces . push_back ( face ) ;
break ;
}
double dot = p0 . dotProduct ( p1 ) / ( m0 * m1 ) ;
angleSum + = acos ( dot ) ;
}
if ( fabs ( 2 * M_PI - angleSum ) < = 0.0001 )
faces . push_back ( face ) ;
}
return faces ;
}
unsigned char compute_faceVertex_light4 ( const world_t * world , const face_t * refFace , const FaceBounds & faceBounds , Vec3 point )
{
auto faces = world_facesWithPoint ( world , point ) ;
if ( faces . empty ( ) )
return 0 ;
const plane_t * refPlane = & world - > planes [ refFace - > plane_id ] ;
vec3_t refNormal = refFace - > side ? - refPlane - > normal : refPlane - > normal ;
unsigned int light = 0 , numSamples = 0 ;
for ( auto faceIter = faces . begin ( ) ; faceIter ! = faces . end ( ) ; + + faceIter )
{
const face_t * face = * faceIter ;
const plane_t * plane = & world - > planes [ face - > plane_id ] ;
vec3_t normal = face - > side ? - plane - > normal : plane - > normal ;
// Check if the face is at a shallow angle with the reference face
float dot = normal . dotProduct ( refNormal ) ;
if ( dot < 0.5f )
continue ;
light + = sample_lightmap ( world , face , faceBounds . find ( face ) - > second , point ) + ( 0xFF - face - > baselight ) ;
numSamples + + ;
}
// We should always end up with at least one sample (that from refFace itself), so if we divide by zero here something is very much wrong
return ( unsigned char ) ( light / numSamples ) ;
}