mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1999 lines
44 KiB
C++
1999 lines
44 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
|
|
//#pragma optimize( "", off )
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#include <GL/gl.h>
|
|
#endif
|
|
|
|
#include "tools/compilers/dmap/dmap.h"
|
|
|
|
/*
|
|
|
|
New vertexes will be created where edges cross.
|
|
|
|
optimization requires an accurate t junction fixer.
|
|
|
|
|
|
|
|
*/
|
|
|
|
idBounds optBounds;
|
|
|
|
#define MAX_OPT_VERTEXES 0x10000
|
|
int numOptVerts;
|
|
optVertex_t optVerts[MAX_OPT_VERTEXES];
|
|
|
|
#define MAX_OPT_EDGES 0x40000
|
|
static int numOptEdges;
|
|
static optEdge_t optEdges[MAX_OPT_EDGES];
|
|
|
|
static bool IsTriangleValid( const optVertex_t *v1, const optVertex_t *v2, const optVertex_t *v3 );
|
|
static bool IsTriangleDegenerate( const optVertex_t *v1, const optVertex_t *v2, const optVertex_t *v3 );
|
|
|
|
static idRandom orandom;
|
|
|
|
/*
|
|
==============
|
|
ValidateEdgeCounts
|
|
==============
|
|
*/
|
|
static void ValidateEdgeCounts( optIsland_t *island ) {
|
|
optVertex_t *vert;
|
|
optEdge_t *e;
|
|
int c;
|
|
|
|
for ( vert = island->verts ; vert ; vert = vert->islandLink ) {
|
|
c = 0;
|
|
for ( e = vert->edges ; e ; ) {
|
|
c++;
|
|
if ( e->v1 == vert ) {
|
|
e = e->v1link;
|
|
} else if ( e->v2 == vert ) {
|
|
e = e->v2link;
|
|
} else {
|
|
common->Error( "ValidateEdgeCounts: mislinked" );
|
|
}
|
|
}
|
|
if ( c != 2 && c != 0 ) {
|
|
// this can still happen at diamond intersections
|
|
// common->Printf( "ValidateEdgeCounts: %i edges\n", c );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
AllocEdge
|
|
====================
|
|
*/
|
|
static optEdge_t *AllocEdge( void ) {
|
|
optEdge_t *e;
|
|
|
|
if ( numOptEdges == MAX_OPT_EDGES ) {
|
|
common->Error( "MAX_OPT_EDGES" );
|
|
}
|
|
e = &optEdges[ numOptEdges ];
|
|
numOptEdges++;
|
|
memset( e, 0, sizeof( *e ) );
|
|
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
RemoveEdgeFromVert
|
|
====================
|
|
*/
|
|
static void RemoveEdgeFromVert( optEdge_t *e1, optVertex_t *vert ) {
|
|
optEdge_t **prev;
|
|
optEdge_t *e;
|
|
|
|
if ( !vert ) {
|
|
return;
|
|
}
|
|
prev = &vert->edges;
|
|
while ( *prev ) {
|
|
e = *prev;
|
|
if ( e == e1 ) {
|
|
if ( e1->v1 == vert ) {
|
|
*prev = e1->v1link;
|
|
} else if ( e1->v2 == vert ) {
|
|
*prev = e1->v2link;
|
|
} else {
|
|
common->Error( "RemoveEdgeFromVert: vert not found" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( e->v1 == vert ) {
|
|
prev = &e->v1link;
|
|
} else if ( e->v2 == vert ) {
|
|
prev = &e->v2link;
|
|
} else {
|
|
common->Error( "RemoveEdgeFromVert: vert not found" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
UnlinkEdge
|
|
====================
|
|
*/
|
|
static void UnlinkEdge( optEdge_t *e, optIsland_t *island ) {
|
|
optEdge_t **prev;
|
|
|
|
RemoveEdgeFromVert( e, e->v1 );
|
|
RemoveEdgeFromVert( e, e->v2 );
|
|
|
|
for ( prev = &island->edges ; *prev ; prev = &(*prev)->islandLink ) {
|
|
if ( *prev == e ) {
|
|
*prev = e->islandLink;
|
|
return;
|
|
}
|
|
}
|
|
|
|
common->Error( "RemoveEdgeFromIsland: couldn't free edge" );
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
LinkEdge
|
|
====================
|
|
*/
|
|
static void LinkEdge( optEdge_t *e ) {
|
|
e->v1link = e->v1->edges;
|
|
e->v1->edges = e;
|
|
|
|
e->v2link = e->v2->edges;
|
|
e->v2->edges = e;
|
|
}
|
|
|
|
/*
|
|
================
|
|
FindOptVertex
|
|
================
|
|
*/
|
|
static optVertex_t *FindOptVertex( idDrawVert *v, optimizeGroup_t *opt ) {
|
|
int i;
|
|
float x, y;
|
|
optVertex_t *vert;
|
|
|
|
// deal with everything strictly as 2D
|
|
x = v->xyz * opt->axis[0];
|
|
y = v->xyz * opt->axis[1];
|
|
|
|
// should we match based on the t-junction fixing hash verts?
|
|
for ( i = 0 ; i < numOptVerts ; i++ ) {
|
|
if ( optVerts[i].pv[0] == x && optVerts[i].pv[1] == y ) {
|
|
return &optVerts[i];
|
|
}
|
|
}
|
|
|
|
if ( numOptVerts >= MAX_OPT_VERTEXES ) {
|
|
common->Error( "MAX_OPT_VERTEXES" );
|
|
return NULL;
|
|
}
|
|
|
|
numOptVerts++;
|
|
|
|
vert = &optVerts[i];
|
|
memset( vert, 0, sizeof( *vert ) );
|
|
vert->v = *v;
|
|
vert->pv[0] = x;
|
|
vert->pv[1] = y;
|
|
vert->pv[2] = 0;
|
|
|
|
optBounds.AddPoint( vert->pv );
|
|
|
|
return vert;
|
|
}
|
|
|
|
/*
|
|
================
|
|
DrawAllEdges
|
|
================
|
|
*/
|
|
static void DrawAllEdges( void ) {
|
|
int i;
|
|
|
|
if ( !dmapGlobals.drawflag ) {
|
|
return;
|
|
}
|
|
|
|
Draw_ClearWindow();
|
|
|
|
qglBegin( GL_LINES );
|
|
for ( i = 0 ; i < numOptEdges ; i++ ) {
|
|
if ( optEdges[i].v1 == NULL ) {
|
|
continue;
|
|
}
|
|
qglColor3f( 1, 0, 0 );
|
|
qglVertex3fv( optEdges[i].v1->pv.ToFloatPtr() );
|
|
qglColor3f( 0, 0, 0 );
|
|
qglVertex3fv( optEdges[i].v2->pv.ToFloatPtr() );
|
|
}
|
|
qglEnd();
|
|
qglFlush();
|
|
|
|
// GLimp_SwapBuffers();
|
|
}
|
|
|
|
/*
|
|
================
|
|
DrawVerts
|
|
================
|
|
*/
|
|
static void DrawVerts( optIsland_t *island ) {
|
|
optVertex_t *vert;
|
|
|
|
if ( !dmapGlobals.drawflag ) {
|
|
return;
|
|
}
|
|
|
|
qglEnable( GL_BLEND );
|
|
qglBlendFunc( GL_ONE, GL_ONE );
|
|
qglColor3f( 0.3f, 0.3f, 0.3f );
|
|
qglPointSize( 3 );
|
|
qglBegin( GL_POINTS );
|
|
for ( vert = island->verts ; vert ; vert = vert->islandLink ) {
|
|
qglVertex3fv( vert->pv.ToFloatPtr() );
|
|
}
|
|
qglEnd();
|
|
qglDisable( GL_BLEND );
|
|
qglFlush();
|
|
}
|
|
|
|
/*
|
|
================
|
|
DrawEdges
|
|
================
|
|
*/
|
|
static void DrawEdges( optIsland_t *island ) {
|
|
optEdge_t *edge;
|
|
|
|
if ( !dmapGlobals.drawflag ) {
|
|
return;
|
|
}
|
|
|
|
Draw_ClearWindow();
|
|
|
|
qglBegin( GL_LINES );
|
|
for ( edge = island->edges ; edge ; edge = edge->islandLink ) {
|
|
if ( edge->v1 == NULL ) {
|
|
continue;
|
|
}
|
|
qglColor3f( 1, 0, 0 );
|
|
qglVertex3fv( edge->v1->pv.ToFloatPtr() );
|
|
qglColor3f( 0, 0, 0 );
|
|
qglVertex3fv( edge->v2->pv.ToFloatPtr() );
|
|
}
|
|
qglEnd();
|
|
qglFlush();
|
|
|
|
// GLimp_SwapBuffers();
|
|
}
|
|
|
|
//=================================================================
|
|
|
|
/*
|
|
=================
|
|
VertexBetween
|
|
=================
|
|
*/
|
|
static bool VertexBetween( const optVertex_t *p1, const optVertex_t *v1, const optVertex_t *v2 ) {
|
|
idVec3 d1, d2;
|
|
float d;
|
|
|
|
d1 = p1->pv - v1->pv;
|
|
d2 = p1->pv - v2->pv;
|
|
d = d1 * d2;
|
|
if ( d < 0 ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
EdgeIntersection
|
|
|
|
Creates a new optVertex_t where the line segments cross.
|
|
This should only be called if PointsStraddleLine returned true
|
|
|
|
Will return NULL if the lines are colinear
|
|
====================
|
|
*/
|
|
static optVertex_t *EdgeIntersection( const optVertex_t *p1, const optVertex_t *p2,
|
|
const optVertex_t *l1, const optVertex_t *l2, optimizeGroup_t *opt ) {
|
|
float f;
|
|
idDrawVert *v;
|
|
idVec3 dir1, dir2, cross1, cross2;
|
|
|
|
dir1 = p1->pv - l1->pv;
|
|
dir2 = p1->pv - l2->pv;
|
|
cross1 = dir1.Cross( dir2 );
|
|
|
|
dir1 = p2->pv - l1->pv;
|
|
dir2 = p2->pv - l2->pv;
|
|
cross2 = dir1.Cross( dir2 );
|
|
|
|
if ( cross1[2] - cross2[2] == 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
f = cross1[2] / ( cross1[2] - cross2[2] );
|
|
|
|
// FIXME: how are we freeing this, since it doesn't belong to a tri?
|
|
v = (idDrawVert *)Mem_Alloc( sizeof( *v ) );
|
|
memset( v, 0, sizeof( *v ) );
|
|
|
|
v->xyz = p1->v.xyz * ( 1.0 - f ) + p2->v.xyz * f;
|
|
v->normal = p1->v.normal * ( 1.0 - f ) + p2->v.normal * f;
|
|
v->normal.Normalize();
|
|
v->st[0] = p1->v.st[0] * ( 1.0 - f ) + p2->v.st[0] * f;
|
|
v->st[1] = p1->v.st[1] * ( 1.0 - f ) + p2->v.st[1] * f;
|
|
|
|
return FindOptVertex( v, opt );
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
PointsStraddleLine
|
|
|
|
Colinear is considdered crossing.
|
|
====================
|
|
*/
|
|
static bool PointsStraddleLine( optVertex_t *p1, optVertex_t *p2, optVertex_t *l1, optVertex_t *l2 ) {
|
|
bool t1, t2;
|
|
|
|
t1 = IsTriangleDegenerate( l1, l2, p1 );
|
|
t2 = IsTriangleDegenerate( l1, l2, p2 );
|
|
if ( t1 && t2 ) {
|
|
// colinear case
|
|
float s1, s2, s3, s4;
|
|
bool positive, negative;
|
|
|
|
s1 = ( p1->pv - l1->pv ) * ( l2->pv - l1->pv );
|
|
s2 = ( p2->pv - l1->pv ) * ( l2->pv - l1->pv );
|
|
s3 = ( p1->pv - l2->pv ) * ( l2->pv - l1->pv );
|
|
s4 = ( p2->pv - l2->pv ) * ( l2->pv - l1->pv );
|
|
|
|
if ( s1 > 0 || s2 > 0 || s3 > 0 || s4 > 0 ) {
|
|
positive = true;
|
|
} else {
|
|
positive = false;
|
|
}
|
|
if ( s1 < 0 || s2 < 0 || s3 < 0 || s4 < 0 ) {
|
|
negative = true;
|
|
} else {
|
|
negative = false;
|
|
}
|
|
|
|
if ( positive && negative ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
} else if ( p1 != l1 && p1 != l2 && p2 != l1 && p2 != l2 ) {
|
|
// no shared verts
|
|
t1 = IsTriangleValid( l1, l2, p1 );
|
|
t2 = IsTriangleValid( l1, l2, p2 );
|
|
if ( t1 && t2 ) {
|
|
return false;
|
|
}
|
|
|
|
t1 = IsTriangleValid( l1, p1, l2 );
|
|
t2 = IsTriangleValid( l1, p2, l2 );
|
|
if ( t1 && t2 ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
// a shared vert, not colinear, so not crossing
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
EdgesCross
|
|
====================
|
|
*/
|
|
static bool EdgesCross( optVertex_t *a1, optVertex_t *a2, optVertex_t *b1, optVertex_t *b2 ) {
|
|
// if both verts match, consider it to be crossed
|
|
if ( a1 == b1 && a2 == b2 ) {
|
|
return true;
|
|
}
|
|
if ( a1 == b2 && a2 == b1 ) {
|
|
return true;
|
|
}
|
|
// if only one vert matches, it might still be colinear, which
|
|
// would be considered crossing
|
|
|
|
// if both lines' verts are on opposite sides of the other
|
|
// line, it is crossed
|
|
if ( !PointsStraddleLine( a1, a2, b1, b2 ) ) {
|
|
return false;
|
|
}
|
|
if ( !PointsStraddleLine( b1, b2, a1, a2 ) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
TryAddNewEdge
|
|
|
|
====================
|
|
*/
|
|
static bool TryAddNewEdge( optVertex_t *v1, optVertex_t *v2, optIsland_t *island ) {
|
|
optEdge_t *e;
|
|
|
|
// if the new edge crosses any other edges, don't add it
|
|
for ( e = island->edges ; e ; e = e->islandLink ) {
|
|
if ( EdgesCross( e->v1, e->v2, v1, v2 ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( dmapGlobals.drawflag ) {
|
|
qglBegin( GL_LINES );
|
|
qglColor3f( 0, ( 128 + orandom.RandomInt( 127 ) )/ 255.0, 0 );
|
|
qglVertex3fv( v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
// add it
|
|
e = AllocEdge();
|
|
|
|
e->islandLink = island->edges;
|
|
island->edges = e;
|
|
e->v1 = v1;
|
|
e->v2 = v2;
|
|
|
|
e->created = true;
|
|
|
|
// link the edge to its verts
|
|
LinkEdge( e );
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef struct {
|
|
optVertex_t *v1, *v2;
|
|
float length;
|
|
} edgeLength_t;
|
|
|
|
|
|
static int LengthSort( const void *a, const void *b ) {
|
|
const edgeLength_t *ea, *eb;
|
|
|
|
ea = (const edgeLength_t *)a;
|
|
eb = (const edgeLength_t *)b;
|
|
if ( ea->length < eb->length ) {
|
|
return -1;
|
|
}
|
|
if ( ea->length > eb->length ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
AddInteriorEdges
|
|
|
|
Add all possible edges between the verts
|
|
==================
|
|
*/
|
|
static void AddInteriorEdges( optIsland_t *island ) {
|
|
int c_addedEdges;
|
|
optVertex_t *vert, *vert2;
|
|
int c_verts;
|
|
edgeLength_t *lengths;
|
|
int numLengths;
|
|
int i;
|
|
|
|
DrawVerts( island );
|
|
|
|
// count the verts
|
|
c_verts = 0;
|
|
for ( vert = island->verts ; vert ; vert = vert->islandLink ) {
|
|
if ( !vert->edges ) {
|
|
continue;
|
|
}
|
|
c_verts++;
|
|
}
|
|
|
|
// allocate space for all the lengths
|
|
lengths = (edgeLength_t *)Mem_Alloc( sizeof( *lengths ) * c_verts * c_verts / 2 );
|
|
numLengths = 0;
|
|
for ( vert = island->verts ; vert ; vert = vert->islandLink ) {
|
|
if ( !vert->edges ) {
|
|
continue;
|
|
}
|
|
for ( vert2 = vert->islandLink ; vert2 ; vert2 = vert2->islandLink ) {
|
|
idVec3 dir;
|
|
|
|
if ( !vert2->edges ) {
|
|
continue;
|
|
}
|
|
lengths[numLengths].v1 = vert;
|
|
lengths[numLengths].v2 = vert2;
|
|
dir = ( vert->pv - vert2->pv ) ;
|
|
lengths[numLengths].length = dir.Length();
|
|
numLengths++;
|
|
}
|
|
}
|
|
|
|
|
|
// sort by length, shortest first
|
|
qsort( lengths, numLengths, sizeof( lengths[0] ), LengthSort );
|
|
|
|
// try to create them in that order
|
|
c_addedEdges = 0;
|
|
for ( i = 0 ; i < numLengths ; i++ ) {
|
|
if ( TryAddNewEdge( lengths[i].v1, lengths[i].v2, island ) ) {
|
|
c_addedEdges++;
|
|
}
|
|
}
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i tested segments\n", numLengths );
|
|
common->Printf( "%6i added interior edges\n", c_addedEdges );
|
|
}
|
|
|
|
Mem_Free( lengths );
|
|
}
|
|
|
|
|
|
|
|
//==================================================================
|
|
|
|
/*
|
|
====================
|
|
RemoveIfColinear
|
|
|
|
====================
|
|
*/
|
|
#define COLINEAR_EPSILON 0.1
|
|
static void RemoveIfColinear( optVertex_t *ov, optIsland_t *island ) {
|
|
optEdge_t *e, *e1, *e2;
|
|
optVertex_t *v1, *v2, *v3;
|
|
idVec3 dir1, dir2;
|
|
float dist;
|
|
idVec3 point;
|
|
idVec3 offset;
|
|
float off;
|
|
|
|
v2 = ov;
|
|
|
|
// we must find exactly two edges before testing for colinear
|
|
e1 = NULL;
|
|
e2 = NULL;
|
|
for ( e = ov->edges ; e ; ) {
|
|
if ( !e1 ) {
|
|
e1 = e;
|
|
} else if ( !e2 ) {
|
|
e2 = e;
|
|
} else {
|
|
return; // can't remove a vertex with three edges
|
|
}
|
|
if ( e->v1 == v2 ) {
|
|
e = e->v1link;
|
|
} else if ( e->v2 == v2 ) {
|
|
e = e->v2link;
|
|
} else {
|
|
common->Error( "RemoveIfColinear: mislinked edge" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// can't remove if no edges
|
|
if ( !e1 ) {
|
|
return;
|
|
}
|
|
|
|
if ( !e2 ) {
|
|
// this may still happen legally when a tiny triangle is
|
|
// the only thing in a group
|
|
common->Printf( "WARNING: vertex with only one edge\n" );
|
|
return;
|
|
}
|
|
|
|
if ( e1->v1 == v2 ) {
|
|
v1 = e1->v2;
|
|
} else if ( e1->v2 == v2 ) {
|
|
v1 = e1->v1;
|
|
} else {
|
|
common->Error( "RemoveIfColinear: mislinked edge" );
|
|
return;
|
|
}
|
|
if ( e2->v1 == v2 ) {
|
|
v3 = e2->v2;
|
|
} else if ( e2->v2 == v2 ) {
|
|
v3 = e2->v1;
|
|
} else {
|
|
common->Error( "RemoveIfColinear: mislinked edge" );
|
|
return;
|
|
}
|
|
|
|
if ( v1 == v3 ) {
|
|
common->Error( "RemoveIfColinear: mislinked edge" );
|
|
return;
|
|
}
|
|
|
|
// they must point in opposite directions
|
|
dist = ( v3->pv - v2->pv ) * ( v1->pv - v2->pv );
|
|
if ( dist >= 0 ) {
|
|
return;
|
|
}
|
|
|
|
// see if they are colinear
|
|
VectorSubtract( v3->v.xyz, v1->v.xyz, dir1 );
|
|
dir1.Normalize();
|
|
VectorSubtract( v2->v.xyz, v1->v.xyz, dir2 );
|
|
dist = DotProduct( dir2, dir1 );
|
|
VectorMA( v1->v.xyz, dist, dir1, point );
|
|
VectorSubtract( point, v2->v.xyz, offset );
|
|
off = offset.Length();
|
|
|
|
if ( off > COLINEAR_EPSILON ) {
|
|
return;
|
|
}
|
|
|
|
if ( dmapGlobals.drawflag ) {
|
|
qglBegin( GL_LINES );
|
|
qglColor3f( 1, 1, 0 );
|
|
qglVertex3fv( v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
qglBegin( GL_LINES );
|
|
qglColor3f( 0, 1, 1 );
|
|
qglVertex3fv( v2->pv.ToFloatPtr() );
|
|
qglVertex3fv( v3->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
// replace the two edges with a single edge
|
|
UnlinkEdge( e1, island );
|
|
UnlinkEdge( e2, island );
|
|
|
|
// v2 should have no edges now
|
|
if ( v2->edges ) {
|
|
common->Error( "RemoveIfColinear: didn't remove properly" );
|
|
return;
|
|
}
|
|
|
|
|
|
// if there is an existing edge that already
|
|
// has these exact verts, we have just collapsed a
|
|
// sliver triangle out of existance, and all the edges
|
|
// can be removed
|
|
for ( e = island->edges ; e ; e = e->islandLink ) {
|
|
if ( ( e->v1 == v1 && e->v2 == v3 )
|
|
|| ( e->v1 == v3 && e->v2 == v1 ) ) {
|
|
UnlinkEdge( e, island );
|
|
RemoveIfColinear( v1, island );
|
|
RemoveIfColinear( v3, island );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if we can't add the combined edge, link
|
|
// the originals back in
|
|
if ( !TryAddNewEdge( v1, v3, island ) ) {
|
|
e1->islandLink = island->edges;
|
|
island->edges = e1;
|
|
LinkEdge( e1 );
|
|
|
|
e2->islandLink = island->edges;
|
|
island->edges = e2;
|
|
LinkEdge( e2 );
|
|
return;
|
|
}
|
|
|
|
// recursively try to combine both verts now,
|
|
// because things may have changed since the last combine test
|
|
RemoveIfColinear( v1, island );
|
|
RemoveIfColinear( v3, island );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CombineColinearEdges
|
|
====================
|
|
*/
|
|
static void CombineColinearEdges( optIsland_t *island ) {
|
|
int c_edges;
|
|
optVertex_t *ov;
|
|
optEdge_t *e;
|
|
|
|
c_edges = 0;
|
|
for ( e = island->edges ; e ; e = e->islandLink ) {
|
|
c_edges++;
|
|
}
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i original exterior edges\n", c_edges );
|
|
}
|
|
|
|
for ( ov = island->verts ; ov ; ov = ov->islandLink ) {
|
|
RemoveIfColinear( ov, island );
|
|
}
|
|
|
|
c_edges = 0;
|
|
for ( e = island->edges ; e ; e = e->islandLink ) {
|
|
c_edges++;
|
|
}
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i optimized exterior edges\n", c_edges );
|
|
}
|
|
}
|
|
|
|
|
|
//==================================================================
|
|
|
|
/*
|
|
===================
|
|
FreeOptTriangles
|
|
|
|
===================
|
|
*/
|
|
static void FreeOptTriangles( optIsland_t *island ) {
|
|
optTri_t *opt, *next;
|
|
|
|
for ( opt = island->tris ; opt ; opt = next ) {
|
|
next = opt->next;
|
|
Mem_Free( opt );
|
|
}
|
|
|
|
island->tris = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
IsTriangleValid
|
|
|
|
empty area will be considered invalid.
|
|
Due to some truly aweful epsilon issues, a triangle can switch between
|
|
valid and invalid depending on which order you look at the verts, so
|
|
consider it invalid if any one of the possibilities is invalid.
|
|
=================
|
|
*/
|
|
static bool IsTriangleValid( const optVertex_t *v1, const optVertex_t *v2, const optVertex_t *v3 ) {
|
|
idVec3 d1, d2, normal;
|
|
|
|
d1 = v2->pv - v1->pv;
|
|
d2 = v3->pv - v1->pv;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
d1 = v3->pv - v2->pv;
|
|
d2 = v1->pv - v2->pv;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
d1 = v1->pv - v3->pv;
|
|
d2 = v2->pv - v3->pv;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
IsTriangleDegenerate
|
|
|
|
Returns false if it is either front or back facing
|
|
=================
|
|
*/
|
|
static bool IsTriangleDegenerate( const optVertex_t *v1, const optVertex_t *v2, const optVertex_t *v3 ) {
|
|
#if 1
|
|
idVec3 d1, d2, normal;
|
|
|
|
d1 = v2->pv - v1->pv;
|
|
d2 = v3->pv - v1->pv;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] == 0 ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
#else
|
|
return (bool)!IsTriangleValid( v1, v2, v3 );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
PointInTri
|
|
|
|
Tests if a 2D point is inside an original triangle
|
|
==================
|
|
*/
|
|
static bool PointInTri( const idVec3 &p, const mapTri_t *tri, optIsland_t *island ) {
|
|
idVec3 d1, d2, normal;
|
|
|
|
// the normal[2] == 0 case is not uncommon when a square is triangulated in
|
|
// the opposite manner to the original
|
|
|
|
d1 = tri->optVert[0]->pv - p;
|
|
d2 = tri->optVert[1]->pv - p;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] < 0 ) {
|
|
return false;
|
|
}
|
|
|
|
d1 = tri->optVert[1]->pv - p;
|
|
d2 = tri->optVert[2]->pv - p;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] < 0 ) {
|
|
return false;
|
|
}
|
|
|
|
d1 = tri->optVert[2]->pv - p;
|
|
d2 = tri->optVert[0]->pv - p;
|
|
normal = d1.Cross( d2 );
|
|
if ( normal[2] < 0 ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
LinkTriToEdge
|
|
|
|
====================
|
|
*/
|
|
static void LinkTriToEdge( optTri_t *optTri, optEdge_t *edge ) {
|
|
if ( ( edge->v1 == optTri->v[0] && edge->v2 == optTri->v[1] )
|
|
|| ( edge->v1 == optTri->v[1] && edge->v2 == optTri->v[2] )
|
|
|| ( edge->v1 == optTri->v[2] && edge->v2 == optTri->v[0] ) ) {
|
|
if ( edge->backTri ) {
|
|
common->Printf( "Warning: LinkTriToEdge: already in use\n" );
|
|
return;
|
|
}
|
|
edge->backTri = optTri;
|
|
return;
|
|
}
|
|
if ( ( edge->v1 == optTri->v[1] && edge->v2 == optTri->v[0] )
|
|
|| ( edge->v1 == optTri->v[2] && edge->v2 == optTri->v[1] )
|
|
|| ( edge->v1 == optTri->v[0] && edge->v2 == optTri->v[2] ) ) {
|
|
if ( edge->frontTri ) {
|
|
common->Printf( "Warning: LinkTriToEdge: already in use\n" );
|
|
return;
|
|
}
|
|
edge->frontTri = optTri;
|
|
return;
|
|
}
|
|
common->Error( "LinkTriToEdge: edge not found on tri" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CreateOptTri
|
|
===============
|
|
*/
|
|
static void CreateOptTri( optVertex_t *first, optEdge_t *e1, optEdge_t *e2, optIsland_t *island ) {
|
|
optEdge_t *opposite;
|
|
optVertex_t *second, *third;
|
|
optTri_t *optTri;
|
|
mapTri_t *tri;
|
|
|
|
if ( e1->v1 == first ) {
|
|
second = e1->v2;
|
|
} else if ( e1->v2 == first ) {
|
|
second = e1->v1;
|
|
} else {
|
|
common->Error( "CreateOptTri: mislinked edge" );
|
|
return;
|
|
}
|
|
|
|
if ( e2->v1 == first ) {
|
|
third = e2->v2;
|
|
} else if ( e2->v2 == first ) {
|
|
third = e2->v1;
|
|
} else {
|
|
common->Error( "CreateOptTri: mislinked edge" );
|
|
return;
|
|
}
|
|
|
|
if ( !IsTriangleValid( first, second, third ) ) {
|
|
common->Error( "CreateOptTri: invalid" );
|
|
return;
|
|
}
|
|
|
|
//DrawEdges( island );
|
|
|
|
// identify the third edge
|
|
if ( dmapGlobals.drawflag ) {
|
|
qglColor3f(1,1,0);
|
|
qglBegin( GL_LINES );
|
|
qglVertex3fv( e1->v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( e1->v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
qglColor3f(0,1,1);
|
|
qglBegin( GL_LINES );
|
|
qglVertex3fv( e2->v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( e2->v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
for ( opposite = second->edges ; opposite ; ) {
|
|
if ( opposite != e1 && ( opposite->v1 == third || opposite->v2 == third ) ) {
|
|
break;
|
|
}
|
|
if ( opposite->v1 == second ) {
|
|
opposite = opposite->v1link;
|
|
} else if ( opposite->v2 == second ) {
|
|
opposite = opposite->v2link;
|
|
} else {
|
|
common->Error( "BuildOptTriangles: mislinked edge" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !opposite ) {
|
|
common->Printf( "Warning: BuildOptTriangles: couldn't locate opposite\n" );
|
|
return;
|
|
}
|
|
|
|
if ( dmapGlobals.drawflag ) {
|
|
qglColor3f(1,0,1);
|
|
qglBegin( GL_LINES );
|
|
qglVertex3fv( opposite->v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( opposite->v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
// create new triangle
|
|
optTri = (optTri_t *)Mem_Alloc( sizeof( *optTri ) );
|
|
optTri->v[0] = first;
|
|
optTri->v[1] = second;
|
|
optTri->v[2] = third;
|
|
optTri->midpoint = ( optTri->v[0]->pv + optTri->v[1]->pv + optTri->v[2]->pv ) * ( 1.0f / 3.0f );
|
|
optTri->next = island->tris;
|
|
island->tris = optTri;
|
|
|
|
if ( dmapGlobals.drawflag ) {
|
|
qglColor3f( 1, 1, 1 );
|
|
qglPointSize( 4 );
|
|
qglBegin( GL_POINTS );
|
|
qglVertex3fv( optTri->midpoint.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
// find the midpoint, and scan through all the original triangles to
|
|
// see if it is inside any of them
|
|
for ( tri = island->group->triList ; tri ; tri = tri->next ) {
|
|
if ( PointInTri( optTri->midpoint, tri, island ) ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( tri ) {
|
|
optTri->filled = true;
|
|
} else {
|
|
optTri->filled = false;
|
|
}
|
|
if ( dmapGlobals.drawflag ) {
|
|
if ( optTri->filled ) {
|
|
qglColor3f( ( 128 + orandom.RandomInt( 127 ) )/ 255.0, 0, 0 );
|
|
} else {
|
|
qglColor3f( 0, ( 128 + orandom.RandomInt( 127 ) ) / 255.0, 0 );
|
|
}
|
|
qglBegin( GL_TRIANGLES );
|
|
qglVertex3fv( optTri->v[0]->pv.ToFloatPtr() );
|
|
qglVertex3fv( optTri->v[1]->pv.ToFloatPtr() );
|
|
qglVertex3fv( optTri->v[2]->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglColor3f( 1, 1, 1 );
|
|
qglBegin( GL_LINE_LOOP );
|
|
qglVertex3fv( optTri->v[0]->pv.ToFloatPtr() );
|
|
qglVertex3fv( optTri->v[1]->pv.ToFloatPtr() );
|
|
qglVertex3fv( optTri->v[2]->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
// link the triangle to it's edges
|
|
LinkTriToEdge( optTri, e1 );
|
|
LinkTriToEdge( optTri, e2 );
|
|
LinkTriToEdge( optTri, opposite );
|
|
}
|
|
|
|
// debugging tool
|
|
#if 0
|
|
static void ReportNearbyVertexes( const optVertex_t *v, const optIsland_t *island ) {
|
|
const optVertex_t *ov;
|
|
float d;
|
|
idVec3 vec;
|
|
|
|
common->Printf( "verts near 0x%p (%f, %f)\n", v, v->pv[0], v->pv[1] );
|
|
for ( ov = island->verts ; ov ; ov = ov->islandLink ) {
|
|
if ( ov == v ) {
|
|
continue;
|
|
}
|
|
|
|
vec = ov->pv - v->pv;
|
|
|
|
d = vec.Length();
|
|
if ( d < 1 ) {
|
|
common->Printf( "0x%p = (%f, %f)\n", ov, ov->pv[0], ov->pv[1] );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
====================
|
|
BuildOptTriangles
|
|
|
|
Generate a new list of triangles from the optEdeges
|
|
====================
|
|
*/
|
|
static void BuildOptTriangles( optIsland_t *island ) {
|
|
optVertex_t *ov, *second = NULL, *third = NULL, *middle = NULL;
|
|
optEdge_t *e1, *e1Next = NULL, *e2, *e2Next = NULL, *check, *checkNext = NULL;
|
|
|
|
// free them
|
|
FreeOptTriangles( island );
|
|
|
|
// clear the vertex emitted flags
|
|
for ( ov = island->verts ; ov ; ov = ov->islandLink ) {
|
|
ov->emited = false;
|
|
}
|
|
|
|
// clear the edge triangle links
|
|
for ( check = island->edges ; check ; check = check->islandLink ) {
|
|
check->frontTri = check->backTri = NULL;
|
|
}
|
|
|
|
// check all possible triangle made up out of the
|
|
// edges coming off the vertex
|
|
for ( ov = island->verts ; ov ; ov = ov->islandLink ) {
|
|
if ( !ov->edges ) {
|
|
continue;
|
|
}
|
|
|
|
#if 0
|
|
if ( dmapGlobals.drawflag && ov == (optVertex_t *)0x1845a60 ) {
|
|
for ( e1 = ov->edges ; e1 ; e1 = e1Next ) {
|
|
qglBegin( GL_LINES );
|
|
qglColor3f( 0,1,0 );
|
|
qglVertex3fv( e1->v1->pv.ToFloatPtr() );
|
|
qglVertex3fv( e1->v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
if ( e1->v1 == ov ) {
|
|
e1Next = e1->v1link;
|
|
} else if ( e1->v2 == ov ) {
|
|
e1Next = e1->v2link;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
for ( e1 = ov->edges ; e1 ; e1 = e1Next ) {
|
|
if ( e1->v1 == ov ) {
|
|
second = e1->v2;
|
|
e1Next = e1->v1link;
|
|
} else if ( e1->v2 == ov ) {
|
|
second = e1->v1;
|
|
e1Next = e1->v2link;
|
|
} else {
|
|
common->Error( "BuildOptTriangles: mislinked edge" );
|
|
}
|
|
|
|
// if the vertex has already been used, it can't be used again
|
|
if ( second->emited ) {
|
|
continue;
|
|
}
|
|
|
|
for ( e2 = ov->edges ; e2 ; e2 = e2Next ) {
|
|
if ( e2->v1 == ov ) {
|
|
third = e2->v2;
|
|
e2Next = e2->v1link;
|
|
} else if ( e2->v2 == ov ) {
|
|
third = e2->v1;
|
|
e2Next = e2->v2link;
|
|
} else {
|
|
common->Error( "BuildOptTriangles: mislinked edge" );
|
|
}
|
|
if ( e2 == e1 ) {
|
|
continue;
|
|
}
|
|
|
|
// if the vertex has already been used, it can't be used again
|
|
if ( third->emited ) {
|
|
continue;
|
|
}
|
|
|
|
// if the triangle is backwards or degenerate, don't use it
|
|
if ( !IsTriangleValid( ov, second, third ) ) {
|
|
continue;
|
|
}
|
|
|
|
// see if any other edge bisects these two, which means
|
|
// this triangle shouldn't be used
|
|
for ( check = ov->edges ; check ; check = checkNext ) {
|
|
if ( check->v1 == ov ) {
|
|
middle = check->v2;
|
|
checkNext = check->v1link;
|
|
} else if ( check->v2 == ov ) {
|
|
middle = check->v1;
|
|
checkNext = check->v2link;
|
|
} else {
|
|
common->Error( "BuildOptTriangles: mislinked edge" );
|
|
}
|
|
|
|
if ( check == e1 || check == e2 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( IsTriangleValid( ov, second, middle )
|
|
&& IsTriangleValid( ov, middle, third ) ) {
|
|
break; // should use the subdivided ones
|
|
}
|
|
}
|
|
|
|
if ( check ) {
|
|
continue; // don't use it
|
|
}
|
|
|
|
// the triangle is valid
|
|
CreateOptTri( ov, e1, e2, island );
|
|
}
|
|
}
|
|
|
|
// later vertexes will not emit triangles that use an
|
|
// edge that this vert has already used
|
|
ov->emited = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
====================
|
|
RegenerateTriangles
|
|
|
|
Add new triangles to the group's regeneratedTris
|
|
====================
|
|
*/
|
|
static void RegenerateTriangles( optIsland_t *island ) {
|
|
optTri_t *optTri;
|
|
mapTri_t *tri;
|
|
int c_out;
|
|
|
|
c_out = 0;
|
|
|
|
for ( optTri = island->tris ; optTri ; optTri = optTri->next ) {
|
|
if ( !optTri->filled ) {
|
|
continue;
|
|
}
|
|
|
|
// create a new mapTri_t
|
|
tri = AllocTri();
|
|
|
|
tri->material = island->group->material;
|
|
tri->mergeGroup = island->group->mergeGroup;
|
|
|
|
tri->v[0] = optTri->v[0]->v;
|
|
tri->v[1] = optTri->v[1]->v;
|
|
tri->v[2] = optTri->v[2]->v;
|
|
|
|
idPlane plane;
|
|
PlaneForTri( tri, plane );
|
|
if ( plane.Normal() * dmapGlobals.mapPlanes[ island->group->planeNum ].Normal() <= 0 ) {
|
|
// this can happen reasonably when a triangle is nearly degenerate in
|
|
// optimization planar space, and winds up being degenerate in 3D space
|
|
common->Printf( "WARNING: backwards triangle generated!\n" );
|
|
// discard it
|
|
FreeTri( tri );
|
|
continue;
|
|
}
|
|
|
|
c_out++;
|
|
tri->next = island->group->regeneratedTris;
|
|
island->group->regeneratedTris = tri;
|
|
}
|
|
|
|
FreeOptTriangles( island );
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i tris out\n", c_out );
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
/*
|
|
====================
|
|
RemoveInteriorEdges
|
|
|
|
Edges that have triangles of the same type (filled / empty)
|
|
on both sides will be removed
|
|
====================
|
|
*/
|
|
static void RemoveInteriorEdges( optIsland_t *island ) {
|
|
int c_interiorEdges;
|
|
int c_exteriorEdges;
|
|
optEdge_t *e, *next;
|
|
bool front, back;
|
|
|
|
c_exteriorEdges = 0;
|
|
c_interiorEdges = 0;
|
|
for ( e = island->edges ; e ; e = next ) {
|
|
// we might remove the edge, so get the next link now
|
|
next = e->islandLink;
|
|
|
|
if ( !e->frontTri ) {
|
|
front = false;
|
|
} else {
|
|
front = e->frontTri->filled;
|
|
}
|
|
if ( !e->backTri ) {
|
|
back = false;
|
|
} else {
|
|
back = e->backTri->filled;
|
|
}
|
|
|
|
if ( front == back ) {
|
|
// free the edge
|
|
UnlinkEdge( e, island );
|
|
c_interiorEdges++;
|
|
continue;
|
|
}
|
|
|
|
c_exteriorEdges++;
|
|
}
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i original interior edges\n", c_interiorEdges );
|
|
common->Printf( "%6i original exterior edges\n", c_exteriorEdges );
|
|
}
|
|
}
|
|
|
|
//==================================================================================
|
|
|
|
typedef struct {
|
|
optVertex_t *v1, *v2;
|
|
} originalEdges_t;
|
|
|
|
/*
|
|
=================
|
|
AddEdgeIfNotAlready
|
|
=================
|
|
*/
|
|
void AddEdgeIfNotAlready( optVertex_t *v1, optVertex_t *v2 ) {
|
|
optEdge_t *e;
|
|
|
|
// make sure that there isn't an identical edge already added
|
|
for ( e = v1->edges ; e ; ) {
|
|
if ( ( e->v1 == v1 && e->v2 == v2 ) || ( e->v1 == v2 && e->v2 == v1 ) ) {
|
|
return; // already added
|
|
}
|
|
if ( e->v1 == v1 ) {
|
|
e = e->v1link;
|
|
} else if ( e->v2 == v1 ) {
|
|
e = e->v2link;
|
|
} else {
|
|
common->Error( "SplitEdgeByList: bad edge link" );
|
|
}
|
|
}
|
|
|
|
// this edge is a keeper
|
|
e = AllocEdge();
|
|
e->v1 = v1;
|
|
e->v2 = v2;
|
|
|
|
e->islandLink = NULL;
|
|
|
|
// link the edge to its verts
|
|
LinkEdge( e );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
DrawOriginalEdges
|
|
=================
|
|
*/
|
|
static void DrawOriginalEdges( int numOriginalEdges, originalEdges_t *originalEdges ) {
|
|
int i;
|
|
|
|
if ( !dmapGlobals.drawflag ) {
|
|
return;
|
|
}
|
|
Draw_ClearWindow();
|
|
|
|
qglBegin( GL_LINES );
|
|
for ( i = 0 ; i < numOriginalEdges ; i++ ) {
|
|
qglColor3f( 1, 0, 0 );
|
|
qglVertex3fv( originalEdges[i].v1->pv.ToFloatPtr() );
|
|
qglColor3f( 0, 0, 0 );
|
|
qglVertex3fv( originalEdges[i].v2->pv.ToFloatPtr() );
|
|
}
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
|
|
|
|
typedef struct edgeCrossing_s {
|
|
struct edgeCrossing_s *next;
|
|
optVertex_t *ov;
|
|
} edgeCrossing_t;
|
|
|
|
static originalEdges_t *originalEdges;
|
|
static int numOriginalEdges;
|
|
|
|
/*
|
|
=================
|
|
AddOriginalTriangle
|
|
=================
|
|
*/
|
|
static void AddOriginalTriangle( optVertex_t *v[3] ) {
|
|
optVertex_t *v1, *v2;
|
|
|
|
// if this triangle is backwards (possible with epsilon issues)
|
|
// ignore it completely
|
|
if ( !IsTriangleValid( v[0], v[1], v[2] ) ) {
|
|
common->Printf( "WARNING: backwards triangle in input!\n" );
|
|
return;
|
|
}
|
|
|
|
for ( int i = 0 ; i < 3 ; i++ ) {
|
|
v1 = v[i];
|
|
v2 = v[(i+1)%3];
|
|
|
|
if ( v1 == v2 ) {
|
|
// this probably shouldn't happen, because the
|
|
// tri would be degenerate
|
|
continue;
|
|
}
|
|
int j;
|
|
// see if there is an existing one
|
|
for ( j = 0 ; j < numOriginalEdges ; j++ ) {
|
|
if ( originalEdges[j].v1 == v1 && originalEdges[j].v2 == v2 ) {
|
|
break;
|
|
}
|
|
if ( originalEdges[j].v2 == v1 && originalEdges[j].v1 == v2 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( j == numOriginalEdges ) {
|
|
// add it
|
|
originalEdges[j].v1 = v1;
|
|
originalEdges[j].v2 = v2;
|
|
numOriginalEdges++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
AddOriginalEdges
|
|
=================
|
|
*/
|
|
static void AddOriginalEdges( optimizeGroup_t *opt ) {
|
|
mapTri_t *tri;
|
|
optVertex_t *v[3];
|
|
int numTris;
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "----\n" );
|
|
common->Printf( "%6i original tris\n", CountTriList( opt->triList ) );
|
|
}
|
|
|
|
optBounds.Clear();
|
|
|
|
// allocate space for max possible edges
|
|
numTris = CountTriList( opt->triList );
|
|
originalEdges = (originalEdges_t *)Mem_Alloc( numTris * 3 * sizeof( *originalEdges ) );
|
|
numOriginalEdges = 0;
|
|
|
|
// add all unique triangle edges
|
|
numOptVerts = 0;
|
|
numOptEdges = 0;
|
|
for ( tri = opt->triList ; tri ; tri = tri->next ) {
|
|
v[0] = tri->optVert[0] = FindOptVertex( &tri->v[0], opt );
|
|
v[1] = tri->optVert[1] = FindOptVertex( &tri->v[1], opt );
|
|
v[2] = tri->optVert[2] = FindOptVertex( &tri->v[2], opt );
|
|
|
|
AddOriginalTriangle( v );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
SplitOriginalEdgesAtCrossings
|
|
=====================
|
|
*/
|
|
void SplitOriginalEdgesAtCrossings( optimizeGroup_t *opt ) {
|
|
int i, j, k, l;
|
|
int numOriginalVerts;
|
|
edgeCrossing_t **crossings;
|
|
|
|
numOriginalVerts = numOptVerts;
|
|
// now split any crossing edges and create optEdges
|
|
// linked to the vertexes
|
|
|
|
// debug drawing bounds
|
|
dmapGlobals.drawBounds = optBounds;
|
|
|
|
dmapGlobals.drawBounds[0][0] -= 2;
|
|
dmapGlobals.drawBounds[0][1] -= 2;
|
|
dmapGlobals.drawBounds[1][0] += 2;
|
|
dmapGlobals.drawBounds[1][1] += 2;
|
|
|
|
// generate crossing points between all the original edges
|
|
crossings = (edgeCrossing_t **)Mem_ClearedAlloc( numOriginalEdges * sizeof( *crossings ) );
|
|
|
|
for ( i = 0 ; i < numOriginalEdges ; i++ ) {
|
|
if ( dmapGlobals.drawflag ) {
|
|
DrawOriginalEdges( numOriginalEdges, originalEdges );
|
|
qglBegin( GL_LINES );
|
|
qglColor3f( 0, 1, 0 );
|
|
qglVertex3fv( originalEdges[i].v1->pv.ToFloatPtr() );
|
|
qglColor3f( 0, 0, 1 );
|
|
qglVertex3fv( originalEdges[i].v2->pv.ToFloatPtr() );
|
|
qglEnd();
|
|
qglFlush();
|
|
}
|
|
for ( j = i+1 ; j < numOriginalEdges ; j++ ) {
|
|
optVertex_t *v1, *v2, *v3, *v4;
|
|
optVertex_t *newVert;
|
|
edgeCrossing_t *cross;
|
|
|
|
v1 = originalEdges[i].v1;
|
|
v2 = originalEdges[i].v2;
|
|
v3 = originalEdges[j].v1;
|
|
v4 = originalEdges[j].v2;
|
|
|
|
if ( !EdgesCross( v1, v2, v3, v4 ) ) {
|
|
continue;
|
|
}
|
|
|
|
// this is the only point in optimization where
|
|
// completely new points are created, and it only
|
|
// happens if there is overlapping coplanar
|
|
// geometry in the source triangles
|
|
newVert = EdgeIntersection( v1, v2, v3, v4, opt );
|
|
|
|
if ( !newVert ) {
|
|
//common->Printf( "lines %i (%i to %i) and %i (%i to %i) are colinear\n", i, v1 - optVerts, v2 - optVerts,
|
|
// j, v3 - optVerts, v4 - optVerts ); // !@#
|
|
// colinear, so add both verts of each edge to opposite
|
|
if ( VertexBetween( v3, v1, v2 ) ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = v3;
|
|
cross->next = crossings[i];
|
|
crossings[i] = cross;
|
|
}
|
|
|
|
if ( VertexBetween( v4, v1, v2 ) ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = v4;
|
|
cross->next = crossings[i];
|
|
crossings[i] = cross;
|
|
}
|
|
|
|
if ( VertexBetween( v1, v3, v4 ) ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = v1;
|
|
cross->next = crossings[j];
|
|
crossings[j] = cross;
|
|
}
|
|
|
|
if ( VertexBetween( v2, v3, v4 ) ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = v2;
|
|
cross->next = crossings[j];
|
|
crossings[j] = cross;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
#if 0
|
|
if ( newVert && newVert != v1 && newVert != v2 && newVert != v3 && newVert != v4 ) {
|
|
common->Printf( "lines %i (%i to %i) and %i (%i to %i) cross at new point %i\n", i, v1 - optVerts, v2 - optVerts,
|
|
j, v3 - optVerts, v4 - optVerts, newVert - optVerts );
|
|
} else if ( newVert ) {
|
|
common->Printf( "lines %i (%i to %i) and %i (%i to %i) intersect at old point %i\n", i, v1 - optVerts, v2 - optVerts,
|
|
j, v3 - optVerts, v4 - optVerts, newVert - optVerts );
|
|
}
|
|
#endif
|
|
if ( newVert != v1 && newVert != v2 ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = newVert;
|
|
cross->next = crossings[i];
|
|
crossings[i] = cross;
|
|
}
|
|
|
|
if ( newVert != v3 && newVert != v4 ) {
|
|
cross = (edgeCrossing_t *)Mem_ClearedAlloc( sizeof( *cross ) );
|
|
cross->ov = newVert;
|
|
cross->next = crossings[j];
|
|
crossings[j] = cross;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// now split each edge by its crossing points
|
|
// colinear edges will have duplicated edges added, but it won't hurt anything
|
|
for ( i = 0 ; i < numOriginalEdges ; i++ ) {
|
|
edgeCrossing_t *cross, *nextCross;
|
|
int numCross;
|
|
optVertex_t **sorted;
|
|
|
|
numCross = 0;
|
|
for ( cross = crossings[i] ; cross ; cross = cross->next ) {
|
|
numCross++;
|
|
}
|
|
numCross += 2; // account for originals
|
|
sorted = (optVertex_t **)Mem_Alloc( numCross * sizeof( *sorted ) );
|
|
sorted[0] = originalEdges[i].v1;
|
|
sorted[1] = originalEdges[i].v2;
|
|
j = 2;
|
|
for ( cross = crossings[i] ; cross ; cross = nextCross ) {
|
|
nextCross = cross->next;
|
|
sorted[j] = cross->ov;
|
|
Mem_Free( cross );
|
|
j++;
|
|
}
|
|
|
|
// add all possible fragment combinations that aren't divided
|
|
// by another point
|
|
for ( j = 0 ; j < numCross ; j++ ) {
|
|
for ( k = j+1 ; k < numCross ; k++ ) {
|
|
for ( l = 0 ; l < numCross ; l++ ) {
|
|
if ( sorted[l] == sorted[j] || sorted[l] == sorted[k] ) {
|
|
continue;
|
|
}
|
|
if ( sorted[j] == sorted[k] ) {
|
|
continue;
|
|
}
|
|
if ( VertexBetween( sorted[l], sorted[j], sorted[k] ) ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( l == numCross ) {
|
|
//common->Printf( "line %i fragment from point %i to %i\n", i, sorted[j] - optVerts, sorted[k] - optVerts );
|
|
AddEdgeIfNotAlready( sorted[j], sorted[k] );
|
|
}
|
|
}
|
|
}
|
|
|
|
Mem_Free( sorted );
|
|
}
|
|
|
|
|
|
Mem_Free( crossings );
|
|
Mem_Free( originalEdges );
|
|
|
|
// check for duplicated edges
|
|
for ( i = 0 ; i < numOptEdges ; i++ ) {
|
|
for ( j = i+1 ; j < numOptEdges ; j++ ) {
|
|
if ( ( optEdges[i].v1 == optEdges[j].v1 && optEdges[i].v2 == optEdges[j].v2 )
|
|
|| ( optEdges[i].v1 == optEdges[j].v2 && optEdges[i].v2 == optEdges[j].v1 ) ) {
|
|
common->Printf( "duplicated optEdge\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i original edges\n", numOriginalEdges );
|
|
common->Printf( "%6i edges after splits\n", numOptEdges );
|
|
common->Printf( "%6i original vertexes\n", numOriginalVerts );
|
|
common->Printf( "%6i vertexes after splits\n", numOptVerts );
|
|
}
|
|
}
|
|
|
|
//=================================================================
|
|
|
|
|
|
/*
|
|
===================
|
|
CullUnusedVerts
|
|
|
|
Unlink any verts with no edges, so they
|
|
won't be used in the retriangulation
|
|
===================
|
|
*/
|
|
static void CullUnusedVerts( optIsland_t *island ) {
|
|
optVertex_t **prev, *vert;
|
|
int c_keep, c_free;
|
|
optEdge_t *edge;
|
|
|
|
c_keep = 0;
|
|
c_free = 0;
|
|
|
|
for ( prev = &island->verts ; *prev ; ) {
|
|
vert = *prev;
|
|
|
|
if ( !vert->edges ) {
|
|
// free it
|
|
*prev = vert->islandLink;
|
|
c_free++;
|
|
} else {
|
|
edge = vert->edges;
|
|
if ( ( edge->v1 == vert && !edge->v1link )
|
|
|| ( edge->v2 == vert && !edge->v2link ) ) {
|
|
// is is occasionally possible to get a vert
|
|
// with only a single edge when colinear optimizations
|
|
// crunch down a complex sliver
|
|
UnlinkEdge( edge, island );
|
|
// free it
|
|
*prev = vert->islandLink;
|
|
c_free++;
|
|
} else {
|
|
prev = &vert->islandLink;
|
|
c_keep++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i verts kept\n", c_keep );
|
|
common->Printf( "%6i verts freed\n", c_free );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
====================
|
|
OptimizeIsland
|
|
|
|
At this point, all needed vertexes are already in the
|
|
list, including any that were added at crossing points.
|
|
|
|
Interior and colinear vertexes will be removed, and
|
|
a new triangulation will be created.
|
|
====================
|
|
*/
|
|
static void OptimizeIsland( optIsland_t *island ) {
|
|
// add space-filling fake edges so we have a complete
|
|
// triangulation of a convex hull before optimization
|
|
AddInteriorEdges( island );
|
|
DrawEdges( island );
|
|
|
|
// determine all the possible triangles, and decide if
|
|
// the are filled or empty
|
|
BuildOptTriangles( island );
|
|
|
|
// remove interior vertexes that have filled triangles
|
|
// between all their edges
|
|
RemoveInteriorEdges( island );
|
|
DrawEdges( island );
|
|
|
|
ValidateEdgeCounts( island );
|
|
|
|
// remove vertexes that only have two colinear edges
|
|
CombineColinearEdges( island );
|
|
CullUnusedVerts( island );
|
|
DrawEdges( island );
|
|
|
|
// add new internal edges between the remaining exterior edges
|
|
// to give us a full triangulation again
|
|
AddInteriorEdges( island );
|
|
DrawEdges( island );
|
|
|
|
// determine all the possible triangles, and decide if
|
|
// the are filled or empty
|
|
BuildOptTriangles( island );
|
|
|
|
// make mapTri_t out of the filled optTri_t
|
|
RegenerateTriangles( island );
|
|
}
|
|
|
|
/*
|
|
================
|
|
AddVertexToIsland_r
|
|
================
|
|
*/
|
|
#if 0
|
|
static void AddVertexToIsland_r( optVertex_t *vert, optIsland_t *island ) {
|
|
optEdge_t *e;
|
|
|
|
// we can't just check islandLink, because the
|
|
// last vert will have a NULL
|
|
if ( vert->addedToIsland ) {
|
|
return;
|
|
}
|
|
vert->addedToIsland = true;
|
|
vert->islandLink = island->verts;
|
|
island->verts = vert;
|
|
|
|
for ( e = vert->edges ; e ; ) {
|
|
if ( !e->addedToIsland ) {
|
|
e->addedToIsland = true;
|
|
|
|
e->islandLink = island->edges;
|
|
island->edges = e;
|
|
}
|
|
|
|
if ( e->v1 == vert ) {
|
|
AddVertexToIsland_r( e->v2, island );
|
|
e = e->v1link;
|
|
continue;
|
|
}
|
|
if ( e->v2 == vert ) {
|
|
AddVertexToIsland_r( e->v1, island );
|
|
e = e->v2link;
|
|
continue;
|
|
}
|
|
common->Error( "AddVertexToIsland_r: mislinked vert" );
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
====================
|
|
SeparateIslands
|
|
|
|
While the algorithm should theoretically handle any collection
|
|
of triangles, there are speed and stability benefits to making
|
|
it work on as small a list as possible, so separate disconnected
|
|
collections of edges and process separately.
|
|
|
|
FIXME: we need to separate the source triangles before
|
|
doing this, because PointInSourceTris() can give a bad answer if
|
|
the source list has triangles not used in the optimization
|
|
====================
|
|
*/
|
|
#if 0
|
|
static void SeparateIslands( optimizeGroup_t *opt ) {
|
|
int i;
|
|
optIsland_t island;
|
|
int numIslands;
|
|
|
|
DrawAllEdges();
|
|
|
|
numIslands = 0;
|
|
for ( i = 0 ; i < numOptVerts ; i++ ) {
|
|
if ( optVerts[i].addedToIsland ) {
|
|
continue;
|
|
}
|
|
numIslands++;
|
|
memset( &island, 0, sizeof( island ) );
|
|
island.group = opt;
|
|
AddVertexToIsland_r( &optVerts[i], &island );
|
|
OptimizeIsland( &island );
|
|
}
|
|
if ( dmapGlobals.verbose ) {
|
|
common->Printf( "%6i islands\n", numIslands );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void DontSeparateIslands( optimizeGroup_t *opt ) {
|
|
int i;
|
|
optIsland_t island;
|
|
|
|
DrawAllEdges();
|
|
|
|
memset( &island, 0, sizeof( island ) );
|
|
island.group = opt;
|
|
|
|
// link everything together
|
|
for ( i = 0 ; i < numOptVerts ; i++ ) {
|
|
optVerts[i].islandLink = island.verts;
|
|
island.verts = &optVerts[i];
|
|
}
|
|
|
|
for ( i = 0 ; i < numOptEdges ; i++ ) {
|
|
optEdges[i].islandLink = island.edges;
|
|
island.edges = &optEdges[i];
|
|
}
|
|
|
|
OptimizeIsland( &island );
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
PointInSourceTris
|
|
|
|
This is a sloppy bounding box check
|
|
====================
|
|
*/
|
|
#if 0
|
|
static bool PointInSourceTris( float x, float y, float z, optimizeGroup_t *opt ) {
|
|
mapTri_t *tri;
|
|
idBounds b;
|
|
idVec3 p;
|
|
|
|
if ( !opt->material->IsDrawn() ) {
|
|
return false;
|
|
}
|
|
|
|
p[0] = x;
|
|
p[1] = y;
|
|
p[2] = z;
|
|
for ( tri = opt->triList ; tri ; tri = tri->next ) {
|
|
b.Clear();
|
|
b.AddPoint( tri->v[0].xyz );
|
|
b.AddPoint( tri->v[1].xyz );
|
|
b.AddPoint( tri->v[2].xyz );
|
|
|
|
if ( b.ContainsPoint( p ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
====================
|
|
OptimizeOptList
|
|
====================
|
|
*/
|
|
static void OptimizeOptList( optimizeGroup_t *opt ) {
|
|
optimizeGroup_t *oldNext;
|
|
|
|
// fix the t junctions among this single list
|
|
// so we can match edges
|
|
// can we avoid doing this if colinear vertexes break edges?
|
|
oldNext = opt->nextGroup;
|
|
opt->nextGroup = NULL;
|
|
FixAreaGroupsTjunctions( opt );
|
|
opt->nextGroup = oldNext;
|
|
|
|
// create the 2D vectors
|
|
dmapGlobals.mapPlanes[opt->planeNum].Normal().NormalVectors( opt->axis[0], opt->axis[1] );
|
|
|
|
AddOriginalEdges( opt );
|
|
SplitOriginalEdgesAtCrossings( opt );
|
|
|
|
#if 0
|
|
// seperate any discontinuous areas for individual optimization
|
|
// to reduce the scope of the problem
|
|
SeparateIslands( opt );
|
|
#else
|
|
DontSeparateIslands( opt );
|
|
#endif
|
|
|
|
// now free the hash verts
|
|
FreeTJunctionHash();
|
|
|
|
// free the original list and use the new one
|
|
FreeTriList( opt->triList );
|
|
opt->triList = opt->regeneratedTris;
|
|
opt->regeneratedTris = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SetGroupTriPlaneNums
|
|
|
|
Copies the group planeNum to every triangle in each group
|
|
==================
|
|
*/
|
|
void SetGroupTriPlaneNums( optimizeGroup_t *groups ) {
|
|
mapTri_t *tri;
|
|
optimizeGroup_t *group;
|
|
|
|
for ( group = groups ; group ; group = group->nextGroup ) {
|
|
for ( tri = group->triList ; tri ; tri = tri->next ) {
|
|
tri->planeNum = group->planeNum;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
OptimizeGroupList
|
|
|
|
This will also fix tjunctions
|
|
|
|
===================
|
|
*/
|
|
void OptimizeGroupList( optimizeGroup_t *groupList ) {
|
|
int c_in, c_edge, c_tjunc2;
|
|
optimizeGroup_t *group;
|
|
|
|
if ( !groupList ) {
|
|
return;
|
|
}
|
|
|
|
c_in = CountGroupListTris( groupList );
|
|
|
|
// optimize and remove colinear edges, which will
|
|
// re-introduce some t junctions
|
|
for ( group = groupList ; group ; group = group->nextGroup ) {
|
|
OptimizeOptList( group );
|
|
}
|
|
c_edge = CountGroupListTris( groupList );
|
|
|
|
// fix t junctions again
|
|
FixAreaGroupsTjunctions( groupList );
|
|
FreeTJunctionHash();
|
|
c_tjunc2 = CountGroupListTris( groupList );
|
|
|
|
SetGroupTriPlaneNums( groupList );
|
|
|
|
common->Printf( "----- OptimizeAreaGroups Results -----\n" );
|
|
common->Printf( "%6i tris in\n", c_in );
|
|
common->Printf( "%6i tris after edge removal optimization\n", c_edge );
|
|
common->Printf( "%6i tris after final t junction fixing\n", c_tjunc2 );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
OptimizeEntity
|
|
==================
|
|
*/
|
|
void OptimizeEntity( uEntity_t *e ) {
|
|
int i;
|
|
|
|
common->Printf( "----- OptimizeEntity -----\n" );
|
|
for ( i = 0 ; i < e->numAreas ; i++ ) {
|
|
OptimizeGroupList( e->areas[i].groups );
|
|
}
|
|
}
|