Repository: DaemonEngine/Daemon
Pull request #1635: Get rid of a bunch of duplicate code in map loading and srf*_t
Author: login: VReaperV
Labels: name: A-Renderer
name: T-Cleanup
Created: 2025-04-01T13:05:48Z | Merged: 2025-04-11T12:21:50Z

I've run into a bunch of duplicate code while making some improvements to map processing, any improvements to which required to add more such duplication, while the code underneath is the exact same.

This pr NUKES `srfTriangles_t` and `srfSurface_t`, and moves all the common information between them, `srfGridMesh_t`, and `srfVBOMesh_t `, into `srfGeneric_t`.

The common parts of `ParseFace()` and `ParseTriSurf()` are moved into a new `ParseTriangleSurface()`, which is all of it except the plane calculation. The comment about `.lwo` models in `ParseTriSurf()` is irrelevant, because we don't support those.

`ParseTriangleSurface()` and `ParseMesh()` now use `SphereFromBounds()` instead of `FinishGenericSurface()`, because the only difference between the 2 was just duplicated code.


Diff:
diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp
index bcfcd4b183..9199584ca0 100644
--- a/src/engine/renderer/tr_backend.cpp
+++ b/src/engine/renderer/tr_backend.cpp
@@ -1016,20 +1016,14 @@ static int MergeInteractionBounds( const matrix_t lightViewProjectionMatrix, int
 			}
 		}
 
-		if ( *surface == surfaceType_t::SF_FACE || *surface == surfaceType_t::SF_GRID || *surface == surfaceType_t::SF_TRIANGLES )
+		if ( *surface == surfaceType_t::SF_FACE || *surface == surfaceType_t::SF_GRID || *surface == surfaceType_t::SF_TRIANGLES
+			|| *surface == surfaceType_t::SF_VBO_MESH )
 		{
 			srfGeneric_t *gen = ( srfGeneric_t * ) surface;
 
 			VectorCopy( gen->bounds[ 0 ], worldBounds[ 0 ] );
 			VectorCopy( gen->bounds[ 1 ], worldBounds[ 1 ] );
 		}
