mirror of
https://github.com/Garux/netradiant-custom.git
synced 2026-09-28 00:00:03 +02:00
3189 lines
84 KiB
C++
3189 lines
84 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"
|
|
|
|
|
|
|
|
/*
|
|
AllocDrawSurface()
|
|
ydnar: gs mods: changed to force an explicit type when allocating
|
|
*/
|
|
|
|
mapDrawSurface_t& AllocDrawSurface( ESurfaceType type ){
|
|
/* bounds check */
|
|
if ( numMapDrawSurfs >= max_map_draw_surfs ) {
|
|
Error( "max_map_draw_surfs (%d) exceeded, consider -maxmapdrawsurfs <N> to increase", max_map_draw_surfs );
|
|
}
|
|
mapDrawSurface_t& ds = mapDrawSurfs[ numMapDrawSurfs ];
|
|
numMapDrawSurfs++;
|
|
|
|
/* ydnar: do initial surface setup */
|
|
new ( &ds ) mapDrawSurface_t{}; // placement new
|
|
ds.type = type;
|
|
ds.fogNum = defaultFogNum; /* ydnar 2003-02-12 */
|
|
ds.surfaceNum = numMapDrawSurfs - 1; /* ydnar 2003-02-16 */
|
|
|
|
return ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FinishSurface()
|
|
ydnar: general surface finish pass
|
|
*/
|
|
static void MakeCelSurface( const mapDrawSurface_t& src, shaderInfo_t *si );
|
|
|
|
static void FinishSurface( mapDrawSurface_t& ds ){
|
|
/* dummy check */
|
|
if ( ds.shaderInfo == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
/* ydnar: rocking tek-fu celshading */
|
|
if ( ds.celShader != nullptr ) {
|
|
MakeCelSurface( ds, ds.celShader );
|
|
}
|
|
|
|
/* backsides stop here */
|
|
if ( ds.backSide ) {
|
|
return;
|
|
}
|
|
|
|
/* ydnar: rocking surface cloning (fur baby yeah!) */
|
|
if ( !strEmptyOrNull( ds.shaderInfo->cloneShader ) ) {
|
|
CloneSurface( ds, &ShaderInfoForShader( ds.shaderInfo->cloneShader ) );
|
|
}
|
|
|
|
/* ydnar: q3map_backShader support */
|
|
if ( !strEmptyOrNull( ds.shaderInfo->backShader ) ) {
|
|
mapDrawSurface_t *ds2 = CloneSurface( ds, &ShaderInfoForShader( ds.shaderInfo->backShader ) );
|
|
ds2->backSide = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
CloneSurface()
|
|
clones a map drawsurface, using the specified shader
|
|
*/
|
|
|
|
mapDrawSurface_t *CloneSurface( const mapDrawSurface_t& src, shaderInfo_t *si ){
|
|
/* dummy check */
|
|
if ( si == nullptr ) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* allocate a new surface */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( src.type );
|
|
|
|
/* copy it */
|
|
ds = src;
|
|
|
|
/* destroy side reference */
|
|
ds.sideRef = nullptr;
|
|
|
|
/* set shader */
|
|
ds.shaderInfo = si;
|
|
|
|
/* return the surface */
|
|
return &ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MakeCelSurface() - ydnar
|
|
makes a copy of a surface, but specific to cel shading
|
|
*/
|
|
|
|
static void MakeCelSurface( const mapDrawSurface_t& src, shaderInfo_t *si ){
|
|
/* dummy check */
|
|
if ( si == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
/* don't create cel surfaces for certain types of shaders */
|
|
if ( ( src.shaderInfo->compileFlags & C_TRANSLUCENT ) ||
|
|
( src.shaderInfo->compileFlags & C_SKY ) ) {
|
|
return;
|
|
}
|
|
|
|
/* make a copy */
|
|
mapDrawSurface_t *ds = CloneSurface( src, si );
|
|
if ( ds == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
/* do some fixups for celshading */
|
|
ds->planar = false;
|
|
ds->planeNum = -1;
|
|
ds->celShader = nullptr; /* don't cel shade cels :P */
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MakeSkyboxSurface() - ydnar
|
|
generates a skybox surface, viewable from everywhere there is sky
|
|
*/
|
|
|
|
static void MakeSkyboxSurface( mapDrawSurface_t& src ){
|
|
/* make a copy */
|
|
mapDrawSurface_t *ds = CloneSurface( src, src.shaderInfo );
|
|
if ( ds == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
/* set parent */
|
|
ds->parent = &src;
|
|
|
|
/* scale the surface vertexes */
|
|
for ( bspDrawVert_t& vert : ds->verts )
|
|
{
|
|
matrix4_transform_point( skyboxTransform, vert.xyz );
|
|
|
|
/* debug code */
|
|
//% bspDrawVerts[ bspDrawSurfaces[ ds->outputNum ].firstVert + i ].color[ 0 ][ 1 ] = 0;
|
|
//% bspDrawVerts[ bspDrawSurfaces[ ds->outputNum ].firstVert + i ].color[ 0 ][ 2 ] = 0;
|
|
}
|
|
|
|
/* so backface culling creep doesn't bork the surface */
|
|
ds->lightmapVecs[ 2 ].set( 0 );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClearSurface() - ydnar
|
|
clears a surface and frees any allocated memory
|
|
*/
|
|
|
|
void ClearSurface( mapDrawSurface_t& ds ){
|
|
ds.type = ESurfaceType::Bad;
|
|
ds.planar = false;
|
|
ds.planeNum = -1;
|
|
ds.verts = DrawVerts(); // deallocate
|
|
ds.indexes = DrawIndexes(); // deallocate
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
TidyEntitySurfaces() - ydnar
|
|
deletes all empty or bad surfaces from the surface list
|
|
*/
|
|
|
|
void TidyEntitySurfaces( const entity_t& e ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- TidyEntitySurfaces ---\n" );
|
|
|
|
/* walk the surface list */
|
|
int deleted = 0;
|
|
for ( int i = e.firstDrawSurf, end = std::exchange( numMapDrawSurfs, i ); i != end; ++i )
|
|
{
|
|
mapDrawSurface_t& ds = mapDrawSurfs[ i ];
|
|
/* this surface ok? */
|
|
if ( ( ds.type != ESurfaceType::Bad && !ds.verts.empty() )
|
|
|| ds.type == ESurfaceType::Flare || ds.type == ESurfaceType::Shader ) {
|
|
/* copy if necessary */
|
|
if ( i != numMapDrawSurfs ) {
|
|
mapDrawSurfs[ numMapDrawSurfs ] = std::move( ds );
|
|
}
|
|
++numMapDrawSurfs;
|
|
}
|
|
else{
|
|
/* nuke it */
|
|
ClearSurface( ds );
|
|
deleted++;
|
|
}
|
|
}
|
|
|
|
/* emit some stats */
|
|
Sys_FPrintf( SYS_VRB, "%9d empty or malformed surfaces deleted\n", deleted );
|
|
}
|
|
|
|
|
|
|
|
static Vector2 CalcSurfaceTextureBias( const mapDrawSurface_t& ds ){
|
|
/* walk the verts and determine min/max st values */
|
|
Vector2 mins( 999999, 999999 ), maxs( -999999, -999999 ), bias;
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
for ( int j = 0; j < 2; ++j )
|
|
{
|
|
value_minimize( mins[ j ], vert.st[ j ] );
|
|
value_maximize( maxs[ j ], vert.st[ j ] );
|
|
}
|
|
}
|
|
|
|
/* clamp to integer range and calculate surface bias values */
|
|
for ( int i = 0; i < 2; ++i )
|
|
bias[ i ] = floor( 0.5f * ( mins[ i ] + maxs[ i ] ) );
|
|
|
|
return bias;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
CalcLightmapAxis() - ydnar
|
|
gives closed lightmap axis for a plane normal
|
|
*/
|
|
|
|
Vector3 CalcLightmapAxis( const Vector3& normal ){
|
|
/* test */
|
|
if ( normal == g_vector3_identity ) {
|
|
return g_vector3_identity;
|
|
}
|
|
|
|
/* get absolute normal */
|
|
const Vector3 absolute( std::fabs( normal[ 0 ] ),
|
|
std::fabs( normal[ 1 ] ),
|
|
std::fabs( normal[ 2 ] ) );
|
|
|
|
/* test and return */
|
|
if ( absolute[ 2 ] > absolute[ 0 ] - 0.0001f && absolute[ 2 ] > absolute[ 1 ] - 0.0001f ) {
|
|
if ( normal[ 2 ] > 0 ) {
|
|
return g_vector3_axis_z;
|
|
}
|
|
else{
|
|
return -g_vector3_axis_z;
|
|
}
|
|
}
|
|
else if ( absolute[ 0 ] > absolute[ 1 ] - 0.0001f && absolute[ 0 ] > absolute[ 2 ] - 0.0001f ) {
|
|
if ( normal[ 0 ] > 0 ) {
|
|
return g_vector3_axis_x;
|
|
}
|
|
else{
|
|
return -g_vector3_axis_x;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( normal[ 1 ] > 0 ) {
|
|
return g_vector3_axis_y;
|
|
}
|
|
else{
|
|
return -g_vector3_axis_y;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClassifySurfaces() - ydnar
|
|
fills out a bunch of info in the surfaces, including planar status, lightmap projection, and bounding box
|
|
*/
|
|
|
|
#define PLANAR_EPSILON 0.5f //% 0.126f 0.25f
|
|
|
|
void ClassifySurface( mapDrawSurface_t& ds ){
|
|
Plane3f plane;
|
|
static const Vector3 axii[ 6 ] =
|
|
{
|
|
{ 0, 0, -1 },
|
|
{ 0, 0, 1 },
|
|
{ -1, 0, 0 },
|
|
{ 1, 0, 0 },
|
|
{ 0, -1, 0 },
|
|
{ 0, 1, 0 }
|
|
};
|
|
|
|
|
|
{
|
|
/* ignore bogus (or flare) surfaces */
|
|
if ( ds.type == ESurfaceType::Bad || ds.verts.empty() ) {
|
|
return;
|
|
}
|
|
|
|
/* get shader */
|
|
shaderInfo_t *si = ds.shaderInfo;
|
|
|
|
/* -----------------------------------------------------------------
|
|
force meta if vertex count is too high or shader requires it
|
|
----------------------------------------------------------------- */
|
|
|
|
if ( ds.type != ESurfaceType::Patch && ds.type != ESurfaceType::Face ) {
|
|
if ( ds.numVerts() > maxSurfaceVerts ) {
|
|
ds.type = ESurfaceType::ForcedMeta;
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------
|
|
plane and bounding box classification
|
|
----------------------------------------------------------------- */
|
|
|
|
/* set surface bounding box */
|
|
ds.minmax.clear();
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
ds.minmax.extend( vert.xyz );
|
|
|
|
/* try to get an existing plane */
|
|
if ( ds.planeNum >= 0 ) {
|
|
plane = mapplanes[ ds.planeNum ].plane;
|
|
}
|
|
|
|
/* construct one from the first vert with a valid normal */
|
|
else
|
|
{
|
|
plane = { 0, 0, 0, 0 };
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
if ( vert.normal != g_vector3_identity ) {
|
|
plane.normal() = vert.normal;
|
|
plane.dist() = vector3_dot( vert.xyz, plane.normal() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* test for bogus plane */
|
|
if ( vector3_length( plane.normal() ) == 0 ) {
|
|
ds.planar = false;
|
|
ds.planeNum = -1;
|
|
}
|
|
else
|
|
{
|
|
/* determine if surface is planar */
|
|
ds.planar = true;
|
|
|
|
/* test each vert */
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
/* point-plane test */
|
|
if ( std::fabs( plane3_distance_to_point( plane, vert.xyz ) ) > PLANAR_EPSILON ) {
|
|
//% if( ds.planeNum >= 0 )
|
|
//% {
|
|
//% Sys_Warning( "Planar surface marked unplanar (%f > %f)\n", std::fabs( dist ), PLANAR_EPSILON );
|
|
//% ds.verts[ i ].color[ 0 ][ 0 ] = ds.verts[ i ].color[ 0 ][ 2 ] = 0;
|
|
//% }
|
|
ds.planar = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find map plane if necessary */
|
|
if ( ds.planar ) {
|
|
if ( ds.planeNum < 0 ) {
|
|
ds.planeNum = FindFloatPlane( plane, 1, &ds.verts[ 0 ].xyz );
|
|
}
|
|
ds.lightmapVecs[ 2 ] = plane.normal();
|
|
}
|
|
else
|
|
{
|
|
ds.planeNum = -1;
|
|
ds.lightmapVecs[ 2 ].set( 0 );
|
|
//% if( ds.type == ESurfaceType::Meta || ds.type == ESurfaceType::Face )
|
|
//% Sys_Warning( "Non-planar face (%d): %s\n", ds.planeNum, ds.shaderInfo->shader );
|
|
}
|
|
|
|
/* -----------------------------------------------------------------
|
|
lightmap bounds and axis projection
|
|
----------------------------------------------------------------- */
|
|
|
|
/* vertex lit surfaces don't need this information */
|
|
if ( si->compileFlags & C_VERTEXLIT || ds.type == ESurfaceType::Triangles || noLightmaps ) {
|
|
ds.lightmapAxis.set( 0 );
|
|
//% ds.lightmapVecs[ 2 ].set( 0 );
|
|
ds.sampleSize = 0;
|
|
return;
|
|
}
|
|
|
|
/* the shader can specify an explicit lightmap axis */
|
|
if ( si->lightmapAxis != g_vector3_identity ) {
|
|
ds.lightmapAxis = si->lightmapAxis;
|
|
}
|
|
else if ( ds.type == ESurfaceType::ForcedMeta ) {
|
|
ds.lightmapAxis.set( 0 );
|
|
}
|
|
else if ( ds.planar ) {
|
|
ds.lightmapAxis = CalcLightmapAxis( plane.normal() );
|
|
}
|
|
else
|
|
{
|
|
/* find best lightmap axis */
|
|
int bestAxis;
|
|
for ( bestAxis = 0; bestAxis < 6; ++bestAxis )
|
|
{
|
|
if ( std::ranges::none_of( ds.verts, [&]( const bspDrawVert_t& vert ){
|
|
//% Sys_Printf( "Comparing %1.3f %1.3f %1.3f to %1.3f %1.3f %1.3f\n",
|
|
//% vert.normal[ 0 ], vert.normal[ 1 ], vert.normal[ 2 ],
|
|
//% axii[ bestAxis ][ 0 ], axii[ bestAxis ][ 1 ], axii[ bestAxis ][ 2 ] );
|
|
return vector3_dot( vert.normal, axii[ bestAxis ] ) < 0.25f; /* fixme: adjust this tolerance to taste */
|
|
} ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set axis if possible */
|
|
if ( bestAxis < 6 ) {
|
|
//% if( ds.type == ESurfaceType::Patch )
|
|
//% Sys_Printf( "Mapped axis %d onto patch\n", bestAxis );
|
|
ds.lightmapAxis = axii[ bestAxis ];
|
|
}
|
|
|
|
/* debug code */
|
|
//% if( ds.type == ESurfaceType::Patch )
|
|
//% Sys_Printf( "Failed to map axis %d onto patch\n", bestAxis );
|
|
}
|
|
|
|
/* calculate lightmap sample size */
|
|
if ( ds.shaderInfo->lightmapSampleSize > 0 ) { /* shader value overrides every other */
|
|
ds.sampleSize = ds.shaderInfo->lightmapSampleSize;
|
|
}
|
|
else if ( ds.sampleSize <= 0 ) { /* may contain the entity asigned value */
|
|
ds.sampleSize = sampleSize; /* otherwise use global default */
|
|
}
|
|
if ( ds.lightmapScale > 0 ) { /* apply surface lightmap scaling factor */
|
|
ds.sampleSize = ds.lightmapScale * ds.sampleSize;
|
|
ds.lightmapScale = 0; /* applied */
|
|
}
|
|
|
|
ds.sampleSize = std::clamp( ds.sampleSize, std::max( minSampleSize, 1 ), 16384 ); /* powers of 2 are preferred */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ClassifyEntitySurfaces() - ydnar
|
|
classifies all surfaces in an entity
|
|
*/
|
|
|
|
void ClassifyEntitySurfaces( const entity_t& e ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- ClassifyEntitySurfaces ---\n" );
|
|
|
|
/* walk the surface list */
|
|
for ( int i = e.firstDrawSurf; i < numMapDrawSurfs; ++i )
|
|
{
|
|
FinishSurface( mapDrawSurfs[ i ] );
|
|
ClassifySurface( mapDrawSurfs[ i ] );
|
|
}
|
|
|
|
/* tidy things up */
|
|
TidyEntitySurfaces( e );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
GetShaderIndexForPoint() - ydnar
|
|
for shader-indexed surfaces (terrain), find a matching index from the indexmap
|
|
*/
|
|
|
|
static byte GetShaderIndexForPoint( const indexMap_t& im, const MinMax& eMinmax, const Vector3& point ){
|
|
/* this code is really broken */
|
|
#if 0
|
|
/* legacy precision fudges for terrain */
|
|
Vector3 mins, maxs;
|
|
for ( int i = 0; i < 3; ++i )
|
|
{
|
|
mins[ i ] = floor( eMinmax.mins[ i ] + 0.1 );
|
|
maxs[ i ] = floor( eMinmax.maxs[ i ] + 0.1 );
|
|
}
|
|
const Vector3 size = maxs - mins;
|
|
|
|
/* find st (fixme: support more than just z-axis projection) */
|
|
const float s = std::clamp( floor( point[ 0 ] + 0.1f - mins[ 0 ] ) / size[ 0 ], 0.0, 1.0 );
|
|
const float t = std::clamp( floor( maxs[ 1 ] - point[ 1 ] + 0.1f ) / size[ 1 ], 0.0, 1.0 );
|
|
|
|
/* make xy */
|
|
const int x = ( im.w - 1 ) * s;
|
|
const int y = ( im.h - 1 ) * t;
|
|
#else
|
|
/* get size */
|
|
const Vector3 size = eMinmax.maxs - eMinmax.mins;
|
|
|
|
/* calc st */
|
|
const float s = ( point[ 0 ] - eMinmax.mins[ 0 ] ) / size[ 0 ];
|
|
const float t = ( eMinmax.maxs[ 1 ] - point[ 1 ] ) / size[ 1 ];
|
|
|
|
/* calc xy */
|
|
const int x = std::clamp( int( s * im.w ), 0, im.w - 1 );
|
|
const int y = std::clamp( int( t * im.h ), 0, im.h - 1 );
|
|
#endif
|
|
|
|
/* return index */
|
|
return im.pixels[ y * im.w + x ];
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
GetIndexedShader() - ydnar
|
|
for a given set of indexes and an indexmap, get a shader and set the vertex alpha in-place
|
|
this combines a couple different functions from terrain.c
|
|
*/
|
|
|
|
static shaderInfo_t& GetIndexedShader( const shaderInfo_t *parent, const indexMap_t& im, int numPoints, byte *shaderIndexes ){
|
|
/* early out if bad data */
|
|
if ( numPoints <= 0 || shaderIndexes == nullptr ) {
|
|
return ShaderInfoForShader( "default" );
|
|
}
|
|
|
|
/* determine min/max index */
|
|
byte minShaderIndex = 255;
|
|
byte maxShaderIndex = 0;
|
|
for ( const byte index : Span( shaderIndexes, numPoints ) )
|
|
{
|
|
value_minimize( minShaderIndex, index );
|
|
value_maximize( maxShaderIndex, index );
|
|
}
|
|
|
|
/* set alpha inline */
|
|
for ( byte& index : Span( shaderIndexes, numPoints ) )
|
|
{
|
|
/* straight rip from terrain.c */
|
|
if ( index < maxShaderIndex ) {
|
|
index = 0;
|
|
}
|
|
else{
|
|
index = 255;
|
|
}
|
|
}
|
|
|
|
/* get the shader */
|
|
shaderInfo_t& si = ShaderInfoForShader( ( minShaderIndex == maxShaderIndex )
|
|
? String64( "textures/", im.shader, '_', int( maxShaderIndex ) )
|
|
: String64( "textures/", im.shader, '_', int( minShaderIndex ), "to", int( maxShaderIndex ) ) );
|
|
|
|
/* inherit a few things from parent shader */
|
|
if ( parent->globalTexture ) {
|
|
si.globalTexture = true;
|
|
}
|
|
if ( parent->forceMeta ) {
|
|
si.forceMeta = true;
|
|
}
|
|
if ( parent->nonplanar ) {
|
|
si.nonplanar = true;
|
|
}
|
|
if ( si.shadeAngleDegrees == 0 ) {
|
|
si.shadeAngleDegrees = parent->shadeAngleDegrees;
|
|
}
|
|
if ( parent->tcGen && !si.tcGen ) {
|
|
/* set xy texture projection */
|
|
si.tcGen = true;
|
|
si.vecs[ 0 ] = parent->vecs[ 0 ];
|
|
si.vecs[ 1 ] = parent->vecs[ 1 ];
|
|
}
|
|
if ( parent->lightmapAxis != g_vector3_identity && si.lightmapAxis == g_vector3_identity ) {
|
|
/* set lightmap projection axis */
|
|
si.lightmapAxis = parent->lightmapAxis;
|
|
}
|
|
|
|
/* return the shader */
|
|
return si;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
DrawSurfaceForSide()
|
|
creates a ESurfaceType::Face drawsurface from a given brush side and winding
|
|
stores references to given brush and side
|
|
*/
|
|
|
|
const double SNAP_FLOAT_TO_INT = 8.0;
|
|
const double SNAP_INT_TO_FLOAT = ( 1.0 / SNAP_FLOAT_TO_INT );
|
|
|
|
static mapDrawSurface_t *DrawSurfaceForShader( const char *shader );
|
|
|
|
mapDrawSurface_t *DrawSurfaceForSide( const entity_t& e, const brush_t& b, const side_t& s, const winding_t& w ){
|
|
shaderInfo_t *si, *parent;
|
|
Vector3 texX, texY;
|
|
float x, y;
|
|
Vector3 vTranslated;
|
|
byte shaderIndexes[ 256 ];
|
|
float offsets[ 256 ];
|
|
|
|
|
|
/* ydnar: don't make a drawsurf for culled sides */
|
|
if ( s.culled ) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* range check */
|
|
if ( w.size() > MAX_POINTS_ON_WINDING ) {
|
|
Error( "DrawSurfaceForSide: w->numpoints = %zu (> %d)", w.size(), MAX_POINTS_ON_WINDING );
|
|
}
|
|
|
|
/* get shader */
|
|
si = s.shaderInfo;
|
|
|
|
/* ydnar: gs mods: check for indexed shader */
|
|
const bool indexed = ( si->indexed && b.im != nullptr );
|
|
if ( indexed ) {
|
|
/* get shader indexes for each point */
|
|
for ( size_t i = 0; i < w.size(); ++i )
|
|
{
|
|
shaderIndexes[ i ] = GetShaderIndexForPoint( *b.im, b.eMinmax, w[ i ] );
|
|
offsets[ i ] = b.im->offsets[ shaderIndexes[ i ] ];
|
|
//% Sys_Printf( "%f ", offsets[ i ] );
|
|
}
|
|
|
|
/* get matching shader and set alpha */
|
|
parent = si;
|
|
si = &GetIndexedShader( parent, *b.im, w.size(), shaderIndexes );
|
|
}
|
|
|
|
/* ydnar: sky hack/fix for GL_CLAMP borders on ati cards */
|
|
if ( skyFixHack && !si->skyParmsImageBase.empty() ) {
|
|
//% Sys_FPrintf( SYS_VRB, "Enabling sky hack for shader %s using env %s\n", si->shader, si->skyParmsImageBase );
|
|
for( const auto *suffix : { "_lf", "_rt", "_ft", "_bk", "_up", "_dn" } )
|
|
DrawSurfaceForShader( String64( si->skyParmsImageBase, suffix ) );
|
|
}
|
|
|
|
/* ydnar: gs mods */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Face );
|
|
ds.entityNum = b.entityNum;
|
|
ds.castShadows = b.castShadows;
|
|
ds.recvShadows = b.recvShadows;
|
|
|
|
ds.planar = true;
|
|
ds.planeNum = s.planenum;
|
|
ds.lightmapVecs[ 2 ] = mapplanes[ s.planenum ].normal();
|
|
|
|
ds.shaderInfo = si;
|
|
ds.mapBrush = &b;
|
|
ds.addSideRef( &s );
|
|
ds.fogNum = FOG_INVALID;
|
|
ds.sampleSize = b.lightmapSampleSize;
|
|
ds.lightmapScale = b.lightmapScale;
|
|
ds.ambientColor = b.ambientColor;
|
|
ds.verts.resize( w.size(), c_bspDrawVert_t0 );
|
|
|
|
/* compute s/t coordinates from brush primitive texture matrix (compute axis base) */
|
|
ComputeAxisBase( mapplanes[ s.planenum ].normal(), texX, texY );
|
|
|
|
/* create the vertexes */
|
|
for ( size_t j = 0; j < w.size(); ++j )
|
|
{
|
|
/* get the drawvert */
|
|
bspDrawVert_t& dv = ds.verts[ j ];
|
|
|
|
/* copy xyz and do potential z offset */
|
|
dv.xyz = w[ j ];
|
|
if ( indexed ) {
|
|
dv.xyz[ 2 ] += offsets[ j ];
|
|
}
|
|
|
|
/* round the xyz to a given precision and translate by origin */
|
|
if( g_brushSnap )
|
|
for ( size_t i = 0; i < 3; ++i )
|
|
dv.xyz[ i ] = SNAP_INT_TO_FLOAT * floor( dv.xyz[ i ] * SNAP_FLOAT_TO_INT + 0.5 );
|
|
vTranslated = dv.xyz + e.originbrush_origin;
|
|
|
|
/* ydnar: tek-fu celshading support for flat shaded shit */
|
|
if ( flat ) {
|
|
dv.st = si->stFlat;
|
|
}
|
|
|
|
/* ydnar: gs mods: added support for explicit shader texcoord generation */
|
|
else if ( si->tcGen ) {
|
|
dv.st[ 0 ] = vector3_dot( si->vecs[ 0 ], vTranslated );
|
|
dv.st[ 1 ] = vector3_dot( si->vecs[ 1 ], vTranslated );
|
|
}
|
|
|
|
/* brush primitive texturing */
|
|
else if ( g_brushType == EBrushType::Bp ) {
|
|
/* calculate texture s/t from brush primitive texture matrix */
|
|
x = vector3_dot( vTranslated, texX );
|
|
y = vector3_dot( vTranslated, texY );
|
|
dv.st[ 0 ] = s.texMat[ 0 ][ 0 ] * x + s.texMat[ 0 ][ 1 ] * y + s.texMat[ 0 ][ 2 ];
|
|
dv.st[ 1 ] = s.texMat[ 1 ][ 0 ] * x + s.texMat[ 1 ][ 1 ] * y + s.texMat[ 1 ][ 2 ];
|
|
}
|
|
|
|
/* old quake-style or valve 220 texturing */
|
|
else {
|
|
/* nearest-axial projection */
|
|
dv.st[ 0 ] = s.vecs[ 0 ][ 3 ] + vector3_dot( s.vecs[ 0 ].vec3(), vTranslated );
|
|
dv.st[ 1 ] = s.vecs[ 1 ][ 3 ] + vector3_dot( s.vecs[ 1 ].vec3(), vTranslated );
|
|
dv.st[ 0 ] /= si->shaderWidth;
|
|
dv.st[ 1 ] /= si->shaderHeight;
|
|
}
|
|
|
|
/* copy normal */
|
|
dv.normal = mapplanes[ s.planenum ].normal();
|
|
|
|
/* ydnar: set color */
|
|
for ( auto& color : dv.color )
|
|
{
|
|
color.set( 255 );
|
|
|
|
/* ydnar: gs mods: handle indexed shader blending */
|
|
if( indexed )
|
|
color.alpha() = shaderIndexes[ j ];
|
|
}
|
|
}
|
|
|
|
/* set cel shader */
|
|
ds.celShader = b.celShader;
|
|
|
|
/* set shade angle */
|
|
if ( b.shadeAngleDegrees > 0 ) {
|
|
ds.shadeAngleDegrees = b.shadeAngleDegrees;
|
|
}
|
|
|
|
/* ydnar: gs mods: moved st biasing elsewhere */
|
|
return &ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
DrawSurfaceForMesh()
|
|
moved here from patch.c
|
|
*/
|
|
|
|
mapDrawSurface_t *DrawSurfaceForMesh( const entity_t& e, parseMesh_t& p, mesh_t *mesh ){
|
|
Plane3f plane;
|
|
shaderInfo_t *si, *parent;
|
|
byte shaderIndexes[ MAX_EXPANDED_AXIS * MAX_EXPANDED_AXIS ];
|
|
float offsets[ MAX_EXPANDED_AXIS * MAX_EXPANDED_AXIS ];
|
|
|
|
|
|
/* get mesh and shader shader */
|
|
if ( mesh == nullptr ) {
|
|
mesh = &p.mesh;
|
|
}
|
|
si = p.shaderInfo;
|
|
if ( mesh == nullptr || si == nullptr ) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* get vertex count */
|
|
const int numVerts = mesh->numVerts();
|
|
|
|
/* to make valid normals for patches with degenerate edges,
|
|
we need to make a copy of the mesh and put the aproximating
|
|
points onto the curve */
|
|
|
|
/* create a copy of the mesh */
|
|
mesh_t copy = CopyMesh( *mesh );
|
|
|
|
/* store off the original (potentially bad) normals */
|
|
MakeMeshNormals( copy );
|
|
for ( int i = 0; i < numVerts; ++i )
|
|
mesh->verts[ i ].normal = copy.verts[ i ].normal;
|
|
|
|
/* put the mesh on the curve */
|
|
PutMeshOnCurve( copy );
|
|
|
|
/* find new normals (to take into account degenerate/flipped edges */
|
|
MakeMeshNormals( copy );
|
|
for ( int i = 0; i < numVerts; ++i )
|
|
{
|
|
/* ydnar: only copy normals that are significantly different from the originals */
|
|
if ( vector3_dot( copy.verts[ i ].normal, mesh->verts[ i ].normal ) < 0.75f ) {
|
|
mesh->verts[ i ].normal = copy.verts[ i ].normal;
|
|
}
|
|
}
|
|
|
|
/* free the old mesh */
|
|
copy.freeVerts();
|
|
|
|
/* ydnar: gs mods: check for indexed shader */
|
|
const bool indexed = ( si->indexed && p.im != nullptr );
|
|
if ( indexed ) {
|
|
/* get shader indexes for each point */
|
|
for ( int i = 0; i < numVerts; ++i )
|
|
{
|
|
shaderIndexes[ i ] = GetShaderIndexForPoint( *p.im, p.eMinmax, mesh->verts[ i ].xyz );
|
|
offsets[ i ] = p.im->offsets[ shaderIndexes[ i ] ];
|
|
}
|
|
|
|
/* get matching shader and set alpha */
|
|
parent = si;
|
|
si = &GetIndexedShader( parent, *p.im, numVerts, shaderIndexes );
|
|
}
|
|
|
|
|
|
/* ydnar: gs mods */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Patch );
|
|
ds.entityNum = p.entityNum;
|
|
ds.castShadows = p.castShadows;
|
|
ds.recvShadows = p.recvShadows;
|
|
|
|
ds.shaderInfo = si;
|
|
ds.sampleSize = p.lightmapSampleSize;
|
|
ds.lightmapScale = p.lightmapScale; /* ydnar */
|
|
ds.ambientColor = p.ambientColor;
|
|
ds.patchWidth = mesh->width;
|
|
ds.patchHeight = mesh->height;
|
|
ds.verts.assign( mesh->verts, mesh->verts + numVerts );
|
|
|
|
ds.fogNum = FOG_INVALID;
|
|
ds.planeNum = -1;
|
|
|
|
ds.longestCurve = p.longestCurve;
|
|
ds.maxIterations = p.maxIterations;
|
|
|
|
/* construct a plane from the first vert */
|
|
plane.normal() = mesh->verts[ 0 ].normal;
|
|
plane.dist() = vector3_dot( mesh->verts[ 0 ].xyz, plane.normal() );
|
|
|
|
/* spew forth errors */
|
|
if ( vector3_length( plane.normal() ) < 0.001f ) {
|
|
Sys_Printf( "DrawSurfaceForMesh: bogus plane\n" );
|
|
}
|
|
|
|
/* test each vert */
|
|
const bool planar = std::ranges::none_of( Span( mesh->verts, numVerts ), [&plane]( const bspDrawVert_t& vert ){
|
|
/* normal test */
|
|
return !VectorCompare( plane.normal(), vert.normal )
|
|
/* point-plane test */
|
|
|| std::fabs( plane3_distance_to_point( plane, vert.xyz ) ) > EQUAL_EPSILON;
|
|
} );
|
|
|
|
/* add a map plane */
|
|
if ( planar ) {
|
|
/* make a map plane */
|
|
ds.planeNum = FindFloatPlane( plane, 1, &mesh->verts[ 0 ].xyz );
|
|
ds.lightmapVecs[ 2 ] = plane.normal();
|
|
|
|
/* push this normal to all verts (ydnar 2003-02-14: bad idea, small patches get screwed up) */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
vert.normal = plane.normal();
|
|
}
|
|
|
|
/* walk the verts to do special stuff */
|
|
for ( int i = 0; i < numVerts; ++i )
|
|
{
|
|
/* get the drawvert */
|
|
bspDrawVert_t& dv = ds.verts[ i ];
|
|
|
|
/* ydnar: tek-fu celshading support for flat shaded shit */
|
|
if ( flat ) {
|
|
dv.st = si->stFlat;
|
|
}
|
|
|
|
/* ydnar: gs mods: added support for explicit shader texcoord generation */
|
|
else if ( si->tcGen ) {
|
|
/* translate by origin and project the texture */
|
|
const Vector3 vTranslated = dv.xyz + e.origin;
|
|
dv.st[ 0 ] = vector3_dot( si->vecs[ 0 ], vTranslated );
|
|
dv.st[ 1 ] = vector3_dot( si->vecs[ 1 ], vTranslated );
|
|
}
|
|
|
|
/* ydnar: set color */
|
|
for ( auto& color : dv.color )
|
|
{
|
|
color.set( 255 );
|
|
|
|
/* ydnar: gs mods: handle indexed shader blending */
|
|
if( indexed )
|
|
color.alpha() = shaderIndexes[ i ];
|
|
}
|
|
|
|
/* ydnar: offset */
|
|
if ( indexed ) {
|
|
dv.xyz[ 2 ] += offsets[ i ];
|
|
}
|
|
}
|
|
|
|
/* set cel shader */
|
|
ds.celShader = p.celShader;
|
|
|
|
/* return the drawsurface */
|
|
return &ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
DrawSurfaceForFlare() - ydnar
|
|
creates a flare draw surface
|
|
*/
|
|
|
|
mapDrawSurface_t *DrawSurfaceForFlare( int entNum, const Vector3& origin, const Vector3& normal, const Vector3& color, const char *flareShader, int lightStyle ){
|
|
/* emit flares? */
|
|
if ( !emitFlares ) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* allocate drawsurface */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Flare );
|
|
ds.entityNum = entNum;
|
|
|
|
/* set it up */
|
|
ds.shaderInfo = &ShaderInfoForShader( !strEmptyOrNull( flareShader )? flareShader : g_game->flareShader );
|
|
ds.lightmapOrigin = origin;
|
|
ds.lightmapVecs[ 2 ] = normal;
|
|
ds.lightmapVecs[ 0 ] = color;
|
|
|
|
/* store light style */
|
|
ds.lightStyle = style_is_valid( lightStyle )? lightStyle : LS_NORMAL;
|
|
|
|
/* fixme: fog */
|
|
|
|
/* return to sender */
|
|
return &ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
DrawSurfaceForShader() - ydnar
|
|
creates a bogus surface to forcing the game to load a shader
|
|
*/
|
|
|
|
static mapDrawSurface_t *DrawSurfaceForShader( const char *shader ){
|
|
/* get shader */
|
|
shaderInfo_t *si = &ShaderInfoForShader( shader );
|
|
|
|
/* find existing surface */
|
|
for ( mapDrawSurface_t& ds : Span( mapDrawSurfs, numMapDrawSurfs ) )
|
|
{
|
|
/* check it */
|
|
if ( ds.shaderInfo == si ) {
|
|
return &ds;
|
|
}
|
|
}
|
|
|
|
/* create a new surface */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Shader );
|
|
ds.entityNum = 0;
|
|
ds.shaderInfo = si;
|
|
|
|
/* return to sender */
|
|
return &ds;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AddSurfaceFlare() - ydnar
|
|
creates flares (coronas) centered on surfaces
|
|
*/
|
|
|
|
static void AddSurfaceFlare( mapDrawSurface_t& ds, const Vector3& entityOrigin ){
|
|
Vector3 origin( 0 );
|
|
/* find centroid */
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
origin += vert.xyz;
|
|
origin /= ds.verts.size();
|
|
origin += entityOrigin;
|
|
|
|
/* push origin off surface a bit */
|
|
origin += ds.lightmapVecs[ 2 ] * 2;
|
|
|
|
/* create the drawsurface */
|
|
DrawSurfaceForFlare( ds.entityNum, origin, ds.lightmapVecs[ 2 ], ds.shaderInfo->color, ds.shaderInfo->flareShader, ds.shaderInfo->lightStyle );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
SubdivideFace()
|
|
subdivides a face surface until it is smaller than the specified size (subdivisions)
|
|
*/
|
|
|
|
static void SubdivideFace_r( const entity_t& e, const brush_t& brush, const side_t& side, winding_t& w, int fogNum, float subdivisions ){
|
|
int axis;
|
|
MinMax bounds;
|
|
const float epsilon = 0.1;
|
|
int subFloor, subCeil;
|
|
mapDrawSurface_t *ds;
|
|
|
|
|
|
/* dummy check */
|
|
if ( w.empty() ) {
|
|
return;
|
|
}
|
|
if ( w.size() < 3 ) {
|
|
Error( "SubdivideFace_r: Bad w->numpoints (%zu < 3)", w.size() );
|
|
}
|
|
|
|
/* determine surface bounds */
|
|
WindingExtendBounds( w, bounds );
|
|
|
|
/* split the face */
|
|
for ( axis = 0; axis < 3; ++axis )
|
|
{
|
|
Vector3 planePoint( 0 );
|
|
Plane3f plane( 0, 0, 0, 0 );
|
|
|
|
|
|
/* create an axial clipping plane */
|
|
subFloor = floor( bounds.mins[ axis ] / subdivisions ) * subdivisions;
|
|
subCeil = ceil( bounds.maxs[ axis ] / subdivisions ) * subdivisions;
|
|
planePoint[ axis ] = subFloor + subdivisions;
|
|
plane.normal()[ axis ] = -1;
|
|
plane.dist() = vector3_dot( planePoint, plane.normal() );
|
|
|
|
/* subdivide if necessary */
|
|
if ( ( subCeil - subFloor ) > subdivisions ) {
|
|
/* clip the winding */
|
|
auto [frontWinding, backWinding] = ClipWindingEpsilon( w, plane, epsilon ); /* not strict; we assume we always keep a winding */
|
|
|
|
/* the clip may not produce two polygons if it was epsilon close */
|
|
if ( frontWinding.empty() ) {
|
|
w.swap( backWinding );
|
|
}
|
|
else if ( backWinding.empty() ) {
|
|
w.swap( frontWinding );
|
|
}
|
|
else
|
|
{
|
|
SubdivideFace_r( e, brush, side, frontWinding, fogNum, subdivisions );
|
|
SubdivideFace_r( e, brush, side, backWinding, fogNum, subdivisions );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* create a face surface */
|
|
ds = DrawSurfaceForSide( e, brush, side, w );
|
|
|
|
/* set correct fog num */
|
|
ds->fogNum = fogNum;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
SubdivideFaceSurfaces()
|
|
chop up brush face surfaces that have subdivision attributes
|
|
ydnar: and subdivide surfaces that exceed specified texture coordinate range
|
|
*/
|
|
|
|
void SubdivideFaceSurfaces( const entity_t& e ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- SubdivideFaceSurfaces ---\n" );
|
|
|
|
/* walk the list of original surfaces, numMapDrawSurfs may increase in the process */
|
|
for ( mapDrawSurface_t& ds : Span( mapDrawSurfs + e.firstDrawSurf, mapDrawSurfs + numMapDrawSurfs ) )
|
|
{
|
|
/* only subdivide brush sides */
|
|
if ( ds.type != ESurfaceType::Face || ds.mapBrush == nullptr || ds.sideRef == nullptr ) {
|
|
continue;
|
|
}
|
|
|
|
/* get bits */
|
|
const brush_t *brush = ds.mapBrush;
|
|
const side_t& side = ds.sideRef->side;
|
|
|
|
/* check subdivision for shader */
|
|
const shaderInfo_t *si = side.shaderInfo;
|
|
if ( si == nullptr ) {
|
|
continue;
|
|
}
|
|
|
|
/* ydnar: don't subdivide sky surfaces */
|
|
if ( si->compileFlags & C_SKY ) {
|
|
continue;
|
|
}
|
|
|
|
/* get subdivisions from shader */
|
|
const float subdivisions = si->subdivisions;
|
|
if ( subdivisions < 1 ) {
|
|
continue;
|
|
}
|
|
|
|
/* preserve fog num */
|
|
const int fogNum = ds.fogNum;
|
|
|
|
/* make a winding and free the surface */
|
|
winding_t w = WindingFromDrawSurf( ds );
|
|
ClearSurface( ds );
|
|
|
|
/* subdivide it */
|
|
SubdivideFace_r( e, *brush, side, w, fogNum, subdivisions );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
====================
|
|
ClipSideIntoTree_r
|
|
|
|
Adds non-opaque leaf fragments to the convex hull
|
|
====================
|
|
*/
|
|
|
|
static void ClipSideIntoTree_r( const winding_t& w, side_t& side, const node_t *node ){
|
|
if ( w.empty() ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
if ( side.planenum == node->planenum ) {
|
|
ClipSideIntoTree_r( w, side, node->children[eFront] );
|
|
return;
|
|
}
|
|
if ( side.planenum == ( node->planenum ^ 1 ) ) {
|
|
ClipSideIntoTree_r( w, side, node->children[eBack] );
|
|
return;
|
|
}
|
|
|
|
const Plane3f& plane = mapplanes[ node->planenum ].plane;
|
|
auto [front, back] = ClipWindingEpsilonStrict( w, plane, ON_EPSILON ); /* strict, we handle the "winding disappeared" case */
|
|
if ( front.empty() && back.empty() ) {
|
|
/* in doubt, register it in both nodes */
|
|
ClipSideIntoTree_r( w, side, node->children[eFront] );
|
|
ClipSideIntoTree_r( w, side, node->children[eBack] );
|
|
}
|
|
else{
|
|
ClipSideIntoTree_r( front, side, node->children[eFront] );
|
|
ClipSideIntoTree_r( back, side, node->children[eBack] );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// if opaque leaf, don't add
|
|
if ( !node->opaque ) {
|
|
AddWindingToConvexHull( w, side.visibleHull, mapplanes[ side.planenum ].normal() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int g_numHiddenFaces, g_numCoinFaces;
|
|
|
|
|
|
|
|
#define CULL_EPSILON 0.1f
|
|
|
|
/*
|
|
SideInBrush() - ydnar
|
|
determines if a brushside lies inside another brush
|
|
*/
|
|
|
|
static bool SideInBrush( side_t& side, const brush_t& b ){
|
|
/* ignore sides w/o windings or shaders */
|
|
if ( side.winding.empty() || side.shaderInfo == nullptr ) {
|
|
return true;
|
|
}
|
|
|
|
/* ignore culled sides and translucent brushes */
|
|
if ( side.culled || ( b.compileFlags & C_TRANSLUCENT ) ) {
|
|
return false;
|
|
}
|
|
|
|
/* side iterator */
|
|
for ( const side_t& bside : b.sides )
|
|
{
|
|
/* fail if any sides are caulk */
|
|
if ( bside.compileFlags & C_NODRAW ) {
|
|
return false;
|
|
}
|
|
|
|
/* check if side's winding is on or behind the plane */
|
|
const Plane3f& plane = mapplanes[ bside.planenum ].plane;
|
|
const EPlaneSide s = WindingOnPlaneSide( side.winding, plane );
|
|
if ( s == eSideFront || s == eSideCross ) {
|
|
return false;
|
|
}
|
|
if( s == eSideOn && bside.culled && vector3_dot( mapplanes[ side.planenum ].normal(), plane.normal() ) > 0 ) /* don't cull by freshly culled with matching plane */
|
|
return false;
|
|
}
|
|
|
|
/* don't cull autosprite or polygonoffset surfaces */
|
|
if ( side.shaderInfo->autosprite || side.shaderInfo->polygonOffset ) {
|
|
return false;
|
|
}
|
|
|
|
/* inside */
|
|
side.culled = true;
|
|
g_numHiddenFaces++;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
CullSides() - ydnar
|
|
culls obscured or buried brushsides from the map
|
|
*/
|
|
|
|
static void CullSides( entity_t& e ){
|
|
int k, l, first, second, dir;
|
|
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- CullSides ---\n" );
|
|
|
|
g_numHiddenFaces = 0;
|
|
g_numCoinFaces = 0;
|
|
|
|
/* brush interator 1 */
|
|
for ( brushlist_t::iterator b1 = e.brushes.begin(); b1 != e.brushes.end(); ++b1 )
|
|
{
|
|
/* sides check */
|
|
if ( b1->sides.empty() ) {
|
|
continue;
|
|
}
|
|
|
|
/* brush iterator 2 */
|
|
for ( brushlist_t::iterator b2 = std::next( b1 ); b2 != e.brushes.end(); ++b2 )
|
|
{
|
|
/* sides check */
|
|
if ( b2->sides.empty() ) {
|
|
continue;
|
|
}
|
|
|
|
/* original check */
|
|
if ( b1->original == b2->original && b1->original != nullptr ) {
|
|
continue;
|
|
}
|
|
|
|
/* bbox check */
|
|
if ( !b1->minmax.test( b2->minmax ) ) {
|
|
continue;
|
|
}
|
|
|
|
/* cull inside sides */
|
|
for ( side_t& side : b1->sides )
|
|
SideInBrush( side, *b2 );
|
|
for ( side_t& side : b2->sides )
|
|
SideInBrush( side, *b1 );
|
|
|
|
/* side iterator 1 */
|
|
for ( side_t& side1 : b1->sides )
|
|
{
|
|
/* winding check */
|
|
winding_t& w1 = side1.winding;
|
|
if ( w1.empty() ) {
|
|
continue;
|
|
}
|
|
const int numPoints = w1.size();
|
|
if ( side1.shaderInfo == nullptr ) {
|
|
continue;
|
|
}
|
|
|
|
/* side iterator 2 */
|
|
for ( side_t& side2 : b2->sides )
|
|
{
|
|
/* winding check */
|
|
winding_t& w2 = side2.winding;
|
|
if ( w2.empty() ) {
|
|
continue;
|
|
}
|
|
if ( side2.shaderInfo == nullptr ) {
|
|
continue;
|
|
}
|
|
if ( w1.size() != w2.size() ) {
|
|
continue;
|
|
}
|
|
if ( side1.culled && side2.culled ) {
|
|
continue;
|
|
}
|
|
|
|
/* compare planes */
|
|
if ( ( side1.planenum & ~0x00000001 ) != ( side2.planenum & ~0x00000001 ) ) {
|
|
continue;
|
|
}
|
|
|
|
/* get autosprite and polygonoffset status */
|
|
if ( side1.shaderInfo->autosprite || side1.shaderInfo->polygonOffset ) {
|
|
continue;
|
|
}
|
|
if ( side2.shaderInfo->autosprite || side2.shaderInfo->polygonOffset ) {
|
|
continue;
|
|
}
|
|
|
|
/* find first common point */
|
|
first = -1;
|
|
for ( k = 0; k < numPoints; ++k )
|
|
{
|
|
if ( VectorCompare( w1[ 0 ], w2[ k ] ) ) {
|
|
first = k;
|
|
break;
|
|
}
|
|
}
|
|
if ( first == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
/* find second common point (regardless of winding order) */
|
|
second = ( ( first + 1 ) < numPoints )? ( first + 1 ) : 0;
|
|
dir = 0;
|
|
if ( vector3_equal_epsilon( w1[ 1 ], w2[ second ], CULL_EPSILON ) ) {
|
|
dir = 1;
|
|
}
|
|
else
|
|
{
|
|
if ( first > 0 ) {
|
|
second = first - 1;
|
|
}
|
|
else{
|
|
second = numPoints - 1;
|
|
}
|
|
if ( vector3_equal_epsilon( w1[ 1 ], w2[ second ], CULL_EPSILON ) ) {
|
|
dir = -1;
|
|
}
|
|
}
|
|
if ( dir == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
/* compare the rest of the points */
|
|
l = first;
|
|
for ( k = 0; k < numPoints; ++k )
|
|
{
|
|
if ( !vector3_equal_epsilon( w1[ k ], w2[ l ], CULL_EPSILON ) ) {
|
|
k = 100000;
|
|
}
|
|
|
|
l += dir;
|
|
if ( l < 0 ) {
|
|
l = numPoints - 1;
|
|
}
|
|
else if ( l >= numPoints ) {
|
|
l = 0;
|
|
}
|
|
}
|
|
if ( k >= 100000 ) {
|
|
continue;
|
|
}
|
|
|
|
/* cull face 1 */
|
|
if ( !side2.culled && !( side2.compileFlags & C_TRANSLUCENT ) && !( side2.compileFlags & C_NODRAW ) ) {
|
|
side1.culled = true;
|
|
g_numCoinFaces++;
|
|
}
|
|
|
|
if ( side1.planenum == side2.planenum && side1.culled ) {
|
|
continue;
|
|
}
|
|
|
|
/* cull face 2 */
|
|
if ( !side1.culled && !( side1.compileFlags & C_TRANSLUCENT ) && !( side1.compileFlags & C_NODRAW ) ) {
|
|
side2.culled = true;
|
|
g_numCoinFaces++;
|
|
}
|
|
|
|
// TODO ? this culls only one of face-to-face windings; SideInBrush culls both tho; is this needed at all or should be improved?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* emit some stats */
|
|
Sys_FPrintf( SYS_VRB, "%9d hidden faces culled\n", g_numHiddenFaces );
|
|
Sys_FPrintf( SYS_VRB, "%9d coincident faces culled\n", g_numCoinFaces );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
ClipSidesIntoTree()
|
|
|
|
creates side->visibleHull for all visible sides
|
|
|
|
the drawsurf for a side will consist of the convex hull of
|
|
all points in non-opaque clusters, which allows overlaps
|
|
to be trimmed off automatically.
|
|
*/
|
|
|
|
void ClipSidesIntoTree( entity_t& e, const tree_t& tree ){
|
|
/* ydnar: cull brush sides */
|
|
CullSides( e );
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- ClipSidesIntoTree ---\n" );
|
|
|
|
/* walk the brush list */
|
|
for ( brush_t& b : e.brushes )
|
|
{
|
|
/* walk the brush sides */
|
|
for ( side_t& side : b.sides )
|
|
{
|
|
if ( side.winding.empty() ) {
|
|
continue;
|
|
}
|
|
|
|
side.visibleHull.clear();
|
|
ClipSideIntoTree_r( side.winding, side, tree.headnode );
|
|
|
|
/* anything left? */
|
|
if ( side.visibleHull.empty() ) {
|
|
continue;
|
|
}
|
|
|
|
/* shader? */
|
|
const shaderInfo_t *si = side.shaderInfo;
|
|
if ( si == nullptr ) {
|
|
continue;
|
|
}
|
|
|
|
/* don't create faces for non-visible sides */
|
|
/* ydnar: except indexed shaders, like common/terrain and nodraw fog surfaces */
|
|
if ( ( si->compileFlags & C_NODRAW ) && !si->indexed && !( si->compileFlags & C_FOG ) ) {
|
|
continue;
|
|
}
|
|
|
|
/* always use the original winding for autosprites and noclip faces */
|
|
const winding_t& w = ( si->autosprite || si->noClip )? side.winding : side.visibleHull;
|
|
|
|
/* save this winding as a visible surface */
|
|
DrawSurfaceForSide( e, b, side, w );
|
|
|
|
/* make a back side for fog */
|
|
if ( si->compileFlags & C_FOG ) {
|
|
/* duplicate the up-facing side */
|
|
side_t& newSide = *new side_t( side );
|
|
newSide.visibleHull = ReverseWinding( w );
|
|
newSide.planenum ^= 1;
|
|
|
|
/* save this winding as a visible surface */
|
|
DrawSurfaceForSide( e, b, newSide, newSide.visibleHull ); // references new side by sideref, leak!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
this section deals with filtering drawsurfaces into the bsp tree,
|
|
adding references to each leaf a surface touches
|
|
|
|
*/
|
|
|
|
/*
|
|
AddReferenceToLeaf() - ydnar
|
|
adds a reference to surface ds in the bsp leaf node
|
|
*/
|
|
|
|
static int AddReferenceToLeaf( mapDrawSurface_t& ds, node_t *node ){
|
|
drawSurfRef_t *dsr;
|
|
const int numBSPDrawSurfaces = bspDrawSurfaces.size();
|
|
|
|
|
|
/* dummy check */
|
|
if ( node->planenum != PLANENUM_LEAF || node->opaque ) {
|
|
return 0;
|
|
}
|
|
|
|
/* try to find an existing reference */
|
|
for ( dsr = node->drawSurfReferences; dsr; dsr = dsr->nextRef )
|
|
{
|
|
if ( dsr->outputNum == numBSPDrawSurfaces ) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* add a new reference */
|
|
dsr = safe_malloc( sizeof( *dsr ) );
|
|
dsr->outputNum = numBSPDrawSurfaces;
|
|
dsr->nextRef = node->drawSurfReferences;
|
|
node->drawSurfReferences = dsr;
|
|
|
|
/* ydnar: sky/skybox surfaces */
|
|
if ( node->skybox ) {
|
|
ds.skybox = true;
|
|
}
|
|
if ( ds.shaderInfo->compileFlags & C_SKY ) {
|
|
node->sky = true;
|
|
}
|
|
|
|
/* return */
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AddReferenceToTree_r() - ydnar
|
|
adds a reference to the specified drawsurface to every leaf in the tree
|
|
*/
|
|
|
|
static int AddReferenceToTree_r( mapDrawSurface_t& ds, node_t *node, bool skybox ){
|
|
/* dummy check */
|
|
if ( node == nullptr ) {
|
|
return 0;
|
|
}
|
|
|
|
/* is this a decision node? */
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
/* add to child nodes and return */
|
|
int refs = 0;
|
|
refs += AddReferenceToTree_r( ds, node->children[ 0 ], skybox );
|
|
refs += AddReferenceToTree_r( ds, node->children[ 1 ], skybox );
|
|
return refs;
|
|
}
|
|
|
|
/* ydnar */
|
|
if ( skybox ) {
|
|
/* skybox surfaces only get added to sky leaves */
|
|
if ( !node->sky ) {
|
|
return 0;
|
|
}
|
|
|
|
/* increase the leaf bounds */
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
node->minmax.extend( vert.xyz );
|
|
}
|
|
|
|
/* add a reference */
|
|
return AddReferenceToLeaf( ds, node );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterPointIntoTree_r() - ydnar
|
|
filters a single point from a surface into the tree
|
|
*/
|
|
|
|
static int FilterPointIntoTree_r( const Vector3& point, mapDrawSurface_t& ds, node_t *node ){
|
|
/* is this a decision node? */
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
/* classify the point in relation to the plane */
|
|
const float d = plane3_distance_to_point( mapplanes[ node->planenum ].plane, point );
|
|
|
|
/* filter by this plane */
|
|
int refs = 0;
|
|
if ( d >= -ON_EPSILON ) {
|
|
refs += FilterPointIntoTree_r( point, ds, node->children[eFront] );
|
|
}
|
|
if ( d <= ON_EPSILON ) {
|
|
refs += FilterPointIntoTree_r( point, ds, node->children[eBack] );
|
|
}
|
|
|
|
/* return */
|
|
return refs;
|
|
}
|
|
|
|
/* add a reference */
|
|
return AddReferenceToLeaf( ds, node );
|
|
}
|
|
|
|
/*
|
|
FilterPointConvexHullIntoTree_r() - ydnar
|
|
filters the convex hull of multiple points from a surface into the tree
|
|
*/
|
|
|
|
static int FilterPointConvexHullIntoTree_r( const std::array<Vector3, 16>& points, mapDrawSurface_t& ds, node_t *node ){
|
|
/* is this a decision node? */
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
/* classify the point in relation to the plane */
|
|
const Plane3f& plane = mapplanes[ node->planenum ].plane;
|
|
|
|
float dmin = std::numeric_limits<float>::max(), dmax = std::numeric_limits<float>::lowest();
|
|
for ( const Vector3& point : points )
|
|
{
|
|
const float d = plane3_distance_to_point( plane, point );
|
|
value_maximize( dmax, d );
|
|
value_minimize( dmin, d );
|
|
}
|
|
|
|
/* filter by this plane */
|
|
int refs = 0;
|
|
if ( dmax >= -ON_EPSILON ) {
|
|
refs += FilterPointConvexHullIntoTree_r( points, ds, node->children[eFront] );
|
|
}
|
|
if ( dmin <= ON_EPSILON ) {
|
|
refs += FilterPointConvexHullIntoTree_r( points, ds, node->children[eBack] );
|
|
}
|
|
|
|
/* return */
|
|
return refs;
|
|
}
|
|
|
|
/* add a reference */
|
|
return AddReferenceToLeaf( ds, node );
|
|
}
|
|
|
|
|
|
/*
|
|
FilterWindingIntoTree_r() - ydnar
|
|
filters a winding from a drawsurface into the tree
|
|
*/
|
|
|
|
static int FilterWindingIntoTree_r( winding_t& w, mapDrawSurface_t& ds, node_t *node ){
|
|
/* get shaderinfo */
|
|
const shaderInfo_t *si = ds.shaderInfo;
|
|
|
|
/* ydnar: is this the head node? */
|
|
if ( node->parent == nullptr && si != nullptr && si->minmax.valid() ) {
|
|
static bool warned = false;
|
|
if ( !warned ) {
|
|
Sys_Warning( "this map uses the deformVertexes move hack\n" );
|
|
warned = true;
|
|
}
|
|
|
|
/* 'fatten' the winding by the shader mins/maxs (parsed from vertexDeform move) */
|
|
/* note this winding is completely invalid (concave, nonplanar, etc) */
|
|
winding_t fat( w.size() * 3 + 3 );
|
|
for ( size_t i = 0; i < w.size(); ++i )
|
|
{
|
|
fat[ i ] = w[ i ];
|
|
fat[ i + ( w.size() + 1 ) ] = w[ i ] + si->minmax.mins;
|
|
fat[ i + ( w.size() + 1 ) * 2 ] = w[ i ] + si->minmax.maxs;
|
|
}
|
|
fat[ w.size() ] = w[ 0 ];
|
|
fat[ w.size() * 2 ] = w[ 0 ] + si->minmax.mins;
|
|
fat[ w.size() * 3 ] = w[ 0 ] + si->minmax.maxs;
|
|
|
|
/*
|
|
* note: this winding is STILL not suitable for ClipWindingEpsilon, and
|
|
* also does not really fulfill the intention as it only contains
|
|
* origin, +mins, +maxs, but thanks to the "closing" points I just
|
|
* added to the three sub-windings, the fattening at least doesn't make
|
|
* it worse
|
|
*/
|
|
|
|
w.swap( fat );
|
|
}
|
|
|
|
/* is this a decision node? */
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
/* get node plane */
|
|
const Plane3f plane1 = mapplanes[ node->planenum ].plane;
|
|
|
|
/* check if surface is planar */
|
|
if ( ds.planeNum >= 0 ) {
|
|
#if 0
|
|
/* get surface plane */
|
|
const Plane3f plane2 = mapplanes[ ds.planeNum ].plane;
|
|
|
|
/* div0: this is the plague (inaccurate) */
|
|
/* invert surface plane */
|
|
const Plane3f reverse = plane3_flipped( plane2 );
|
|
|
|
/* compare planes */
|
|
if ( vector3_dot( plane1.normal(), plane2.normal() ) > 0.999f && std::fabs( plane1.dist() - plane2.dist() ) < 0.001f ) {
|
|
return FilterWindingIntoTree_r( w, ds, node->children[ 0 ] );
|
|
}
|
|
if ( vector3_dot( plane1.normal(), reverse.normal() ) > 0.999f && std::fabs( plane1.dist() - reverse.dist() ) < 0.001f ) {
|
|
return FilterWindingIntoTree_r( w, ds, node->children[ 1 ] );
|
|
}
|
|
#else
|
|
/* div0: this is the cholera (doesn't hit enough) */
|
|
|
|
/* the drawsurf might have an associated plane, if so, force a filter here */
|
|
if ( ds.planeNum == node->planenum ) {
|
|
return FilterWindingIntoTree_r( w, ds, node->children[eFront] );
|
|
}
|
|
if ( ds.planeNum == ( node->planenum ^ 1 ) ) {
|
|
return FilterWindingIntoTree_r( w, ds, node->children[eBack] );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* clip the winding by this plane */
|
|
auto [front, back] = ClipWindingEpsilonStrict( w, plane1, ON_EPSILON ); /* strict; we handle the "winding disappeared" case */
|
|
|
|
/* filter by this plane */
|
|
int refs = 0;
|
|
if ( front.empty() && back.empty() ) {
|
|
/* same plane, this is an ugly hack */
|
|
/* but better too many than too few refs */
|
|
winding_t wcopy( w );
|
|
refs += FilterWindingIntoTree_r( wcopy, ds, node->children[eFront] );
|
|
refs += FilterWindingIntoTree_r( w, ds, node->children[eBack] );
|
|
}
|
|
if ( !front.empty() ) {
|
|
refs += FilterWindingIntoTree_r( front, ds, node->children[eFront] );
|
|
}
|
|
if ( !back.empty() ) {
|
|
refs += FilterWindingIntoTree_r( back, ds, node->children[eBack] );
|
|
}
|
|
|
|
/* return */
|
|
return refs;
|
|
}
|
|
|
|
/* add a reference */
|
|
return AddReferenceToLeaf( ds, node );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterFaceIntoTree()
|
|
filters a planar winding face drawsurface into the bsp tree
|
|
*/
|
|
|
|
static int FilterFaceIntoTree( mapDrawSurface_t& ds, tree_t& tree ){
|
|
/* make a winding and filter it into the tree */
|
|
winding_t w = WindingFromDrawSurf( ds );
|
|
int refs = FilterWindingIntoTree_r( w, ds, tree.headnode );
|
|
|
|
/* return */
|
|
return refs;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterPatchIntoTree()
|
|
subdivides a patch into an approximate curve and filters it into the tree
|
|
*/
|
|
|
|
static int FilterPatchIntoTree( mapDrawSurface_t& ds, tree_t& tree ){
|
|
int refs = 0;
|
|
|
|
for ( int y = 0; y + 2 < ds.patchHeight; y += 2 )
|
|
for ( int x = 0; x + 2 < ds.patchWidth; x += 2 )
|
|
{
|
|
const Vector3& p0 = ds.verts[( y + 0 ) * ds.patchWidth + ( x + 0 )].xyz;
|
|
const Vector3& p1 = ds.verts[( y + 0 ) * ds.patchWidth + ( x + 1 )].xyz;
|
|
const Vector3& p2 = ds.verts[( y + 0 ) * ds.patchWidth + ( x + 2 )].xyz;
|
|
const Vector3& p3 = ds.verts[( y + 1 ) * ds.patchWidth + ( x + 0 )].xyz;
|
|
const Vector3& p4 = ds.verts[( y + 1 ) * ds.patchWidth + ( x + 1 )].xyz;
|
|
const Vector3& p5 = ds.verts[( y + 1 ) * ds.patchWidth + ( x + 2 )].xyz;
|
|
const Vector3& p6 = ds.verts[( y + 2 ) * ds.patchWidth + ( x + 0 )].xyz;
|
|
const Vector3& p7 = ds.verts[( y + 2 ) * ds.patchWidth + ( x + 1 )].xyz;
|
|
const Vector3& p8 = ds.verts[( y + 2 ) * ds.patchWidth + ( x + 2 )].xyz;
|
|
|
|
// add 4 invariant points + 12 of those which are used to calculate subdivisionless patch LoD
|
|
// convex hull defined by them guaranteedly encompasses any patch LoD
|
|
std::array<Vector3, 16> points = { p0, p2, p6, p8,
|
|
vector3_mid( p0, p1 ), vector3_mid( p1, p2 ),
|
|
vector3_mid( p6, p7 ), vector3_mid( p7, p8 ),
|
|
vector3_mid( p0, p3 ), vector3_mid( p3, p6 ),
|
|
vector3_mid( p2, p5 ), vector3_mid( p5, p8 ) };
|
|
// final curve points
|
|
const Vector3 p1_ = vector3_mid( points[4], points[5] );
|
|
const Vector3 p7_ = vector3_mid( points[6], points[7] );
|
|
const Vector3 p3_ = vector3_mid( points[8], points[9] );
|
|
const Vector3 p5_ = vector3_mid( points[10], points[11] );
|
|
const Vector3 p4_ = ( p1 + p4 * 2 + p7 ) * .25; // PutPointsOnCurve() lerps colums 1st
|
|
// then row pass
|
|
points[12] = vector3_mid( p3_, p4_ );
|
|
points[13] = vector3_mid( p4_, p5_ );
|
|
const Vector3 p4__ = vector3_mid( points[12], points[13] ); // final central point
|
|
// find original central point as if column lerp occured last and produced the final point
|
|
// (a + 2c + b )/4 = f
|
|
// c = (4f - a - b)/2
|
|
const Vector3 p4o = p4__ * 2 - vector3_mid( p1_, p7_ );
|
|
points[14] = vector3_mid( p1_, p4o );
|
|
points[15] = vector3_mid( p4o, p7_ );
|
|
|
|
refs += FilterPointConvexHullIntoTree_r( points, ds, tree.headnode );
|
|
}
|
|
|
|
return refs;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterTrianglesIntoTree()
|
|
filters a triangle surface (meta, model) into the bsp
|
|
*/
|
|
|
|
static int FilterTrianglesIntoTree( mapDrawSurface_t& ds, tree_t& tree ){
|
|
int refs = 0;
|
|
|
|
/* ydnar: gs mods: this was creating bogus triangles before */
|
|
for ( auto i = ds.indexes.cbegin(); i != ds.indexes.cend(); i += 3 )
|
|
{
|
|
/* error check */
|
|
if ( *( i + 0 ) >= ds.numVerts() ||
|
|
*( i + 1 ) >= ds.numVerts() ||
|
|
*( i + 2 ) >= ds.numVerts() ) {
|
|
Error( "Index %d greater than vertex count %d", *i, ds.numVerts() );
|
|
}
|
|
|
|
/* make a triangle winding and filter it into the tree */
|
|
winding_t w{
|
|
ds.verts[ *( i + 0 ) ].xyz,
|
|
ds.verts[ *( i + 1 ) ].xyz,
|
|
ds.verts[ *( i + 2 ) ].xyz };
|
|
refs += FilterWindingIntoTree_r( w, ds, tree.headnode );
|
|
}
|
|
|
|
/* use point filtering as well */
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
refs += FilterPointIntoTree_r( vert.xyz, ds, tree.headnode );
|
|
|
|
return refs;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterFoliageIntoTree()
|
|
filters a foliage surface (wolf et/splash damage)
|
|
*/
|
|
|
|
static int FilterFoliageIntoTree( mapDrawSurface_t& ds, tree_t& tree ){
|
|
/* walk origin list */
|
|
int refs = 0;
|
|
for ( bspDrawVert_t& instance : Span( &ds.verts[ ds.patchHeight ], ds.numFoliageInstances ) )
|
|
{
|
|
/* walk triangle list */
|
|
for ( auto i = ds.indexes.cbegin(); i != ds.indexes.cend(); i += 3 )
|
|
{
|
|
/* error check */
|
|
if ( *( i + 0 ) >= ds.numVerts() ||
|
|
*( i + 1 ) >= ds.numVerts() ||
|
|
*( i + 2 ) >= ds.numVerts() ) {
|
|
Error( "Index %d greater than vertex count %d", *i, ds.numVerts() );
|
|
}
|
|
|
|
/* make a triangle winding and filter it into the tree */
|
|
winding_t w{
|
|
instance.xyz + ds.verts[ *( i + 0 ) ].xyz,
|
|
instance.xyz + ds.verts[ *( i + 1 ) ].xyz,
|
|
instance.xyz + ds.verts[ *( i + 2 ) ].xyz };
|
|
refs += FilterWindingIntoTree_r( w, ds, tree.headnode );
|
|
}
|
|
|
|
/* use point filtering as well */
|
|
for ( const bspDrawVert_t& vert : Span( ds.verts.cbegin(), ds.verts.cend() - ds.numFoliageInstances ) )
|
|
{
|
|
refs += FilterPointIntoTree_r( instance.xyz + vert.xyz, ds, tree.headnode );
|
|
}
|
|
}
|
|
|
|
return refs;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterFlareIntoTree()
|
|
simple point filtering for flare surfaces
|
|
*/
|
|
static int FilterFlareSurfIntoTree( mapDrawSurface_t& ds, tree_t& tree ){
|
|
return FilterPointIntoTree_r( ds.lightmapOrigin, ds, tree.headnode );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
EmitDrawVerts() - ydnar
|
|
emits bsp drawverts from a map drawsurface
|
|
*/
|
|
|
|
static void EmitDrawVerts( const mapDrawSurface_t& ds, bspDrawSurface_t& out ){
|
|
/* get stuff */
|
|
const float offset = ds.shaderInfo->offset;
|
|
|
|
/* copy the verts */
|
|
out.firstVert = bspDrawVerts.size();
|
|
out.numVerts = ds.verts.size();
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
/* allocate a new vert */ /* copy it */
|
|
bspDrawVert_t& dv = bspDrawVerts.emplace_back( vert );
|
|
|
|
/* offset? */
|
|
if ( offset != 0 ) {
|
|
dv.xyz += dv.normal * offset;
|
|
}
|
|
|
|
/* expand model bounds
|
|
necessary because of misc_model surfaces on entities
|
|
note: does not happen on worldspawn as its bounds is only used for determining lightgrid bounds */
|
|
if ( bspModels.size() > 1 ) {
|
|
bspModels.back().minmax.extend( dv.xyz );
|
|
}
|
|
|
|
/* debug color? */
|
|
if ( debugSurfaces ) {
|
|
for ( auto& color : dv.color )
|
|
color.rgb() = debugColors[ ( &ds - mapDrawSurfs ) % 12 ];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FindDrawIndexes() - ydnar
|
|
this attempts to find a run of indexes in the bsp that match the given indexes
|
|
this tends to reduce the size of the bsp index pool by 1/3 or more
|
|
returns numIndexes + 1 if the search failed
|
|
*/
|
|
|
|
static int FindDrawIndexes( const DrawIndexes& indexes ){
|
|
const size_t numBSPDrawIndexes = bspDrawIndexes.size();
|
|
|
|
|
|
/* dummy check */
|
|
if ( indexes.empty() || numBSPDrawIndexes < indexes.size() ) {
|
|
return numBSPDrawIndexes;
|
|
}
|
|
|
|
/* set limit */
|
|
const size_t numTestIndexes = 1 + numBSPDrawIndexes - indexes.size();
|
|
|
|
/* handle 3 indexes as a special case for performance */
|
|
if ( indexes.size() == 3 ) {
|
|
/* run through all indexes */
|
|
for ( size_t i = 0; i < numTestIndexes; ++i )
|
|
{
|
|
/* test 3 indexes */
|
|
if ( indexes[ 0 ] == bspDrawIndexes[ i + 0 ] &&
|
|
indexes[ 1 ] == bspDrawIndexes[ i + 1 ] &&
|
|
indexes[ 2 ] == bspDrawIndexes[ i + 2 ] ) {
|
|
numRedundantIndexes += indexes.size();
|
|
return i;
|
|
}
|
|
}
|
|
|
|
/* failed */
|
|
return numBSPDrawIndexes;
|
|
}
|
|
|
|
/* handle 4 or more indexes */
|
|
for ( size_t i = 0; i < numTestIndexes; ++i )
|
|
{
|
|
/* test first 4 indexes */
|
|
if ( indexes[ 0 ] == bspDrawIndexes[ i + 0 ] &&
|
|
indexes[ 1 ] == bspDrawIndexes[ i + 1 ] &&
|
|
indexes[ 2 ] == bspDrawIndexes[ i + 2 ] &&
|
|
indexes[ 3 ] == bspDrawIndexes[ i + 3 ] ) {
|
|
/* handle 4 indexes */
|
|
if ( indexes.size() == 4 ) {
|
|
return i;
|
|
}
|
|
|
|
/* test the remainder */
|
|
for ( size_t j = 4; j < indexes.size(); ++j )
|
|
{
|
|
if ( indexes[ j ] != bspDrawIndexes[ i + j ] ) {
|
|
break;
|
|
}
|
|
else if ( j == ( indexes.size() - 1 ) ) {
|
|
numRedundantIndexes += indexes.size();
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* failed */
|
|
return numBSPDrawIndexes;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
EmitDrawIndexes() - ydnar
|
|
attempts to find an existing run of drawindexes before adding new ones
|
|
*/
|
|
|
|
static void EmitDrawIndexes( const mapDrawSurface_t& ds, bspDrawSurface_t& out ){
|
|
/* attempt to use redundant indexing */
|
|
out.firstIndex = FindDrawIndexes( ds.indexes );
|
|
out.numIndexes = ds.indexes.size();
|
|
if ( out.firstIndex == int( bspDrawIndexes.size() ) ) {
|
|
/* copy new unique indexes */
|
|
for ( int i : ds.indexes )
|
|
{
|
|
int& index = bspDrawIndexes.emplace_back( i );
|
|
|
|
/* validate the index */
|
|
if ( ds.type != ESurfaceType::Patch ) {
|
|
if ( index < 0 || index >= ds.numVerts() ) {
|
|
Sys_Warning( "%zu %s has invalid index %d\n",
|
|
bspDrawSurfaces.size() - 1,
|
|
ds.shaderInfo->shader.c_str(),
|
|
index );
|
|
index = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
EmitFlareSurface()
|
|
emits a bsp flare drawsurface
|
|
*/
|
|
|
|
static void EmitFlareSurface( mapDrawSurface_t& ds ){
|
|
/* ydnar: nuking useless flare drawsurfaces */
|
|
if ( !emitFlares && ds.type != ESurfaceType::Shader ) {
|
|
return;
|
|
}
|
|
|
|
/* allocate a new surface */
|
|
bspDrawSurface_t& out = bspDrawSurfaces.emplace_back();
|
|
ds.outputNum = bspDrawSurfaces.size() - 1;
|
|
|
|
/* set it up */
|
|
out.surfaceType = MST_FLARE;
|
|
out.shaderNum = EmitShader( ds.shaderInfo->shader, &ds.shaderInfo->contentFlags, &ds.shaderInfo->surfaceFlags );
|
|
out.fogNum = ds.fogNum;
|
|
|
|
/* RBSP */
|
|
for ( int i = 0; i < MAX_LIGHTMAPS; ++i )
|
|
{
|
|
out.lightmapNum[ i ] = -3;
|
|
out.lightmapStyles[ i ] = LS_NONE;
|
|
out.vertexStyles[ i ] = LS_NONE;
|
|
}
|
|
out.lightmapStyles[ 0 ] = ds.lightStyle;
|
|
out.vertexStyles[ 0 ] = ds.lightStyle;
|
|
|
|
out.lightmapOrigin = ds.lightmapOrigin; /* origin */
|
|
out.lightmapVecs[ 0 ] = ds.lightmapVecs[ 0 ]; /* color */
|
|
out.lightmapVecs[ 1 ] = ds.lightmapVecs[ 1 ];
|
|
out.lightmapVecs[ 2 ] = ds.lightmapVecs[ 2 ]; /* normal */
|
|
|
|
/* add to count */
|
|
numSurfacesByType[ static_cast<std::size_t>( ds.type ) ]++;
|
|
}
|
|
|
|
/*
|
|
EmitPatchSurface()
|
|
emits a bsp patch drawsurface
|
|
*/
|
|
|
|
static void EmitPatchSurface( const entity_t& e, mapDrawSurface_t& ds ){
|
|
/* vortex: _patchMeta support */
|
|
const bool forcePatchMeta = e.boolForKey( "_patchMeta", "patchMeta" );
|
|
|
|
/* invert the surface if necessary */
|
|
if ( ds.backSide || ds.shaderInfo->invert ) {
|
|
/* walk the verts, flip the normal */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
vector3_negate( vert.normal );
|
|
|
|
/* walk the verts again, but this time reverse their order */
|
|
for ( int j = 0; j < ds.patchHeight; ++j )
|
|
{
|
|
for ( int i = 0; i < ( ds.patchWidth / 2 ); ++i )
|
|
{
|
|
std::swap( ds.verts[ j * ds.patchWidth + i ],
|
|
ds.verts[ j * ds.patchWidth + ( ds.patchWidth - i - 1 ) ] );
|
|
}
|
|
}
|
|
|
|
/* invert facing */
|
|
vector3_negate( ds.lightmapVecs[ 2 ] );
|
|
}
|
|
|
|
/* allocate a new surface */
|
|
bspDrawSurface_t& out = bspDrawSurfaces.emplace_back();
|
|
ds.outputNum = bspDrawSurfaces.size() - 1;
|
|
|
|
/* set it up */
|
|
out.surfaceType = MST_PATCH;
|
|
if ( debugSurfaces ) {
|
|
out.shaderNum = EmitShader( "debugsurfaces", nullptr, nullptr );
|
|
}
|
|
else if ( patchMeta || forcePatchMeta ) {
|
|
/* patch meta requires that we have nodraw patches for collision */
|
|
int surfaceFlags = ds.shaderInfo->surfaceFlags;
|
|
int contentFlags = ds.shaderInfo->contentFlags;
|
|
ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, nullptr );
|
|
ApplySurfaceParm( "pointlight", &contentFlags, &surfaceFlags, nullptr );
|
|
|
|
/* we don't want this patch getting lightmapped */
|
|
ds.lightmapVecs[ 2 ].set( 0 );
|
|
ds.lightmapAxis.set( 0 );
|
|
ds.sampleSize = 0;
|
|
|
|
/* emit the new fake shader */
|
|
out.shaderNum = EmitShader( ds.shaderInfo->shader, &contentFlags, &surfaceFlags );
|
|
}
|
|
else{
|
|
out.shaderNum = EmitShader( ds.shaderInfo->shader, &ds.shaderInfo->contentFlags, &ds.shaderInfo->surfaceFlags );
|
|
}
|
|
out.patchWidth = ds.patchWidth;
|
|
out.patchHeight = ds.patchHeight;
|
|
out.fogNum = ds.fogNum;
|
|
|
|
/* RBSP */
|
|
for ( int i = 0; i < MAX_LIGHTMAPS; ++i )
|
|
{
|
|
out.lightmapNum[ i ] = -3;
|
|
out.lightmapStyles[ i ] = LS_NONE;
|
|
out.vertexStyles[ i ] = LS_NONE;
|
|
}
|
|
out.lightmapStyles[ 0 ] = LS_NORMAL;
|
|
out.vertexStyles[ 0 ] = LS_NORMAL;
|
|
|
|
/* ydnar: gs mods: previously, the lod bounds were stored in lightmapVecs[ 0 ] and [ 1 ], moved to bounds[ 0 ] and [ 1 ] */
|
|
out.lightmapOrigin = ds.lightmapOrigin;
|
|
out.lightmapVecs[ 0 ] = ds.bounds.mins;
|
|
out.lightmapVecs[ 1 ] = ds.bounds.maxs;
|
|
out.lightmapVecs[ 2 ] = ds.lightmapVecs[ 2 ];
|
|
|
|
/* ydnar: gs mods: clear out the plane normal */
|
|
if ( !ds.planar ) {
|
|
out.lightmapVecs[ 2 ].set( 0 );
|
|
}
|
|
|
|
/* emit the verts and indexes */
|
|
EmitDrawVerts( ds, out );
|
|
EmitDrawIndexes( ds, out );
|
|
|
|
/* add to count */
|
|
numSurfacesByType[ static_cast<std::size_t>( ds.type ) ]++;
|
|
}
|
|
|
|
/*
|
|
Autosprite2Deform() function in vanilla Q3 engine and most of sourceports
|
|
produces inconsistent results, which depend on vertex order and indexing.
|
|
Try to please that windy lady.
|
|
*/
|
|
static void FixAutosprite2Surface( mapDrawSurface_t& ds ){
|
|
if( ds.verts.size() != 4 || ds.indexes.size() != 6 ){
|
|
Sys_Warning( "autosprite2 surface: ds.numVerts != 4 or ds.numIndexes != 6: must be simple rectangle\n" );
|
|
return;
|
|
}
|
|
|
|
Plane3f plane;
|
|
if( !PlaneFromPoints( plane, ds.verts[ds.indexes[0]].xyz, ds.verts[ds.indexes[1]].xyz, ds.verts[ds.indexes[2]].xyz ) ){
|
|
Sys_Warning( "autosprite2 surface: degenerate triangle\n" );
|
|
return;
|
|
}
|
|
|
|
// reproduce Autosprite2Deform() calculations
|
|
const int edgeVerts[6][2] = {
|
|
{ 0, 1 },
|
|
{ 0, 2 },
|
|
{ 0, 3 },
|
|
{ 1, 2 },
|
|
{ 1, 3 },
|
|
{ 2, 3 }
|
|
};
|
|
float lengths[2] = { 999999, 999999 };
|
|
int edgeIdx[2] = {0};
|
|
|
|
// identify the two shortest edges
|
|
for ( int j = 0; j < 6; ++j ) {
|
|
const float l = vector3_length_squared( ds.verts[edgeVerts[j][0]].xyz - ds.verts[edgeVerts[j][1]].xyz );
|
|
|
|
if ( l < lengths[0] ) {
|
|
edgeIdx[1] = edgeIdx[0];
|
|
lengths[1] = lengths[0];
|
|
edgeIdx[0] = j;
|
|
lengths[0] = l;
|
|
} else if ( l < lengths[1] ) {
|
|
edgeIdx[1] = j;
|
|
lengths[1] = l;
|
|
}
|
|
}
|
|
// ref edges
|
|
const bspDrawVert_t *edges[2][2] = { { &ds.verts[ edgeVerts[edgeIdx[0]][0] ], &ds.verts[ edgeVerts[edgeIdx[0]][1] ] },
|
|
{ &ds.verts[ edgeVerts[edgeIdx[1]][0] ], &ds.verts[ edgeVerts[edgeIdx[1]][1] ] } };
|
|
|
|
if( edges[0][0] == edges[1][0]
|
|
|| edges[0][0] == edges[1][1]
|
|
|| edges[0][1] == edges[1][0]
|
|
|| edges[0][1] == edges[1][1] ){
|
|
Sys_Warning( "autosprite2 surface: two shortest edges share a vertex\n" ); // note also fails on exact square
|
|
return;
|
|
}
|
|
|
|
// find the midpoints
|
|
const Vector3 mid[2] = { vector3_mid( edges[0][0]->xyz, edges[0][1]->xyz ),
|
|
vector3_mid( edges[1][0]->xyz, edges[1][1]->xyz ) };
|
|
|
|
// find the vector of the major axis
|
|
const Vector3 major = mid[1] - mid[0];
|
|
|
|
// cross this with the view direction to get minor axis
|
|
const Vector3 minor = vector3_cross( major, -plane.normal() );
|
|
|
|
/* the rest of Autosprite2Deform() algorithm is srsly hot trash
|
|
thus simply force the order and indexing, which are known as working */
|
|
|
|
/* 1-----------2 C C 1-----------2 A ^minor
|
|
| | | | |
|
|
B 0-----------3 A B 0-----------3 -----------> major */
|
|
|
|
if( vector3_dot( edges[0][1]->xyz - edges[0][0]->xyz, minor ) < 0 )
|
|
std::swap( edges[0][0], edges[0][1] );
|
|
if( vector3_dot( edges[1][1]->xyz - edges[1][0]->xyz, minor ) > 0 )
|
|
std::swap( edges[1][0], edges[1][1] );
|
|
|
|
ds.verts.assign( { *edges[0][0], *edges[0][1], *edges[1][0], *edges[1][1] } );
|
|
ds.indexes.assign( { 3, 0, 2, 2, 0, 1 } );
|
|
}
|
|
|
|
/*
|
|
OptimizeTriangleSurface() - ydnar
|
|
optimizes the vertex/index data in a triangle surface
|
|
*/
|
|
|
|
#define VERTEX_CACHE_SIZE 16
|
|
|
|
static void OptimizeTriangleSurface( mapDrawSurface_t& ds ){
|
|
int first, best, bestScore, score;
|
|
int vertexCache[ VERTEX_CACHE_SIZE + 1 ]; /* one more for optimizing insert */
|
|
|
|
|
|
/* certain surfaces don't get optimized */
|
|
if ( ds.indexes.size() <= VERTEX_CACHE_SIZE ||
|
|
ds.shaderInfo->autosprite ) {
|
|
return;
|
|
}
|
|
|
|
/* create index scratch pad */
|
|
DrawIndexes indexes = ds.indexes;
|
|
|
|
/* setup */
|
|
std::copy_n( indexes.cbegin(), std::min( size_t( VERTEX_CACHE_SIZE + 1 ), ds.indexes.size() ), vertexCache );
|
|
|
|
/* add triangles in a vertex cache-aware order */
|
|
for ( auto i = ds.indexes.begin(); i != ds.indexes.end(); i += 3 )
|
|
{
|
|
/* find best triangle given the current vertex cache */
|
|
first = -1;
|
|
best = -1;
|
|
bestScore = -1;
|
|
for ( size_t j = 0; j < ds.indexes.size(); j += 3 )
|
|
{
|
|
/* valid triangle? */
|
|
if ( indexes[ j ] != -1 ) {
|
|
/* set first if necessary */
|
|
if ( first < 0 ) {
|
|
first = j;
|
|
}
|
|
|
|
/* score the triangle */
|
|
score = 0;
|
|
for ( int k = 0; k < VERTEX_CACHE_SIZE; ++k )
|
|
{
|
|
if ( indexes[ j ] == vertexCache[ k ] || indexes[ j + 1 ] == vertexCache[ k ] || indexes[ j + 2 ] == vertexCache[ k ] ) {
|
|
score++;
|
|
}
|
|
}
|
|
|
|
/* better triangle? */
|
|
if ( score > bestScore ) {
|
|
bestScore = score;
|
|
best = j;
|
|
}
|
|
|
|
/* a perfect score of 3 means this triangle's verts are already present in the vertex cache */
|
|
if ( score == 3 ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check if no decent triangle was found, and use first available */
|
|
if ( best < 0 ) {
|
|
best = first;
|
|
}
|
|
|
|
/* valid triangle? */
|
|
if ( best >= 0 ) {
|
|
/* add triangle to vertex cache */
|
|
for ( int j = 0; j < 3; ++j )
|
|
{
|
|
int k;
|
|
for ( k = 0; k < VERTEX_CACHE_SIZE; ++k )
|
|
{
|
|
if ( indexes[ best + j ] == vertexCache[ k ] ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( k >= VERTEX_CACHE_SIZE ) {
|
|
/* pop off top of vertex cache */
|
|
for ( k = VERTEX_CACHE_SIZE; k > 0; --k )
|
|
vertexCache[ k ] = vertexCache[ k - 1 ];
|
|
|
|
/* add vertex */
|
|
vertexCache[ 0 ] = indexes[ best + j ];
|
|
}
|
|
}
|
|
|
|
/* add triangle to surface */
|
|
*( i + 0 ) = indexes[ best + 0 ];
|
|
*( i + 1 ) = indexes[ best + 1 ];
|
|
*( i + 2 ) = indexes[ best + 2 ];
|
|
|
|
/* clear from input pool */
|
|
indexes[ best + 0 ] = -1;
|
|
indexes[ best + 1 ] = -1;
|
|
indexes[ best + 2 ] = -1;
|
|
|
|
/* sort triangle windings (312 -> 123) */
|
|
while ( *i > *( i + 1 ) || *i > *( i + 2 ) )
|
|
{
|
|
std::swap( *( i + 0 ), *( i + 1 ) );
|
|
std::swap( *( i + 1 ), *( i + 2 ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
EmitTriangleSurface()
|
|
creates a bsp drawsurface from arbitrary triangle surfaces
|
|
*/
|
|
|
|
static void EmitTriangleSurface( mapDrawSurface_t& ds ){
|
|
/* invert the surface if necessary */
|
|
if ( ds.backSide || ds.shaderInfo->invert ) {
|
|
/* walk the indexes, reverse the triangle order */
|
|
for ( auto i = ds.indexes.begin(); i != ds.indexes.end(); i += 3 )
|
|
{
|
|
std::swap( *i, *( i + 1 ) );
|
|
}
|
|
|
|
/* walk the verts, flip the normal */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
vector3_negate( vert.normal );
|
|
|
|
/* invert facing */
|
|
vector3_negate( ds.lightmapVecs[ 2 ] );
|
|
}
|
|
|
|
if( ds.shaderInfo->autosprite
|
|
&& ds.shaderInfo->shaderText != nullptr
|
|
&& strIstr( ds.shaderInfo->shaderText, "autosprite2" ) != nullptr ){
|
|
FixAutosprite2Surface( ds );
|
|
}
|
|
|
|
/* allocate a new surface */
|
|
bspDrawSurface_t& out = bspDrawSurfaces.emplace_back();
|
|
ds.outputNum = bspDrawSurfaces.size() - 1;
|
|
|
|
/* ydnar/sd: handle wolf et foliage surfaces */
|
|
if ( ds.type == ESurfaceType::Foliage ) {
|
|
out.surfaceType = MST_FOLIAGE;
|
|
}
|
|
|
|
/* ydnar: gs mods: handle lightmapped terrain (force to planar type) */
|
|
//% else if( vector3_length( ds.lightmapAxis ) == 0 || ds.type == ESurfaceType::Triangles || ds.type == ESurfaceType::Foghull || debugSurfaces )
|
|
else if ( ( ds.lightmapAxis == g_vector3_identity && !ds.planar ) ||
|
|
ds.type == ESurfaceType::Triangles ||
|
|
ds.type == ESurfaceType::Foghull ||
|
|
ds.numVerts() > maxLMSurfaceVerts ||
|
|
debugSurfaces ) {
|
|
out.surfaceType = MST_TRIANGLE_SOUP;
|
|
}
|
|
|
|
/* set to a planar face */
|
|
else{
|
|
out.surfaceType = MST_PLANAR;
|
|
}
|
|
|
|
/* set it up */
|
|
if ( debugSurfaces ) {
|
|
out.shaderNum = EmitShader( "debugsurfaces", nullptr, nullptr );
|
|
}
|
|
else{
|
|
out.shaderNum = EmitShader( ds.shaderInfo->shader, &ds.shaderInfo->contentFlags, &ds.shaderInfo->surfaceFlags );
|
|
}
|
|
out.patchWidth = ds.patchWidth;
|
|
out.patchHeight = ds.patchHeight;
|
|
out.fogNum = ds.fogNum;
|
|
|
|
/* debug inset (push each triangle vertex towards the center of each triangle it is on */
|
|
if ( debugInset ) {
|
|
/* walk triangle list */
|
|
for ( auto i = ds.indexes.begin(); i != ds.indexes.end(); i += 3 )
|
|
{
|
|
/* get verts */
|
|
bspDrawVert_t
|
|
&a = ds.verts[ *( i + 0 ) ],
|
|
&b = ds.verts[ *( i + 1 ) ],
|
|
&c = ds.verts[ *( i + 2 ) ];
|
|
|
|
/* calculate centroid */
|
|
const Vector3 cent = ( a.xyz + b.xyz + c.xyz ) / 3;
|
|
|
|
/* offset each vertex */
|
|
a.xyz += VectorNormalized( cent - a.xyz );
|
|
b.xyz += VectorNormalized( cent - b.xyz );
|
|
c.xyz += VectorNormalized( cent - c.xyz );
|
|
}
|
|
}
|
|
|
|
/* RBSP */
|
|
for ( int i = 0; i < MAX_LIGHTMAPS; ++i )
|
|
{
|
|
out.lightmapNum[ i ] = -3;
|
|
out.lightmapStyles[ i ] = LS_NONE;
|
|
out.vertexStyles[ i ] = LS_NONE;
|
|
}
|
|
out.lightmapStyles[ 0 ] = LS_NORMAL;
|
|
out.vertexStyles[ 0 ] = LS_NORMAL;
|
|
|
|
/* lightmap vectors (lod bounds for patches) */
|
|
out.lightmapOrigin = ds.lightmapOrigin;
|
|
out.lightmapVecs[ 0 ] = ds.lightmapVecs[ 0 ];
|
|
out.lightmapVecs[ 1 ] = ds.lightmapVecs[ 1 ];
|
|
out.lightmapVecs[ 2 ] = ds.lightmapVecs[ 2 ];
|
|
|
|
/* ydnar: gs mods: clear out the plane normal */
|
|
if ( !ds.planar ) {
|
|
out.lightmapVecs[ 2 ].set( 0 );
|
|
}
|
|
|
|
/* optimize the surface's triangles */
|
|
OptimizeTriangleSurface( ds );
|
|
|
|
/* emit the verts and indexes */
|
|
EmitDrawVerts( ds, out );
|
|
EmitDrawIndexes( ds, out );
|
|
|
|
/* add to count */
|
|
numSurfacesByType[ static_cast<std::size_t>( ds.type ) ]++;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
EmitFaceSurface()
|
|
emits a bsp planar winding (brush face) drawsurface
|
|
*/
|
|
|
|
static void EmitFaceSurface( mapDrawSurface_t& ds ){
|
|
/* strip/fan finding was moved elsewhere */
|
|
if ( maxAreaFaceSurface ) {
|
|
MaxAreaFaceSurface( ds );
|
|
}
|
|
else{
|
|
StripFaceSurface( ds );
|
|
}
|
|
EmitTriangleSurface( ds );
|
|
}
|
|
|
|
|
|
/*
|
|
MakeDebugPortalSurfs_r() - ydnar
|
|
generates drawsurfaces for passable portals in the bsp
|
|
*/
|
|
|
|
static void MakeDebugPortalSurfs_r( const node_t *node, shaderInfo_t& si ){
|
|
/* recurse if decision node */
|
|
if ( node->planenum != PLANENUM_LEAF ) {
|
|
MakeDebugPortalSurfs_r( node->children[ 0 ], si );
|
|
MakeDebugPortalSurfs_r( node->children[ 1 ], si );
|
|
return;
|
|
}
|
|
|
|
/* don't bother with opaque leaves */
|
|
if ( node->opaque ) {
|
|
return;
|
|
}
|
|
|
|
/* walk the list of portals */
|
|
int c = 0;
|
|
for ( const portal_t *p = node->portals; p != nullptr; ++c, p = p->nextPortal( node ) )
|
|
{
|
|
/* get winding and side even/odd */
|
|
const winding_t& w = p->winding;
|
|
|
|
/* is this a valid portal for this leaf? */
|
|
if ( !w.empty() && p->nodes[eFront] == node ) {
|
|
/* is this portal passable? */
|
|
if ( !PortalPassable( p ) ) {
|
|
continue;
|
|
}
|
|
|
|
/* check max points */
|
|
if ( w.size() > 64 ) {
|
|
Error( "MakePortalSurfs_r: w->numpoints = %zu", w.size() );
|
|
}
|
|
|
|
/* allocate a drawsurface */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Face );
|
|
ds.shaderInfo = &si;
|
|
ds.planar = true;
|
|
ds.planeNum = FindFloatPlane( p->plane.plane, 0, nullptr );
|
|
ds.lightmapVecs[ 2 ] = p->plane.normal();
|
|
ds.fogNum = FOG_INVALID;
|
|
ds.verts.resize( w.size(), c_bspDrawVert_t0 );
|
|
|
|
/* walk the winding */
|
|
for ( size_t i = 0; i < w.size(); ++i )
|
|
{
|
|
/* get vert */
|
|
bspDrawVert_t& dv = ds.verts[ i ];
|
|
|
|
/* set it */
|
|
dv.xyz = w[ i ];
|
|
dv.normal = p->plane.normal();
|
|
dv.st = { 0, 0 };
|
|
for ( auto& color : dv.color )
|
|
{
|
|
color.rgb() = debugColors[ c % 12 ];
|
|
color.alpha() = 32;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MakeDebugPortalSurfs() - ydnar
|
|
generates drawsurfaces for passable portals in the bsp
|
|
*/
|
|
|
|
void MakeDebugPortalSurfs( const tree_t& tree ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- MakeDebugPortalSurfs ---\n" );
|
|
|
|
/* walk the tree */
|
|
MakeDebugPortalSurfs_r( tree.headnode, ShaderInfoForShader( "debugportals" ) );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
MakeFogHullSurfs()
|
|
generates drawsurfaces for a foghull (this MUST use a sky shader)
|
|
*/
|
|
|
|
void MakeFogHullSurfs( const char *shader ){
|
|
int indexes[] =
|
|
{
|
|
0, 1, 2, 0, 2, 3,
|
|
4, 7, 5, 5, 7, 6,
|
|
1, 5, 6, 1, 6, 2,
|
|
0, 4, 5, 0, 5, 1,
|
|
2, 6, 7, 2, 7, 3,
|
|
3, 7, 4, 3, 4, 0
|
|
};
|
|
|
|
|
|
/* dummy check */
|
|
if ( strEmptyOrNull( shader ) ) {
|
|
return;
|
|
}
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- MakeFogHullSurfs ---\n" );
|
|
|
|
/* get hull bounds */
|
|
const Vector3 fogMins = g_mapMinmax.mins - Vector3( 128 );
|
|
const Vector3 fogMaxs = g_mapMinmax.maxs + Vector3( 128 );
|
|
|
|
/* allocate a drawsurface */
|
|
mapDrawSurface_t& ds = AllocDrawSurface( ESurfaceType::Foghull );
|
|
ds.shaderInfo = &ShaderInfoForShader( shader );
|
|
ds.fogNum = FOG_INVALID;
|
|
ds.verts.resize( 8, c_bspDrawVert_t0 );
|
|
|
|
/* set verts */
|
|
ds.verts[ 0 ].xyz = { fogMins[ 0 ], fogMins[ 1 ], fogMins[ 2 ] };
|
|
ds.verts[ 1 ].xyz = { fogMins[ 0 ], fogMaxs[ 1 ], fogMins[ 2 ] };
|
|
ds.verts[ 2 ].xyz = { fogMaxs[ 0 ], fogMaxs[ 1 ], fogMins[ 2 ] };
|
|
ds.verts[ 3 ].xyz = { fogMaxs[ 0 ], fogMins[ 1 ], fogMins[ 2 ] };
|
|
|
|
ds.verts[ 4 ].xyz = { fogMins[ 0 ], fogMins[ 1 ], fogMaxs[ 2 ] };
|
|
ds.verts[ 5 ].xyz = { fogMins[ 0 ], fogMaxs[ 1 ], fogMaxs[ 2 ] };
|
|
ds.verts[ 6 ].xyz = { fogMaxs[ 0 ], fogMaxs[ 1 ], fogMaxs[ 2 ] };
|
|
ds.verts[ 7 ].xyz = { fogMaxs[ 0 ], fogMins[ 1 ], fogMaxs[ 2 ] };
|
|
|
|
/* set indexes */
|
|
ds.indexes.assign( std::cbegin( indexes ), std::cend( indexes ) );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
BiasSurfaceTextures()
|
|
biases a surface's texcoords as close to 0 as possible
|
|
*/
|
|
|
|
static void BiasSurfaceTextures( mapDrawSurface_t& ds ){
|
|
/* don't bias globaltextured shaders */
|
|
if ( ds.shaderInfo->globalTexture ) {
|
|
return;
|
|
}
|
|
|
|
/* calculate the surface texture bias */
|
|
const Vector2 bias = CalcSurfaceTextureBias( ds );
|
|
|
|
/* bias the texture coordinates */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
vert.st -= bias;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AddSurfaceModelsToTriangle_r()
|
|
adds models to a specified triangle, returns the number of models added
|
|
*/
|
|
|
|
static int AddSurfaceModelsToTriangle_r( mapDrawSurface_t& ds, const surfaceModel_t& model, const TriRef& tri, entity_t& entity ){
|
|
int max, n, localNumSurfaceModels;
|
|
|
|
|
|
/* init */
|
|
localNumSurfaceModels = 0;
|
|
|
|
/* subdivide calc */
|
|
{
|
|
/* find the longest edge and split it */
|
|
max = -1;
|
|
float maxDist = 0;
|
|
for ( int i = 0; i < 3; ++i )
|
|
{
|
|
/* get dist */
|
|
const float dist = vector3_length_squared( tri[ i ]->xyz - tri[ ( i + 1 ) % 3 ]->xyz );
|
|
|
|
/* longer? */
|
|
if ( dist > maxDist ) {
|
|
maxDist = dist;
|
|
max = i;
|
|
}
|
|
}
|
|
|
|
/* is the triangle small enough? */
|
|
if ( max < 0 || maxDist <= ( model.density * model.density ) ) {
|
|
float odds, r, angle;
|
|
Vector3 axis[ 3 ];
|
|
|
|
|
|
/* roll the dice (model's odds scaled by vertex alpha) */
|
|
odds = model.odds * ( tri[ 0 ]->color[ 0 ].alpha() + tri[ 0 ]->color[ 0 ].alpha() + tri[ 0 ]->color[ 0 ].alpha() ) / 765.0f;
|
|
r = Random();
|
|
if ( r > odds ) {
|
|
return 0;
|
|
}
|
|
|
|
/* calculate scale */
|
|
r = model.minScale + Random() * ( model.maxScale - model.minScale );
|
|
const Vector3 scale( r );
|
|
|
|
/* calculate angle */
|
|
angle = model.minAngle + Random() * ( model.maxAngle - model.minAngle );
|
|
|
|
/* set angles */
|
|
const Vector3 angles( 0, 0, angle );
|
|
|
|
/* calculate average origin */
|
|
const Vector3 origin = ( tri[ 0 ]->xyz + tri[ 1 ]->xyz + tri[ 2 ]->xyz ) / 3;
|
|
|
|
/* clear transform matrix */
|
|
Matrix4 transform( g_matrix4_identity );
|
|
|
|
/* handle oriented models */
|
|
if ( model.oriented ) {
|
|
/* calculate average normal */
|
|
axis[ 2 ] = tri[ 0 ]->normal + tri[ 1 ]->normal + tri[ 2 ]->normal;
|
|
if ( VectorNormalize( axis[ 2 ] ) == 0 ) {
|
|
axis[ 2 ] = tri[ 0 ]->normal;
|
|
}
|
|
|
|
/* make perpendicular vectors */
|
|
MakeNormalVectors( axis[ 2 ], axis[ 1 ], axis[ 0 ] );
|
|
|
|
/* copy to matrix */
|
|
Matrix4 temp( g_matrix4_identity );
|
|
temp.x().vec3() = axis[0];
|
|
temp.y().vec3() = axis[1];
|
|
temp.z().vec3() = axis[2];
|
|
|
|
/* scale */
|
|
matrix4_scale_by_vec3( temp, scale );
|
|
|
|
/* rotate around z axis */
|
|
matrix4_rotate_by_euler_xyz_degrees( temp, angles );
|
|
|
|
/* translate */
|
|
matrix4_translate_by_vec3( transform, origin );
|
|
|
|
/* transform into axis space */
|
|
matrix4_multiply_by_matrix4( transform, temp );
|
|
}
|
|
|
|
/* handle z-up models */
|
|
else
|
|
{
|
|
/* set matrix */
|
|
matrix4_transform_by_euler_xyz_degrees( transform, origin, angles, scale );
|
|
}
|
|
|
|
/* insert the model */
|
|
InsertModel( model.model.c_str(), nullptr, 0, transform, nullptr, entity, 0, clipDepthGlobal,
|
|
EntityCompileParams{
|
|
.castShadows = ds.castShadows,
|
|
.recvShadows = ds.recvShadows,
|
|
.celShader = ds.celShader,
|
|
.lightmapSampleSize = 0,
|
|
.lightmapScale = ds.lightmapScale,
|
|
.shadeAngle = 0,
|
|
.ambientColor = ds.ambientColor
|
|
} );
|
|
/* return to sender */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* split the longest edge and map it */
|
|
const bspDrawVert_t mid = LerpDrawVert( *tri[ max ], *tri[ ( max + 1 ) % 3 ] );
|
|
|
|
/* recurse to first triangle */
|
|
TriRef tri2 = tri;
|
|
tri2[ max ] = ∣
|
|
n = AddSurfaceModelsToTriangle_r( ds, model, tri2, entity );
|
|
if ( n < 0 ) {
|
|
return n;
|
|
}
|
|
localNumSurfaceModels += n;
|
|
|
|
/* recurse to second triangle */
|
|
tri2 = tri;
|
|
tri2[ ( max + 1 ) % 3 ] = ∣
|
|
n = AddSurfaceModelsToTriangle_r( ds, model, tri2, entity );
|
|
if ( n < 0 ) {
|
|
return n;
|
|
}
|
|
localNumSurfaceModels += n;
|
|
|
|
/* return count */
|
|
return localNumSurfaceModels;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AddSurfaceModels()
|
|
adds a surface's shader models to the surface
|
|
*/
|
|
|
|
static int AddSurfaceModels( mapDrawSurface_t& ds, entity_t& entity ){
|
|
/* dummy check */
|
|
if ( ds.shaderInfo == nullptr || ds.shaderInfo->surfaceModels.empty() ) {
|
|
return 0;
|
|
}
|
|
|
|
/* init */
|
|
int localNumSurfaceModels = 0;
|
|
|
|
/* walk the model list */
|
|
for ( const auto& model : ds.shaderInfo->surfaceModels )
|
|
{
|
|
/* switch on type */
|
|
switch ( ds.type )
|
|
{
|
|
/* handle brush faces and decals */
|
|
case ESurfaceType::Face:
|
|
case ESurfaceType::Decal:
|
|
{
|
|
/* calculate centroid */
|
|
bspDrawVert_t centroid = c_bspDrawVert_t0;
|
|
float alpha = 0;
|
|
|
|
/* walk verts */
|
|
for ( const bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
centroid.xyz += vert.xyz;
|
|
centroid.normal += vert.normal;
|
|
centroid.st += vert.st;
|
|
alpha += vert.color[ 0 ].alpha();
|
|
}
|
|
|
|
/* average */
|
|
centroid.xyz /= ds.verts.size();
|
|
if ( VectorNormalize( centroid.normal ) == 0 ) {
|
|
centroid.normal = ds.verts[ 0 ].normal;
|
|
}
|
|
centroid.st /= ds.verts.size();
|
|
centroid.color[ 0 ] = { 255, 255, 255, color_to_byte( alpha / ds.verts.size() ) };
|
|
|
|
/* walk fanned triangles */
|
|
for ( auto prev = std::prev( ds.verts.cend() ), next = ds.verts.cbegin(); next != ds.verts.cend(); prev = next, ++next )
|
|
{
|
|
/* create models */
|
|
const int n = AddSurfaceModelsToTriangle_r( ds, model, TriRef{
|
|
¢roid, /* head vert is centroid */
|
|
prev.operator->(),
|
|
next.operator->() }, entity );
|
|
if ( n < 0 ) {
|
|
return n;
|
|
}
|
|
localNumSurfaceModels += n;
|
|
}
|
|
break;
|
|
}
|
|
/* handle patches */
|
|
case ESurfaceType::Patch:
|
|
{
|
|
/* subdivide the surface */
|
|
mesh_t mesh = TessellatedMesh( mesh_t( ds.patchWidth, ds.patchHeight, ds.verts.data() ), IterationsForCurve( ds.longestCurve, patchSubdivisions ) );
|
|
|
|
/* subdivide each quad to place the models */
|
|
for ( int y = 0; y < ( mesh.height - 1 ); ++y )
|
|
{
|
|
for ( int x = 0; x < ( mesh.width - 1 ); ++x )
|
|
{
|
|
/* set indexes */
|
|
const int pw[ 5 ] = {
|
|
x + ( y * mesh.width ),
|
|
x + ( ( y + 1 ) * mesh.width ),
|
|
x + 1 + ( ( y + 1 ) * mesh.width ),
|
|
x + 1 + ( y * mesh.width ),
|
|
x + ( y * mesh.width ), /* same as pw[ 0 ] */
|
|
};
|
|
/* set radix */
|
|
const int r = ( x + y ) & 1;
|
|
|
|
/* triangle 1 */
|
|
const int n = AddSurfaceModelsToTriangle_r( ds, model, TriRef{
|
|
&mesh.verts[ pw[ r + 0 ] ],
|
|
&mesh.verts[ pw[ r + 1 ] ],
|
|
&mesh.verts[ pw[ r + 2 ] ] }, entity );
|
|
if ( n < 0 ) {
|
|
return n;
|
|
}
|
|
localNumSurfaceModels += n;
|
|
|
|
/* triangle 2 */
|
|
const int n2 = AddSurfaceModelsToTriangle_r( ds, model, TriRef{
|
|
&mesh.verts[ pw[ r + 0 ] ],
|
|
&mesh.verts[ pw[ r + 2 ] ],
|
|
&mesh.verts[ pw[ r + 3 ] ] }, entity );
|
|
if ( n2 < 0 ) {
|
|
return n2;
|
|
}
|
|
localNumSurfaceModels += n2;
|
|
}
|
|
}
|
|
|
|
/* free the subdivided mesh */
|
|
mesh.freeVerts();
|
|
break;
|
|
}
|
|
/* handle triangle surfaces */
|
|
case ESurfaceType::Triangles:
|
|
case ESurfaceType::ForcedMeta:
|
|
case ESurfaceType::Meta:
|
|
/* walk the triangle list */
|
|
for ( auto i = ds.indexes.cbegin(); i != ds.indexes.cend(); i += 3 )
|
|
{
|
|
const int n = AddSurfaceModelsToTriangle_r( ds, model, TriRef{
|
|
&ds.verts[ *( i + 0 ) ],
|
|
&ds.verts[ *( i + 1 ) ],
|
|
&ds.verts[ *( i + 2 ) ] }, entity );
|
|
if ( n < 0 ) {
|
|
return n;
|
|
}
|
|
localNumSurfaceModels += n;
|
|
}
|
|
break;
|
|
|
|
/* no support for flares, foghull, etc */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* return count */
|
|
return localNumSurfaceModels;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AddEntitySurfaceModels() - ydnar
|
|
adds surfacemodels to an entity's surfaces
|
|
*/
|
|
|
|
void AddEntitySurfaceModels( entity_t& e ){
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- AddEntitySurfaceModels ---\n" );
|
|
|
|
/* walk the surface list */
|
|
for ( int i = e.firstDrawSurf; i < numMapDrawSurfs; ++i )
|
|
numSurfaceModels += AddSurfaceModels( mapDrawSurfs[ i ], e );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
VolumeColorMods() - ydnar
|
|
applies brush/volumetric color/alpha modulation to vertexes
|
|
*/
|
|
|
|
static void VolumeColorMods( const entity_t& e, mapDrawSurface_t& ds ){
|
|
/* iterate brushes */
|
|
for ( const brush_t *b : e.colorModBrushes )
|
|
{
|
|
/* worldspawn alpha brushes affect all, grouped ones only affect original entity */
|
|
if ( b->entityNum != 0 && b->entityNum != ds.entityNum ) {
|
|
continue;
|
|
}
|
|
|
|
/* test bbox */
|
|
if ( !b->minmax.test( ds.minmax ) ) {
|
|
continue;
|
|
}
|
|
|
|
/* iterate verts */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
{
|
|
if( std::ranges::none_of( b->sides, [&vert]( const side_t& side ){
|
|
return plane3_distance_to_point( mapplanes[ side.planenum ].plane, vert.xyz ) > 1; /* point-plane test */
|
|
} ) )
|
|
/* apply colormods */
|
|
ColorMod( b->contentShader->colorMod, Span( &vert, 1 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FilterDrawsurfsIntoTree()
|
|
upon completion, all drawsurfs that actually generate a reference
|
|
will have been emitted to the bspfile arrays, and the references
|
|
will have valid final indexes
|
|
*/
|
|
|
|
void FilterDrawsurfsIntoTree( entity_t& e, tree_t& tree ){
|
|
int refs;
|
|
int numSurfs, numRefs, numSkyboxSurfaces;
|
|
bool sb;
|
|
|
|
|
|
/* note it */
|
|
Sys_FPrintf( SYS_VRB, "--- FilterDrawsurfsIntoTree ---\n" );
|
|
|
|
/* filter surfaces into the tree */
|
|
numSurfs = 0;
|
|
numRefs = 0;
|
|
numSkyboxSurfaces = 0;
|
|
for ( int i = e.firstDrawSurf; i < numMapDrawSurfs; ++i )
|
|
{
|
|
/* get surface and try to early out */
|
|
mapDrawSurface_t& ds = mapDrawSurfs[ i ];
|
|
if ( ds.verts.empty() && ds.type != ESurfaceType::Flare && ds.type != ESurfaceType::Shader ) {
|
|
continue;
|
|
}
|
|
|
|
/* get shader */
|
|
shaderInfo_t *si = ds.shaderInfo;
|
|
|
|
/* ydnar: skybox surfaces are special */ /* these are ones generated by MakeSkyboxSurface() */
|
|
if ( ds.skybox ) {
|
|
refs = AddReferenceToTree_r( ds, tree.headnode, true );
|
|
ds.skybox = false;
|
|
sb = true;
|
|
}
|
|
else
|
|
{
|
|
sb = false;
|
|
|
|
/* refs initially zero */
|
|
refs = 0;
|
|
|
|
/* apply texture coordinate mods */
|
|
for ( bspDrawVert_t& vert : ds.verts )
|
|
TCMod( si->mod, vert.st );
|
|
|
|
/* ydnar: apply shader colormod */
|
|
ColorMod( ds.shaderInfo->colorMod, Span( ds.verts ) );
|
|
|
|
/* ydnar: apply brush colormod */
|
|
VolumeColorMods( e, ds );
|
|
|
|
/* ydnar: make fur surfaces */
|
|
if ( si->furNumLayers > 0 ) {
|
|
Fur( ds );
|
|
}
|
|
|
|
/* ydnar/sd: make foliage surfaces */
|
|
if ( !si->foliage.empty() ) {
|
|
Foliage( ds, e );
|
|
}
|
|
|
|
/* create a flare surface if necessary */
|
|
if ( !strEmptyOrNull( si->flareShader ) ) {
|
|
AddSurfaceFlare( ds, e.origin );
|
|
}
|
|
|
|
/* ydnar: don't emit nodraw surfaces (like nodraw fog) */
|
|
if ( ( si->compileFlags & C_NODRAW ) && ds.type != ESurfaceType::Patch ) {
|
|
continue;
|
|
}
|
|
|
|
/* ydnar: bias the surface textures */
|
|
BiasSurfaceTextures( ds );
|
|
|
|
/* ydnar: globalizing of fog volume handling (eek a hack) */
|
|
if ( &e != &entities[0] && !si->noFog ) {
|
|
/* offset surface by entity origin */
|
|
const MinMax minmax( ds.minmax.mins + e.origin, ds.minmax.maxs + e.origin );
|
|
|
|
/* set the fog number for this surface */
|
|
ds.fogNum = FogForBounds( minmax, 1.0f ); //% FogForPoint( origin, 0.0f );
|
|
}
|
|
}
|
|
|
|
/* ydnar: remap shader */
|
|
/* if ( !strEmptyOrNull( ds->shaderInfo->remapShader ) ) {
|
|
ds->shaderInfo = &ShaderInfoForShader( ds->shaderInfo->remapShader );
|
|
}
|
|
*/
|
|
/* ydnar: gs mods: handle the various types of surfaces */
|
|
switch ( ds.type )
|
|
{
|
|
/* handle brush faces */
|
|
case ESurfaceType::Face:
|
|
case ESurfaceType::Decal:
|
|
if ( refs == 0 ) {
|
|
refs = FilterFaceIntoTree( ds, tree );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitFaceSurface( ds );
|
|
}
|
|
break;
|
|
|
|
/* handle patches */
|
|
case ESurfaceType::Patch:
|
|
if ( refs == 0 ) {
|
|
refs = FilterPatchIntoTree( ds, tree );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitPatchSurface( e, ds );
|
|
}
|
|
break;
|
|
|
|
/* handle triangle surfaces */
|
|
case ESurfaceType::Triangles:
|
|
case ESurfaceType::ForcedMeta:
|
|
case ESurfaceType::Meta:
|
|
//% Sys_FPrintf( SYS_VRB, "Surface %4d: [%1d] %4d verts %s\n", numSurfs, ds->planar, ds->numVerts, si->shader );
|
|
if ( refs == 0 ) {
|
|
refs = FilterTrianglesIntoTree( ds, tree );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitTriangleSurface( ds );
|
|
}
|
|
break;
|
|
|
|
/* handle foliage surfaces (splash damage/wolf et) */
|
|
case ESurfaceType::Foliage:
|
|
//% Sys_FPrintf( SYS_VRB, "Surface %4d: [%d] %4d verts %s\n", numSurfs, ds->numFoliageInstances, ds->numVerts, si->shader );
|
|
if ( refs == 0 ) {
|
|
refs = FilterFoliageIntoTree( ds, tree );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitTriangleSurface( ds );
|
|
}
|
|
break;
|
|
|
|
/* handle foghull surfaces */
|
|
case ESurfaceType::Foghull:
|
|
if ( refs == 0 ) {
|
|
refs = AddReferenceToTree_r( ds, tree.headnode, false );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitTriangleSurface( ds );
|
|
}
|
|
break;
|
|
|
|
/* handle flares */
|
|
case ESurfaceType::Flare:
|
|
if ( refs == 0 ) {
|
|
refs = FilterFlareSurfIntoTree( ds, tree );
|
|
}
|
|
if ( refs > 0 ) {
|
|
EmitFlareSurface( ds );
|
|
}
|
|
break;
|
|
|
|
/* handle shader-only surfaces */
|
|
case ESurfaceType::Shader:
|
|
refs = 1;
|
|
EmitFlareSurface( ds );
|
|
break;
|
|
|
|
/* no references */
|
|
default:
|
|
refs = 0;
|
|
break;
|
|
}
|
|
|
|
/* maybe surface got marked as skybox again */
|
|
/* if we keep that flag, it will get scaled up AGAIN */
|
|
if ( sb ) {
|
|
ds.skybox = false;
|
|
}
|
|
|
|
/* tot up the references */
|
|
if ( refs > 0 ) {
|
|
/* tot up counts */
|
|
numSurfs++;
|
|
numRefs += refs;
|
|
|
|
/* emit extra surface data */
|
|
SetSurfaceExtra( ds );
|
|
//% Sys_FPrintf( SYS_VRB, "%d verts %d indexes\n", ds->numVerts, ds->numIndexes );
|
|
|
|
/* one last sanity check */
|
|
{
|
|
const bspDrawSurface_t& out = bspDrawSurfaces.back();
|
|
if ( out.numVerts == 3 && out.numIndexes > 3 ) {
|
|
Sys_Printf( "\n" );
|
|
Sys_Warning( "Potentially bad %s surface (%zu: %d, %d)\n %s\n",
|
|
surfaceTypeName( ds.type ),
|
|
bspDrawSurfaces.size(), out.numVerts, out.numIndexes, si->shader.c_str() );
|
|
}
|
|
}
|
|
|
|
/* ydnar: handle skybox surfaces */ /* these are ones found in _skybox area */
|
|
if ( ds.skybox ) {
|
|
MakeSkyboxSurface( ds );
|
|
numSkyboxSurfaces++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* emit some statistics */
|
|
Sys_FPrintf( SYS_VRB, "%9d references\n", numRefs );
|
|
Sys_FPrintf( SYS_VRB, "%9d (%zu) emitted drawsurfs\n", numSurfs, bspDrawSurfaces.size() );
|
|
Sys_FPrintf( SYS_VRB, "%9d stripped face surfaces\n", numStripSurfaces );
|
|
Sys_FPrintf( SYS_VRB, "%9d fanned face surfaces\n", numFanSurfaces );
|
|
Sys_FPrintf( SYS_VRB, "%9d maxarea'd face surfaces\n", numMaxAreaSurfaces );
|
|
Sys_FPrintf( SYS_VRB, "%9d surface models generated\n", numSurfaceModels );
|
|
Sys_FPrintf( SYS_VRB, "%9d skybox surfaces generated\n", numSkyboxSurfaces );
|
|
for ( std::size_t i = 0; i < std::size( numSurfacesByType ); ++i )
|
|
Sys_FPrintf( SYS_VRB, "%9d %s surfaces\n", numSurfacesByType[ i ], surfaceTypeName( static_cast<ESurfaceType>( i ) ) );
|
|
|
|
Sys_FPrintf( SYS_VRB, "%9d redundant indexes suppressed, saving %d Kbytes\n", numRedundantIndexes, ( numRedundantIndexes * 4 / 1024 ) );
|
|
}
|