* improved brush faces triangulation wrt degenerate triangles production (non -maxarea algorithm)

This commit is contained in:
Garux
2024-11-11 04:59:17 +05:00
parent e0169cdc63
commit c166e4214e
8 changed files with 162 additions and 126 deletions
+1
View File
@@ -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)
+29 -1
View File
@@ -352,4 +352,32 @@ inline void ColorNormalize( Vector3& color ) {
else{
color *= ( 1.f / max );
}
}
}
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 ) );
}
+4 -12
View File
@@ -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;
}
-1
View File
@@ -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 );
-20
View File
@@ -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
+43 -47
View File
@@ -30,6 +30,7 @@
/* dependencies */
#include "q3map2.h"
#include "tjunction.h"
#include "timer.h"
#include <map>
#include <set>
@@ -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<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 );
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 ) );
+36 -45
View File
@@ -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 );
+49
View File
@@ -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;
}