/* ------------------------------------------------------------------------------- Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant 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 GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------------- This code has been altered significantly from its original form, to support several games based on the Quake III Arena engine, in the form of "Q3Map2." ------------------------------------------------------------------------------- */ /* dependencies */ #include "q3map2.h" #include #include const Plane3f c_spatial_sort_plane( 0.786868, 0.316861, 0.529564, 0 ); const float c_spatial_EQUAL_EPSILON = EQUAL_EPSILON * 2; inline float spatial_distance( const Vector3& point ){ return plane3_distance_to_point( c_spatial_sort_plane, point ); } struct metaTriangle_t; struct metaVertex_t : public bspDrawVert_t { std::vector m_triangles; // references to triangles, introducing this vertex // bspDrawVert_equal() std::list *m_metaVertexGroup; // reference to own group of vertices with equal .xyz position metaVertex_t() = default; metaVertex_t( const bspDrawVert_t& vert ) : bspDrawVert_t( vert ){} }; using MetaVertexGroups = std::multimap >.front().xyz ) std::list>; // must be maintained non empty struct MinMax1D { float min, max; MinMax1D() : min( std::numeric_limits::max() ), max( std::numeric_limits::lowest() ){} void extend( float val ){ value_minimize( min, val ); value_maximize( max, val ); } }; /* ydnar: metasurfaces are constructed from lists of metatriangles so they can be merged in the best way */ struct metaTriangle_t { shaderInfo_t *si; const side_t *side; int entityNum, surfaceNum, planeNum, fogNum, sampleSize, castShadows, recvShadows; float shadeAngleDegrees; Plane3f plane; Vector3 lightmapAxis; std::array m_vertices; MinMax1D minmax; }; #define VERTS_EXCEEDED -1000 static int numMetaSurfaces, numPatchMetaSurfaces; static MetaVertexGroups metaVerts; static std::list metaTriangles; /* ClearMetaVertexes() called before staring a new entity to clear out the triangle list */ void ClearMetaTriangles( void ){ metaVerts.clear(); metaTriangles.clear(); } inline bool bspDrawVert_equal( const bspDrawVert_t& a, const bspDrawVert_t& b ){ return VectorCompare( a.xyz, b.xyz ) && VectorCompare( a.normal, b.normal ) && vector2_equal_epsilon( a.st, b.st, 1e-4f ) && a.color[ 0 ].alpha() == b.color[ 0 ].alpha(); } /* metaVertex_findOrInsert() finds a matching metavertex in the global list or inserts new one */ static metaVertex_t* metaVertex_findOrInsert( const bspDrawVert_t& src ){ /* try to find an existing drawvert */ const auto begin = metaVerts.lower_bound( spatial_distance( src.xyz ) - c_spatial_EQUAL_EPSILON ); const auto end = metaVerts.upper_bound( spatial_distance( src.xyz ) + c_spatial_EQUAL_EPSILON ); for( auto it = begin; it != end; ++it ){ for( auto& vertex : it->second ) if( bspDrawVert_equal( src, vertex ) ) return &vertex; } /* try to put to exisitng group */ for( auto it = begin; it != end; ++it ){ auto& list = it->second; if( VectorCompare( src.xyz, list.front().xyz ) ){ auto& newVertex = list.emplace_back( src ); newVertex.m_metaVertexGroup = &list; return &newVertex; } } /* add new vertex group */ auto& list = metaVerts.emplace_hint( begin, spatial_distance( src.xyz ), decltype( metaVerts )::mapped_type() )->second; auto& newVertex = list.emplace_back( src ); newVertex.m_metaVertexGroup = &list; /* return the vertex */ return &newVertex; } /* CompareMetaTriangles compare functor for std::sort() */ template struct CompareMetaTriangles { bool operator()( const metaTriangle_t& a, const metaTriangle_t& b ) const { /* shader first */ if ( a.si != b.si ) { return a.si < b.si; } /* then fog */ else if ( a.fogNum != b.fogNum ) { return a.fogNum < b.fogNum; } else if ( a.entityNum != b.entityNum ) { /* ydnar: added 2002-07-06 */ return a.entityNum < b.entityNum; } else if ( a.castShadows != b.castShadows ) { return a.castShadows < b.castShadows; } else if ( a.recvShadows != b.recvShadows ) { return a.recvShadows < b.recvShadows; } else if ( a.sampleSize != b.sampleSize ) { return a.sampleSize < b.sampleSize; } /* then position in world */ if constexpr ( sort_spatially ){ return a.minmax.min < b.minmax.min; } /* functionally equivalent */ return false; } /* equal in terms of mergeability */ static bool equal( const metaTriangle_t& a, const metaTriangle_t& b ) { return ( a.si == b.si ) && ( a.fogNum == b.fogNum ) && ( a.entityNum == b.entityNum ) && ( a.castShadows == b.castShadows ) && ( a.recvShadows == b.recvShadows ) && ( a.sampleSize == b.sampleSize ); } }; /* metaTriangle_insert() finds a matching metatriangle in the global list, otherwise adds it */ static void metaTriangle_insert( metaTriangle_t& src, std::array verts, int planeNum ){ /* detect degenerate triangles fixme: do something proper here */ if ( vector3_length( verts[0].xyz - verts[1].xyz ) < 0.125f || vector3_length( verts[1].xyz - verts[2].xyz ) < 0.125f || vector3_length( verts[2].xyz - verts[0].xyz ) < 0.125f ) { return; } /* find plane */ if ( planeNum >= 0 ) { /* because of precision issues with small triangles, try to use the specified plane */ src.planeNum = planeNum; src.plane = mapplanes[ planeNum ].plane; } else { /* calculate a plane from the triangle's points (and bail if a plane can't be constructed) */ src.planeNum = -1; if ( !PlaneFromPoints( src.plane, verts[0].xyz, verts[1].xyz, verts[2].xyz ) ) { return; } } /* ydnar 2002-10-03: repair any bogus normals (busted ase import kludge) */ for( auto& ve : verts ) if ( vector3_length( ve.normal ) == 0.0f ) ve.normal = src.plane.normal(); /* ydnar 2002-10-04: set lightmap axis if not already set */ if ( !( src.si->compileFlags & C_VERTEXLIT ) && src.lightmapAxis == g_vector3_identity ) { /* the shader can specify an explicit lightmap axis */ if ( src.si->lightmapAxis != g_vector3_identity ) { src.lightmapAxis = src.si->lightmapAxis; } /* new axis-finding code */ else{ src.lightmapAxis = CalcLightmapAxis( src.plane.normal() ); } } /* fill out the src triangle */ src.m_vertices[0] = metaVertex_findOrInsert( verts[0] ); src.m_vertices[1] = metaVertex_findOrInsert( verts[1] ); src.m_vertices[2] = metaVertex_findOrInsert( verts[2] ); /* try to find an existing triangle */ if( !src.m_vertices[0]->m_triangles.empty() // all vertices aren't brand new and have triangles assinged already && !src.m_vertices[1]->m_triangles.empty() && !src.m_vertices[2]->m_triangles.empty() ) { /* find common triangle */ for( const auto t1 : src.m_vertices[0]->m_triangles ){ for( const auto t2 : src.m_vertices[1]->m_triangles ){ if( t1 == t2 ){ for( const auto t3 : src.m_vertices[2]->m_triangles ){ if( t1 == t3 ){ if( CompareMetaTriangles::equal( src, *t1 ) ){ // equal to src Sys_Warning( "Duplicate or Flipped triangle: (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f)\n", verts[0].xyz.x(), verts[0].xyz.y(), verts[0].xyz.z(), verts[1].xyz.x(), verts[1].xyz.y(), verts[1].xyz.z(), verts[2].xyz.x(), verts[2].xyz.y(), verts[2].xyz.z() ); return; } } } } } } } /* add the triangle */ auto& newTriangle = metaTriangles.emplace_back( src ); /* reference it */ for( auto ve : newTriangle.m_vertices ) ve->m_triangles.push_back( &newTriangle ); } /* SurfaceToMetaTriangles() converts a classified surface to metatriangles */ static void SurfaceToMetaTriangles( mapDrawSurface_t *ds ){ /* only handle certain types of surfaces */ if ( ds->type != ESurfaceType::Face && ds->type != ESurfaceType::Meta && ds->type != ESurfaceType::ForcedMeta && ds->type != ESurfaceType::Decal ) { return; } /* only handle valid surfaces */ if ( ds->type != ESurfaceType::Bad && ds->numVerts >= 3 && ds->numIndexes >= 3 ) { /* walk the indexes and create triangles */ for ( int i = 0; i < ds->numIndexes; i += 3 ) { /* sanity check the indexes */ if ( ds->indexes[ i ] == ds->indexes[ i + 1 ] || ds->indexes[ i ] == ds->indexes[ i + 2 ] || ds->indexes[ i + 1 ] == ds->indexes[ i + 2 ] ) { //% Sys_Printf( "%d! ", ds->numVerts ); continue; } /* build a metatriangle */ metaTriangle_t src; src.si = ds->shaderInfo; src.side = ( ds->sideRef != NULL ? ds->sideRef->side : NULL ); src.entityNum = ds->entityNum; src.surfaceNum = ds->surfaceNum; src.planeNum = ds->planeNum; src.castShadows = ds->castShadows; src.recvShadows = ds->recvShadows; src.fogNum = ds->fogNum; src.sampleSize = ds->sampleSize; src.shadeAngleDegrees = ds->shadeAngleDegrees; src.lightmapAxis = ds->lightmapAxis; metaTriangle_insert( src, { ds->verts[ ds->indexes[ i ] ], ds->verts[ ds->indexes[ i + 1 ] ], ds->verts[ ds->indexes[ i + 2 ] ] }, ds->planeNum ); } /* add to count */ numMetaSurfaces++; } /* clear the surface (free verts and indexes, sets it to ESurfaceType::Bad) */ ClearSurface( ds ); } /* TriangulatePatchSurface() creates triangles from a patch */ void TriangulatePatchSurface( entity_t *e, mapDrawSurface_t *ds ){ int x, y, pw[ 5 ], r; mapDrawSurface_t *dsNew; mesh_t src, *subdivided, *mesh; /* vortex: _patchMeta, _patchQuality, _patchSubdivide support */ const bool forcePatchMeta = e->boolForKey( "_patchMeta", "patchMeta" ); /* try to early out */ if ( ds->numVerts == 0 || ds->type != ESurfaceType::Patch || ( !patchMeta && !forcePatchMeta ) ) { return; } /* make a mesh from the drawsurf */ src.width = ds->patchWidth; src.height = ds->patchHeight; src.verts = ds->verts; //% subdivided = SubdivideMesh( src, 8, 999 ); int iterations; int patchSubdivision; if ( e->read_keyvalue( patchSubdivision, "_patchSubdivide", "patchSubdivide" ) ) { iterations = IterationsForCurve( ds->longestCurve, patchSubdivision ); } else{ const int patchQuality = e->intForKey( "_patchQuality", "patchQuality" ); iterations = IterationsForCurve( ds->longestCurve, patchSubdivisions / ( patchQuality == 0? 1 : patchQuality ) ); } subdivided = SubdivideMesh2( src, iterations ); //% ds->maxIterations /* fit it to the curve and remove colinear verts on rows/columns */ PutMeshOnCurve( *subdivided ); mesh = RemoveLinearMeshColumnsRows( subdivided ); FreeMesh( subdivided ); //% MakeMeshNormals( mesh ); /* make a copy of the drawsurface */ dsNew = AllocDrawSurface( ESurfaceType::Meta ); memcpy( dsNew, ds, sizeof( *ds ) ); /* if the patch is nonsolid, then discard it */ if ( !( ds->shaderInfo->compileFlags & C_SOLID ) && !( ds->shaderInfo->contentFlags & GetRequiredSurfaceParm( "playerclip"_Tstring ).contentFlags ) ) { ClearSurface( ds ); } /* set new pointer */ ds = dsNew; /* basic transmogrification */ ds->type = ESurfaceType::Meta; ds->numIndexes = 0; ds->indexes = safe_malloc( mesh->width * mesh->height * 6 * sizeof( int ) ); /* copy the verts in */ ds->numVerts = ( mesh->width * mesh->height ); ds->verts = mesh->verts; /* iterate through the mesh quads */ for ( y = 0; y < ( mesh->height - 1 ); y++ ) { for ( x = 0; x < ( mesh->width - 1 ); x++ ) { /* set indexes */ pw[ 0 ] = x + ( y * mesh->width ); pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); pw[ 3 ] = x + 1 + ( y * mesh->width ); pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ /* set radix */ r = ( x + y ) & 1; /* make first triangle */ ds->indexes[ ds->numIndexes++ ] = pw[ r + 0 ]; ds->indexes[ ds->numIndexes++ ] = pw[ r + 1 ]; ds->indexes[ ds->numIndexes++ ] = pw[ r + 2 ]; /* make second triangle */ ds->indexes[ ds->numIndexes++ ] = pw[ r + 0 ]; ds->indexes[ ds->numIndexes++ ] = pw[ r + 2 ]; ds->indexes[ ds->numIndexes++ ] = pw[ r + 3 ]; } } /* free the mesh, but not the verts */ free( mesh ); /* add to count */ numPatchMetaSurfaces++; /* classify it */ ClassifySurfaces( 1, ds ); } #define TINY_AREA 1.0f #define MAXAREA_MAXTRIES 8 int MaxAreaIndexes( bspDrawVert_t *vert, int cnt, int *indexes ){ int r, s, t, bestR = 0, bestS = 1, bestT = 2; int i, j; double A, bestA = -1, V, bestV = -1; bspDrawVert_t *buf; double shiftWidth; if ( cnt < 3 ) { return 0; } /* calculate total area */ A = 0; for ( i = 1; i + 1 < cnt; ++i ) { A += vector3_length( vector3_cross( vert[i].xyz - vert[0].xyz, vert[i + 1].xyz - vert[0].xyz ) ); } V = 0; for ( i = 0; i < cnt; ++i ) { V += vector3_length( vert[( i + 1 ) % cnt].xyz - vert[i].xyz ); } /* calculate shift width from the area sensibly, assuming the polygon * fits about 25% of the screen in both dimensions * we assume 1280x1024 * 1 pixel is then about sqrt(A) / (0.25 * screenwidth) * 8 pixels are then about sqrt(A) / (0.25 * 1280) * 8 * 8 pixels are then about sqrt(A) * 0.025 * */ shiftWidth = sqrt( A ) * 0.0125; /* 3->1 6->2 12->3 ... */ if ( A - ceil( log( cnt / 1.5 ) / log( 2 ) ) * V * shiftWidth * 2 < 0 ) { /* Sys_FPrintf( SYS_WRN, "Small triangle detected (area %f, circumference %f), adjusting shiftWidth from %f to ", A, V, shiftWidth ); */ shiftWidth = A / ( ceil( log( cnt / 1.5 ) / log( 2 ) ) * V * 2 ); /* Sys_FPrintf( SYS_WRN, "%f\n", shiftWidth ); */ } /* find the triangle with highest area */ for ( r = 0; r + 2 < cnt; ++r ) for ( s = r + 1; s + 1 < cnt; ++s ) for ( t = s + 1; t < cnt; ++t ) { const Vector3 ab = vert[s].xyz - vert[r].xyz; const Vector3 ac = vert[t].xyz - vert[r].xyz; const Vector3 bc = vert[t].xyz - vert[s].xyz; A = vector3_length( vector3_cross( ab, ac ) ); V = A - ( vector3_length( ab ) - vector3_length( ac ) - vector3_length( bc ) ) * shiftWidth; /* value = A - circumference * shiftWidth, i.e. we back out by shiftWidth units from each side, to prevent too acute triangles */ /* this kind of simulates "number of shiftWidth*shiftWidth fragments in the triangle not touched by an edge" */ if ( bestA < 0 || V > bestV ) { bestA = A; bestV = V; bestR = r; bestS = s; bestT = t; } } /* if(bestV < 0) Sys_FPrintf( SYS_WRN, "value was REALLY bad\n" ); */ for ( int trai = 0; trai < MAXAREA_MAXTRIES; ++trai ) { if ( trai ) { bestR = rand() % cnt; bestS = rand() % cnt; bestT = rand() % cnt; if ( bestR == bestS || bestR == bestT || bestS == bestT ) { continue; } // bubblesort inline // abc acb bac bca cab cba if ( bestR > bestS ) { j = bestR; bestR = bestS; bestS = j; } // abc acb abc bca acb bca if ( bestS > bestT ) { j = bestS; bestS = bestT; bestT = j; } // abc abc abc bac abc bac if ( bestR > bestS ) { j = bestR; bestR = bestS; bestS = j; } // abc abc abc abc abc abc bestA = vector3_length( vector3_cross( vert[bestS].xyz - vert[bestR].xyz, vert[bestT].xyz - vert[bestR].xyz ) ); } if ( bestA < TINY_AREA ) { /* the biggest triangle is degenerate - then every other is too, and the other algorithms wouldn't generate anything useful either */ continue; } i = 0; indexes[i++] = bestR; indexes[i++] = bestS; indexes[i++] = bestT; /* uses 3 */ /* identify the other fragments */ /* full polygon without triangle (bestR,bestS,bestT) = three new polygons: * 1. bestR..bestS * 2. bestS..bestT * 3. bestT..bestR */ j = MaxAreaIndexes( vert + bestR, bestS - bestR + 1, indexes + i ); if ( j < 0 ) { continue; } j += i; for (; i < j; ++i ) indexes[i] += bestR; /* uses 3*(bestS-bestR+1)-6 */ j = MaxAreaIndexes( vert + bestS, bestT - bestS + 1, indexes + i ); if ( j < 0 ) { continue; } j += i; for (; i < j; ++i ) indexes[i] += bestS; /* uses 3*(bestT-bestS+1)-6 */ /* can'bestT recurse this one directly... therefore, buffering */ if ( cnt + bestR - bestT + 1 >= 3 ) { buf = safe_malloc( sizeof( *vert ) * ( cnt + bestR - bestT + 1 ) ); memcpy( buf, vert + bestT, sizeof( *vert ) * ( cnt - bestT ) ); memcpy( buf + ( cnt - bestT ), vert, sizeof( *vert ) * ( bestR + 1 ) ); j = MaxAreaIndexes( buf, cnt + bestR - bestT + 1, indexes + i ); if ( j < 0 ) { free( buf ); continue; } j += i; for (; i < j; ++i ) indexes[i] = ( indexes[i] + bestT ) % cnt; /* uses 3*(cnt+bestR-bestT+1)-6 */ free( buf ); } /* together 3 + 3*(cnt+3) - 18 = 3*cnt-6 q.e.d. */ return i; } return -1; } /* MaxAreaFaceSurface() - divVerent creates a triangle list using max area indexes */ void MaxAreaFaceSurface( mapDrawSurface_t *ds ){ int n; /* try to early out */ if ( !ds->numVerts || ( ds->type != ESurfaceType::Face && ds->type != ESurfaceType::Decal ) ) { return; } /* is this a simple triangle? */ if ( ds->numVerts == 3 ) { ds->numIndexes = 3; ds->indexes = safe_malloc( ds->numIndexes * sizeof( int ) ); ds->indexes[0] = 0; ds->indexes[1] = 1; ds->indexes[2] = 2; numMaxAreaSurfaces++; return; } /* do it! */ ds->numIndexes = 3 * ds->numVerts - 6; ds->indexes = safe_malloc( ds->numIndexes * sizeof( int ) ); n = MaxAreaIndexes( ds->verts, ds->numVerts, ds->indexes ); if ( n < 0 ) { /* whatever we do, it's degenerate */ free( ds->indexes ); ds->numIndexes = 0; StripFaceSurface( ds ); return; } ds->numIndexes = n; /* add to count */ numMaxAreaSurfaces++; /* classify it */ ClassifySurfaces( 1, ds ); } /* FanFaceSurface() - ydnar creates a tri-fan from a brush face winding loosely based on SurfaceAsTriFan() */ void FanFaceSurface( mapDrawSurface_t *ds ){ int i, k, a, b, c; Color4f color[ MAX_LIGHTMAPS ]; for ( auto& co : color ) co.set( 0 ); bspDrawVert_t *verts, *centroid, *dv; double iv; /* try to early out */ if ( !ds->numVerts || ( ds->type != ESurfaceType::Face && ds->type != ESurfaceType::Decal ) ) { return; } /* add a new vertex at the beginning of the surface */ verts = safe_malloc( ( ds->numVerts + 1 ) * sizeof( bspDrawVert_t ) ); memset( verts, 0, sizeof( bspDrawVert_t ) ); memcpy( &verts[ 1 ], ds->verts, ds->numVerts * sizeof( bspDrawVert_t ) ); free( ds->verts ); ds->verts = verts; /* add up the drawverts to create a centroid */ centroid = &verts[ 0 ]; for ( i = 1, dv = &verts[ 1 ]; i < ( ds->numVerts + 1 ); i++, dv++ ) { centroid->xyz += dv->xyz; centroid->normal += dv->normal; centroid->st += dv->st; for ( k = 0; k < MAX_LIGHTMAPS; k++ ){ color[ k ] += dv->color[ k ]; centroid->lightmap[ k ] += dv->lightmap[ k ]; } } /* average the centroid */ iv = 1.0f / ds->numVerts; centroid->xyz *= iv; if ( VectorNormalize( centroid->normal ) == 0 ) { centroid->normal = verts[ 1 ].normal; } centroid->st *= iv; for ( k = 0; k < MAX_LIGHTMAPS; k++ ){ centroid->lightmap[ k ] *= iv; centroid->color[ k ] = color_to_byte( color[ k ] / ds->numVerts ); } /* add to vert count */ ds->numVerts++; /* fill indexes in triangle fan order */ ds->numIndexes = 0; ds->indexes = safe_malloc( ds->numVerts * 3 * sizeof( int ) ); for ( i = 1; i < ds->numVerts; i++ ) { a = 0; b = i; c = ( i + 1 ) % ds->numVerts; c = c ? c : 1; ds->indexes[ ds->numIndexes++ ] = a; ds->indexes[ ds->numIndexes++ ] = b; ds->indexes[ ds->numIndexes++ ] = c; } /* add to count */ numFanSurfaces++; /* classify it */ ClassifySurfaces( 1, ds ); } /* StripFaceSurface() - ydnar attempts to create a valid tri-strip w/o degenerate triangles from a brush face winding based on SurfaceAsTriStrip() */ #define MAX_INDEXES 1024 void StripFaceSurface( mapDrawSurface_t *ds ){ int i, r, least, rotate, numIndexes, ni, a, b, c, indexes[ MAX_INDEXES ]; /* try to early out */ if ( !ds->numVerts || ( ds->type != ESurfaceType::Face && ds->type != ESurfaceType::Decal ) ) { return; } /* is this a simple triangle? */ if ( ds->numVerts == 3 ) { numIndexes = 3; indexes[0] = 0; indexes[1] = 1; indexes[2] = 2; } else { /* ydnar: find smallest coordinate */ least = 0; if ( ds->shaderInfo != NULL && !ds->shaderInfo->autosprite ) { for ( i = 0; i < ds->numVerts; i++ ) { /* get points */ const Vector3& v1 = ds->verts[ i ].xyz; const Vector3& v2 = ds->verts[ least ].xyz; /* compare */ if ( v1[ 0 ] < v2[ 0 ] || ( v1[ 0 ] == v2[ 0 ] && v1[ 1 ] < v2[ 1 ] ) || ( v1[ 0 ] == v2[ 0 ] && v1[ 1 ] == v2[ 1 ] && v1[ 2 ] < v2[ 2 ] ) ) { least = i; } } } /* determine the triangle strip order */ numIndexes = ( ds->numVerts - 2 ) * 3; if ( numIndexes > MAX_INDEXES ) { Error( "MAX_INDEXES exceeded for surface (%d > %d) (%d verts)", numIndexes, MAX_INDEXES, ds->numVerts ); } /* try all possible orderings of the points looking for a non-degenerate strip order */ ni = 0; for ( r = 0; r < ds->numVerts; r++ ) { /* set rotation */ rotate = ( r + least ) % ds->numVerts; /* walk the winding in both directions */ for ( ni = 0, i = 0; i < ds->numVerts - 2 - i; i++ ) { /* make indexes */ a = ( ds->numVerts - 1 - i + rotate ) % ds->numVerts; b = ( i + rotate ) % ds->numVerts; c = ( ds->numVerts - 2 - i + rotate ) % ds->numVerts; /* test this triangle */ if ( ds->numVerts > 4 && IsTriangleDegenerate( ds->verts, a, b, c ) ) { break; } indexes[ ni++ ] = a; indexes[ ni++ ] = b; indexes[ ni++ ] = c; /* handle end case */ if ( i + 1 != ds->numVerts - 1 - i ) { /* make indexes */ a = ( ds->numVerts - 2 - i + rotate ) % ds->numVerts; b = ( i + rotate ) % ds->numVerts; c = ( i + 1 + rotate ) % ds->numVerts; /* test triangle */ if ( ds->numVerts > 4 && IsTriangleDegenerate( ds->verts, a, b, c ) ) { break; } indexes[ ni++ ] = a; indexes[ ni++ ] = b; indexes[ ni++ ] = c; } } /* valid strip? */ if ( ni == numIndexes ) { break; } } /* if any triangle in the strip is degenerate, render from a centered fan point instead */ if ( ni < numIndexes ) { FanFaceSurface( ds ); return; } } /* copy strip triangle indexes */ ds->numIndexes = numIndexes; ds->indexes = safe_malloc( ds->numIndexes * sizeof( int ) ); memcpy( ds->indexes, indexes, ds->numIndexes * sizeof( int ) ); /* add to count */ numStripSurfaces++; /* classify it */ ClassifySurfaces( 1, ds ); } /* EmitMetaStatictics vortex: prints meta statistics in general output */ void EmitMetaStats(){ Sys_Printf( "--- EmitMetaStats ---\n" ); Sys_Printf( "%9d total meta surfaces\n", numMetaSurfaces ); Sys_Printf( "%9d stripped surfaces\n", numStripSurfaces ); Sys_Printf( "%9d fanned surfaces\n", numFanSurfaces ); Sys_Printf( "%9d maxarea'd surfaces\n", numMaxAreaSurfaces ); Sys_Printf( "%9d patch meta surfaces\n", numPatchMetaSurfaces ); Sys_Printf( "%9zu meta verts\n", metaVerts.size() ); Sys_Printf( "%9zu meta triangles\n", metaTriangles.size() ); } /* MakeEntityMetaTriangles() builds meta triangles from brush faces (tristrips and fans) */ void MakeEntityMetaTriangles( entity_t *e ){ /* note it */ Sys_FPrintf( SYS_VRB, "--- MakeEntityMetaTriangles ---\n" ); /* init pacifier */ int fOld = -1; const int start = I_FloatTime(); /* walk the list of surfaces in the entity */ for ( int i = e->firstDrawSurf; i < numMapDrawSurfs; ++i ) { /* print pacifier */ if ( const int f = 10 * ( i - e->firstDrawSurf ) / ( numMapDrawSurfs - e->firstDrawSurf ); f != fOld ) { fOld = f; Sys_FPrintf( SYS_VRB, "%d...", f ); } /* get surface */ mapDrawSurface_t *ds = &mapDrawSurfs[ i ]; if ( ds->numVerts <= 0 ) { continue; } /* ignore autosprite surfaces */ if ( ds->shaderInfo->autosprite ) { continue; } /* meta this surface? */ if ( !meta && !ds->shaderInfo->forceMeta ) { continue; } /* switch on type */ switch ( ds->type ) { case ESurfaceType::Face: case ESurfaceType::Decal: if ( maxAreaFaceSurface ) { MaxAreaFaceSurface( ds ); } else{ StripFaceSurface( ds ); } SurfaceToMetaTriangles( ds ); break; case ESurfaceType::Patch: TriangulatePatchSurface( e, ds ); break; case ESurfaceType::Triangles: break; case ESurfaceType::ForcedMeta: case ESurfaceType::Meta: SurfaceToMetaTriangles( ds ); break; default: break; } } /* print time */ if ( ( numMapDrawSurfs - e->firstDrawSurf ) ) { Sys_FPrintf( SYS_VRB, " (%d)\n", (int) ( I_FloatTime() - start ) ); } /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d total meta surfaces\n", numMetaSurfaces ); Sys_FPrintf( SYS_VRB, "%9d stripped surfaces\n", numStripSurfaces ); Sys_FPrintf( SYS_VRB, "%9d fanned surfaces\n", numFanSurfaces ); Sys_FPrintf( SYS_VRB, "%9d maxarea'd surfaces\n", numMaxAreaSurfaces ); Sys_FPrintf( SYS_VRB, "%9d patch meta surfaces\n", numPatchMetaSurfaces ); Sys_FPrintf( SYS_VRB, "%9zu meta verts\n", metaVerts.size() ); Sys_FPrintf( SYS_VRB, "%9zu meta triangles\n", metaTriangles.size() ); /* tidy things up */ TidyEntitySurfaces( e ); } /* CreateEdge() sets up an edge structure from a plane and 2 points that the edge ab falls lies in */ struct edge_t { Vector3 origin; Plane3f edge; float length, kingpinLength; int kingpin; Plane3f plane; }; void CreateEdge( const Plane3f& plane, const Vector3& a, const Vector3& b, edge_t *edge ){ /* copy edge origin */ edge->origin = a; /* create vector aligned with winding direction of edge */ edge->edge.normal() = b - a; edge->kingpin = vector3_max_abs_component_index( edge->edge.normal() ); edge->kingpinLength = edge->edge.normal()[ edge->kingpin ]; VectorNormalize( edge->edge.normal() ); edge->edge.dist() = vector3_dot( a, edge->edge.normal() ); edge->length = plane3_distance_to_point( edge->edge, b ); /* create perpendicular plane that edge lies in */ edge->plane.normal() = vector3_cross( plane.normal(), edge->edge.normal() ); edge->plane.dist() = vector3_dot( a, edge->plane.normal() ); } /* FixMetaTJunctions() fixes t-junctions on meta triangles */ #define TJ_PLANE_EPSILON ( 1.0f / 8.0f ) #define TJ_EDGE_EPSILON ( 1.0f / 8.0f ) #define TJ_POINT_EPSILON ( 1.0f / 8.0f ) void FixMetaTJunctions( void ){ #if 0 int i, j, k, f, fOld, start, vertIndex, triIndex, numTJuncs; metaTriangle_t *tri, *newTri; shaderInfo_t *si; bspDrawVert_t *a, *b, *c, junc; float amount; Plane3f plane; edge_t edges[ 3 ]; /* this code is crap; revisit later */ return; /* note it */ Sys_FPrintf( SYS_VRB, "--- FixMetaTJunctions ---\n" ); /* init pacifier */ fOld = -1; start = I_FloatTime(); /* walk triangle list */ numTJuncs = 0; for ( i = 0; i < numMetaTriangles; i++ ) { /* get triangle */ tri = &metaTriangles[ i ]; /* print pacifier */ f = 10 * i / numMetaTriangles; if ( f != fOld ) { fOld = f; Sys_FPrintf( SYS_VRB, "%d...", f ); } /* attempt to early out */ si = tri->si; if ( ( si->compileFlags & C_NODRAW ) || si->autosprite || si->notjunc ) { continue; } /* calculate planes */ plane = tri->plane; CreateEdge( plane, metaVerts[ tri->indexes[ 0 ] ].xyz, metaVerts[ tri->indexes[ 1 ] ].xyz, &edges[ 0 ] ); CreateEdge( plane, metaVerts[ tri->indexes[ 1 ] ].xyz, metaVerts[ tri->indexes[ 2 ] ].xyz, &edges[ 1 ] ); CreateEdge( plane, metaVerts[ tri->indexes[ 2 ] ].xyz, metaVerts[ tri->indexes[ 0 ] ].xyz, &edges[ 2 ] ); /* walk meta vert list */ for ( j = 0; j < numMetaVerts; j++ ) { /* get vert */ const Vector3 pt = metaVerts[ j ].xyz; /* determine if point lies in the triangle's plane */ if ( fabs( plane3_distance_to_point( plane, pt ) ) > TJ_PLANE_EPSILON ) { continue; } /* skip this point if it already exists in the triangle */ for ( k = 0; k < 3; k++ ) { if ( vector3_equal_epsilon( pt, metaVerts[ tri->indexes[ k ] ].xyz, TJ_POINT_EPSILON ) ) { break; } } if ( k < 3 ) { continue; } /* walk edges */ for ( k = 0; k < 3; k++ ) { /* ignore bogus edges */ if ( fabs( edges[ k ].kingpinLength ) < TJ_EDGE_EPSILON ) { continue; } /* determine if point lies on the edge */ if ( fabs( plane3_distance_to_point( edges[ k ].plane, pt ) ) > TJ_EDGE_EPSILON ) { continue; } /* determine how far along the edge the point lies */ amount = ( pt[ edges[ k ].kingpin ] - edges[ k ].origin[ edges[ k ].kingpin ] ) / edges[ k ].kingpinLength; if ( amount <= 0.0f || amount >= 1.0f ) { continue; } #if 0 dist = plane3_distance_to_point( edges[ k ].edge, pt ); if ( dist <= -0.0f || dist >= edges[ k ].length ) { continue; } amount = dist / edges[ k ].length; #endif /* the edge opposite the zero-weighted vertex was hit, so use that as an amount */ a = &metaVerts[ tri->indexes[ k % 3 ] ]; b = &metaVerts[ tri->indexes[ ( k + 1 ) % 3 ] ]; c = &metaVerts[ tri->indexes[ ( k + 2 ) % 3 ] ]; /* make new vert */ LerpDrawVertAmount( a, b, amount, &junc ); junc.xyz = pt; /* compare against existing verts */ if ( VectorCompare( junc.xyz, a->xyz ) || VectorCompare( junc.xyz, b->xyz ) || VectorCompare( junc.xyz, c->xyz ) ) { continue; } /* see if we can just re-use the existing vert */ if ( !memcmp( &metaVerts[ j ], &junc, sizeof( junc ) ) ) { vertIndex = j; } else { /* find new vertex (note: a and b are invalid pointers after this) */ firstSearchMetaVert = numMetaVerts; vertIndex = metaVertex_findOrInsert( &junc ); if ( vertIndex < 0 ) { continue; } } /* make new triangle */ triIndex = AddMetaTriangle(); if ( triIndex < 0 ) { continue; } /* get triangles */ tri = &metaTriangles[ i ]; newTri = &metaTriangles[ triIndex ]; /* copy the triangle */ memcpy( newTri, tri, sizeof( *tri ) ); /* fix verts */ tri->indexes[ ( k + 1 ) % 3 ] = vertIndex; newTri->indexes[ k ] = vertIndex; /* recalculate edges */ CreateEdge( plane, metaVerts[ tri->indexes[ 0 ] ].xyz, metaVerts[ tri->indexes[ 1 ] ].xyz, &edges[ 0 ] ); CreateEdge( plane, metaVerts[ tri->indexes[ 1 ] ].xyz, metaVerts[ tri->indexes[ 2 ] ].xyz, &edges[ 1 ] ); CreateEdge( plane, metaVerts[ tri->indexes[ 2 ] ].xyz, metaVerts[ tri->indexes[ 0 ] ].xyz, &edges[ 2 ] ); /* debug code */ metaVerts[ vertIndex ].color[ 0 ].rgb() = { 255, 204, 0 }; /* add to counter and end processing of this vert */ numTJuncs++; break; } } } /* print time */ Sys_FPrintf( SYS_VRB, " (%d)\n", (int) ( I_FloatTime() - start ) ); /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d T-junctions added\n", numTJuncs ); #endif } /* SmoothMetaTriangles() averages coincident vertex normals in the meta triangles */ #define THETA_EPSILON 0.000001 #define EQUAL_NORMAL_EPSILON 0.01f void SmoothMetaTriangles( void ){ const double start = I_FloatTime(); int numSmoothed = 0; /* note it */ Sys_FPrintf( SYS_VRB, "--- SmoothMetaTriangles ---\n" ); /* set default shade angle */ const float defaultShadeAngle = degrees_to_radians( npDegrees ); for( auto& [ d, list ] : metaVerts ){ if( list.size() > 1 ){ float maxShadeAngle = 0.f; std::vector verts; verts.reserve( list.size() ); for( auto& v : list ) verts.push_back( &v ); /* allocate shade angle table */ std::vector shadeAngles( list.size(), 179 ); /* allocate smoothed table */ std::vector smoothed( list.size(), false ); /* get per-vertex smoothing angle */ for( size_t i = 0; i < verts.size(); ++i ) { for( metaTriangle_t *tri : verts[i]->m_triangles ) { float shadeAngle = defaultShadeAngle; /* get shade angle from shader */ if ( tri->si->shadeAngleDegrees > 0.0f ) { shadeAngle = degrees_to_radians( tri->si->shadeAngleDegrees ); } /* get shade angle from entity */ else if ( tri->shadeAngleDegrees > 0.0f ) { shadeAngle = degrees_to_radians( tri->shadeAngleDegrees ); } /* flag verts */ value_minimize( shadeAngles[i], shadeAngle ); smoothed[i] = shadeAngles[i] <= 0; } value_maximize( maxShadeAngle, shadeAngles[i] ); } if( maxShadeAngle > 0 ){ /* go through the list of vertexes */ for ( size_t i = 0; i < verts.size(); ++i ) { /* already smoothed? */ if ( smoothed[ i ] ) { continue; } /* clear */ Vector3 average( 0 ); std::vector smoothedVerts; std::vector votes; /* test the rest, including self */ for ( size_t j = i; j < verts.size(); ++j ) { /* already smoothed? */ if ( smoothed[ j ] ) { continue; } /* use smallest shade angle */ const float shadeAngle = std::min( shadeAngles[ i ], shadeAngles[ j ] ); /* check shade angle */ const double dot = std::clamp( vector3_dot( verts[ i ]->normal, verts[ j ]->normal ), -1.0, 1.0 ); if ( acos( dot ) + THETA_EPSILON >= shadeAngle ) { continue; } /* add to the list */ smoothedVerts.push_back( verts[j] ); /* flag vertex */ smoothed[ j ] = true; /* see if this normal has already been voted */ if( votes.end() == std::find_if( votes.begin(), votes.end(), [normal = verts[j]->normal]( const Vector3& vote ){ return vector3_equal_epsilon( normal, vote, EQUAL_NORMAL_EPSILON ); } ) ) { /* add a new vote */ average += verts[ j ]->normal; votes.push_back( verts[ j ]->normal ); } } /* don't average for less than 2 verts */ if ( smoothedVerts.size() > 1 ) { /* average normal */ if ( VectorNormalize( average ) != 0 ) { /* smooth */ for ( auto v : smoothedVerts ) v->normal = average; numSmoothed++; } } } } } } /* print time */ Sys_FPrintf( SYS_VRB, " (%d)\n", (int) ( I_FloatTime() - start ) ); /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d smoothed vertexes\n", numSmoothed ); } using Sorted_indices = std::multimap ; // index of bspDrawVert_t in mapDrawSurface_t::verts array /* AddMetaVertToSurface() adds a drawvert to a surface unless an existing vert matching already exists returns the index of that vert (or < 0 on failure) */ int AddMetaVertToSurface( mapDrawSurface_t *ds, const bspDrawVert_t& dv1, const Sorted_indices& sorted_indices, int *coincident ){ /* go through the verts and find a suitable candidate */ const auto begin = sorted_indices.lower_bound( spatial_distance( dv1.xyz ) - c_spatial_EQUAL_EPSILON ); const auto end = sorted_indices.upper_bound( spatial_distance( dv1.xyz ) + c_spatial_EQUAL_EPSILON ); for( auto it = begin; it != end; ++it ){ /* get test vert */ const bspDrawVert_t& dv2 = ds->verts[ it->second ]; /* compare xyz and normal */ if ( !VectorCompare( dv1.xyz, dv2.xyz ) ) { continue; } if ( !VectorCompare( dv1.normal, dv2.normal ) ) { continue; } /* good enough at this point */ ( *coincident )++; /* compare texture coordinates and color */ if ( !vector2_equal_epsilon( dv1.st, dv2.st, 1e-4f ) ) { continue; } if ( dv1.color[ 0 ].alpha() != dv2.color[ 0 ].alpha() ) { continue; } /* found a winner */ numMergedVerts++; return it->second; } /* overflow check */ if ( ds->numVerts >= ( ( ds->shaderInfo->compileFlags & C_VERTEXLIT ) ? maxSurfaceVerts : maxLMSurfaceVerts ) ) { return VERTS_EXCEEDED; } /* made it this far, add the vert and return */ ds->verts[ ds->numVerts ] = dv1; return ds->numVerts++; } /* AddMetaTriangleToSurface() attempts to add a metatriangle to a surface returns the score of the triangle added */ #define AXIS_SCORE 100000 #define AXIS_MIN 100000 #define VERT_SCORE 10000 #define SURFACE_SCORE 1000 #define ST_SCORE 50 #define ST_SCORE2 ( 2 * ( ST_SCORE ) ) #define DEFAULT_ADEQUATE_SCORE ( (AXIS_MIN) + 1 * (VERT_SCORE) ) #define DEFAULT_GOOD_SCORE ( (AXIS_MIN) + 2 * (VERT_SCORE) + 4 * (ST_SCORE) ) #define PERFECT_SCORE ( (AXIS_MIN) + 3 * (VERT_SCORE) + (SURFACE_SCORE) + 4 * (ST_SCORE) ) #define ADEQUATE_SCORE ( metaAdequateScore >= 0 ? metaAdequateScore : DEFAULT_ADEQUATE_SCORE ) #define GOOD_SCORE ( metaGoodScore >= 0 ? metaGoodScore : DEFAULT_GOOD_SCORE ) static int AddMetaTriangleToSurface( mapDrawSurface_t *ds, const metaTriangle_t& tri, MinMax& texMinMax, Sorted_indices& sorted_indices, bool testAdd ){ int i, score, coincident, ai, bi, ci; /* test the triangle */ #if 0 if ( !( ds->shaderInfo->compileFlags & C_VERTEXLIT ) && //% !VectorCompare( ds->lightmapAxis, tri.lightmapAxis ) ) vector3_dot( ds->lightmapAxis, tri.plane.normal() ) < 0.25f ) { return 0; } #endif /* planar surfaces will only merge with triangles in the same plane */ if ( npDegrees == 0.0f && !ds->shaderInfo->nonplanar && ds->planeNum >= 0 ) { if ( tri.planeNum >= 0 && tri.planeNum != ds->planeNum ) { return 0; } if ( !VectorCompare( mapplanes[ ds->planeNum ].normal(), tri.plane.normal() ) || mapplanes[ ds->planeNum ].dist() != tri.plane.dist() ) { return 0; } } /* set initial score */ score = tri.surfaceNum == ds->surfaceNum ? SURFACE_SCORE : 0; /* score the the dot product of lightmap axis to plane */ if ( ( ds->shaderInfo->compileFlags & C_VERTEXLIT ) || VectorCompare( ds->lightmapAxis, tri.lightmapAxis ) ) { score += AXIS_SCORE; } else{ score += AXIS_SCORE * vector3_dot( ds->lightmapAxis, tri.plane.normal() ); } /* preserve old drawsurface if this fails */ mapDrawSurface_t old( *ds ); /* attempt to add the verts */ const int numVerts_original = ds->numVerts; coincident = 0; ai = AddMetaVertToSurface( ds, *tri.m_vertices[ 0 ], sorted_indices, &coincident ); bi = AddMetaVertToSurface( ds, *tri.m_vertices[ 1 ], sorted_indices, &coincident ); ci = AddMetaVertToSurface( ds, *tri.m_vertices[ 2 ], sorted_indices, &coincident ); /* check vertex underflow */ if ( ai < 0 || bi < 0 || ci < 0 ) { memcpy( ds, &old, sizeof( *ds ) ); return 0; } /* score coincident vertex count (2003-02-14: changed so this only matters on planar surfaces) */ score += ( coincident * VERT_SCORE ); /* add new vertex bounds to mins/maxs */ MinMax minmax( ds->minmax ); minmax.extend( tri.m_vertices[ 0 ]->xyz ); minmax.extend( tri.m_vertices[ 1 ]->xyz ); minmax.extend( tri.m_vertices[ 2 ]->xyz ); /* check lightmap bounds overflow (after at least 1 triangle has been added) */ if ( !( ds->shaderInfo->compileFlags & C_VERTEXLIT ) && ds->numIndexes > 0 && ds->lightmapAxis != g_vector3_identity && ( !VectorCompare( ds->minmax.mins, minmax.mins ) || !VectorCompare( ds->minmax.maxs, minmax.maxs ) ) ) { /* set maximum size before lightmap scaling (normally 2032 units) */ /* 2004-02-24: scale lightmap test size by 2 to catch larger brush faces */ /* 2004-04-11: reverting to actual lightmap size */ const float lmMax = ( ds->sampleSize * ( ds->shaderInfo->lmCustomWidth - 1 ) ); for ( i = 0; i < 3; i++ ) { if ( ( minmax.maxs[ i ] - minmax.mins[ i ] ) > lmMax ) { memcpy( ds, &old, sizeof( *ds ) ); return 0; } } } /* check texture range overflow */ MinMax newTexMinMax( texMinMax ); { newTexMinMax.extend( Vector3( tri.m_vertices[ 0 ]->st ) ); newTexMinMax.extend( Vector3( tri.m_vertices[ 1 ]->st ) ); newTexMinMax.extend( Vector3( tri.m_vertices[ 2 ]->st ) ); if( texMinMax.surrounds( newTexMinMax ) ){ score += 4 * ST_SCORE; } else{ const Vector2 wh( ds->shaderInfo->shaderWidth, ds->shaderInfo->shaderHeight ); BasicVector2 oldTexRange( ( texMinMax.maxs - texMinMax.mins ).vec2() * wh ); BasicVector2 newTexRange( ( newTexMinMax.maxs - newTexMinMax.mins ).vec2() * wh ); /* score texture range */ if ( newTexRange[ 0 ] <= oldTexRange[ 0 ] ) { score += ST_SCORE2; } else if ( oldTexRange[ 1 ] > oldTexRange[ 0 ] ) { score += ST_SCORE; } if ( newTexRange[ 1 ] <= oldTexRange[ 1 ] ) { score += ST_SCORE2; } else if ( oldTexRange[ 0 ] > oldTexRange[ 1 ] ) { score += ST_SCORE; } } } /* check index overflow */ if ( ds->numIndexes + 3 > maxSurfaceIndexes ) { memcpy( ds, &old, sizeof( *ds ) ); return 0; } else{ /* add the triangle indexes */ ds->indexes[ ds->numIndexes++ ] = ai; ds->indexes[ ds->numIndexes++ ] = bi; ds->indexes[ ds->numIndexes++ ] = ci; } /* sanity check the indexes */ if ( ds->numIndexes >= 3 && ( ds->indexes[ ds->numIndexes - 3 ] == ds->indexes[ ds->numIndexes - 2 ] || ds->indexes[ ds->numIndexes - 3 ] == ds->indexes[ ds->numIndexes - 1 ] || ds->indexes[ ds->numIndexes - 2 ] == ds->indexes[ ds->numIndexes - 1 ] ) ) { Sys_Printf( "DEG:%d! ", ds->numVerts ); } /* testing only? */ if ( testAdd ) { memcpy( ds, &old, sizeof( *ds ) ); } else { /* store new bounds */ ds->minmax = minmax; texMinMax = newTexMinMax; /* add a side reference */ ds->sideRef = AllocSideRef( tri.side, ds->sideRef ); for( const auto id : { ai, bi, ci } ){ if( id >= numVerts_original ) sorted_indices.emplace( spatial_distance( ds->verts[id].xyz ), id ); } } /* return to sender */ return score; } /* MetaTrianglesToSurface() creates map drawsurface(s) from the list of possibles */ static void MetaTrianglesToSurface( int *fOld, int *numAdded ){ /* allocate arrays */ bspDrawVert_t *verts = safe_malloc( sizeof( *verts ) * maxSurfaceVerts ); int *indexes = safe_malloc( sizeof( *indexes ) * maxSurfaceIndexes ); /* walk the list of triangles */ for ( auto& seed : metaTriangles ) { /* skip this triangle if it has already been merged */ if ( seed.si == NULL ) { continue; } /* ----------------------------------------------------------------- initial drawsurf construction ----------------------------------------------------------------- */ /* start a new drawsurface */ mapDrawSurface_t *ds = AllocDrawSurface( ESurfaceType::Meta ); ds->entityNum = seed.entityNum; ds->surfaceNum = seed.surfaceNum; ds->castShadows = seed.castShadows; ds->recvShadows = seed.recvShadows; ds->shaderInfo = seed.si; ds->planeNum = seed.planeNum; ds->fogNum = seed.fogNum; ds->sampleSize = seed.sampleSize; ds->shadeAngleDegrees = seed.shadeAngleDegrees; ds->verts = verts; ds->indexes = indexes; ds->lightmapAxis = seed.lightmapAxis; ds->sideRef = AllocSideRef( seed.side, NULL ); ds->minmax.clear(); MinMax texMinMax; Sorted_indices sorted_indices; /* strategy: DEFAULT_ADEQUATE_SCORE implies at least single vertex coincident with ones of surface thus we crosslink vertices<->triangles on construction and only test cloud of linked triangles */ std::vector testCloud; const auto expand_cloud = [&testCloud]( metaTriangle_t& triangle ){ for( metaVertex_t *trivert : triangle.m_vertices ){ for( metaVertex_t& groupvert : *trivert->m_metaVertexGroup ){ for( metaTriangle_t *tri : groupvert.m_triangles ){ if( tri->si != nullptr && CompareMetaTriangles::equal( *tri, triangle ) // note: triangle.si must be still there for comparison && std::find( testCloud.cbegin(), testCloud.cend(), tri ) == testCloud.cend() ){ testCloud.push_back( tri ); } } } } /* mark triangle as used */ triangle.si = NULL; /* remove from cloud */ if( auto it = std::find( testCloud.cbegin(), testCloud.cend(), &triangle ); it != testCloud.cend() ){ testCloud.erase( it ); } #if 0 /* sort spatially */ std::sort( testCloud.begin(), testCloud.end(), []( const metaTriangle_t *a, const metaTriangle_t *b ){ return a->minmax.min < b->minmax.min; } ); #endif }; /* add the first triangle */ if ( AddMetaTriangleToSurface( ds, seed, texMinMax, sorted_indices, false ) ) { ( *numAdded )++; } expand_cloud( seed ); /* ----------------------------------------------------------------- add triangles ----------------------------------------------------------------- */ /* progressively walk the list until no more triangles can be added */ for( bool added = true; added; ) { /* print pacifier */ if ( const int f = 10 * *numAdded / metaTriangles.size(); f > *fOld ) { *fOld = f; Sys_FPrintf( SYS_VRB, "%d...", f ); } /* reset best score */ metaTriangle_t *best = nullptr; int bestScore = 0; added = false; /* walk the list of possible candidates for merging */ for ( metaTriangle_t *test : testCloud ) { /* skip this triangle if it has already been merged */ if ( test->si == NULL ) { continue; } /* score this triangle */ const int score = AddMetaTriangleToSurface( ds, *test, texMinMax, sorted_indices, true ); if ( score > bestScore ) { best = test; bestScore = score; /* if we have a score over a certain threshold, just use it */ if ( bestScore >= GOOD_SCORE ) { /* add it and restart loop; this way we make sure to add all possibly newly available GOOD_SCORE candidates; produces fewer and more quality surfaces */ break; } } } /* add best candidate */ if ( best != nullptr && bestScore > ADEQUATE_SCORE ) { if ( AddMetaTriangleToSurface( ds, *best, texMinMax, sorted_indices, false ) ) { ( *numAdded )++; expand_cloud( *best ); } /* reset */ added = true; } } /* copy the verts and indexes to the new surface */ ds->verts = safe_malloc( ds->numVerts * sizeof( bspDrawVert_t ) ); memcpy( ds->verts, verts, ds->numVerts * sizeof( bspDrawVert_t ) ); ds->indexes = safe_malloc( ds->numIndexes * sizeof( int ) ); memcpy( ds->indexes, indexes, ds->numIndexes * sizeof( int ) ); /* classify the surface */ ClassifySurfaces( 1, ds ); //% Sys_Warning( "numV: %d numIdx: %d\n", ds->numVerts, ds->numIndexes ); /* add to count */ numMergedSurfaces++; } /* free arrays */ free( verts ); free( indexes ); } /* MergeMetaTriangles() merges meta triangles into drawsurfaces */ void MergeMetaTriangles( void ){ /* only do this if there are meta triangles */ if ( metaTriangles.empty() ) { return; } /* note it */ Sys_FPrintf( SYS_VRB, "--- MergeMetaTriangles ---\n" ); /* init pacifier */ int fOld = -1; int start = I_FloatTime(); int numAdded = 0; #if 1 for( metaTriangle_t& tri : metaTriangles ){ for( const metaVertex_t *vert : tri.m_vertices ){ tri.minmax.extend( spatial_distance( vert->xyz ) ); } } metaTriangles.sort( CompareMetaTriangles() ); #endif MetaTrianglesToSurface( &fOld, &numAdded ); /* clear meta triangle list */ ClearMetaTriangles(); /* print time */ Sys_FPrintf( SYS_VRB, " (%d)\n", (int) ( I_FloatTime() - start ) ); /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d surfaces merged\n", numMergedSurfaces ); Sys_FPrintf( SYS_VRB, "%9d vertexes merged\n", numMergedVerts ); }