mirror of
https://github.com/Garux/netradiant-custom.git
synced 2026-09-28 00:00:03 +02:00
629 lines
15 KiB
C++
629 lines
15 KiB
C++
/* -------------------------------------------------------------------------------
|
|
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
----------------------------------------------------------------------------------
|
|
|
|
This code has been altered significantly from its original form, to support
|
|
several games based on the Quake III Arena engine, in the form of "Q3Map2."
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* dependencies */
|
|
#include "q3map2.h"
|
|
#include <optional>
|
|
|
|
|
|
|
|
static int numFogFragments;
|
|
static int numFogPatchFragments;
|
|
|
|
|
|
|
|
/*
|
|
SplitMeshByPlane()
|
|
chops a mesh by a plane
|
|
*/
|
|
/// \returns either {front, back} or {front, {}} or {{}, back}
|
|
/// frees or reuses \param in
|
|
static std::pair<std::optional<mesh_t>, std::optional<mesh_t>> SplitMeshByPlane( mesh_t&& in, const Plane3f& plane ){
|
|
int split;
|
|
float d[MAX_PATCH_SIZE][MAX_PATCH_SIZE];
|
|
int c_front, c_back, c_on;
|
|
|
|
for ( int i = 0; i < 2; ++i ) {
|
|
const bspDrawVert_t *dv = in.verts();
|
|
c_front = 0;
|
|
c_back = 0;
|
|
c_on = 0;
|
|
for ( int h = 0; h < in.height; ++h ) {
|
|
for ( int w = 0; w < in.width; ++w, ++dv ) {
|
|
d[h][w] = plane3_distance_to_point( plane, dv->xyz );
|
|
if ( d[h][w] > ON_EPSILON ) {
|
|
c_front++;
|
|
}
|
|
else if ( d[h][w] < -ON_EPSILON ) {
|
|
c_back++;
|
|
}
|
|
else {
|
|
c_on++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !c_front ) {
|
|
return { {}, std::move( in ) };
|
|
}
|
|
if ( !c_back ) {
|
|
return { std::move( in ), {} };
|
|
}
|
|
|
|
// find a split point
|
|
split = -1;
|
|
for ( int w = 0; w < in.width - 1; ++w ) {
|
|
if ( ( d[0][w] < 0 ) != ( d[0][w + 1] < 0 ) ) {
|
|
if ( split == -1 ) {
|
|
split = w;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( split == -1 ) {
|
|
if ( i == 1 ) {
|
|
Sys_FPrintf( SYS_WRN | SYS_VRBflag, "No crossing points in patch\n" );
|
|
return { std::move( in ), {} };
|
|
}
|
|
|
|
RotateMesh( in );
|
|
continue;
|
|
}
|
|
|
|
// make sure the split point stays the same for all other rows
|
|
for ( int h = 1; h < in.height; ++h ) {
|
|
for ( int w = 0; w < in.width - 1; ++w ) {
|
|
if ( ( d[h][w] < 0 ) != ( d[h][w + 1] < 0 ) ) {
|
|
if ( w != split ) {
|
|
Sys_Warning( "multiple crossing points for patch -- can't clip\n" );
|
|
return { std::move( in ), {} };
|
|
}
|
|
}
|
|
}
|
|
if ( ( d[h][split] < 0 ) == ( d[h][split + 1] < 0 ) ) {
|
|
Sys_Warning( "differing crossing points for patch -- can't clip\n" );
|
|
return { std::move( in ), {} };
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
// create two new meshes
|
|
int fwidth = split + 2, frontAprox = 0;
|
|
if ( !( fwidth & 1 ) ) {
|
|
fwidth++;
|
|
frontAprox = 1;
|
|
}
|
|
if ( fwidth > MAX_PATCH_SIZE ) {
|
|
Error( "MAX_PATCH_SIZE after split" );
|
|
}
|
|
|
|
int bwidth = in.width - split, backAprox = 0;
|
|
if ( !( bwidth & 1 ) ) {
|
|
bwidth++;
|
|
backAprox = 1;
|
|
}
|
|
if ( bwidth > MAX_PATCH_SIZE ) {
|
|
Error( "MAX_PATCH_SIZE after split" );
|
|
}
|
|
|
|
mesh_t f( fwidth, in.height );
|
|
mesh_t b( bwidth, in.height );
|
|
|
|
// distribute the points
|
|
for ( int w = 0; w < in.width; ++w ) {
|
|
for ( int h = 0; h < in.height; ++h ) {
|
|
if ( w <= split ) {
|
|
f[ h ][ w ] = in[ h ][ w ];
|
|
}
|
|
else {
|
|
b[ h ][ w - split + backAprox ] = in[ h ][ w ];
|
|
}
|
|
}
|
|
}
|
|
|
|
// clip the crossing line
|
|
// fixme if 'green' mesh point is on clipping plane, insertion of equal 'green' + 'pink' points leads to fucked up tcs in q3 engine
|
|
for ( int h = 0; h < in.height; ++h )
|
|
{
|
|
bspDrawVert_t& dv = f[ h ][ split + 1 ];
|
|
const bspDrawVert_t& v1 = in[ h ][ split ];
|
|
const bspDrawVert_t& v2 = in[ h ][ split + 1 ];
|
|
|
|
const float frac = d[h][split] / ( d[h][split] - d[h][split + 1] );
|
|
|
|
/* interpolate */
|
|
//% for( i = 0; i < 10; ++i )
|
|
//% dv->xyz[ i ] = v1->xyz[ i ] + frac * (v2->xyz[ i ] - v1->xyz[ i ]);
|
|
//% dv->xyz[10] = 0; // set all 4 colors to 0
|
|
LerpDrawVertAmount( v1, v2, frac, dv );
|
|
|
|
if ( frontAprox ) {
|
|
f[ h ][ split + 2 ] = dv;
|
|
}
|
|
b[ h ][ 0 ] = dv;
|
|
if ( backAprox ) {
|
|
b[ h ][ 1 ] = dv;
|
|
}
|
|
}
|
|
|
|
/*
|
|
PrintMesh( in );
|
|
PrintMesh( f );
|
|
PrintMesh( b );
|
|
*/
|
|
|
|
if ( d[0][0] > 0 )
|
|
return { std::move( f ), std::move( b ) };
|
|
else
|
|
return { std::move( b ), std::move( f ) };
|
|
}
|
|
|
|
|
|
/*
|
|
ChopPatchSurfaceByBrush()
|
|
chops a patch up by a fog brush
|
|
*/
|
|
|
|
static bool ChopPatchSurfaceByBrush( mapDrawSurface_t& ds, const brush_t *b ){
|
|
mesh_t outside[MAX_BRUSH_SIDES];
|
|
int numOutside = 0;
|
|
|
|
mesh_t m( ds.patchWidth, ds.patchHeight, ds.verts.data() );
|
|
|
|
// only split by the top and bottom planes to avoid
|
|
// some messy patch clipping issues
|
|
|
|
for ( int i = 4; i <= 5; ++i ) {
|
|
const plane_t& plane = mapplanes[ b->sides[ i ].planenum ];
|
|
|
|
auto [front, back] = SplitMeshByPlane( std::move( m ), plane.plane );
|
|
|
|
if ( !back ) {
|
|
// nothing actually contained inside
|
|
return false;
|
|
}
|
|
m = std::move( *back );
|
|
|
|
if ( front ) {
|
|
if ( numOutside == MAX_BRUSH_SIDES ) {
|
|
Error( "MAX_BRUSH_SIDES" );
|
|
}
|
|
outside[ numOutside ] = std::move( *front );
|
|
numOutside++;
|
|
}
|
|
}
|
|
|
|
/* all of outside fragments become separate drawsurfs */
|
|
numFogPatchFragments += numOutside;
|
|
for ( int i = 0; i < numOutside; ++i )
|
|
{
|
|
/* ydnar: do this the hacky right way */
|
|
mapDrawSurface_t& newds = AllocDrawSurface( ds );
|
|
newds.patchWidth = outside[ i ].width;
|
|
newds.patchHeight = outside[ i ].height;
|
|
newds.verts.assign( outside[ i ].begin(), outside[ i ].end() );
|
|
}
|
|
|
|
/* only rejigger this patch if it was chopped */
|
|
//% Sys_Printf( "Inside: %d x %d\n", m.width, m.height );
|
|
if ( numOutside > 0 ) {
|
|
/* replace ds with m */
|
|
ds.patchWidth = m.width;
|
|
ds.patchHeight = m.height;
|
|
ds.verts.assign( m.begin(), m.end() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
WindingFromDrawSurf()
|
|
creates a winding from a surface's verts
|
|
*/
|
|
|
|
winding_t WindingFromDrawSurf( const mapDrawSurface_t& ds ){
|
|
// we use the first point of the surface, maybe something more clever would be useful
|
|
// (actually send the whole draw surface would be cool?)
|
|
if ( ds.verts.size() >= MAX_POINTS_ON_WINDING ) {
|
|
const int max = std::min( ds.numVerts(), 256 );
|
|
Vector3 p[256];
|
|
|
|
for ( int i = 0; i < max; ++i ) {
|
|
p[i] = ds.verts[i].xyz;
|
|
}
|
|
|
|
xml_Winding( "WindingFromDrawSurf failed: MAX_POINTS_ON_WINDING exceeded", p, max, true );
|
|
}
|
|
|
|
winding_t w = AllocWinding( ds.verts.size() );
|
|
for ( const bspDrawVert_t& vert : ds.verts ) {
|
|
w.push_back( vert.xyz );
|
|
}
|
|
return w;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ChopFaceSurfaceByBrush()
|
|
chops up a face drawsurface by a fog brush, with a potential fragment left inside
|
|
*/
|
|
|
|
static bool ChopFaceSurfaceByBrush( const entity_t& e, mapDrawSurface_t& ds, const brush_t *b ){
|
|
std::list<winding_t> outside;
|
|
mapDrawSurface_t *newds;
|
|
|
|
|
|
/* dummy check */
|
|
if ( ds.sideRefs.empty() ) {
|
|
return false;
|
|
}
|
|
const side_t& sideRef = *ds.sideRefs.front();
|
|
|
|
/* initial setup */
|
|
winding_t w = WindingFromDrawSurf( ds );
|
|
|
|
/* chop by each brush side */
|
|
for ( const side_t& side : b->sides )
|
|
{
|
|
/* get brush plane */
|
|
const plane_t& plane = mapplanes[ side.planenum ];
|
|
|
|
/* handle coplanar outfacing (don't fog) */
|
|
if ( sideRef.planenum == side.planenum ) {
|
|
return false;
|
|
}
|
|
|
|
/* handle coplanar infacing (keep inside) */
|
|
if ( ( sideRef.planenum ^ 1 ) == side.planenum ) {
|
|
continue;
|
|
}
|
|
|
|
/* general case */
|
|
auto [front, back] = ClipWindingEpsilonStrict( w, plane.plane, ON_EPSILON ); /* strict; if plane is "almost identical" to face, both ways to continue can be wrong, so we better not fog it */
|
|
|
|
if ( back.empty() ) {
|
|
/* nothing actually contained inside */
|
|
return false;
|
|
}
|
|
|
|
if ( !front.empty() ) {
|
|
if ( outside.size() == MAX_BRUSH_SIDES ) {
|
|
Error( "MAX_BRUSH_SIDES" );
|
|
}
|
|
outside.push_back( std::move( front ) );
|
|
}
|
|
|
|
w.swap( back );
|
|
}
|
|
|
|
/* fixme: celshaded surface fragment errata */
|
|
|
|
/* all of outside fragments become separate drawsurfs */
|
|
numFogFragments += outside.size();
|
|
for ( const winding_t& wi : outside )
|
|
{
|
|
newds = DrawSurfaceForSide( e, *ds.mapBrush, sideRef, wi );
|
|
newds->fogNum = ds.fogNum;
|
|
}
|
|
|
|
/* ydnar: the old code neglected to snap to 0.125 for the fragment
|
|
inside the fog brush, leading to sparklies. this new code does
|
|
the right thing and uses the original surface's brush side */
|
|
|
|
/* build a drawsurf for it */
|
|
newds = DrawSurfaceForSide( e, *ds.mapBrush, sideRef, w );
|
|
if ( newds == nullptr ) {
|
|
return false;
|
|
}
|
|
|
|
/* copy new to original */
|
|
ds = std::move( *newds );
|
|
|
|
/* didn't really add a new drawsurface... :) */
|
|
numMapDrawSurfs--;
|
|
|
|
/* return ok */
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FogDrawSurfaces()
|
|
call after the surface list has been pruned, before tjunction fixing
|
|
*/
|
|
|
|
void FogDrawSurfaces( const entity_t& e ){
|
|
int fogged, numFogged;
|
|
int numBaseDrawSurfs;
|
|
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "----- FogDrawSurfs -----\n" );
|
|
|
|
/* reset counters */
|
|
numFogged = 0;
|
|
numFogFragments = 0;
|
|
|
|
/* walk fog list */
|
|
for ( size_t fogNum = 0; fogNum < mapFogs.size(); ++fogNum )
|
|
{
|
|
/* get fog */
|
|
const fog_t& fog = mapFogs[ fogNum ];
|
|
|
|
/* clip each surface into this, but don't clip any of the resulting fragments to the same brush */
|
|
numBaseDrawSurfs = numMapDrawSurfs;
|
|
for ( int i = 0; i < numBaseDrawSurfs; ++i )
|
|
{
|
|
/* get the drawsurface */
|
|
mapDrawSurface_t& ds = mapDrawSurfs[ i ];
|
|
|
|
/* no fog? */
|
|
if ( ds.shaderInfo->noFog ) {
|
|
continue;
|
|
}
|
|
|
|
/* global fog doesn't have a brush */
|
|
if ( fog.brush == nullptr ) {
|
|
/* don't re-fog already fogged surfaces */
|
|
if ( ds.fogNum > FOG_INVALID ) {
|
|
continue;
|
|
}
|
|
fogged = 1;
|
|
}
|
|
else
|
|
{
|
|
/* find drawsurface bounds */
|
|
MinMax minmax;
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
minmax.extend( vert.xyz );
|
|
|
|
/* check against the fog brush */
|
|
if( !minmax.test( fog.brush->minmax ) ){
|
|
continue; /* no intersection */
|
|
}
|
|
|
|
/* ydnar: gs mods: handle the various types of surfaces */
|
|
switch ( ds.type )
|
|
{
|
|
/* handle brush faces */
|
|
case ESurfaceType::Face:
|
|
fogged = ChopFaceSurfaceByBrush( e, ds, fog.brush );
|
|
break;
|
|
|
|
/* handle patches */
|
|
case ESurfaceType::Patch:
|
|
fogged = ChopPatchSurfaceByBrush( ds, fog.brush );
|
|
break;
|
|
|
|
/* handle triangle surfaces (fixme: split triangle surfaces) */
|
|
case ESurfaceType::Triangles:
|
|
case ESurfaceType::ForcedMeta:
|
|
case ESurfaceType::Meta:
|
|
fogged = 1;
|
|
break;
|
|
|
|
/* no fogging */
|
|
default:
|
|
fogged = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* is this surface fogged? */
|
|
if ( fogged ) {
|
|
numFogged += fogged;
|
|
ds.fogNum = fogNum;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* emit some statistics */
|
|
Sys_FPrintf( SYS_VRB, "%9d fog polygon fragments\n", numFogFragments );
|
|
Sys_FPrintf( SYS_VRB, "%9d fog patch fragments\n", numFogPatchFragments );
|
|
Sys_FPrintf( SYS_VRB, "%9d fogged drawsurfs\n", numFogged );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FogForPoint() - ydnar
|
|
gets the fog number for a point in space
|
|
*/
|
|
|
|
int FogForPoint( const Vector3& point, float epsilon ){
|
|
/* start with bogus fog num */
|
|
int fogNum = defaultFogNum;
|
|
|
|
/* walk the list of fog volumes */
|
|
for ( size_t i = 0; i < mapFogs.size(); ++i )
|
|
{
|
|
/* sof2: global fog doesn't reference a brush */
|
|
if ( mapFogs[ i ].brush == nullptr ) {
|
|
fogNum = i;
|
|
continue;
|
|
}
|
|
|
|
/* check point against all planes */
|
|
bool inside = true;
|
|
for ( const side_t& side : mapFogs[ i ].brush->sides )
|
|
{
|
|
if ( plane3_distance_to_point( mapplanes[ side.planenum ].plane, point ) > epsilon ) {
|
|
inside = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if inside, return the fog num */
|
|
if ( inside ) {
|
|
//% Sys_Printf( "FogForPoint: %f, %f, %f in fog %d\n", point[ 0 ], point[ 1 ], point[ 2 ], i );
|
|
return i;
|
|
}
|
|
}
|
|
|
|
/* if the point made it this far, it's not inside any fog volumes (or inside global fog) */
|
|
return fogNum;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FogForBounds() - ydnar
|
|
gets the fog number for a bounding box
|
|
*/
|
|
|
|
int FogForBounds( const MinMax& minmax, float epsilon ){
|
|
/* start with bogus fog num */
|
|
int fogNum = defaultFogNum;
|
|
|
|
/* init */
|
|
float bestVolume = 0;
|
|
|
|
/* walk the list of fog volumes */
|
|
for ( size_t i = 0; i < mapFogs.size(); ++i )
|
|
{
|
|
/* sof2: global fog doesn't reference a brush */
|
|
if ( mapFogs[ i ].brush == nullptr ) {
|
|
fogNum = i;
|
|
continue;
|
|
}
|
|
|
|
/* get fog brush */
|
|
const brush_t *brush = mapFogs[ i ].brush;
|
|
|
|
/* get bounds */
|
|
const MinMax fogMinmax( brush->minmax.mins - Vector3( epsilon ),
|
|
brush->minmax.maxs + Vector3( epsilon ) );
|
|
/* check against bounds */
|
|
if( !minmax.test( fogMinmax ) ){
|
|
continue; /* no overlap */
|
|
}
|
|
const Vector3 overlap( std::max( 1.f, std::min( minmax.maxs[0], fogMinmax.maxs[0] ) - std::max( minmax.mins[0], fogMinmax.mins[0] ) ),
|
|
std::max( 1.f, std::min( minmax.maxs[1], fogMinmax.maxs[1] ) - std::max( minmax.mins[1], fogMinmax.mins[1] ) ),
|
|
std::max( 1.f, std::min( minmax.maxs[2], fogMinmax.maxs[2] ) - std::max( minmax.mins[2], fogMinmax.mins[2] ) ) );
|
|
|
|
/* get volume */
|
|
const float volume = overlap[0] * overlap[1] * overlap[2];
|
|
|
|
/* test against best volume */
|
|
if ( volume > bestVolume ) {
|
|
bestVolume = volume;
|
|
fogNum = i;
|
|
}
|
|
}
|
|
|
|
/* if the point made it this far, it's not inside any fog volumes (or inside global fog) */
|
|
return fogNum;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
CreateMapFogs() - ydnar
|
|
generates a list of map fogs
|
|
*/
|
|
|
|
void CreateMapFogs(){
|
|
/* skip? */
|
|
if ( nofog ) {
|
|
return;
|
|
}
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- CreateMapFogs ---\n" );
|
|
|
|
/* walk entities */
|
|
for ( const auto& e : entities )
|
|
{
|
|
/* walk entity brushes */
|
|
for ( const brush_t& brush : e.brushes )
|
|
{
|
|
/* ignore non-fog brushes */
|
|
if ( !brush.contentShader->fogParms ) {
|
|
continue;
|
|
}
|
|
|
|
/* set up fog */
|
|
fog_t& fog = mapFogs.emplace_back();
|
|
fog.si = brush.contentShader;
|
|
fog.brush = &brush;
|
|
fog.visibleSide = -1;
|
|
|
|
/* if shader specifies an explicit direction, then find a matching brush side with an opposed normal */
|
|
if ( fog.si->fogDir != g_vector3_identity ) {
|
|
double bestDot = 0;
|
|
/* find the brush side */
|
|
for ( size_t j = 0; j < brush.sides.size(); ++j )
|
|
{
|
|
if( !brush.sides[ j ].bevel ){
|
|
const double dot = vector3_dot( fog.si->fogDir, mapplanes[ brush.sides[ j ].planenum ].normal() );
|
|
if( dot < bestDot ){
|
|
bestDot = dot;
|
|
fog.visibleSide = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ydnar: global fog */
|
|
if ( const char *globalFog; entities[ 0 ].read_keyvalue( globalFog, "_fog", "fog" ) ) {
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "Map has global fog shader %s\n", globalFog );
|
|
|
|
/* set up fog */
|
|
fog_t& fog = mapFogs.emplace_back();
|
|
fog.si = ShaderInfoForShaderNull( globalFog );
|
|
if ( fog.si == nullptr ) {
|
|
Error( "Invalid shader \"%s\" referenced trying to add global fog", globalFog );
|
|
}
|
|
fog.brush = nullptr;
|
|
fog.visibleSide = -1;
|
|
|
|
/* set as default fog */
|
|
defaultFogNum = mapFogs.size() - 1;
|
|
|
|
/* mark all worldspawn brushes as fogged */
|
|
for ( brush_t& brush : entities[ 0 ].brushes )
|
|
ApplySurfaceParm( "fog", &brush.contentFlags, nullptr, &brush.compileFlags );
|
|
}
|
|
|
|
/* emit some stats */
|
|
Sys_FPrintf( SYS_VRB, "%9zu fogs\n", mapFogs.size() );
|
|
}
|