-		else if ( *surface == surfaceType_t::SF_VBO_MESH )
-		{
-			srfVBOMesh_t *srf = ( srfVBOMesh_t * ) surface;
-
-			VectorCopy( srf->bounds[ 0 ], worldBounds[ 0 ] );
-			VectorCopy( srf->bounds[ 1 ], worldBounds[ 1 ] );
-		}
 		else if ( *surface == surfaceType_t::SF_MDV )
 		{
 			goto skipInteraction;
diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp
index 8625cb0cce..a98fa1f48a 100644
--- a/src/engine/renderer/tr_bsp.cpp
+++ b/src/engine/renderer/tr_bsp.cpp
@@ -746,74 +746,6 @@ static void R_LoadVisibility( lump_t *l )
 
 //===============================================================================
 
-/*
-===============
-ShaderForShaderNum
-===============
-*/
-static shader_t *ShaderForShaderNum( int shaderNum )
-{
-	shader_t  *shader;
-	dshader_t *dsh;
-
-	shaderNum = LittleLong( shaderNum ) + 0;  // silence the warning
-
-	if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders )
-	{
-		Sys::Drop( "ShaderForShaderNum: bad num %i", shaderNum );
-	}
-
-	dsh = &s_worldData.shaders[ shaderNum ];
-
-	shader = R_FindShader( dsh->shader, shaderType_t::SHADER_3D_STATIC, RSF_DEFAULT );
-
-	// if the shader had errors, just use default shader
-	if ( shader->defaultShader )
-	{
-		return tr.defaultShader;
-	}
-
-	return shader;
-}
-
-/*
-SphereFromBounds() - ydnar
-creates a bounding sphere from a bounding box
-*/
-
-static void SphereFromBounds( vec3_t mins, vec3_t maxs, vec3_t origin, float *radius )
-{
-	vec3_t temp;
-
-	VectorAdd( mins, maxs, origin );
-	VectorScale( origin, 0.5, origin );
-	VectorSubtract( maxs, origin, temp );
-	*radius = VectorLength( temp );
-}
-
-/*
-FinishGenericSurface() - ydnar
-handles final surface classification
-*/
-
-static void FinishGenericSurface( dsurface_t *ds, srfGeneric_t *gen, vec3_t pt )
-{
-	// set bounding sphere
-	SphereFromBounds( gen->bounds[ 0 ], gen->bounds[ 1 ], gen->origin, &gen->radius );
-
-	if ( gen->surfaceType == surfaceType_t::SF_FACE )
-	{
-		srfSurfaceFace_t *srf = ( srfSurfaceFace_t * )gen;
-		// take the plane normal from the lightmap vector and classify it
-		srf->plane.normal[ 0 ] = LittleFloat( ds->lightmapVecs[ 2 ][ 0 ] );
-		srf->plane.normal[ 1 ] = LittleFloat( ds->lightmapVecs[ 2 ][ 1 ] );
-		srf->plane.normal[ 2 ] = LittleFloat( ds->lightmapVecs[ 2 ][ 2 ] );
-		srf->plane.dist = DotProduct( pt, srf->plane.normal );
-		SetPlaneSignbits( &srf->plane );
-		srf->plane.type = PlaneTypeForNormal( srf->plane.normal );
-	}
-}
-
 // Generate the skybox mesh and add it to world
 static void FinishSkybox() {
 	// Min and max coordinates of the skybox cube corners
@@ -887,77 +819,84 @@ static void FinishSkybox() {
 	tr.skybox = skybox;
 }
 
-/*
-===============
-ParseFace
-===============
-*/
-static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, int *indexes )
-{
-	int              i, j;
-	srfSurfaceFace_t *cv;
-	srfTriangle_t    *tri;
-	int              numVerts, numTriangles;
-	int              realLightmapNum;
-	struct vertexComponent_t {
-		vec2_t stBounds[ 2 ];
-		int    minVertex;
-	} *components;
-	bool         updated;
+static shader_t* ShaderForShaderNum( int shaderNum ) {
+	shaderNum = LittleLong( shaderNum ) + 0;  // silence the warning
 
-	// get lightmap
-	realLightmapNum = LittleLong( ds->lightmapNum );
+	if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) {
+		Sys::Drop( "ShaderForShaderNum: bad num %i", shaderNum );
+	}
 
-	if ( tr.worldLightMapping || tr.worldDeluxeMapping )
-	{
-		surf->lightmapNum = realLightmapNum;
+	dshader_t* dsh = &s_worldData.shaders[shaderNum];
+
+	shader_t* shader = R_FindShader( dsh->shader, shaderType_t::SHADER_3D_STATIC, RSF_DEFAULT );
+
+	// If the shader had errors, just use default shader
+	if ( shader->defaultShader ) {
+		return tr.defaultShader;
 	}
-	else
-	{
+
+	return shader;
+}
+
+static void SphereFromBounds( vec3_t mins, vec3_t maxs, vec3_t origin, float* radius ) {
+	vec3_t temp;
+
+	VectorAdd( mins, maxs, origin );
+	VectorScale( origin, 0.5, origin );
+	VectorSubtract( maxs, origin, temp );
+	*radius = VectorLength( temp );
+}
+
+static void ParseTriangleSurface( dsurface_t* ds, drawVert_t* verts, bspSurface_t* surf, int* indexes ) {
+	int realLightmapNum = LittleLong( ds->lightmapNum );
+
+	if ( tr.worldLightMapping || tr.worldDeluxeMapping ) {
+		surf->lightmapNum = realLightmapNum;
+	} else {
 		surf->lightmapNum = -1;
 	}
 
-	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 )
-	{
+	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 ) {
 		surf->lightmapNum /= 2;
 	}
 
-	// get fog volume
 	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
-
-	// get shader value
 	surf->shader = ShaderForShaderNum( ds->shaderNum );
 
-	if ( r_singleShader->integer && !surf->shader->isSky )
-	{
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
 		surf->shader = tr.defaultShader;
 	}
 
-	numVerts = LittleLong( ds->numVerts );
-
-	numTriangles = LittleLong( ds->numIndexes ) / 3;
+	// We may have a nodraw surface, because they might still need to be around for movement clipping
+	if ( s_worldData.shaders[LittleLong( ds->shaderNum )].surfaceFlags & SURF_NODRAW ) {
+		surfaceType_t skipData = surfaceType_t::SF_SKIP;
+		surf->data = &skipData;
+		return;
+	}
 
-	cv = (srfSurfaceFace_t*) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
-	cv->surfaceType = surfaceType_t::SF_FACE;
+	srfGeneric_t* cv = ( srfGeneric_t* ) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
+	cv->surfaceType = surfaceType_t::SF_BAD; // Will be set later by ParseFace() or ParseTriSurf()
 
-	cv->numTriangles = numTriangles;
-	cv->triangles = (srfTriangle_t*) ri.Hunk_Alloc( numTriangles * sizeof( cv->triangles[ 0 ] ), ha_pref::h_low );
+	cv->numTriangles = LittleLong( ds->numIndexes ) / 3;
+	cv->triangles = ( srfTriangle_t* ) ri.Hunk_Alloc( cv->numTriangles * sizeof( cv->triangles[ 0 ] ), ha_pref::h_low );
 
-	cv->numVerts = numVerts;
-	cv->verts = (srfVert_t*) ri.Hunk_Alloc( numVerts * sizeof( cv->verts[ 0 ] ), ha_pref::h_low );
+	cv->numVerts = LittleLong( ds->numVerts );
+	cv->verts = ( srfVert_t* ) ri.Hunk_Alloc( cv->numVerts * sizeof( cv->verts[ 0 ] ), ha_pref::h_low );
 
-	surf->data = ( surfaceType_t * ) cv;
+	surf->data = ( surfaceType_t* ) cv;
 
-	// copy vertexes
+	// Copy vertexes
 	ClearBounds( cv->bounds[ 0 ], cv->bounds[ 1 ] );
 	verts += LittleLong( ds->firstVert );
 
-	components = (struct vertexComponent_t *)ri.Hunk_AllocateTempMemory( numVerts * sizeof( struct vertexComponent_t ) );
+	struct vertexComponent_t {
+		vec2_t stBounds[ 2 ];
+		int minVertex;
+	};
+	vertexComponent_t* components = ( vertexComponent_t* ) ri.Hunk_AllocateTempMemory( cv->numVerts * sizeof( vertexComponent_t ) );
 
-	for ( i = 0; i < numVerts; i++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
+	for ( int i = 0; i < cv->numVerts; i++ ) {
+		for ( int j = 0; j < 3; j++ ) {
 			cv->verts[ i ].xyz[ j ] = LittleFloat( verts[ i ].xyz[ j ] );
 			cv->verts[ i ].normal[ j ] = LittleFloat( verts[ i ].normal[ j ] );
 		}
@@ -966,8 +905,7 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in
 
 		components[ i ].minVertex = i;
 
-		for ( j = 0; j < 2; j++ )
-		{
+		for ( int j = 0; j < 2; j++ ) {
 			cv->verts[ i ].st[ j ] = LittleFloat( verts[ i ].st[ j ] );
 			cv->verts[ i ].lightmap[ j ] = LittleFloat( verts[ i ].lightmap[ j ] );
 
@@ -980,59 +918,58 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in
 
 		cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
 
-
-		if ( tr.overbrightBits < tr.mapOverBrightBits )
-		{
+		if ( tr.overbrightBits < tr.mapOverBrightBits ) {
 			R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
 		}
 	}
 
-	// copy triangles
+	// Copy triangles
 	indexes += LittleLong( ds->firstIndex );
 
-	for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
+	for ( int i = 0; i < cv->numTriangles; i++ ) {
+		srfTriangle_t* tri = &cv->triangles[ i ];
+		for ( int j = 0; j < 3; j++ ) {
 			tri->indexes[ j ] = LittleLong( indexes[ i * 3 + j ] );
 
-			if ( tri->indexes[ j ] < 0 || tri->indexes[ j ] >= numVerts )
-			{
-				Sys::Drop( "Bad index in face surface" );
+			if ( tri->indexes[j] < 0 || tri->indexes[j] >= cv->numVerts ) {
+				Sys::Drop( "Index out of range in surface: %i not in [0, %i] (index in BSP: %i, surface: indexes: %i-%i, shader: %s)",
+					tri->indexes[j], cv->numVerts, i * 3 + j, ds->firstIndex, ds->firstIndex + ds->numIndexes, surf->shader->name );
 			}
 		}
 	}
 
-	// compute strongly connected components and TC bounds per component
+	// Compute strongly connected components and TC bounds per component
+	bool updated;
 	do {
 		updated = false;
 
-		for( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ ) {
+		for ( int i = 0; i < cv->numTriangles; i++ ) {
+			srfTriangle_t* tri = &cv->triangles[ i ];
 			int minVertex = std::min( std::min( components[ tri->indexes[ 0 ] ].minVertex,
-						  components[ tri->indexes[ 1 ] ].minVertex ),
-					     components[ tri->indexes[ 2 ] ].minVertex );
-			for( j = 0; j < 3; j++ ) {
+				components[ tri->indexes[ 1 ] ].minVertex ),
+				components[ tri->indexes[ 2 ] ].minVertex );
+			for ( int j = 0; j < 3; j++ ) {
 				int vertex = tri->indexes[ j ];
-				if( components[ vertex ].minVertex != minVertex ) {
+				if ( components[ vertex ].minVertex != minVertex ) {
 					updated = true;
 					components[ vertex ].minVertex = minVertex;
 					components[ minVertex ].stBounds[ 0 ][ 0 ] = std::min( components[ minVertex ].stBounds[ 0 ][ 0 ],
-											  components[ vertex ].stBounds[ 0 ][ 0 ] );
+						components[ vertex ].stBounds[ 0 ][ 0 ] );
 					components[ minVertex ].stBounds[ 0 ][ 1 ] = std::min( components[ minVertex ].stBounds[ 0 ][ 1 ],
-											  components[ vertex ].stBounds[ 0 ][ 1 ] );
+						components[ vertex ].stBounds[ 0 ][ 1 ] );
 					components[ minVertex ].stBounds[ 1 ][ 0 ] = std::max( components[ minVertex ].stBounds[ 1 ][ 0 ],
-											  components[ vertex ].stBounds[ 1 ][ 0 ] );
+						components[ vertex ].stBounds[ 1 ][ 0 ] );
 					components[ minVertex ].stBounds[ 1 ][ 1 ] = std::max( components[ minVertex ].stBounds[ 1 ][ 1 ],
-											  components[ vertex ].stBounds[ 1 ][ 1 ] );
+						components[ vertex ].stBounds[ 1 ][ 1 ] );
 				}
 			}
 		}
-	} while( updated );
+	} while ( updated );
 
-	// center texture coords
-	for( i = 0; i < numVerts; i++ ) {
-		if( components[ i ].minVertex == i ) {
-			for( j = 0; j < 2; j++ ) {
+	// Center texture coords
+	for ( int i = 0; i < cv->numVerts; i++ ) {
+		if ( components[ i ].minVertex == i ) {
+			for ( int j = 0; j < 2; j++ ) {
 				/* Some words about the “minus 0.5” trick:
 				 *
 				 * The vertexpack engine tries to optimize texture coords, because fp16 numbers have 1/2048 resolution only between -1.0 and 1.0,
@@ -1049,61 +986,81 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in
 				 * And rounding x - 0.5 actually generates fewer instructions. So it looks like the original version is pretty great.
 				 * -- @slipher https://github.com/DaemonEngine/Daemon/pull/208#discussion_r299864660
 				 */
-				components[ i ].stBounds[ 0 ][ j ] = rintf( 0.5f * (components[ i ].stBounds[ 1 ][ j ] + components[ i ].stBounds[ 0 ][ j ]) - 0.5f );
+				components[ i ].stBounds[ 0 ][ j ] =
+					rintf( 0.5f * ( components[ i ].stBounds[ 1 ][ j ] + components[ i ].stBounds[ 0 ][ j ] ) - 0.5f );
 			}
 		}
 
-		for ( j = 0; j < 2; j++ )
-		{
+		for ( int j = 0; j < 2; j++ ) {
 			cv->verts[ i ].st[ j ] -= components[ components[ i ].minVertex ].stBounds[ 0 ][ j ];
 		}
 	}
 
 	ri.Hunk_FreeTempMemory( components );
 
-	// take the plane information from the lightmap vector
-	for ( i = 0; i < 3; i++ )
-	{
-		cv->plane.normal[ i ] = LittleFloat( ds->lightmapVecs[ 2 ][ i ] );
+	surf->data = ( surfaceType_t* ) cv;
+
+	for ( int i = 0; i < cv->numTriangles; i++ ) {
+		srfTriangle_t* tri = &cv->triangles[ i ];
+		srfVert_t* dv0 = &cv->verts[ tri->indexes[ 0 ] ];
+		srfVert_t* dv1 = &cv->verts[ tri->indexes[ 1 ] ];
+		srfVert_t* dv2 = &cv->verts[ tri->indexes[ 2 ] ];
+
+		vec3_t tangent, binormal;
+		R_CalcTangents( tangent, binormal,
+			dv0->xyz, dv1->xyz, dv2->xyz,
+			dv0->st, dv1->st, dv2->st );
+		R_TBNtoQtangents( tangent, binormal, dv0->normal,
+			dv0->qtangent );
+		R_TBNtoQtangents( tangent, binormal, dv1->normal,
+			dv1->qtangent );
+		R_TBNtoQtangents( tangent, binormal, dv2->normal,
+			dv2->qtangent );
 	}
 
-	cv->plane.dist = DotProduct( cv->verts[ 0 ].xyz, cv->plane.normal );
-	SetPlaneSignbits( &cv->plane );
-	cv->plane.type = PlaneTypeForNormal( cv->plane.normal );
+	SphereFromBounds( cv->bounds[0], cv->bounds[1], cv->origin, &cv->radius );
+}
 
-	surf->data = ( surfaceType_t * ) cv;
+static void ParseFace( dsurface_t* ds, drawVert_t* verts, bspSurface_t* surf, int* indexes ) {
+	ParseTriangleSurface( ds, verts, surf, indexes );
 
-	{
-		srfVert_t *dv0, *dv1, *dv2;
-		vec3_t    tangent, binormal;
+	srfGeneric_t* surface = ( srfGeneric_t* ) surf->data;
+	if ( surface->surfaceType == surfaceType_t::SF_SKIP ) {
+		return;
+	}
 
-		for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-		{
-			dv0 = &cv->verts[ tri->indexes[ 0 ] ];
-			dv1 = &cv->verts[ tri->indexes[ 1 ] ];
-			dv2 = &cv->verts[ tri->indexes[ 2 ] ];
+	surface->surfaceType = surfaceType_t::SF_FACE;
 
-			R_CalcTangents( tangent, binormal,
-					dv0->xyz, dv1->xyz, dv2->xyz,
-					dv0->st, dv1->st, dv2->st );
-			R_TBNtoQtangents( tangent, binormal, dv0->normal,
-					  dv0->qtangent );
-			R_TBNtoQtangents( tangent, binormal, dv1->normal,
-					  dv1->qtangent );
-			R_TBNtoQtangents( tangent, binormal, dv2->normal,
-					  dv2->qtangent );
-		}
+	// take the plane information from the lightmap vector
+	for ( int i = 0; i < 3; i++ ) {
+		surface->plane.normal[ i ] = LittleFloat( ds->lightmapVecs[ 2 ][ i ] );
 	}
 
-	// finish surface
-	FinishGenericSurface( ds, ( srfGeneric_t * ) cv, cv->verts[ 0 ].xyz );
+	surface->plane.dist = DotProduct( surface->verts[ 0 ].xyz, surface->plane.normal );
+	SetPlaneSignbits( &surface->plane );
+	surface->plane.type = PlaneTypeForNormal( surface->plane.normal );
+}
+
+static void ParseTriSurf( dsurface_t* ds, drawVert_t* verts, bspSurface_t* surf, int* indexes ) {
+	ParseTriangleSurface( ds, verts, surf, indexes );
+
+	srfGeneric_t* surface = ( srfGeneric_t* ) surf->data;
+	if ( surface->surfaceType == surfaceType_t::SF_SKIP ) {
+		return;
+	}
+
+	surface->surfaceType = surfaceType_t::SF_TRIANGLES;
+
+	plane_t plane;
+	srfVert_t* v1 = surface->verts + surface->triangles[0].indexes[0];
+	srfVert_t* v2 = surface->verts + surface->triangles[0].indexes[1];
+	srfVert_t* v3 = surface->verts + surface->triangles[0].indexes[2];
+
+	PlaneFromPoints( plane, v1->xyz, v2->xyz, v3->xyz );
+	VectorCopy( plane.normal, surface->plane.normal );
+	surface->plane.dist = plane.dist;
 }
 
-/*
-===============
-ParseMesh
-===============
-*/
 static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf )
 {
 	srfGridMesh_t        *grid;
@@ -1157,7 +1114,8 @@ static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf )
 
 	if ( width < 0 || width > MAX_PATCH_SIZE || height < 0 || height > MAX_PATCH_SIZE )
 	{
-		Sys::Drop( "ParseMesh: bad size" );
+		Sys::Drop( "ParseMesh: bad size: width: %i (range: [0, %i]), height: %i (range: [0, %i]), surface: firstVertex: %i,"
+		" shader: %s", width, MAX_PATCH_SIZE, height, MAX_PATCH_SIZE, ds->firstVert, surf->shader->name );
 	}
 
 	verts += LittleLong( ds->firstVert );
@@ -1229,201 +1187,7 @@ static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf )
 	VectorSubtract( bounds[ 0 ], grid->lodOrigin, tmpVec );
 	grid->lodRadius = VectorLength( tmpVec );
 
-	// finish surface
-	FinishGenericSurface( ds, ( srfGeneric_t * ) grid, grid->verts[ 0 ].xyz );
-}
-
-/*
-===============
-ParseTriSurf
-===============
-*/
-static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, int *indexes )
-{
-	srfTriangles_t       *cv;
-	srfTriangle_t        *tri;
-	int                  i, j;
-	int                  numVerts, numTriangles;
-	static surfaceType_t skipData = surfaceType_t::SF_SKIP;
-	struct vertexComponent_t {
-		vec2_t stBounds[ 2 ];
-		int    minVertex;
-	} *components;
-	bool         updated;
-
-	// get lightmap
-	surf->lightmapNum = -1; // FIXME LittleLong(ds->lightmapNum);
-
-	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 )
-	{
-		surf->lightmapNum /= 2;
-	}
-
-	// get fog volume
-	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
-
-	// get shader
-	surf->shader = ShaderForShaderNum( ds->shaderNum );
-
-	if ( r_singleShader->integer && !surf->shader->isSky )
-	{
-		surf->shader = tr.defaultShader;
-	}
-
-	// we may have a nodraw surface, because they might still need to
-	// be around for movement clipping
-	if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW )
-	{
-		surf->data = &skipData;
-		return;
-	}
-
-	numVerts = LittleLong( ds->numVerts );
-	numTriangles = LittleLong( ds->numIndexes ) / 3;
-
-	cv = (srfTriangles_t*) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
-	cv->surfaceType = surfaceType_t::SF_TRIANGLES;
-
-	cv->numTriangles = numTriangles;
-	cv->triangles = (srfTriangle_t*) ri.Hunk_Alloc( numTriangles * sizeof( cv->triangles[ 0 ] ), ha_pref::h_low );
-
-	cv->numVerts = numVerts;
-	cv->verts = (srfVert_t*) ri.Hunk_Alloc( numVerts * sizeof( cv->verts[ 0 ] ), ha_pref::h_low );
-
-	surf->data = ( surfaceType_t * ) cv;
-
-	// copy vertexes
-	verts += LittleLong( ds->firstVert );
-
-	components = (struct vertexComponent_t *)ri.Hunk_AllocateTempMemory( numVerts * sizeof( struct vertexComponent_t ) );
-
-	for ( i = 0; i < numVerts; i++ )
-	{
-		components[ i ].minVertex = i;
-
-		for ( j = 0; j < 3; j++ )
-		{
-			cv->verts[ i ].xyz[ j ] = LittleFloat( verts[ i ].xyz[ j ] );
-			cv->verts[ i ].normal[ j ] = LittleFloat( verts[ i ].normal[ j ] );
-		}
-
-		for ( j = 0; j < 2; j++ )
-		{
-			cv->verts[ i ].st[ j ] = LittleFloat( verts[ i ].st[ j ] );
-			cv->verts[ i ].lightmap[ j ] = LittleFloat( verts[ i ].lightmap[ j ] );
-
-			components[ i ].stBounds[ 0 ][ j ] = cv->verts[ i ].st[ j ];
-			components[ i ].stBounds[ 1 ][ j ] = cv->verts[ i ].st[ j ];
-		}
-
-			cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
-
-		if ( tr.overbrightBits < tr.mapOverBrightBits )
-		{
-			R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
-		}
-	}
-
-	// copy triangles
-	indexes += LittleLong( ds->firstIndex );
-
-	for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
-			tri->indexes[ j ] = LittleLong( indexes[ i * 3 + j ] );
-
-			if ( tri->indexes[ j ] < 0 || tri->indexes[ j ] >= numVerts )
-			{
-				Sys::Drop( "Bad index in face surface" );
-			}
-		}
-	}
-
-	// compute strongly connected components and TC bounds per component
-	do {
-		updated = false;
-
-		for( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ ) {
-			int minVertex = std::min( std::min( components[ tri->indexes[ 0 ] ].minVertex,
-						  components[ tri->indexes[ 1 ] ].minVertex ),
-					     components[ tri->indexes[ 2 ] ].minVertex );
-			for( j = 0; j < 3; j++ ) {
-				int vertex = tri->indexes[ j ];
-				if( components[ vertex ].minVertex != minVertex ) {
-					updated = true;
-					components[ vertex ].minVertex = minVertex;
-					components[ minVertex ].stBounds[ 0 ][ 0 ] = std::min( components[ minVertex ].stBounds[ 0 ][ 0 ],
-											  components[ vertex ].stBounds[ 0 ][ 0 ] );
-					components[ minVertex ].stBounds[ 0 ][ 1 ] = std::min( components[ minVertex ].stBounds[ 0 ][ 1 ],
-											  components[ vertex ].stBounds[ 0 ][ 1 ] );
-					components[ minVertex ].stBounds[ 1 ][ 0 ] = std::max( components[ minVertex ].stBounds[ 1 ][ 0 ],
-											  components[ vertex ].stBounds[ 1 ][ 0 ] );
-					components[ minVertex ].stBounds[ 1 ][ 1 ] = std::max( components[ minVertex ].stBounds[ 1 ][ 1 ],
-											  components[ vertex ].stBounds[ 1 ][ 1 ] );
-				}
-			}
-		}
-	} while( updated );
-
-	// center texture coords
-	for( i = 0; i < numVerts; i++ ) {
-		if( components[ i ].minVertex == i ) {
-			for( j = 0; j < 2; j++ ) {
-				/* Reuse the “minus 0.5” trick:
-				 *
-				 * This is the loader for triangle meshes, there are probably no rotating textures on triangle meshes,
-				 * but it's better to keep it consistent.
-				 * -- @gimhael https://github.com/DaemonEngine/Daemon/pull/208#discussion_r299809045
-				 */
-				components[ i ].stBounds[ 0 ][ j ] = rintf( 0.5f * (components[ i ].stBounds[ 1 ][ j ] + components[ i ].stBounds[ 0 ][ j ]) - 0.5f );
-			}
-		}
-
-		for ( j = 0; j < 2; j++ )
-		{
-			cv->verts[ i ].st[ j ] -= components[ components[ i ].minVertex ].stBounds[ 0 ][ j ];
-		}
-	}
-
-	ri.Hunk_FreeTempMemory( components );
-
-	// calc bounding box
-	// HACK: don't loop only through the vertices because they can contain bad data with .lwo models ...
-	ClearBounds( cv->bounds[ 0 ], cv->bounds[ 1 ] );
-
-	for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-	{
-		AddPointToBounds( cv->verts[ tri->indexes[ 0 ] ].xyz, cv->bounds[ 0 ], cv->bounds[ 1 ] );
-		AddPointToBounds( cv->verts[ tri->indexes[ 1 ] ].xyz, cv->bounds[ 0 ], cv->bounds[ 1 ] );
-		AddPointToBounds( cv->verts[ tri->indexes[ 2 ] ].xyz, cv->bounds[ 0 ], cv->bounds[ 1 ] );
-	}
-
-	// Tr3B - calc tangent spaces
-	{
-		srfVert_t *dv0, *dv1, *dv2;
-		vec3_t    tangent, binormal;
-
-		for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-		{
-			dv0 = &cv->verts[ tri->indexes[ 0 ] ];
-			dv1 = &cv->verts[ tri->indexes[ 1 ] ];
-			dv2 = &cv->verts[ tri->indexes[ 2 ] ];
-
-			R_CalcTangents( tangent, binormal,
-					dv0->xyz, dv1->xyz, dv2->xyz,
-					dv0->st, dv1->st, dv2->st );
-			R_TBNtoQtangents( tangent, binormal, dv0->normal,
-					  dv0->qtangent );
-			R_TBNtoQtangents( tangent, binormal, dv1->normal,
-					  dv1->qtangent );
-			R_TBNtoQtangents( tangent, binormal, dv2->normal,
-					  dv2->qtangent );
-		}
-	}
-
-	// finish surface
-	FinishGenericSurface( ds, ( srfGeneric_t * ) cv, cv->verts[ 0 ].xyz );
+	SphereFromBounds( grid->bounds[0], grid->bounds[1], grid->origin, &grid->radius );
 }
 
 /*
@@ -2849,26 +2613,13 @@ static void R_CreateWorldVBO()
 			continue;
 		}
 
-		if ( *surface->data == surfaceType_t::SF_FACE )
+		if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID
+			|| *surface->data == surfaceType_t::SF_TRIANGLES )
 		{
-			srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surface->data;
+			srfGeneric_t* srf = ( srfGeneric_t* ) surface->data;
 
-			numVerts += face->numVerts;
-			numTriangles += face->numTriangles;
-		}
-		else if ( *surface->data == surfaceType_t::SF_GRID )
-		{
-			srfGridMesh_t *grid = ( srfGridMesh_t * ) surface->data;
-
-			numVerts += grid->numVerts;
-			numTriangles += grid->numTriangles;
-		}
-		else if ( *surface->data == surfaceType_t::SF_TRIANGLES )
-		{
-			srfTriangles_t *tri = ( srfTriangles_t * ) surface->data;
-
-			numVerts += tri->numVerts;
-			numTriangles += tri->numTriangles;
+			numVerts += srf->numVerts;
+			numTriangles += srf->numTriangles;
 		}
 		else
 		{
@@ -3020,29 +2771,10 @@ static void R_CreateWorldVBO()
 		int numSurfVerts;
 		const srfTriangle_t *surfTriangle, *surfTriangleEnd;
 
-		if ( *surface->data == surfaceType_t::SF_FACE )
-		{
-			srfSurfaceFace_t *srf = ( srfSurfaceFace_t * ) surface->data;
-
-			srf->firstIndex = vboNumIndexes;
-			surfVerts = srf->verts;
-			numSurfVerts = srf->numVerts;
-			surfTriangle = srf->triangles;
-			surfTriangleEnd = surfTriangle + srf->numTriangles;
-		}
-		else if ( *surface->data == surfaceType_t::SF_GRID )
-		{
-			srfGridMesh_t *srf = ( srfGridMesh_t * ) surface->data;
-
-			srf->firstIndex = vboNumIndexes;
-			surfVerts = srf->verts;
-			numSurfVerts = srf->numVerts;
-			surfTriangle = srf->triangles;
-			surfTriangleEnd = surfTriangle + srf->numTriangles;
-		}
-		else if ( *surface->data == surfaceType_t::SF_TRIANGLES )
+		if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID
+			|| *surface->data == surfaceType_t::SF_TRIANGLES )
 		{
-			srfTriangles_t *srf = ( srfTriangles_t * ) surface->data;
+			srfGeneric_t* srf = ( srfGeneric_t* ) surface->data;
 
 			srf->firstIndex = vboNumIndexes;
 			surfVerts = srf->verts;
@@ -3176,20 +2908,11 @@ static void R_CreateWorldVBO()
 
 			oldViewCount = surf1->viewCount;
 
-			if ( *surf1->data == surfaceType_t::SF_FACE )
+			if ( *surf1->data == surfaceType_t::SF_FACE || *surf1->data == surfaceType_t::SF_TRIANGLES
+				|| *surf1->data == surfaceType_t::SF_GRID )
 			{
-				srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surf1->data;
-				firstIndex = face->firstIndex;
-			}
-			else if ( *surf1->data == surfaceType_t::SF_TRIANGLES )
-			{
-				srfTriangles_t *tris = ( srfTriangles_t * ) surf1->data;
-				firstIndex = tris->firstIndex;
-			}
-			else if ( *surf1->data == surfaceType_t::SF_GRID )
-			{
-				srfGridMesh_t *grid = ( srfGridMesh_t * ) surf1->data;
-				firstIndex = grid->firstIndex;
+				srfGeneric_t* srf = ( srfGeneric_t* ) surf1->data;
+				firstIndex = srf->firstIndex;
 			}
 
 			// count verts and indexes and add bounds for the merged surface
@@ -3204,26 +2927,13 @@ static void R_CreateWorldVBO()
 					break;
 				}
 
-				if ( *surf2->data == surfaceType_t::SF_FACE )
-				{
-					srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surf2->data;
-					surfIndexes += face->numTriangles * 3;
-					surfVerts += face->numVerts;
-					BoundsAdd( bounds[ 0 ], bounds[ 1 ], face->bounds[ 0 ], face->bounds[ 1 ] );
-				}
-				else if ( *surf2->data == surfaceType_t::SF_TRIANGLES )
-				{
-					srfTriangles_t *tris = ( srfTriangles_t * ) surf2->data;
-					surfIndexes += tris->numTriangles * 3;
-					surfVerts += tris->numVerts;
-					BoundsAdd( bounds[ 0 ], bounds[ 1 ], tris->bounds[ 0 ], tris->bounds[ 1 ] );
-				}
-				else if ( *surf2->data == surfaceType_t::SF_GRID )
+				if ( *surf2->data == surfaceType_t::SF_FACE || *surf2->data == surfaceType_t::SF_TRIANGLES
+					|| *surf2->data == surfaceType_t::SF_GRID )
 				{
-					srfGridMesh_t *grid = ( srfGridMesh_t * ) surf2->data;
-					surfIndexes += grid->numTriangles * 3;
-					surfVerts += grid->numVerts;
-					BoundsAdd( bounds[ 0 ], bounds[ 1 ], grid->bounds[ 0 ], grid->bounds[ 1 ] );
+					srfGeneric_t* srf = ( srfGeneric_t* ) surf2->data;
+					surfIndexes += srf->numTriangles * 3;
+					surfVerts += srf->numVerts;
+					BoundsAdd( bounds[ 0 ], bounds[ 1 ], srf->bounds[ 0 ], srf->bounds[ 1 ] );
 				}
 			}
 
@@ -3281,21 +2991,10 @@ static void R_CreateWorldVBO()
 	{
 		surface = surfaces[ k ];
 
-		if ( *surface->data == surfaceType_t::SF_FACE )
-		{
-			srfSurfaceFace_t *srf = ( srfSurfaceFace_t * ) surface->data;
-			srf->vbo = s_worldData.vbo;
-			srf->ibo = s_worldData.ibo;
-		}
-		else if ( *surface->data == surfaceType_t::SF_GRID )
-		{
-			srfGridMesh_t *srf = ( srfGridMesh_t * ) surface->data;
-			srf->vbo = s_worldData.vbo;
-			srf->ibo = s_worldData.ibo;
-		}
-		else if ( *surface->data == surfaceType_t::SF_TRIANGLES )
+		if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID
+			|| *surface->data == surfaceType_t::SF_TRIANGLES )
 		{
-			srfTriangles_t *srf = ( srfTriangles_t * ) surface->data;
+			srfGeneric_t* srf = ( srfGeneric_t* ) surface->data;
 			srf->vbo = s_worldData.vbo;
 			srf->ibo = s_worldData.ibo;
 		}
diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h
index 2db65593e2..a1291fcd14 100644
--- a/src/engine/renderer/tr_local.h
+++ b/src/engine/renderer/tr_local.h
@@ -1701,8 +1701,6 @@ enum class ssaoMode {
 		interaction_t *next;
 	};
 
-#define MAX_FACE_POINTS 64
-
 #define MAX_PATCH_SIZE  64 // max dimensions of a patch mesh in map file
 #define MAX_GRID_SIZE   65 // max dimensions of a grid mesh in memory
 
@@ -1736,97 +1734,57 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
-
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
-
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
-
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-		int           numVerts;
-		srfVert_t     *verts;
+		// Static render data
+		VBO_t* vbo;
+		IBO_t* ibo;
 
 		// BSP VBO offset
 		int firstIndex;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
-	};
-
-	struct srfSurfaceFace_t : srfGeneric_t
-	{
-		cplane_t     plane;
-
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		cplane_t plane; // Valid in: SF_FACE, SF_TRIANGLES
 
-		int           numVerts;
-		srfVert_t     *verts;
+		int numVerts; // Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
+		srfVert_t* verts; // Valid in: SF_FACE, SF_GRID, SF_TRIANGLES
 
-		// BSP VBO offset
-		int firstIndex;
-
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES
+		int numTriangles;
+		srfTriangle_t* triangles;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES
 	};
 
-// misc_models in maps are turned into direct geometry by q3map
-	struct srfTriangles_t : srfGeneric_t
-	{
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
-
-		int           numVerts;
-		srfVert_t     *verts;
-
-		// BSP VBO offset
-		int firstIndex;
+	struct srfGridMesh_t : srfGeneric_t {
+		// lod information, which may be different
+		// than the culling information to allow for
+		// groups of curves that LOD as a unit
+		vec3_t lodOrigin;
+		float lodRadius;
+		int lodFixed;
+		int lodStitched;
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
+		// triangle definitions
+		int width, height;
+		float* widthLodError;
+		float* heightLodError;
 	};
 
-	struct srfVBOMesh_t : srfGeneric_t
-	{
+	struct srfVBOMesh_t : srfGeneric_t {
 		struct shader_t *shader; // FIXME move this to somewhere else
 
-		int             lightmapNum; // FIXME get rid of this by merging all lightmaps at level load
-		int             fogIndex;
+		int lightmapNum; // FIXME get rid of this by merging all lightmaps at level load
+		int fogIndex;
 
 		// backEnd stats
-		int firstIndex;
 		int numIndexes;
-		int numVerts;
-
-		// static render data
-		VBO_t *vbo;
-		IBO_t *ibo;
 	};
 
 	struct srfVBOMD5Mesh_t
diff --git a/src/engine/renderer/tr_main.cpp b/src/engine/renderer/tr_main.cpp
index cec31369ea..47e938794f 100644
--- a/src/engine/renderer/tr_main.cpp
+++ b/src/engine/renderer/tr_main.cpp
@@ -1203,11 +1203,8 @@ void R_MirrorPoint( vec3_t in, orientation_t *surface, orientation_t *camera, ve
 R_PlaneForSurface
 =============
 */
