1624 lines
46 KiB
C++
Executable file
1624 lines
46 KiB
C++
Executable file
/* -------------------------------------------------------------------------------
|
|
|
|
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 <map>
|
|
|
|
|
|
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
|
|
|
|
/* ydnar: metasurfaces are constructed from lists of metatriangles so they can be merged in the best way */
|
|
struct metaTriangle_t
|
|
{
|
|
shaderInfo_t *si; // only null when triangle has already been merged
|
|
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<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;
|
|
}
|
|
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<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 )
|
|
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.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.sideRefs.empty()? nullptr : ds.sideRefs.front() );
|
|
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( 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.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;
|
|
}
|
|
|
|
Array4<Color4f> color = makeArray4( Color4f( 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->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<double>::max(); // 2x area
|
|
double m_angle = std::numeric_limits<double>::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<SYS_VRB> 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<SYS_VRB> 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<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 ) {
|
|
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<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::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
|
|
<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( 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, 0 ) );
|
|
newTexMinMax.extend( Vector3( tri.m_vertices[ 1 ]->st, 0 ) );
|
|
newTexMinMax.extend( Vector3( tri.m_vertices[ 2 ]->st, 0 ) );
|
|
if( numVerts_original == 0 || 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 ( 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<SYS_VRB> 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, *seed.si );
|
|
ds.entityNum = seed.entityNum;
|
|
ds.surfaceNum = seed.surfaceNum;
|
|
ds.castShadows = seed.castShadows;
|
|
ds.recvShadows = seed.recvShadows;
|
|
|
|
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<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::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<true>() );
|
|
#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 );
|
|
}
|