1720 lines
48 KiB
C++
1720 lines
48 KiB
C++
/* -------------------------------------------------------------------------------
|
|
|
|
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 "timer.h"
|
|
#include <map>
|
|
#include <set>
|
|
|
|
|
|
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<metaTriangle_t*> m_triangles; // references to triangles, introducing this vertex // bspDrawVert_equal()
|
|
std::list<metaVertex_t> *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
|
|
<float, // spatial_distance( std::list<metaVertex_t>>.front().xyz )
|
|
std::list<metaVertex_t>>; // must be maintained non empty
|
|
|
|
struct MinMax1D
|
|
{
|
|
float min, max;
|
|
MinMax1D() : min( std::numeric_limits<float>::max() ), max( std::numeric_limits<float>::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<metaVertex_t*, 3> m_vertices;
|
|
MinMax1D minmax;
|
|
};
|
|
|
|
|
|
|
|
|
|
#define VERTS_EXCEEDED -1000
|
|
|
|
static int numMetaSurfaces, numPatchMetaSurfaces;
|
|
|
|
static MetaVertexGroups metaVerts;
|
|
static std::list<metaTriangle_t> 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<bool sort_spatially>
|
|
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<bspDrawVert_t, 3> 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<false>::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
|
|
*/
|
|
|
|
static void TriangulatePatchSurface( const 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
|
|
static 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()
|
|
*/
|
|
|
|
static 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( const entity_t& e ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- MakeEntityMetaTriangles ---\n" );
|
|
|
|
/* init pacifier */
|
|
int fOld = -1;
|
|
Timer timer;
|
|
|
|
/* 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( 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, fOld, 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;
|
|
Timer timer;
|
|
|
|
/* walk triangle list */
|
|
numTJuncs = 0;
|
|
for ( i = 0; i < numMetaTriangles; i++ )
|
|
{
|
|
/* get triangle */
|
|
tri = &metaTriangles[ i ];
|
|
|
|
/* print pacifier */
|
|
if ( const int f = 10 * i / numMetaTriangles; 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( 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.f;
|
|
|
|
struct VT{ metaVertex_t *vertex; metaTriangle_t *triangle; float angle{}; bool skipped{}; bool smoothed{}; Vector3 newnormal{ 0 }; };
|
|
std::vector<VT> 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.0f ) {
|
|
shadeAngle = degrees_to_radians( v.triangle->si->shadeAngleDegrees );
|
|
}
|
|
/* get shade angle from entity */
|
|
else if ( v.triangle->shadeAngleDegrees > 0.0f ) {
|
|
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<VT*> smoothedVerts;
|
|
std::vector<Vector3> 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::none_of( votes.begin(), votes.end(),
|
|
[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::any_of( verts.cbegin(), verts.cend(), []( const VT& vt ){ return vt.smoothed; } ) ){
|
|
decltype( list ) newlist;
|
|
for( auto& v : verts ){
|
|
bspDrawVert_t newv = *v.vertex;
|
|
if( v.smoothed )
|
|
newv.normal = v.newnormal;
|
|
|
|
auto it = std::find_if( newlist.begin(), newlist.end(), [&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::find( v.triangle->m_vertices.begin(), v.triangle->m_vertices.end(), 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
|
|
<float, // plane3_distance_to_point( c_spatial_sort_plane, point )
|
|
int>; // 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( 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<int> oldTexRange( ( texMinMax.maxs - texMinMax.mins ).vec2() * wh );
|
|
BasicVector2<int> 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<metaTriangle_t*> 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<false>::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 );
|
|
/* ClassifySurfaces() 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++;
|
|
}
|
|
|
|
/* free arrays */
|
|
free( verts );
|
|
free( indexes );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
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 */
|
|
int fOld = -1;
|
|
Timer timer;
|
|
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<true>() );
|
|
#endif
|
|
MetaTrianglesToSurface( &fOld, &numAdded );
|
|
|
|
/* 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 );
|
|
}
|