-void R_PlaneForSurface( surfaceType_t *surfType, cplane_t *plane )
-{
-	srfTriangles_t *tri;
-	srfPoly_t      *poly;
-	srfVert_t      *v1, *v2, *v3;
+void R_PlaneForSurface( surfaceType_t *surfType, cplane_t *plane ) {
+	srfPoly_t *poly;
 
 	if ( !surfType )
 	{
@@ -1220,26 +1217,19 @@ void R_PlaneForSurface( surfaceType_t *surfType, cplane_t *plane )
 	switch ( *surfType )
 	{
 		case surfaceType_t::SF_FACE:
-			*plane = ( ( srfSurfaceFace_t * ) surfType )->plane;
-			return;
-
 		case surfaceType_t::SF_TRIANGLES:
-			tri = ( srfTriangles_t * ) surfType;
-			v1 = tri->verts + tri->triangles[ 0 ].indexes[ 0 ];
-			v2 = tri->verts + tri->triangles[ 0 ].indexes[ 1 ];
-			v3 = tri->verts + tri->triangles[ 0 ].indexes[ 2 ];
-			PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
-			VectorCopy( plane4.normal, plane->normal );
-			plane->dist = plane4.dist;
+		{
+			*plane = ( ( srfGeneric_t* ) surfType )->plane;
 			return;
-
+		}
 		case surfaceType_t::SF_POLY:
-			poly = ( srfPoly_t * ) surfType;
-			PlaneFromPoints( plane4, poly->verts[ 0 ].xyz, poly->verts[ 1 ].xyz, poly->verts[ 2 ].xyz );
+		{
+			poly = ( srfPoly_t* ) surfType;
+			PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz );
 			VectorCopy( plane4.normal, plane->normal );
 			plane->dist = plane4.dist;
 			return;
-
+		}
 		default:
 			memset( plane, 0, sizeof( *plane ) );
 			plane->normal[ 0 ] = 1;
diff --git a/src/engine/renderer/tr_marks.cpp b/src/engine/renderer/tr_marks.cpp
index c54e82f383..dfb059455d 100644
--- a/src/engine/renderer/tr_marks.cpp
+++ b/src/engine/renderer/tr_marks.cpp
@@ -199,13 +199,13 @@ void R_BoxSurfaces_r( bspNode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t *
 		else if ( * ( surf->data ) == surfaceType_t::SF_FACE )
 		{
 			// the face plane should go through the box
-			s = BoxOnPlaneSide( mins, maxs, & ( ( srfSurfaceFace_t * ) surf->data )->plane );
+			s = BoxOnPlaneSide( mins, maxs, & ( ( srfGeneric_t* ) surf->data )->plane );
 
 			if ( s == 1 || s == 2 )
 			{
 				surf->viewCount = tr.viewCountNoReset;
 			}
-			else if ( DotProduct( ( ( srfSurfaceFace_t * ) surf->data )->plane.normal, dir ) > -0.5 )
+			else if ( DotProduct( ( ( srfGeneric_t* ) surf->data )->plane.normal, dir ) > -0.5 )
 			{
 				// don't add faces that make sharp angles with the projection direction
 				surf->viewCount = tr.viewCountNoReset;
@@ -301,9 +301,7 @@ int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projectio
 	vec3_t           clipPoints[ 2 ][ MAX_VERTS_ON_POLY ];
 	int              numClipPoints;
 	float            *v;
-	srfSurfaceFace_t *face;
 	srfGridMesh_t    *cv;
-	srfTriangles_t   *trisurf;
 	srfVert_t        *dv;
 	srfTriangle_t    *tri;
 	vec3_t           normal;
@@ -451,7 +449,7 @@ int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projectio
 		}
 		else if ( *surfaces[ i ] == surfaceType_t::SF_FACE )
 		{
-			face = ( srfSurfaceFace_t * ) surfaces[ i ];
+			srfGeneric_t* face = ( srfGeneric_t* ) surfaces[i];
 
 			// check the normal of this face
 			if ( DotProduct( face->plane.normal, projectionDir ) > -0.5 )
@@ -481,7 +479,7 @@ int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projectio
 		}
 		else if ( *surfaces[ i ] == surfaceType_t::SF_TRIANGLES && !r_noMarksOnTrisurfs->integer )
 		{
-			trisurf = ( srfTriangles_t * ) surfaces[ i ];
+			srfGeneric_t* trisurf = ( srfGeneric_t* ) surfaces[ i ];
 
 			for ( k = 0, tri = trisurf->triangles; k < trisurf->numTriangles; k++, tri++ )
 			{
diff --git a/src/engine/renderer/tr_surface.cpp b/src/engine/renderer/tr_surface.cpp
index 6f8249ae4f..a67908727f 100644
--- a/src/engine/renderer/tr_surface.cpp
+++ b/src/engine/renderer/tr_surface.cpp
@@ -748,7 +748,7 @@ static void Tess_SurfacePolychain( srfPoly_t *p )
 Tess_SurfaceFace
 ==============
 */
-static void Tess_SurfaceFace( srfSurfaceFace_t *srf )
+static void Tess_SurfaceFace( srfGeneric_t* srf )
 {
 	GLIMP_LOGCOMMENT( "--- Tess_SurfaceFace ---" );
 
@@ -778,7 +778,7 @@ static void Tess_SurfaceGrid( srfGridMesh_t *srf )
 Tess_SurfaceTriangles
 =============
 */
-static void Tess_SurfaceTriangles( srfTriangles_t *srf )
+static void Tess_SurfaceTriangles( srfGeneric_t* srf )
 {
 	GLIMP_LOGCOMMENT( "--- Tess_SurfaceTriangles ---" );
 
diff --git a/src/engine/renderer/tr_world.cpp b/src/engine/renderer/tr_world.cpp
index 4ca207aec0..8d3fd19172 100644
--- a/src/engine/renderer/tr_world.cpp
+++ b/src/engine/renderer/tr_world.cpp
@@ -62,12 +62,12 @@ static bool R_CullSurface( surfaceType_t *surface, shader_t *shader, int planeBi
 	}
 
 	// get generic surface
-	gen = ( srfGeneric_t * ) surface;
+	gen = ( srfGeneric_t* ) surface;
 
 	// plane cull
 	if ( *surface == surfaceType_t::SF_FACE && r_facePlaneCull->integer )
 	{
-		srfSurfaceFace_t *srf = ( srfSurfaceFace_t * )gen;
+		srfGeneric_t* srf = ( srfGeneric_t* ) gen;
 		d = DotProduct( tr.orientation.viewOrigin, srf->plane.normal ) - srf->plane.dist;
 
 		// don't cull exactly on the plane, because there are levels of rounding
@@ -167,7 +167,7 @@ static bool R_CullLightSurface( surfaceType_t *surface, shader_t *shader, trRefL
 	// plane cull
 	if ( *surface == surfaceType_t::SF_FACE && r_facePlaneCull->integer )
 	{
-		srfSurfaceFace_t *srf = ( srfSurfaceFace_t * )gen;
+		srfGeneric_t* srf = ( srfGeneric_t* ) gen;
 		if ( light->l.rlType == refLightType_t::RL_DIRECTIONAL )
 		{
 			d = DotProduct( tr.sunDirection, srf->plane.normal );


Reviews:
user: login: slipher
body: There are some tiny model lighting changes from `Move some common stuff from srf* to srfGeneric_t` and `Move duplicate code from ParseFace() and ParseTriSurf() into ParseTriangleSurface()`. I will try to check later if those diffs are anything.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:24:58Z
user: login: VReaperV
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T21:00:12Z
user: login: slipher
body: I think the last commit, `NUKE srfSurfaceFace_t`, should be dropped. It will just cause confusion for one of the surface types to not have its own struct. But a couple changes from that commit of changing some casts to `srfGeneric_t *` should be kept (these fix comments I made on earlier commits complaining of casting to an inappropriate type).
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T07:23:00Z
user: login: VReaperV
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:43:21Z
user: login: VReaperV
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:45:01Z
user: login: slipher
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T13:15:51Z
user: login: VReaperV
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T15:55:21Z
user: login: VReaperV
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T18:05:41Z
user: login: slipher
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-11T11:43:43Z

Review comments:
diff_hunk: @@ -1706,17 +1706,15 @@ enum class ssaoMode {
 
 // when cgame directly specifies a polygon, it becomes a srfPoly_t
 // as soon as it is called
-	struct srfPoly_t
-	{
+	struct srfPoly_t {
 		surfaceType_t surfaceType;
-		qhandle_t     hShader;
-		int16_t       numVerts;
-		int16_t       fogIndex;
-		polyVert_t    *verts;
+		qhandle_t hShader;
path: src/engine/renderer/tr_local.h
user: login: slipher
body: Please remove random whitespace changes from `Move some common stuff from srf* to srfGeneric_t `
created_at: 2025-04-02T22:18:59Z
author_association: MEMBER
diff_hunk: @@ -1706,17 +1706,15 @@ enum class ssaoMode {
 
 // when cgame directly specifies a polygon, it becomes a srfPoly_t
 // as soon as it is called
-	struct srfPoly_t
-	{
+	struct srfPoly_t {
 		surfaceType_t surfaceType;
-		qhandle_t     hShader;
-		int16_t       numVerts;
-		int16_t       fogIndex;
-		polyVert_t    *verts;
+		qhandle_t hShader;
path: src/engine/renderer/tr_local.h
user: login: VReaperV
body: Done.
created_at: 2025-04-04T21:00:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2847,26 +2847,13 @@ static void R_CreateWorldVBO()
 			continue;
 		}
 
-		if ( *surface->data == surfaceType_t::SF_FACE )
+		if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID
+			|| *surface->data == surfaceType_t::SF_TRIANGLES )
 		{
-			srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surface->data;
+			srfSurfaceFace_t* srf = ( srfSurfaceFace_t * ) surface->data;
path: src/engine/renderer/tr_bsp.cpp
user: login: slipher
body: Cast to wrong type
created_at: 2025-04-08T06:48:56Z
author_association: MEMBER
diff_hunk: @@ -3174,21 +3142,12 @@ static void R_CreateWorldVBO()
 
 			oldViewCount = surf1->viewCount;
 
-			if ( *surf1->data == surfaceType_t::SF_FACE )
+			if ( *surf1->data == surfaceType_t::SF_FACE || *surf1->data == surfaceType_t::SF_TRIANGLES
+				|| *surf1->data == surfaceType_t::SF_GRID )
 			{
-				srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surf1->data;
+				srfGeneric_t* face = ( srfGeneric_t* ) surf1->data;
path: src/engine/renderer/tr_bsp.cpp
user: login: slipher
body: Variable name `face` should be changed to be generic
created_at: 2025-04-08T06:49:44Z
author_association: MEMBER
diff_hunk: @@ -1736,97 +1734,53 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
+		cplane_t plane;
 
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
+		int numVerts;
path: src/engine/renderer/tr_local.h
user: login: slipher
body: Please add comments to indicate which surface types each field is defined in. It's really annoying to have fields that are only valid in a random subset of the objects and not know which ones.
created_at: 2025-04-08T07:05:45Z
author_association: MEMBER
diff_hunk: @@ -887,152 +887,127 @@ static void FinishSkybox() {
 	tr.skybox = skybox;
 }
 
-/*
-===============
-ParseFace
-===============
-*/
-static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, int *indexes )
-{
-	int              i, j;
-	srfSurfaceFace_t *cv;
-	srfTriangle_t    *tri;
-	int              numVerts, numTriangles;
-	int              realLightmapNum;
-	struct vertexComponent_t {
-		vec2_t stBounds[ 2 ];
-		int    minVertex;
-	} *components;
-	bool         updated;
+static void ParseTriangleSurface( dsurface_t* ds, drawVert_t* verts, bspSurface_t* surf, int* indexes ) {
+	int realLightmapNum = LittleLong( ds->lightmapNum );
 
-	// get lightmap
-	realLightmapNum = LittleLong( ds->lightmapNum );
-
-	if ( tr.worldLightMapping || tr.worldDeluxeMapping )
-	{
+	if ( tr.worldLightMapping || tr.worldDeluxeMapping ) {
 		surf->lightmapNum = realLightmapNum;
-	}
-	else
-	{
+	} else {
 		surf->lightmapNum = -1;
 	}
 
-	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 )
-	{
+	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 ) {
 		surf->lightmapNum /= 2;
 	}
 
-	// get fog volume
 	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
-
-	// get shader value
 	surf->shader = ShaderForShaderNum( ds->shaderNum );
 
-	if ( r_singleShader->integer && !surf->shader->isSky )
-	{
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
 		surf->shader = tr.defaultShader;
 	}
 
-	numVerts = LittleLong( ds->numVerts );
-
-	numTriangles = LittleLong( ds->numIndexes ) / 3;
+	// We may have a nodraw surface, because they might still need to be around for movement clipping
+	if ( s_worldData.shaders[LittleLong( ds->shaderNum )].surfaceFlags & SURF_NODRAW ) {
+		surfaceType_t skipData = surfaceType_t::SF_SKIP;
+		surf->data = &skipData;
+		return;
+	}
 
-	cv = (srfSurfaceFace_t*) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
-	cv->surfaceType = surfaceType_t::SF_FACE;
+	srfGeneric_t* cv = ( srfGeneric_t* ) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
 
-	cv->numTriangles = numTriangles;
-	cv->triangles = (srfTriangle_t*) ri.Hunk_Alloc( numTriangles * sizeof( cv->triangles[ 0 ] ), ha_pref::h_low );
+	cv->numTriangles = LittleLong( ds->numIndexes ) / 3;
+	cv->triangles = ( srfTriangle_t* ) ri.Hunk_Alloc( cv->numTriangles * sizeof( cv->triangles[0] ), ha_pref::h_low );
 
-	cv->numVerts = numVerts;
-	cv->verts = (srfVert_t*) ri.Hunk_Alloc( numVerts * sizeof( cv->verts[ 0 ] ), ha_pref::h_low );
+	cv->numVerts = LittleLong( ds->numVerts );
+	cv->verts = ( srfVert_t* ) ri.Hunk_Alloc( cv->numVerts * sizeof( cv->verts[0] ), ha_pref::h_low );
 
-	surf->data = ( surfaceType_t * ) cv;
+	surf->data = ( surfaceType_t* ) cv;
 
-	// copy vertexes
-	ClearBounds( cv->bounds[ 0 ], cv->bounds[ 1 ] );
+	// Copy vertexes
+	ClearBounds( cv->bounds[0], cv->bounds[1] );
 	verts += LittleLong( ds->firstVert );
 
-	components = (struct vertexComponent_t *)ri.Hunk_AllocateTempMemory( numVerts * sizeof( struct vertexComponent_t ) );
+	struct vertexComponent_t {
+		vec2_t stBounds[2];
+		int minVertex;
+	};
+	vertexComponent_t* components = ( vertexComponent_t* ) ri.Hunk_AllocateTempMemory( cv->numVerts * sizeof( vertexComponent_t ) );
 
-	for ( i = 0; i < numVerts; i++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
-			cv->verts[ i ].xyz[ j ] = LittleFloat( verts[ i ].xyz[ j ] );
-			cv->verts[ i ].normal[ j ] = LittleFloat( verts[ i ].normal[ j ] );
+	for ( int i = 0; i < cv->numVerts; i++ ) {
+		for ( int j = 0; j < 3; j++ ) {
+			cv->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
+			cv->verts[i].normal[j] = LittleFloat( verts[i].normal[j] );
 		}
 
-		AddPointToBounds( cv->verts[ i ].xyz, cv->bounds[ 0 ], cv->bounds[ 1 ] );
+		AddPointToBounds( cv->verts[i].xyz, cv->bounds[0], cv->bounds[1] );
 
-		components[ i ].minVertex = i;
+		components[i].minVertex = i;
 
-		for ( j = 0; j < 2; j++ )
-		{
-			cv->verts[ i ].st[ j ] = LittleFloat( verts[ i ].st[ j ] );
-			cv->verts[ i ].lightmap[ j ] = LittleFloat( verts[ i ].lightmap[ j ] );
+		for ( int j = 0; j < 2; j++ ) {
+			cv->verts[i].st[j] = LittleFloat( verts[i].st[j] );
+			cv->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] );
 
-			components[ i ].stBounds[ 0 ][ j ] = cv->verts[ i ].st[ j ];
-			components[ i ].stBounds[ 1 ][ j ] = cv->verts[ i ].st[ j ];
+			components[i].stBounds[0][j] = cv->verts[i].st[j];
+			components[i].stBounds[1][j] = cv->verts[i].st[j];
 		}
 
-		cv->verts[ i ].lightmap[ 0 ] = LittleFloat( verts[ i ].lightmap[ 0 ] );
-		cv->verts[ i ].lightmap[ 1 ] = LittleFloat( verts[ i ].lightmap[ 1 ] );
-
-		cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
+		cv->verts[i].lightmap[0] = LittleFloat( verts[i].lightmap[0] );
+		cv->verts[i].lightmap[1] = LittleFloat( verts[i].lightmap[1] );
 
+		cv->verts[i].lightColor = Color::Adapt( verts[i].color );
 
-		if ( tr.overbrightBits < tr.mapOverBrightBits )
-		{
-			R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
+		if ( tr.overbrightBits < tr.mapOverBrightBits ) {
+			R_ColorShiftLightingBytes( cv->verts[i].lightColor.ToArray() );
 		}
 	}
 
-	// copy triangles
+	// Copy triangles
 	indexes += LittleLong( ds->firstIndex );
 
-	for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
-			tri->indexes[ j ] = LittleLong( indexes[ i * 3 + j ] );
+	for ( int i = 0; i < cv->numTriangles; i++ ) {
+		srfTriangle_t* tri = &cv->triangles[i];
+		for ( int j = 0; j < 3; j++ ) {
+			tri->indexes[j] = LittleLong( indexes[i * 3 + j] );
 
-			if ( tri->indexes[ j ] < 0 || tri->indexes[ j ] >= numVerts )
-			{
+			if ( tri->indexes[j] < 0 || tri->indexes[j] >= cv->numVerts ) {
 				Sys::Drop( "Bad index in face surface" );
 			}
 		}
 	}
 
-	// compute strongly connected components and TC bounds per component
+	// Compute strongly connected components and TC bounds per component
+	bool updated;
 	do {
 		updated = false;
 
-		for( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ ) {
-			int minVertex = std::min( std::min( components[ tri->indexes[ 0 ] ].minVertex,
-						  components[ tri->indexes[ 1 ] ].minVertex ),
-					     components[ tri->indexes[ 2 ] ].minVertex );
-			for( j = 0; j < 3; j++ ) {
-				int vertex = tri->indexes[ j ];
-				if( components[ vertex ].minVertex != minVertex ) {
+		for ( int i = 0; i < cv->numTriangles; i++ ) {
+			srfTriangle_t* tri = &cv->triangles[i];
+			int minVertex = std::min( std::min( components[tri->indexes[0]].minVertex,
path: src/engine/renderer/tr_bsp.cpp
user: login: slipher
body: There are a ton of random whitespace changes in this commit (` Move duplicate code from ParseFace() and ParseTriSurf() into ParseTriangleSurface()`). Can you configure your formatter to add the spaces inside square brackets? That seems to be the cause of most of the diffs.
created_at: 2025-04-08T07:06:58Z
author_association: MEMBER
diff_hunk: @@ -1218,19 +1217,9 @@ void R_PlaneForSurface( surfaceType_t *surfType, cplane_t *plane ) {
 	switch ( *surfType )
 	{
 		case surfaceType_t::SF_FACE:
-		{
-			*plane = ( ( srfSurfaceFace_t* ) surfType )->plane;
-			return;
-		}
 		case surfaceType_t::SF_TRIANGLES:
 		{
-			srfGeneric_t* tri = ( srfGeneric_t* ) surfType;
-			v1 = tri->verts + tri->triangles[0].indexes[0];
-			v2 = tri->verts + tri->triangles[0].indexes[1];
-			v3 = tri->verts + tri->triangles[0].indexes[2];
-			PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
-			VectorCopy( plane4.normal, plane->normal );
-			plane->dist = plane4.dist;
+			*plane = ( ( srfSurfaceFace_t* ) surfType )->plane;
path: src/engine/renderer/tr_main.cpp
user: login: slipher
body: Use srfGeneric_t cast
created_at: 2025-04-08T07:08:05Z
author_association: MEMBER
diff_hunk: @@ -887,152 +887,127 @@ static void FinishSkybox() {
 	tr.skybox = skybox;
 }
 
-/*
-===============
-ParseFace
-===============
-*/
-static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, int *indexes )
-{
-	int              i, j;
-	srfSurfaceFace_t *cv;
-	srfTriangle_t    *tri;
-	int              numVerts, numTriangles;
-	int              realLightmapNum;
-	struct vertexComponent_t {
-		vec2_t stBounds[ 2 ];
-		int    minVertex;
-	} *components;
-	bool         updated;
+static void ParseTriangleSurface( dsurface_t* ds, drawVert_t* verts, bspSurface_t* surf, int* indexes ) {
+	int realLightmapNum = LittleLong( ds->lightmapNum );
 
-	// get lightmap
-	realLightmapNum = LittleLong( ds->lightmapNum );
-
-	if ( tr.worldLightMapping || tr.worldDeluxeMapping )
-	{
+	if ( tr.worldLightMapping || tr.worldDeluxeMapping ) {
 		surf->lightmapNum = realLightmapNum;
-	}
-	else
-	{
+	} else {
 		surf->lightmapNum = -1;
 	}
 
-	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 )
-	{
+	if ( tr.worldDeluxeMapping && surf->lightmapNum >= 2 ) {
 		surf->lightmapNum /= 2;
 	}
 
-	// get fog volume
 	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
-
-	// get shader value
 	surf->shader = ShaderForShaderNum( ds->shaderNum );
 
-	if ( r_singleShader->integer && !surf->shader->isSky )
-	{
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
 		surf->shader = tr.defaultShader;
 	}
 
-	numVerts = LittleLong( ds->numVerts );
-
-	numTriangles = LittleLong( ds->numIndexes ) / 3;
+	// We may have a nodraw surface, because they might still need to be around for movement clipping
+	if ( s_worldData.shaders[LittleLong( ds->shaderNum )].surfaceFlags & SURF_NODRAW ) {
+		surfaceType_t skipData = surfaceType_t::SF_SKIP;
+		surf->data = &skipData;
+		return;
+	}
 
-	cv = (srfSurfaceFace_t*) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
-	cv->surfaceType = surfaceType_t::SF_FACE;
+	srfGeneric_t* cv = ( srfGeneric_t* ) ri.Hunk_Alloc( sizeof( *cv ), ha_pref::h_low );
 
-	cv->numTriangles = numTriangles;
-	cv->triangles = (srfTriangle_t*) ri.Hunk_Alloc( numTriangles * sizeof( cv->triangles[ 0 ] ), ha_pref::h_low );
+	cv->numTriangles = LittleLong( ds->numIndexes ) / 3;
+	cv->triangles = ( srfTriangle_t* ) ri.Hunk_Alloc( cv->numTriangles * sizeof( cv->triangles[0] ), ha_pref::h_low );
 
-	cv->numVerts = numVerts;
-	cv->verts = (srfVert_t*) ri.Hunk_Alloc( numVerts * sizeof( cv->verts[ 0 ] ), ha_pref::h_low );
+	cv->numVerts = LittleLong( ds->numVerts );
+	cv->verts = ( srfVert_t* ) ri.Hunk_Alloc( cv->numVerts * sizeof( cv->verts[0] ), ha_pref::h_low );
 
-	surf->data = ( surfaceType_t * ) cv;
+	surf->data = ( surfaceType_t* ) cv;
 
-	// copy vertexes
-	ClearBounds( cv->bounds[ 0 ], cv->bounds[ 1 ] );
+	// Copy vertexes
+	ClearBounds( cv->bounds[0], cv->bounds[1] );
 	verts += LittleLong( ds->firstVert );
 
-	components = (struct vertexComponent_t *)ri.Hunk_AllocateTempMemory( numVerts * sizeof( struct vertexComponent_t ) );
+	struct vertexComponent_t {
+		vec2_t stBounds[2];
+		int minVertex;
+	};
+	vertexComponent_t* components = ( vertexComponent_t* ) ri.Hunk_AllocateTempMemory( cv->numVerts * sizeof( vertexComponent_t ) );
 
-	for ( i = 0; i < numVerts; i++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
-			cv->verts[ i ].xyz[ j ] = LittleFloat( verts[ i ].xyz[ j ] );
-			cv->verts[ i ].normal[ j ] = LittleFloat( verts[ i ].normal[ j ] );
+	for ( int i = 0; i < cv->numVerts; i++ ) {
+		for ( int j = 0; j < 3; j++ ) {
+			cv->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
+			cv->verts[i].normal[j] = LittleFloat( verts[i].normal[j] );
 		}
 
-		AddPointToBounds( cv->verts[ i ].xyz, cv->bounds[ 0 ], cv->bounds[ 1 ] );
+		AddPointToBounds( cv->verts[i].xyz, cv->bounds[0], cv->bounds[1] );
 
-		components[ i ].minVertex = i;
+		components[i].minVertex = i;
 
-		for ( j = 0; j < 2; j++ )
-		{
-			cv->verts[ i ].st[ j ] = LittleFloat( verts[ i ].st[ j ] );
-			cv->verts[ i ].lightmap[ j ] = LittleFloat( verts[ i ].lightmap[ j ] );
+		for ( int j = 0; j < 2; j++ ) {
+			cv->verts[i].st[j] = LittleFloat( verts[i].st[j] );
+			cv->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] );
 
-			components[ i ].stBounds[ 0 ][ j ] = cv->verts[ i ].st[ j ];
-			components[ i ].stBounds[ 1 ][ j ] = cv->verts[ i ].st[ j ];
+			components[i].stBounds[0][j] = cv->verts[i].st[j];
+			components[i].stBounds[1][j] = cv->verts[i].st[j];
 		}
 
-		cv->verts[ i ].lightmap[ 0 ] = LittleFloat( verts[ i ].lightmap[ 0 ] );
-		cv->verts[ i ].lightmap[ 1 ] = LittleFloat( verts[ i ].lightmap[ 1 ] );
-
-		cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
+		cv->verts[i].lightmap[0] = LittleFloat( verts[i].lightmap[0] );
+		cv->verts[i].lightmap[1] = LittleFloat( verts[i].lightmap[1] );
 
+		cv->verts[i].lightColor = Color::Adapt( verts[i].color );
 
-		if ( tr.overbrightBits < tr.mapOverBrightBits )
-		{
-			R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
+		if ( tr.overbrightBits < tr.mapOverBrightBits ) {
+			R_ColorShiftLightingBytes( cv->verts[i].lightColor.ToArray() );
 		}
 	}
 
-	// copy triangles
+	// Copy triangles
 	indexes += LittleLong( ds->firstIndex );
 
-	for ( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ )
-	{
-		for ( j = 0; j < 3; j++ )
-		{
-			tri->indexes[ j ] = LittleLong( indexes[ i * 3 + j ] );
+	for ( int i = 0; i < cv->numTriangles; i++ ) {
+		srfTriangle_t* tri = &cv->triangles[i];
+		for ( int j = 0; j < 3; j++ ) {
+			tri->indexes[j] = LittleLong( indexes[i * 3 + j] );
 
-			if ( tri->indexes[ j ] < 0 || tri->indexes[ j ] >= numVerts )
-			{
+			if ( tri->indexes[j] < 0 || tri->indexes[j] >= cv->numVerts ) {
 				Sys::Drop( "Bad index in face surface" );
 			}
 		}
 	}
 
-	// compute strongly connected components and TC bounds per component
+	// Compute strongly connected components and TC bounds per component
+	bool updated;
 	do {
 		updated = false;
 
-		for( i = 0, tri = cv->triangles; i < numTriangles; i++, tri++ ) {
-			int minVertex = std::min( std::min( components[ tri->indexes[ 0 ] ].minVertex,
-						  components[ tri->indexes[ 1 ] ].minVertex ),
-					     components[ tri->indexes[ 2 ] ].minVertex );
-			for( j = 0; j < 3; j++ ) {
-				int vertex = tri->indexes[ j ];
-				if( components[ vertex ].minVertex != minVertex ) {
+		for ( int i = 0; i < cv->numTriangles; i++ ) {
+			srfTriangle_t* tri = &cv->triangles[i];
+			int minVertex = std::min( std::min( components[tri->indexes[0]].minVertex,
path: src/engine/renderer/tr_bsp.cpp
user: login: VReaperV
body: Done.
created_at: 2025-04-08T10:43:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3174,21 +3142,12 @@ static void R_CreateWorldVBO()
 
 			oldViewCount = surf1->viewCount;
 
-			if ( *surf1->data == surfaceType_t::SF_FACE )
+			if ( *surf1->data == surfaceType_t::SF_FACE || *surf1->data == surfaceType_t::SF_TRIANGLES
+				|| *surf1->data == surfaceType_t::SF_GRID )
 			{
-				srfSurfaceFace_t *face = ( srfSurfaceFace_t * ) surf1->data;
+				srfGeneric_t* face = ( srfGeneric_t* ) surf1->data;
path: src/engine/renderer/tr_bsp.cpp
user: login: VReaperV
body: Fixed.
created_at: 2025-04-08T10:45:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1736,97 +1734,57 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
-
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
-
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
-
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-		int           numVerts;
-		srfVert_t     *verts;
+		// Static render data
+		VBO_t* vbo;
+		IBO_t* ibo;
 
 		// BSP VBO offset
 		int firstIndex;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
-	};
-
-	struct srfSurfaceFace_t : srfGeneric_t
-	{
-		cplane_t     plane;
-
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		cplane_t plane; // Valid in: SF_FACE, SF_TRIANGLES
path: src/engine/renderer/tr_local.h
user: login: slipher
body: Does SF_TRIANGLES really have a plane? It doesn't make much sense and the old `srfTriangles_t` doesn't have it.

Also I don't think it's necessary to have comments on ones that are always valid; you can remove those if you want.
created_at: 2025-04-09T13:15:51Z
author_association: MEMBER
diff_hunk: @@ -1736,97 +1734,57 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
-
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
-
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
-
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-		int           numVerts;
-		srfVert_t     *verts;
+		// Static render data
+		VBO_t* vbo;
+		IBO_t* ibo;
 
 		// BSP VBO offset
 		int firstIndex;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
-	};
-
-	struct srfSurfaceFace_t : srfGeneric_t
-	{
-		cplane_t     plane;
-
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		cplane_t plane; // Valid in: SF_FACE, SF_TRIANGLES
path: src/engine/renderer/tr_local.h
user: login: VReaperV
body: I thought that too, but `R_PlaneForSurface()` was already using the first triangle's plane as surface plane for `SF_TRIANGLES`: https://github.com/DaemonEngine/Daemon/blob/bc09529b0b7b63d08acc5c2fe3d87ef3cf9203ba/src/engine/renderer/tr_main.cpp#L1226-L1234
created_at: 2025-04-09T15:55:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1736,97 +1734,57 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
-
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
-
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
-
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-		int           numVerts;
-		srfVert_t     *verts;
+		// Static render data
+		VBO_t* vbo;
+		IBO_t* ibo;
 
 		// BSP VBO offset
 		int firstIndex;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
-	};
-
-	struct srfSurfaceFace_t : srfGeneric_t
-	{
-		cplane_t     plane;
-
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		cplane_t plane; // Valid in: SF_FACE, SF_TRIANGLES
path: src/engine/renderer/tr_local.h
user: login: VReaperV
body: For code comments, I'll adjust them in a later pr, since I found some more things that can be cleaned up there, but don't want to overload this pr.
created_at: 2025-04-09T18:05:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1736,97 +1734,57 @@ enum class ssaoMode {
 	};
 
 // ydnar: plain map drawsurfaces must match this header
-	struct srfGeneric_t
-	{
+	struct srfGeneric_t {
+		// v Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 		surfaceType_t surfaceType;
 
-		// culling information
-		vec3_t   bounds[ 2 ];
-		vec3_t   origin;
-		float    radius;
-	};
-
-	struct srfGridMesh_t : srfGeneric_t
-	{
-		// lod information, which may be different
-		// than the culling information to allow for
-		// groups of curves that LOD as a unit
-		vec3_t lodOrigin;
-		float  lodRadius;
-		int    lodFixed;
-		int    lodStitched;
-
-		// triangle definitions
-		int           width, height;
-		float         *widthLodError;
-		float         *heightLodError;
-
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		// Culling information
+		vec3_t bounds[2];
+		vec3_t origin;
+		float radius;
 
-		int           numVerts;
-		srfVert_t     *verts;
+		// Static render data
+		VBO_t* vbo;
+		IBO_t* ibo;
 
 		// BSP VBO offset
 		int firstIndex;
+		// ^ Valid in: SF_FACE, SF_GRID, SF_TRIANGLES, SF_VBO_MESH
 
-		// static render data
-		VBO_t *vbo; // points to bsp model VBO
-		IBO_t *ibo;
-	};
-
-	struct srfSurfaceFace_t : srfGeneric_t
-	{
-		cplane_t     plane;
-
-		// triangle definitions
-		int           numTriangles;
-		srfTriangle_t *triangles;
+		cplane_t plane; // Valid in: SF_FACE, SF_TRIANGLES
path: src/engine/renderer/tr_local.h
user: login: slipher
body: I see. It seems R_PlaneForSurface is only used for portals.
created_at: 2025-04-11T11:43:42Z
author_association: MEMBER

PR comments:
user: login: VReaperV
created_at: 2025-04-01T13:26:24Z
body: CI fail is unrelated, and is due to Ubuntu 20.04 being phased out on azure pipelines.
author_association: CONTRIBUTOR
user: login: VReaperV
created_at: 2025-04-03T11:52:31Z
body: > There are some tiny model lighting changes from `Move some common stuff from srf* to srfGeneric_t` and `Move duplicate code from ParseFace() and ParseTriSurf() into ParseTriangleSurface()`. I will try to check later if those diffs are anything.

For BSP models? I don't think other models can even be affected by this.
author_association: CONTRIBUTOR
user: login: slipher
created_at: 2025-04-04T09:02:50Z
body: No, it was a buildable model.
author_association: MEMBER
user: login: VReaperV
created_at: 2025-04-04T09:18:44Z
body: That's weird... Buildings should only ever use `srfVBOMD5Mesh_t` or `srfVBOMDVMesh_t`.
author_association: CONTRIBUTOR
user: login: slipher
created_at: 2025-04-04T21:37:32Z
body: I can reproduce a similar small diff by adding unused fields to `srfGeneric_t`, e.g.

```diff
diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h
index fe658c0b2..c3c4a9b3d 100644
--- a/src/engine/renderer/tr_local.h
+++ b/src/engine/renderer/tr_local.h
@@ -1738,9 +1738,15 @@ enum class ssaoMode {
        {
                surfaceType_t surfaceType;
 
+               int argle;
+
                // culling information
                vec3_t   bounds[ 2 ];
+
+               int bargle;
+
                vec3_t   origin;
+
                float    radius;
        };
```

Maybe the layout is assumed somewhere else.
author_association: MEMBER
user: login: slipher
created_at: 2025-04-04T21:52:06Z
body: Without/with the above patch, the lower part of the Overmind looks a bit more brightly lit on the before side.

![unvanquished-nano-overmind](https://github.com/user-attachments/assets/0fbfc98d-2a47-4d16-b155-93f9085d9ef9)

![unvanquished-nano-overmind](https://github.com/user-attachments/assets/0dabf47f-1122-450e-b5fc-f5812e0def7b)


author_association: MEMBER
user: login: slipher
created_at: 2025-04-05T02:49:57Z
body: The lighting diffs reproduce with different OS, compiler, and GPU.

I tried getting rid of `srfGeneric_t` and commenting out or rewriting all places that use it. After that I still get the same diff by adding extra fields in `srfVBOMesh_t` only.

The diffs go away if I disable `r_deluxeMapping`.
author_association: MEMBER
user: login: slipher
created_at: 2025-04-05T04:11:34Z
body: I found the cause of that stuff in https://github.com/DaemonEngine/Daemon/pull/1639.
author_association: MEMBER
user: login: VReaperV
created_at: 2025-04-05T07:49:02Z
body: Thanks, that is very helpful!
author_association: CONTRIBUTOR
user: login: VReaperV
created_at: 2025-04-08T09:10:31Z
body: > I think the last commit, `NUKE srfSurfaceFace_t`, should be dropped. It will just cause confusion for one of the surface types to not have its own struct.

But this is also the case with `srfTriangles_t`.
author_association: CONTRIBUTOR
user: login: slipher
created_at: 2025-04-08T09:12:53Z
body: > > I think the last commit, `NUKE srfSurfaceFace_t`, should be dropped. It will just cause confusion for one of the surface types to not have its own struct.
> 
> But this is also the case with `srfTriangles_t`.

OK then I complain about that one too :stuck_out_tongue: 
author_association: MEMBER
user: login: VReaperV
created_at: 2025-04-08T09:18:08Z
body: I guess I can revert those, though I don't see much point in keeping around structs that have nothing in them other than stuff they inherit.
author_association: CONTRIBUTOR
user: login: slipher
created_at: 2025-04-08T09:21:22Z
body: I guess it's fine as long as there are comments on the SF_FACE etc. enum.
author_association: MEMBER
user: login: VReaperV
created_at: 2025-04-08T10:24:54Z
body: I've added the comments into `srfGeneric_t`.
author_association: CONTRIBUTOR
user: login: slipher
created_at: 2025-04-11T11:43:46Z
body: LGTM
author_association: MEMBER