diff --git a/docs/changelog-custom.txt b/docs/changelog-custom.txt index 6818f872..2fb42ad7 100644 --- a/docs/changelog-custom.txt +++ b/docs/changelog-custom.txt @@ -176,6 +176,7 @@ Q3map2: -light -extlmhacksize 1024 -trisoup //gen shaders for external lms and mark as trisoup * generate shaders to force nomipmaps flag on external lightmap images created by lightstyles and external lms hacks * -nobrushsnap: disable brush vertices snapping (of drawsurfs) (experimental, keep eye on meta merge efficiency) + * improved brush faces triangulation wrt degenerate triangles production (non -maxarea algorithm) diff --git a/tools/quake3/common/qmath.h b/tools/quake3/common/qmath.h index 4a5c2983..4443ff4f 100644 --- a/tools/quake3/common/qmath.h +++ b/tools/quake3/common/qmath.h @@ -352,4 +352,32 @@ inline void ColorNormalize( Vector3& color ) { else{ color *= ( 1.f / max ); } -} \ No newline at end of file +} + + +inline double angle_squared_sin( const Vector3& a, const Vector3& b, const Vector3& c ){ + const Vector3 d1 = b - a; + const Vector3 d2 = c - a; + const Vector3 normal = vector3_cross( d2, d1 ); + /* https://en.wikipedia.org/wiki/Cross_product#Geometric_meaning + cross( a, b ).length = a.length b.length sin( angle ) */ + const double lengthsSquared = vector3_length_squared( d1 ) * vector3_length_squared( d2 ); + return lengthsSquared == 0? 0 : ( vector3_length_squared( normal ) / lengthsSquared ); +} + +inline double triangle_min_angle_squared_sin( const Vector3& a, const Vector3& b, const Vector3& c ){ + const Vector3 d[3] = { b - a, c - a, c - b }; + const double l[3] = { vector3_length_squared( d[0] ), vector3_length_squared( d[1] ), vector3_length_squared( d[2] ) }; + const size_t mini = ( l[0] < l[1] ) ? ( ( l[0] < l[2] ) ? 0 : 2 ) : ( ( l[1] < l[2] ) ? 1 : 2 ); + if( l[mini] == 0 ) + return 0; + + const size_t minj = mini == 2? 0 : mini + 1; + const size_t mink = minj == 2? 0 : minj + 1; + return vector3_length_squared( vector3_cross( d[minj], d[mink] ) ) / ( l[minj] * l[mink] ); +} + + +inline double triangle_area2x( const Vector3& a, const Vector3& b, const Vector3& c ){ + return vector3_length( vector3_cross( b - a, c - a ) ); +} diff --git a/tools/quake3/q3map2/model.cpp b/tools/quake3/q3map2/model.cpp index 875767ee..3c3d346a 100644 --- a/tools/quake3/q3map2/model.cpp +++ b/tools/quake3/q3map2/model.cpp @@ -584,18 +584,10 @@ static void ClipModel( int spawnFlags, float clipDepth, shaderInfo_t *si, const } /* sanity check */ - { - const Vector3 d1 = points[1] - points[0]; - const Vector3 d2 = points[2] - points[0]; - const Vector3 normal = vector3_cross( d2, d1 ); - /* https://en.wikipedia.org/wiki/Cross_product#Geometric_meaning - cross( a, b ).length = a.length b.length sin( angle ) */ - const double lengthsSquared = vector3_length_squared( d1 ) * vector3_length_squared( d2 ); - if ( lengthsSquared == 0 || ( vector3_length_squared( normal ) / lengthsSquared ) < 1e-8 ) { - Sys_Warning( "triangle (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) of %s was not autoclipped: points on line\n", - points[0][0], points[0][1], points[0][2], points[1][0], points[1][1], points[1][2], points[2][0], points[2][1], points[2][2], modelName ); - continue; - } + if ( triangle_min_angle_squared_sin( points[0], points[1], points[2] ) < 1e-8 ) { + Sys_Warning( "triangle (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) of %s was not autoclipped: points on line\n", + points[0][0], points[0][1], points[0][2], points[1][0], points[1][1], points[1][2], points[2][0], points[2][1], points[2][2], modelName ); + continue; } diff --git a/tools/quake3/q3map2/q3map2.h b/tools/quake3/q3map2/q3map2.h index f09b96b3..eb339aef 100644 --- a/tools/quake3/q3map2/q3map2.h +++ b/tools/quake3/q3map2/q3map2.h @@ -1545,7 +1545,6 @@ void ClassifySurfaces( int numSurfs, mapDrawSurface_t *ds void ClassifyEntitySurfaces( const entity_t& e ); void TidyEntitySurfaces( const entity_t& e ); mapDrawSurface_t *CloneSurface( mapDrawSurface_t *src, shaderInfo_t *si ); -bool IsTriangleDegenerate( bspDrawVert_t *points, int a, int b, int c ); void ClearSurface( mapDrawSurface_t *ds ); mapDrawSurface_t *DrawSurfaceForSide( const entity_t& e, const brush_t& b, const side_t& s, const winding_t& w ); mapDrawSurface_t *DrawSurfaceForMesh( const entity_t& e, parseMesh_t *p, mesh_t *mesh ); diff --git a/tools/quake3/q3map2/surface.cpp b/tools/quake3/q3map2/surface.cpp index 39f1a582..b1198497 100644 --- a/tools/quake3/q3map2/surface.cpp +++ b/tools/quake3/q3map2/surface.cpp @@ -218,26 +218,6 @@ static mapDrawSurface_t *MakeSkyboxSurface( mapDrawSurface_t *src ){ -/* - IsTriangleDegenerate - returns true if all three points are colinear, backwards, or the triangle is just plain bogus - */ - -#define TINY_AREA 1.0f - -bool IsTriangleDegenerate( bspDrawVert_t *points, int a, int b, int c ){ - /* calcuate the area of the triangle */ - /* assume all very small or backwards triangles will cause problems */ - if ( vector3_length( vector3_cross( points[ b ].xyz - points[ a ].xyz, points[ c ].xyz - points[ a ].xyz ) ) < TINY_AREA ) { - return true; - } - - /* must be a good triangle */ - return false; -} - - - /* ClearSurface() - ydnar clears a surface and frees any allocated memory diff --git a/tools/quake3/q3map2/surface_meta.cpp b/tools/quake3/q3map2/surface_meta.cpp index 287e97be..94232a4d 100644 --- a/tools/quake3/q3map2/surface_meta.cpp +++ b/tools/quake3/q3map2/surface_meta.cpp @@ -30,6 +30,7 @@ /* dependencies */ #include "q3map2.h" +#include "tjunction.h" #include "timer.h" #include #include @@ -435,7 +436,7 @@ static void TriangulatePatchSurface( const entity_t& e, mapDrawSurface_t *ds ){ ClassifySurfaces( 1, ds ); } -#define TINY_AREA 1.0f +#define TINY_AREA 1.0 #define MAXAREA_MAXTRIES 8 static int MaxAreaIndexes( bspDrawVert_t *vert, int cnt, int *indexes ){ int r, s, t, bestR = 0, bestS = 1, bestT = 2; @@ -452,7 +453,7 @@ static int MaxAreaIndexes( bspDrawVert_t *vert, int cnt, int *indexes ){ 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 ) ); + A += triangle_area2x( vert[0].xyz, vert[i].xyz, vert[i + 1].xyz ); } V = 0; for ( i = 0; i < cnt; ++i ) @@ -533,7 +534,7 @@ static int MaxAreaIndexes( bspDrawVert_t *vert, int cnt, int *indexes ){ } // abc abc abc abc abc abc - bestA = vector3_length( vector3_cross( vert[bestS].xyz - vert[bestR].xyz, vert[bestT].xyz - vert[bestR].xyz ) ); + bestA = triangle_area2x( vert[bestR].xyz, vert[bestS].xyz, vert[bestT].xyz ); } if ( bestA < TINY_AREA ) { @@ -727,8 +728,7 @@ static void FanFaceSurface( mapDrawSurface_t *ds ){ #define MAX_INDEXES 1024 void StripFaceSurface( mapDrawSurface_t *ds ){ - int i, r, least, rotate, numIndexes, ni, a, b, c, indexes[ MAX_INDEXES ]; - + int numIndexes, indexes[ MAX_INDEXES ]; /* try to early out */ if ( !ds->numVerts || ( ds->type != ESurfaceType::Face && ds->type != ESurfaceType::Decal ) ) { @@ -745,9 +745,9 @@ void StripFaceSurface( mapDrawSurface_t *ds ){ else { /* ydnar: find smallest coordinate */ - least = 0; + int least = 0; if ( ds->shaderInfo != NULL && !ds->shaderInfo->autosprite ) { - for ( i = 0; i < ds->numVerts; i++ ) + for ( int i = 0; i < ds->numVerts; i++ ) { /* get points */ const Vector3& v1 = ds->verts[ i ].xyz; @@ -768,59 +768,55 @@ void StripFaceSurface( mapDrawSurface_t *ds ){ 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++ ) + class TriEval { - /* set rotation */ - rotate = ( r + least ) % ds->numVerts; + const bspDrawVert_t *m_verts; + double m_area = std::numeric_limits::max(); // 2x area + double m_angle = std::numeric_limits::max(); // squared sin of the angle + public: + TriEval( const bspDrawVert_t *verts ) : m_verts( verts ){ + } + void push( int a, int b, int c ){ + value_minimize( m_angle, triangle_min_angle_squared_sin( m_verts[a].xyz, m_verts[b].xyz, m_verts[c].xyz ) ); + value_minimize( m_area, triangle_area2x( m_verts[a].xyz, m_verts[b].xyz, m_verts[c].xyz ) ); + } + bool decent() const { + return m_angle > 1e-5 && m_area > TINY_AREA; + } + void reset(){ + *this = TriEval( m_verts ); + } + } triEval( ds->verts ); + 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 ( ni = 0, i = 0; i < ds->numVerts - 2 - i; i++ ) + 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; ) { - /* 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 ) ) { + if ( triEval.push( i, j, k ), !triEval.decent() ) { 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; - } + indexes[ out++ ] = i; + indexes[ out++ ] = j; + indexes[ out++ ] = k; } - /* valid strip? */ - if ( ni == numIndexes ) { - break; - } + if( triEval.decent() ) + goto okej; } /* if any triangle in the strip is degenerate, render from a centered fan point instead */ - if ( ni < numIndexes ) { - FanFaceSurface( ds ); - return; - } + return FanFaceSurface( ds ); } - +okej: /* copy strip triangle indexes */ ds->numIndexes = numIndexes; ds->indexes = safe_malloc( ds->numIndexes * sizeof( int ) ); diff --git a/tools/quake3/q3map2/tjunction.cpp b/tools/quake3/q3map2/tjunction.cpp index 8ac77e58..2b8197e4 100644 --- a/tools/quake3/q3map2/tjunction.cpp +++ b/tools/quake3/q3map2/tjunction.cpp @@ -30,6 +30,7 @@ /* dependencies */ #include "q3map2.h" +#include "tjunction.h" @@ -209,8 +210,7 @@ static void AddSurfaceEdges( mapDrawSurface_t& ds ){ for ( int i = 0; i < ds.numVerts; i++ ) { /* save the edge number in the lightmap field so we don't need to look it up again */ - ds.verts[i].lightmap[ 0 ][ 0 ] = - AddEdge( ds.verts[ i ], ds.verts[ ( i + 1 ) % ds.numVerts ], false ); + bspDrawVert_edge_index_write( ds.verts[ i ], AddEdge( ds.verts[ i ], ds.verts[ ( i + 1 ) % ds.numVerts ], false ) ); } } @@ -309,18 +309,13 @@ static void AddPatchEdges( mapDrawSurface_t& ds ) { */ #define MAX_SURFACE_VERTS 256 static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { - int i, j, k; - edgeLine_t *e; - edgePoint_t *p; int counts[MAX_SURFACE_VERTS]; int originals[MAX_SURFACE_VERTS]; - bspDrawVert_t verts[MAX_SURFACE_VERTS], *v1, *v2; - int numVerts; - float start, end, c; + bspDrawVert_t verts[MAX_SURFACE_VERTS]; + int numVerts = 0; - numVerts = 0; - for ( i = 0; i < ds.numVerts; ++i ) + for ( int i = 0; i < ds.numVerts; ++i ) { counts[i] = 0; @@ -333,28 +328,20 @@ static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { numVerts++; // check to see if there are any t junctions before the next vert - v1 = &ds.verts[i]; - v2 = &ds.verts[ ( i + 1 ) % ds.numVerts ]; + const bspDrawVert_t& v1 = ds.verts[i]; + const bspDrawVert_t& v2 = ds.verts[ ( i + 1 ) % ds.numVerts ]; - j = (int)ds.verts[i].lightmap[ 0 ][ 0 ]; + const int j = bspDrawVert_edge_index_read( ds.verts[ i ] ); if ( j == -1 ) { continue; // degenerate edge } - e = &edgeLines[ j ]; + const edgeLine_t& e = edgeLines[ j ]; - start = vector3_dot( v1->xyz - e->origin, e->dir ); + const float start = vector3_dot( v1.xyz - e.origin, e.dir ); - end = vector3_dot( v2->xyz - e->origin, e->dir ); + const float end = vector3_dot( v2.xyz - e.origin, e.dir ); - - if ( start < end ) { - p = e->chain->next; - } - else { - p = e->chain->prev; - } - - for ( ; p != e->chain; ) { + for ( edgePoint_t *p = ( start < end )? e.chain->next : e.chain->prev; p != e.chain; p = ( start < end )? p->next : p->prev ) { if ( start < end ) { if ( p->intercept > end - ON_EPSILON ) { break; @@ -372,39 +359,36 @@ static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { if ( numVerts == MAX_SURFACE_VERTS ) { Error( "MAX_SURFACE_VERTS" ); } + bspDrawVert_t& v = verts[ numVerts ]; /* take the exact intercept point */ - verts[ numVerts ].xyz = p->xyz; + v.xyz = p->xyz; /* interpolate the texture coordinates */ const float frac = ( p->intercept - start ) / ( end - start ); - verts[ numVerts ].st = v1->st + ( v2->st - v1->st ) * frac; + v.st = v1.st + ( v2.st - v1.st ) * frac; /* copy the normal (FIXME: what about nonplanar surfaces? */ - verts[ numVerts ].normal = v1->normal; + v.normal = v1.normal; /* ydnar: interpolate the color */ - for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + for ( int k = 0; k < MAX_LIGHTMAPS; ++k ) { - for ( j = 0; j < 4; j++ ) + for ( int j = 0; j < 4; ++j ) { - c = (float) v1->color[ k ][ j ] + frac * ( (float) v2->color[ k ][ j ] - (float) v1->color[ k ][ j ] ); - verts[ numVerts ].color[ k ][ j ] = color_to_byte( c ); + const float c = v1.color[ k ][ j ] + frac * ( v2.color[ k ][ j ] - v1.color[ k ][ j ] ); + v.color[ k ][ j ] = color_to_byte( c ); } + v.lightmap[ k ] = { 0, 0 }; // do zero init } + v.lightmap[ 0 ] = vector2_mid( v1.lightmap[ 0 ], v2.lightmap[ 0 ] ); + bspDrawVert_mark_tjunc( v ); /* next... */ originals[ numVerts ] = i; numVerts++; counts[ i ]++; } - - if ( start < end ) { - p = p->next; - } - else { - p = p->prev; - } } } @@ -417,12 +401,13 @@ static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { // rotate the points so that the initial vertex is between // two non-subdivided edges + int i; for ( i = 0; i < numVerts; ++i ) { if ( originals[ ( i + 1 ) % numVerts ] == originals[ i ] ) { continue; } - j = ( i + numVerts - 1 ) % numVerts; - k = ( i + numVerts - 2 ) % numVerts; + const int j = ( i + numVerts - 1 ) % numVerts; + const int k = ( i + numVerts - 2 ) % numVerts; if ( originals[ j ] == originals[ k ] ) { continue; } @@ -433,6 +418,7 @@ static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { // fine the way it is c_natural++; + free( ds.verts ); ds.numVerts = numVerts; ds.verts = safe_malloc( numVerts * sizeof( *ds.verts ) ); memcpy( ds.verts, verts, numVerts * sizeof( *ds.verts ) ); @@ -464,10 +450,11 @@ static void FixSurfaceJunctions( mapDrawSurface_t& ds ) { } + free( ds.verts ); ds.numVerts = numVerts; ds.verts = safe_malloc( numVerts * sizeof( *ds.verts ) ); - for ( j = 0; j < ds.numVerts; ++j ) { + for ( int j = 0; j < ds.numVerts; ++j ) { ds.verts[j] = verts[ ( j + i ) % ds.numVerts ]; } } @@ -512,10 +499,14 @@ static bool FixBrokenSurface( mapDrawSurface_t& ds ){ /* lightmap st/colors */ for ( int k = 0; k < MAX_LIGHTMAPS; ++k ) { - avg.lightmap[ k ] = vector2_mid( dv1.lightmap[ k ], dv2.lightmap[ k ] ); + avg.lightmap[ k ] = { 0, 0 }; for ( int j = 0; j < 4; ++j ) - avg.color[ k ][ j ] = (int) ( dv1.color[ k ][ j ] + dv2.color[ k ][ j ] ) >> 1; + avg.color[ k ][ j ] = ( dv1.color[ k ][ j ] + dv2.color[ k ][ j ] ) >> 1; } + avg.lightmap[ 0 ] = vector2_mid( dv1.lightmap[ 0 ], dv2.lightmap[ 0 ] ); + + if( bspDrawVert_is_tjunc( dv1 ) && bspDrawVert_is_tjunc( dv2 ) ) + bspDrawVert_mark_tjunc( avg ); /* ydnar: der... */ dv1 = avg; @@ -602,7 +593,7 @@ void FixTJunctions( const entity_t& ent ){ // add the non-axial edges, longest first // this gives the most accurate edge description for ( originalEdge_t& e : Span( originalEdges, numOriginalEdges ) ) { // originalEdges might not change during AddEdge( true ) - e.dv[ 0 ]->lightmap[ 0 ][ 0 ] = AddEdge( *e.dv[ 0 ], *e.dv[ 1 ], true ); + bspDrawVert_edge_index_write( *e.dv[ 0 ], AddEdge( *e.dv[ 0 ], *e.dv[ 1 ], true ) ); } Sys_FPrintf( SYS_VRB, "%9d axial edge lines\n", axialEdgeLines ); diff --git a/tools/quake3/q3map2/tjunction.h b/tools/quake3/q3map2/tjunction.h new file mode 100644 index 00000000..daec456e --- /dev/null +++ b/tools/quake3/q3map2/tjunction.h @@ -0,0 +1,49 @@ +/* ------------------------------------------------------------------------------- + + 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." + + ------------------------------------------------------------------------------- */ + + +#pragma once + +/* dependencies */ +#include "q3map2.h" + +inline void bspDrawVert_edge_index_write( bspDrawVert_t& dv, int index ){ + dv.lightmap[MAX_LIGHTMAPS - 1][0] = index; +} + +inline int bspDrawVert_edge_index_read( const bspDrawVert_t& dv ){ + return dv.lightmap[MAX_LIGHTMAPS - 1][0]; +} + +inline void bspDrawVert_mark_tjunc( bspDrawVert_t& dv ){ + dv.lightmap[MAX_LIGHTMAPS - 1][1] = 1; +} + +inline bool bspDrawVert_is_tjunc( const bspDrawVert_t& dv ){ + return dv.lightmap[MAX_LIGHTMAPS - 1][1] == 1; +}