/* ------------------------------------------------------------------------------- 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 "tjunction.h" #include "qspatial.h" #include "timer.h" #include 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 /* 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; // can be nullptr int entityNum, surfaceNum, planeNum, fogNum, sampleSize, castShadows, recvShadows; float shadeAngleDegrees; Vector3 ambientColor; 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(){ 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; } else if ( a.ambientColor[0] != b.ambientColor[0] ) { // may be different inside one entityNum for attached misc_models return a.ambientColor[0] < b.ambientColor[0]; } else if ( a.ambientColor[1] != b.ambientColor[1] ) { return a.ambientColor[1] < b.ambientColor[1]; } else if ( a.ambientColor[2] != b.ambientColor[2] ) { return a.ambientColor[2] < b.ambientColor[2]; } /* 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 ) && ( a.ambientColor == b.ambientColor ); } }; /* 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 ) 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.verts.size() >= 3 && ds.indexes.size() >= 3 ) { /* walk the indexes and create triangles */ for ( auto i = ds.indexes.cbegin(); i != ds.indexes.cend(); i += 3 ) { /* sanity check the indexes */ if ( *( i + 0 ) == *( i + 1 ) || *( i + 0 ) == *( i + 2 ) || *( i + 1 ) == *( i + 2 ) ) { //% Sys_Printf( "%d! ", ds.numVerts ); continue; } /* build a metatriangle */ metaTriangle_t src; src.si = ds.shaderInfo; src.side = ( ds.sideRef != nullptr ? &ds.sideRef->side : nullptr ); 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.ambientColor = ds.ambientColor; src.lightmapAxis = ds.lightmapAxis; metaTriangle_insert( src, { ds.verts[ *( i + 0 ) ], ds.verts[ *( i + 1 ) ], ds.verts[ *( 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 */ static void TriangulatePatchSurface( const entity_t& e, mapDrawSurface_t& ds ){ /* vortex: _patchMeta, _patchQuality, _patchSubdivide support */ const bool forcePatchMeta = e.boolForKey( "_patchMeta", "patchMeta" ); /* try to early out */ if ( ds.verts.empty() || ds.type != ESurfaceType::Patch || ( !patchMeta && !forcePatchMeta ) ) { return; } int iterations; if ( int patchSubdivision; 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 ) ); } /* make a mesh from the drawsurf */ const mesh_t mesh = TessellatedMesh( mesh_view_t( ds.patchWidth, ds.patchHeight, ds.verts.data() ), iterations ); //% ds.maxIterations /* make a copy of the drawsurface */ mapDrawSurface_t& dsNew = AllocDrawSurface( ESurfaceType::Meta ); dsNew = ds; /* if the patch is nonsolid, then discard it */ if ( !( ds.shaderInfo->compileFlags & C_SOLID ) && !( ds.shaderInfo->contentFlags & GetRequiredSurfaceParm<"playerclip">().contentFlags ) ) { ClearSurface( ds ); } /* basic transmogrification */ dsNew.type = ESurfaceType::Meta; dsNew.indexes.clear(); dsNew.indexes.reserve( ( mesh.width - 1 ) * ( mesh.height - 1 ) * 6 ); /* copy the verts in */ dsNew.verts.assign( mesh.begin(), mesh.end() ); /* iterate through the mesh quads */ for( MeshQuadIterator it( mesh ); it; ++it ){ /* make first triangle */ dsNew.indexes.push_back( it.idx()[0] ); dsNew.indexes.push_back( it.idx()[1] ); dsNew.indexes.push_back( it.idx()[2] ); /* make second triangle */ dsNew.indexes.push_back( it.idx()[0] ); dsNew.indexes.push_back( it.idx()[2] ); dsNew.indexes.push_back( it.idx()[3] ); } /* add to count */ numPatchMetaSurfaces++; /* classify it */ ClassifySurface( dsNew ); } #define TINY_AREA 1.0 #define MAXAREA_MAXTRIES 8 static int MaxAreaIndexes( const 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 += triangle_area2x( vert[0].xyz, vert[i].xyz, vert[i + 1].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 = triangle_area2x( vert[bestR].xyz, vert[bestS].xyz, vert[bestT].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 ){ /* try to early out */ if ( ds.verts.empty() || ( ds.type != ESurfaceType::Face && ds.type != ESurfaceType::Decal ) ) { return; } /* is this a simple triangle? */ if ( ds.verts.size() == 3 ) { ds.indexes.assign( { 0, 1, 2 } ); numMaxAreaSurfaces++; return; } /* do it! */ ds.indexes.resize( 3 * ds.verts.size() - 6 ); const int n = MaxAreaIndexes( ds.verts.data(), ds.verts.size(), ds.indexes.data() ); if ( n < 0 ) { /* whatever we do, it's degenerate */ ds.indexes.clear(); StripFaceSurface( ds ); return; } ds.indexes.resize( n ); /* add to count */ numMaxAreaSurfaces++; /* classify it */ ClassifySurface( ds ); } /* FanFaceSurface() - ydnar creates a tri-fan from a brush face winding loosely based on SurfaceAsTriFan() */ static void FanFaceSurface( mapDrawSurface_t& ds ){ /* try to early out */ if ( ds.verts.empty() || ( ds.type != ESurfaceType::Face && ds.type != ESurfaceType::Decal ) ) { return; } Color4f color[ MAX_LIGHTMAPS ]; for ( auto& co : color ) co.set( 0 ); /* add up the drawverts to create a centroid */ DoubleVector3 cnt( 0 ); bspDrawVert_t centroid = c_bspDrawVert_t0; for ( const bspDrawVert_t& dv : ds.verts ) { cnt += dv.xyz; centroid.normal += dv.normal; centroid.st += dv.st; for ( int k = 0; k < MAX_LIGHTMAPS; ++k ){ color[ k ] += dv.color[ k ]; centroid.lightmap[ k ] += dv.lightmap[ k ]; } } /* average the centroid */ const double iv = 1.0 / ds.verts.size(); centroid.xyz = cnt * iv; if ( VectorNormalize( centroid.normal ) == 0 ) { centroid.normal = ds.verts[ 0 ].normal; } centroid.st *= iv; for ( int k = 0; k < MAX_LIGHTMAPS; ++k ){ centroid.lightmap[ k ] *= iv; centroid.color[ k ] = color_to_byte( color[ k ] / ds.verts.size() ); } /* add a new vertex at the beginning of the surface */ ds.verts.insert( ds.verts.cbegin(), centroid ); /* fill indexes in triangle fan order */ ds.indexes.clear(); ds.indexes.reserve( ( ds.verts.size() - 1 ) * 3 ); for ( size_t prev = ds.verts.size() - 1, next = 1; next != ds.verts.size(); prev = next++ ) { ds.indexes.push_back( 0 ); ds.indexes.push_back( prev ); ds.indexes.push_back( next ); } /* add to count */ numFanSurfaces++; /* classify it */ ClassifySurface( 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 numIndexes, indexes[ MAX_INDEXES ]; /* try to early out */ if ( ds.verts.empty() || ( ds.type != ESurfaceType::Face && ds.type != ESurfaceType::Decal ) ) { return; } /* is this a simple triangle? */ if ( ds.verts.size() == 3 ) { numIndexes = 3; indexes[0] = 0; indexes[1] = 1; indexes[2] = 2; } else { /* ydnar: find smallest coordinate */ int least = 0; if ( ds.shaderInfo != nullptr && !ds.shaderInfo->autosprite ) { for ( size_t i = 0; i < ds.verts.size(); ++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.verts.size() - 2 ) * 3; if ( numIndexes > MAX_INDEXES ) { Error( "MAX_INDEXES exceeded for surface (%d > %d) (%zu verts)", numIndexes, MAX_INDEXES, ds.verts.size() ); } class TriEval { const bspDrawVert_t *m_verts; double m_area = std::numeric_limits::max(); // 2x area double m_angle = std::numeric_limits::max(); // squared sin of the angle public: TriEval( const bspDrawVert_t *verts ) : m_verts( verts ){ } void push( int a, int b, int c ){ value_minimize( m_angle, triangle_min_angle_squared_sin( m_verts[a].xyz, m_verts[b].xyz, m_verts[c].xyz ) ); value_minimize( m_area, triangle_area2x( m_verts[a].xyz, m_verts[b].xyz, m_verts[c].xyz ) ); } bool decent() const { return m_angle > 1e-5 && m_area > TINY_AREA; } void reset(){ *this = TriEval( m_verts ); } } triEval( ds.verts.data() ); const auto idx = [n = ds.numVerts()]( int i ){ return i < 0? i + n : i < n? i : i - n; }; /* try all possible orderings of the points looking for a non-degenerate strip order */ for ( int r = 0; r < ds.numVerts(); ++r ) { triEval.reset(); /* walk the winding in both directions */ for( int i = idx( r + least ), j = idx( i - 1 ), k, swap = 0, out = 0; ( swap ^= bspDrawVert_is_tjunc( ds.verts[idx( swap? i + 1 : j - 1 )] ) >= bspDrawVert_is_tjunc( ds.verts[idx( swap? j - 1 : i + 1 )] ) ) ? ( k = j, j = idx( --j ) ) : ( k = i, i = idx( ++i ) ), i != j; ) { /* test this triangle */ if ( triEval.push( i, j, k ), !triEval.decent() ) { break; } indexes[ out++ ] = i; indexes[ out++ ] = j; indexes[ out++ ] = k; } if( triEval.decent() ) goto okej; } /* if any triangle in the strip is degenerate, render from a centered fan point instead */ return FanFaceSurface( ds ); } okej: /* copy strip triangle indexes */ ds.indexes.assign( indexes, indexes + numIndexes ); /* add to count */ numStripSurfaces++; /* classify it */ ClassifySurface( 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( const entity_t& e ){ /* note it */ Sys_FPrintf( SYS_VRB, "--- MakeEntityMetaTriangles ---\n" ); /* init pacifier */ Pacifier pacifier( numMapDrawSurfs - e.firstDrawSurf ); Timer timer; /* walk the list of surfaces in the entity */ /* numMapDrawSurfs increases if patchMeta */ for ( int i = e.firstDrawSurf; i < numMapDrawSurfs; ++i ) { /* print pacifier */ ++pacifier; /* get surface */ mapDrawSurface_t& ds = mapDrawSurfs[ i ]; if ( ds.verts.empty() ) { 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( timer.elapsed_sec() ) ); } /* 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; }; static 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(){ #if 0 int i, j, k, 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 */ Pacifier pacifier( numMetaTriangles ); Timer timer; /* walk triangle list */ numTJuncs = 0; for ( i = 0; i < numMetaTriangles; ++i ) { /* get triangle */ tri = &metaTriangles[ i ]; /* print pacifier */ ++pacifier; /* 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 ( std::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 ( std::fabs( edges[ k ].kingpinLength ) < TJ_EDGE_EPSILON ) { continue; } /* determine if point lies on the edge */ if ( std::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 || amount >= 1 ) { 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( timer.elapsed_sec() ) ); /* 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(){ Timer timer; 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 || ( list.size() == 1 && list.front().m_triangles.size() > 1 ) ){ float maxShadeAngle = 0; struct VT{ metaVertex_t *vertex; metaTriangle_t *triangle; float angle{}; bool skipped{}; bool smoothed{}; Vector3 newnormal{ 0 }; }; std::vector verts; for( auto& v : list ) for( auto *t : v.m_triangles ) verts.push_back( VT{ &v, t } ); /* get per-vertex smoothing angle */ for( auto& v : verts ) { float shadeAngle = defaultShadeAngle; /* get shade angle from shader */ if ( v.triangle->si->shadeAngleDegrees > 0 ) { shadeAngle = degrees_to_radians( v.triangle->si->shadeAngleDegrees ); } /* get shade angle from entity */ else if ( v.triangle->shadeAngleDegrees > 0 ) { shadeAngle = degrees_to_radians( v.triangle->shadeAngleDegrees ); } /* flag verts */ v.angle = shadeAngle; v.skipped = shadeAngle <= 0; value_maximize( maxShadeAngle, shadeAngle ); } if( maxShadeAngle > 0 ){ /* go through the list of vertexes */ for ( auto v = verts.begin(); v != verts.end(); ++v ) { /* already smoothed? */ if ( v->skipped || v->smoothed ) { continue; } /* clear */ Vector3 average( 0 ); std::vector smoothedVerts; std::vector votes; /* test the rest, including self */ for ( auto v2 = v; v2 != verts.end(); ++v2 ) { /* already smoothed? */ if ( v2->skipped || v2->smoothed ) { continue; } /* use smallest shade angle */ const float shadeAngle = std::min( v->angle, v2->angle ); /* check shade angle */ const double dot = std::clamp( vector3_dot( v->vertex->normal, v2->vertex->normal ), -1.0, 1.0 ); if ( acos( dot ) + THETA_EPSILON >= shadeAngle ) { continue; } /* add to the list */ smoothedVerts.push_back( v2.operator->() ); /* see if this normal has already been voted */ if( std::ranges::none_of( votes, [normal = v2->vertex->normal]( const Vector3& vote ){ return vector3_equal_epsilon( normal, vote, EQUAL_NORMAL_EPSILON ); } ) ) { /* add a new vote */ average += v2->vertex->normal; votes.push_back( v2->vertex->normal ); } } /* flag vertices */ /* don't average for less than 2 verts */ if ( smoothedVerts.size() > 1 && VectorNormalize( average ) != 0 ) { for ( auto *v : smoothedVerts ){ v->smoothed = true; v->newnormal = average; } numSmoothed++; } else{ for ( auto *v : smoothedVerts ){ v->skipped = true; } } } /* reconstruct meta data from smoothed verts */ if( std::ranges::any_of( verts, &VT::smoothed ) ){ decltype( list ) newlist; for( auto& v : verts ){ bspDrawVert_t newv = *v.vertex; if( v.smoothed ) newv.normal = v.newnormal; auto it = std::ranges::find_if( newlist, [&newv]( const metaVertex_t& v ){ return bspDrawVert_equal( newv, v ); } ); if( it == newlist.end() ){ /* insert vertex */ newlist.push_back( newv ); it = --newlist.end(); it->m_metaVertexGroup = &list; } /* link vertex <> triangle */ it->m_triangles.push_back( v.triangle ); *std::ranges::find( v.triangle->m_vertices, v.vertex ) = it.operator->(); } list.swap( newlist ); } } } } /* print time */ Sys_FPrintf( SYS_VRB, " (%d)\n", int( timer.elapsed_sec() ) ); /* 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) */ static int AddMetaVertToSurface( const mapDrawSurface_t& ds, const bspDrawVert_t& dv1, DrawVerts& verts, 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 = 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 ( int( verts.size() ) >= ( ( ds.shaderInfo->compileFlags & C_VERTEXLIT ) ? maxSurfaceVerts : maxLMSurfaceVerts ) ) { return VERTS_EXCEEDED; } /* made it this far, add the vert and return */ verts.push_back( dv1 ); return verts.size() - 1; } /* 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, DrawVerts& verts, DrawIndexes& indexes, MinMax& texMinMax, Sorted_indices& sorted_indices, bool testAdd ){ int score; /* 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 && !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() ); } /* attempt to add the verts */ /* preserve size if this fails */ const int numVerts_original = verts.size(); int coincident = 0; const int ai = AddMetaVertToSurface( ds, *tri.m_vertices[ 0 ], verts, sorted_indices, &coincident ); const int bi = AddMetaVertToSurface( ds, *tri.m_vertices[ 1 ], verts, sorted_indices, &coincident ); const int ci = AddMetaVertToSurface( ds, *tri.m_vertices[ 2 ], verts, sorted_indices, &coincident ); /* check vertex underflow */ if ( ai < 0 || bi < 0 || ci < 0 ) { verts.resize( numVerts_original ); 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 ) && !indexes.empty() && 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 ( int i = 0; i < 3; ++i ) { if ( ( minmax.maxs[ i ] - minmax.mins[ i ] ) > lmMax ) { verts.resize( numVerts_original ); 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( numVerts_original == 0 || 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 ( int( indexes.size() + 3 ) > maxSurfaceIndexes ) { verts.resize( numVerts_original ); return 0; } /* sanity check the indexes */ if ( ai == bi || ai == ci || bi == ci ) { Sys_Printf( "DEG:%zu! ", verts.size() ); } /* testing only? */ if ( testAdd ) { verts.resize( numVerts_original ); } else { /* note modding ds & indexes only at this point */ /* add the triangle indexes */ indexes.push_back( ai ); indexes.push_back( bi ); indexes.push_back( ci ); /* store new bounds */ ds.minmax = minmax; texMinMax = newTexMinMax; /* add a side reference */ ds.addSideRef( tri.side ); for( const int id : { ai, bi, ci } ){ if( id >= numVerts_original ) sorted_indices.emplace( spatial_distance( verts[id].xyz ), id ); } } /* return to sender */ return score; } /* MetaTrianglesToSurface() creates map drawsurface(s) from the list of possibles */ static void MetaTrianglesToSurface(){ /* allocate arrays */ DrawVerts verts; DrawIndexes indexes; Pacifier pacifier( metaTriangles.size() ); /* walk the list of triangles */ for ( auto& seed : metaTriangles ) { /* skip this triangle if it has already been merged */ if ( seed.si == nullptr ) { 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.ambientColor = seed.ambientColor; ds.lightmapAxis = seed.lightmapAxis; ds.addSideRef( seed.side ); ds.minmax.clear(); verts.clear(); indexes.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::ranges::find( testCloud, tri ) == testCloud.cend() ){ testCloud.push_back( tri ); } } } } /* mark triangle as used */ triangle.si = nullptr; /* remove from cloud */ if( auto it = std::ranges::find( testCloud, &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, verts, indexes, texMinMax, sorted_indices, false ) ) { ++pacifier; } expand_cloud( seed ); /* ----------------------------------------------------------------- add triangles ----------------------------------------------------------------- */ /* progressively walk the list until no more triangles can be added */ for( bool added = true; added; ) { /* 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 == nullptr ) { continue; } /* score this triangle */ const int score = AddMetaTriangleToSurface( ds, *test, verts, indexes, 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, verts, indexes, texMinMax, sorted_indices, false ) ) { ++pacifier; expand_cloud( *best ); } /* reset */ added = true; } } /* copy the verts and indexes to the new surface */ ds.verts = verts; ds.indexes = indexes; /* classify the surface */ ClassifySurface( ds ); //% Sys_Warning( "numV: %d numIdx: %d\n", ds.numVerts, ds.numIndexes ); /* ClassifySurface() sets axis from vertex normals method is very questionable and axis actually happens to be wrong after normals passed through SmoothMetaTriangles() use metaTriangle_t::lightmapAxis which is guaranteedly set and used as main factor for triangles merge */ ds.lightmapAxis = seed.lightmapAxis; /* add to count */ numMergedSurfaces++; } } /* MergeMetaTriangles() merges meta triangles into drawsurfaces */ void MergeMetaTriangles(){ /* only do this if there are meta triangles */ if ( metaTriangles.empty() ) { return; } /* note it */ Sys_FPrintf( SYS_VRB, "--- MergeMetaTriangles ---\n" ); /* init pacifier */ Timer timer; #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(); /* clear meta triangle list */ ClearMetaTriangles(); /* print time */ Sys_FPrintf( SYS_VRB, " (%d)\n", int( timer.elapsed_sec() ) ); /* emit some stats */ Sys_FPrintf( SYS_VRB, "%9d surfaces merged\n", numMergedSurfaces ); Sys_FPrintf( SYS_VRB, "%9d vertexes merged\n", numMergedVerts ); }