mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-24 21:11:47 +00:00
70f301e4ff
cvar type+range extension module tracking for cvars and commands lots of help text
1496 lines
46 KiB
C++
1496 lines
46 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "tr_local.h"
|
|
|
|
/*
|
|
Loads and prepares a map file for scene rendering.
|
|
A single entry point: void RE_LoadWorldMap( const char* name );
|
|
*/
|
|
|
|
static world_t s_worldData;
|
|
static byte* fileBase;
|
|
|
|
|
|
static void R_ColorShiftLightingBytes( const byte in[4], byte out[4] )
|
|
{
|
|
// shift the color data based on overbright range
|
|
const int shift = Com_ClampInt( 0, 2, r_mapOverBrightBits->integer - tr.overbrightBits );
|
|
|
|
// shift the data based on overbright range
|
|
int r = in[0] << shift;
|
|
int g = in[1] << shift;
|
|
int b = in[2] << shift;
|
|
|
|
// normalize by color instead of saturating to white
|
|
if ( ( r | g | b ) > 255 ) {
|
|
int max;
|
|
max = r > g ? r : g;
|
|
max = max > b ? max : b;
|
|
r = r * 255 / max;
|
|
g = g * 255 / max;
|
|
b = b * 255 / max;
|
|
}
|
|
|
|
out[0] = r;
|
|
out[1] = g;
|
|
out[2] = b;
|
|
out[3] = in[3];
|
|
}
|
|
|
|
|
|
static void R_LoadLightmaps( const lump_t* l )
|
|
{
|
|
const int LIGHTMAP_SIZE = 128;
|
|
|
|
int i, j, k;
|
|
byte image[LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4];
|
|
|
|
// if we are in r_vertexLight mode, we don't need the lightmaps at all
|
|
if (r_vertexLight->integer)
|
|
return;
|
|
|
|
int len = l->filelen;
|
|
if (!len)
|
|
return;
|
|
|
|
byte* p = fileBase + l->fileofs;
|
|
|
|
// we are about to upload textures
|
|
R_SyncRenderThread();
|
|
|
|
// create all the lightmaps
|
|
tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3);
|
|
if ( tr.numLightmaps >= MAX_LIGHTMAPS ) {
|
|
ri.Printf( PRINT_WARNING, "WARNING: number of lightmaps > MAX_LIGHTMAPS\n" );
|
|
tr.numLightmaps = MAX_LIGHTMAPS;
|
|
}
|
|
|
|
for ( i = 0; i < tr.numLightmaps; ++i ) {
|
|
// expand the 24 bit on-disk to 32 bit
|
|
for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; ++j ) {
|
|
R_ColorShiftLightingBytes( &p[j * 3], &image[j * 4] );
|
|
|
|
for ( k = 0; k < 3; ++k ) {
|
|
const float c1 = (float)image[j * 4 + k] / 255.0f;
|
|
const float c2 = Com_Clamp( 0.0f, 1.0f, c1 );
|
|
image[j * 4 + k] = (byte)(c2 * 255.0f);
|
|
}
|
|
image[j * 4 + 3] = 255;
|
|
}
|
|
tr.lightmaps[i] = R_CreateImage( va("*lightmap%d",i), image,
|
|
LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL_RGB, IMG_NOMIPMAP | IMG_NOPICMIP, GL_CLAMP );
|
|
p += LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3;
|
|
}
|
|
}
|
|
|
|
|
|
// called by the clipmodel subsystem so we can share the PVS
|
|
|
|
void RE_SetWorldVisData( const byte* vis )
|
|
{
|
|
tr.externalVisData = vis;
|
|
}
|
|
|
|
|
|
static void R_LoadVisibility( const lump_t* l )
|
|
{
|
|
int len = ( s_worldData.numClusters + 63 ) & ~63;
|
|
s_worldData.novis = RI_New<byte>( len );
|
|
Com_Memset( s_worldData.novis, 0xff, len );
|
|
|
|
len = l->filelen;
|
|
if ( !len )
|
|
return;
|
|
|
|
byte* p = fileBase + l->fileofs;
|
|
|
|
s_worldData.numClusters = LittleLong( ((int *)p)[0] );
|
|
s_worldData.clusterBytes = LittleLong( ((int *)p)[1] );
|
|
|
|
// CM_Load should have given us the vis data to share, so
|
|
// we don't need to allocate another copy
|
|
if ( tr.externalVisData ) {
|
|
s_worldData.vis = tr.externalVisData;
|
|
} else {
|
|
byte* dest = RI_New<byte>( len - 8 );
|
|
Com_Memcpy( dest, p + 8, len - 8 );
|
|
s_worldData.vis = dest;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static shader_t* ShaderForShaderNum( int shaderNum, int lightmapNum )
|
|
{
|
|
shaderNum = LittleLong( shaderNum );
|
|
if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders )
|
|
ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum );
|
|
|
|
const dshader_t* dsh = &s_worldData.shaders[ shaderNum ];
|
|
|
|
if (r_vertexLight->integer)
|
|
lightmapNum = LIGHTMAP_BY_VERTEX;
|
|
|
|
shader_t* shader = R_FindShader( dsh->shader, lightmapNum, qtrue );
|
|
|
|
if ( r_singleShader->integer && (shader->sort != SS_ENVIRONMENT) )
|
|
return tr.defaultShader;
|
|
|
|
// if the shader had errors, just use default shader
|
|
if ( shader->defaultShader )
|
|
return tr.defaultShader;
|
|
|
|
return shader;
|
|
}
|
|
|
|
|
|
template <class T> T* AllocSurface( int numVerts, int numIndexes )
|
|
{
|
|
T* surf = (T*)RI_New<byte>
|
|
( sizeof(T) + (numVerts * sizeof(*surf->verts)) + (numIndexes * sizeof(*surf->indexes)) );
|
|
surf->verts = (srfVert_t*)(surf + 1);
|
|
surf->indexes = (int*)(surf->verts + numVerts);
|
|
surf->numVerts = numVerts;
|
|
surf->numIndexes = numIndexes;
|
|
return surf;
|
|
}
|
|
|
|
|
|
static void ParseFace( const dsurface_t* ds, const drawVert_t* verts, msurface_t* surf, const int* indexes )
|
|
{
|
|
int i, j;
|
|
|
|
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
|
|
surf->shader = ShaderForShaderNum( ds->shaderNum, LittleLong( ds->lightmapNum ) );
|
|
|
|
int numVerts = LittleLong( ds->numVerts );
|
|
if (numVerts > MAX_FACE_POINTS) {
|
|
ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numVerts );
|
|
numVerts = MAX_FACE_POINTS;
|
|
surf->shader = tr.defaultShader;
|
|
}
|
|
|
|
int numIndexes = LittleLong( ds->numIndexes );
|
|
|
|
srfSurfaceFace_t* cv = AllocSurface<srfSurfaceFace_t>( numVerts, numIndexes );
|
|
cv->surfaceType = SF_FACE;
|
|
|
|
surf->data = (surfaceType_t*)cv;
|
|
|
|
verts += LittleLong( ds->firstVert );
|
|
for ( i = 0 ; i < numVerts ; i++ ) {
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
cv->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
|
|
}
|
|
for ( j = 0 ; j < 2 ; j++ ) {
|
|
cv->verts[i].st[j] = LittleFloat( verts[i].st[j] );
|
|
cv->verts[i].st2[j] = LittleFloat( verts[i].lightmap[j] );
|
|
}
|
|
R_ColorShiftLightingBytes( verts[i].color, cv->verts[i].rgba );
|
|
}
|
|
|
|
indexes += LittleLong( ds->firstIndex );
|
|
for ( i = 0 ; i < numIndexes ; i++ ) {
|
|
cv->indexes[i] = LittleLong( indexes[i] );
|
|
}
|
|
|
|
// take the plane information from the lightmap vector
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
|
|
}
|
|
cv->plane.dist = DotProduct( cv->verts[0].xyz, cv->plane.normal );
|
|
SetPlaneSignbits( &cv->plane );
|
|
cv->plane.type = PlaneTypeForNormal( cv->plane.normal );
|
|
}
|
|
|
|
|
|
static void ParseMesh( const dsurface_t* ds, const drawVert_t* verts, msurface_t* surf )
|
|
{
|
|
static surfaceType_t skipData = SF_SKIP;
|
|
drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE];
|
|
|
|
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
|
|
surf->shader = ShaderForShaderNum( ds->shaderNum, LittleLong( ds->lightmapNum ) );
|
|
|
|
// we may have a nodraw surface, because they might still need to
|
|
// be around for movement clipping
|
|
if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) {
|
|
surf->data = &skipData;
|
|
return;
|
|
}
|
|
|
|
int w = LittleLong( ds->patchWidth );
|
|
int h = LittleLong( ds->patchHeight );
|
|
int numPoints = w * h;
|
|
|
|
verts += LittleLong( ds->firstVert );
|
|
|
|
for (int i = 0; i < numPoints; ++i) {
|
|
for (int j = 0; j < 3; ++j) {
|
|
points[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
|
|
points[i].normal[j] = LittleFloat( verts[i].normal[j] );
|
|
}
|
|
points[i].st[0] = LittleFloat( verts[i].st[0] );
|
|
points[i].lightmap[0] = LittleFloat( verts[i].lightmap[0] );
|
|
points[i].st[1] = LittleFloat( verts[i].st[1] );
|
|
points[i].lightmap[1] = LittleFloat( verts[i].lightmap[1] );
|
|
R_ColorShiftLightingBytes( verts[i].color, points[i].color );
|
|
}
|
|
|
|
// pre-tesseleate
|
|
srfGridMesh_t* grid = R_SubdividePatchToGrid( w, h, points );
|
|
surf->data = (surfaceType_t*)grid;
|
|
|
|
// copy the level of detail origin, which is the center
|
|
// of the group of all curves that must subdivide the same
|
|
// to avoid cracking
|
|
vec3_t bounds[2], v;
|
|
for (int i = 0; i < 3; ++i) {
|
|
bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] );
|
|
bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] );
|
|
}
|
|
VectorAdd( bounds[0], bounds[1], bounds[1] );
|
|
VectorScale( bounds[1], 0.5f, grid->lodOrigin );
|
|
VectorSubtract( bounds[0], grid->lodOrigin, v );
|
|
grid->lodRadius = VectorLength( v );
|
|
}
|
|
|
|
|
|
static void ParseTriSurf( const dsurface_t* ds, const drawVert_t* verts, msurface_t* surf, const int* indexes )
|
|
{
|
|
int i, j;
|
|
|
|
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
|
|
surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
|
|
|
|
int numVerts = LittleLong( ds->numVerts );
|
|
int numIndexes = LittleLong( ds->numIndexes );
|
|
|
|
srfTriangles_t* tri = AllocSurface<srfTriangles_t>( numVerts, numIndexes );
|
|
tri->surfaceType = SF_TRIANGLES;
|
|
|
|
surf->data = (surfaceType_t*)tri;
|
|
|
|
ClearBounds( tri->bounds[0], tri->bounds[1] );
|
|
verts += LittleLong( ds->firstVert );
|
|
for ( i = 0 ; i < numVerts ; i++ ) {
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
|
|
tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] );
|
|
}
|
|
AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] );
|
|
for ( j = 0 ; j < 2 ; j++ ) {
|
|
tri->verts[i].st[j] = LittleFloat( verts[i].st[j] );
|
|
tri->verts[i].st2[j] = LittleFloat( verts[i].lightmap[j] );
|
|
}
|
|
R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].rgba );
|
|
}
|
|
|
|
indexes += LittleLong( ds->firstIndex );
|
|
for ( i = 0 ; i < numIndexes ; i++ ) {
|
|
tri->indexes[i] = LittleLong( indexes[i] );
|
|
if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) {
|
|
ri.Error( ERR_DROP, "Bad index in triangle surface" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void ParseFlare( const dsurface_t* ds, const drawVert_t* verts, msurface_t* surf, const int* indexes )
|
|
{
|
|
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
|
|
surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
|
|
|
|
srfFlare_t* flare = RI_New<srfFlare_t>();
|
|
flare->surfaceType = SF_FLARE;
|
|
|
|
surf->data = (surfaceType_t*)flare;
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] );
|
|
flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] );
|
|
flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_MergedWidthPoints
|
|
|
|
returns qtrue if there are grid points merged on a width edge
|
|
=================
|
|
*/
|
|
int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) {
|
|
int i, j;
|
|
|
|
for (i = 1; i < grid->width-1; i++) {
|
|
for (j = i + 1; j < grid->width-1; j++) {
|
|
if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue;
|
|
if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue;
|
|
if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue;
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_MergedHeightPoints
|
|
|
|
returns qtrue if there are grid points merged on a height edge
|
|
=================
|
|
*/
|
|
int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) {
|
|
int i, j;
|
|
|
|
for (i = 1; i < grid->height-1; i++) {
|
|
for (j = i + 1; j < grid->height-1; j++) {
|
|
if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue;
|
|
if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue;
|
|
if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue;
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_FixSharedVertexLodError_r
|
|
|
|
NOTE: never sync LoD through grid edges with merged points!
|
|
|
|
FIXME: write generalized version that also avoids cracks between a patch and one that meets half way?
|
|
=================
|
|
*/
|
|
void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) {
|
|
int j, k, l, m, n, offset1, offset2, touch;
|
|
srfGridMesh_t *grid2;
|
|
|
|
for ( j = start; j < s_worldData.numsurfaces; j++ ) {
|
|
//
|
|
grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
|
|
// if this surface is not a grid
|
|
if ( grid2->surfaceType != SF_GRID ) continue;
|
|
// if the LOD errors are already fixed for this patch
|
|
if ( grid2->lodFixed == 2 ) continue;
|
|
// grids in the same LOD group should have the exact same lod radius
|
|
if ( grid1->lodRadius != grid2->lodRadius ) continue;
|
|
// grids in the same LOD group should have the exact same lod origin
|
|
if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
|
|
if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
|
|
if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
|
|
//
|
|
touch = qfalse;
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = (grid1->height-1) * grid1->width;
|
|
else offset1 = 0;
|
|
if (R_MergedWidthPoints(grid1, offset1)) continue;
|
|
for (k = 1; k < grid1->width-1; k++) {
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
if (R_MergedWidthPoints(grid2, offset2)) continue;
|
|
for ( l = 1; l < grid2->width-1; l++) {
|
|
//
|
|
if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
|
|
if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
|
|
if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
|
|
// ok the points are equal and should have the same lod error
|
|
grid2->widthLodError[l] = grid1->widthLodError[k];
|
|
touch = qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
if (R_MergedHeightPoints(grid2, offset2)) continue;
|
|
for ( l = 1; l < grid2->height-1; l++) {
|
|
//
|
|
if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
|
|
if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
|
|
if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
|
|
// ok the points are equal and should have the same lod error
|
|
grid2->heightLodError[l] = grid1->widthLodError[k];
|
|
touch = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = grid1->width-1;
|
|
else offset1 = 0;
|
|
if (R_MergedHeightPoints(grid1, offset1)) continue;
|
|
for (k = 1; k < grid1->height-1; k++) {
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
if (R_MergedWidthPoints(grid2, offset2)) continue;
|
|
for ( l = 1; l < grid2->width-1; l++) {
|
|
//
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
|
|
// ok the points are equal and should have the same lod error
|
|
grid2->widthLodError[l] = grid1->heightLodError[k];
|
|
touch = qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
if (R_MergedHeightPoints(grid2, offset2)) continue;
|
|
for ( l = 1; l < grid2->height-1; l++) {
|
|
//
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
|
|
if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
|
|
// ok the points are equal and should have the same lod error
|
|
grid2->heightLodError[l] = grid1->heightLodError[k];
|
|
touch = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (touch) {
|
|
grid2->lodFixed = 2;
|
|
R_FixSharedVertexLodError_r ( start, grid2 );
|
|
//NOTE: this would be correct but makes things really slow
|
|
//grid2->lodFixed = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_FixSharedVertexLodError
|
|
|
|
This function assumes that all patches in one group are nicely stitched together for the highest LoD.
|
|
If this is not the case this function will still do its job but won't fix the highest LoD cracks.
|
|
=================
|
|
*/
|
|
void R_FixSharedVertexLodError( void ) {
|
|
int i;
|
|
srfGridMesh_t *grid1;
|
|
|
|
for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
|
|
//
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
|
|
// if this surface is not a grid
|
|
if ( grid1->surfaceType != SF_GRID )
|
|
continue;
|
|
//
|
|
if ( grid1->lodFixed )
|
|
continue;
|
|
//
|
|
grid1->lodFixed = 2;
|
|
// recursively fix other patches in the same LOD group
|
|
R_FixSharedVertexLodError_r( i + 1, grid1);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
R_StitchPatches
|
|
===============
|
|
*/
|
|
int R_StitchPatches( int grid1num, int grid2num ) {
|
|
float *v1, *v2;
|
|
srfGridMesh_t *grid1, *grid2;
|
|
int k, l, m, n, offset1, offset2, row, column;
|
|
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
|
|
grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data;
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = (grid1->height-1) * grid1->width;
|
|
else offset1 = 0;
|
|
if (R_MergedWidthPoints(grid1, offset1))
|
|
continue;
|
|
for (k = 0; k < grid1->width-2; k += 2) {
|
|
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if ( grid2->width >= MAX_GRID_SIZE )
|
|
break;
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->width-1; l++) {
|
|
//
|
|
v1 = grid1->verts[k + offset1].xyz;
|
|
v2 = grid2->verts[l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[k + 2 + offset1].xyz;
|
|
v2 = grid2->verts[l + 1 + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[l + offset2].xyz;
|
|
v2 = grid2->verts[l + 1 + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert column into grid2 right after after column l
|
|
if (m) row = grid2->height-1;
|
|
else row = 0;
|
|
grid2 = R_GridInsertColumn( grid2, l+1, row,
|
|
grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (grid2->height >= MAX_GRID_SIZE)
|
|
break;
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->height-1; l++) {
|
|
//
|
|
v1 = grid1->verts[k + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[k + 2 + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert row into grid2 right after after row l
|
|
if (m) column = grid2->width-1;
|
|
else column = 0;
|
|
grid2 = R_GridInsertRow( grid2, l+1, column,
|
|
grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = grid1->width-1;
|
|
else offset1 = 0;
|
|
if (R_MergedHeightPoints(grid1, offset1))
|
|
continue;
|
|
for (k = 0; k < grid1->height-2; k += 2) {
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if ( grid2->width >= MAX_GRID_SIZE )
|
|
break;
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->width-1; l++) {
|
|
//
|
|
v1 = grid1->verts[grid1->width * k + offset1].xyz;
|
|
v2 = grid2->verts[l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
|
|
v2 = grid2->verts[l + 1 + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[l + offset2].xyz;
|
|
v2 = grid2->verts[(l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert column into grid2 right after after column l
|
|
if (m) row = grid2->height-1;
|
|
else row = 0;
|
|
grid2 = R_GridInsertColumn( grid2, l+1, row,
|
|
grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (grid2->height >= MAX_GRID_SIZE)
|
|
break;
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->height-1; l++) {
|
|
//
|
|
v1 = grid1->verts[grid1->width * k + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert row into grid2 right after after row l
|
|
if (m) column = grid2->width-1;
|
|
else column = 0;
|
|
grid2 = R_GridInsertRow( grid2, l+1, column,
|
|
grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = (grid1->height-1) * grid1->width;
|
|
else offset1 = 0;
|
|
if (R_MergedWidthPoints(grid1, offset1))
|
|
continue;
|
|
for (k = grid1->width-1; k > 1; k -= 2) {
|
|
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if ( grid2->width >= MAX_GRID_SIZE )
|
|
break;
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->width-1; l++) {
|
|
//
|
|
v1 = grid1->verts[k + offset1].xyz;
|
|
v2 = grid2->verts[l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[k - 2 + offset1].xyz;
|
|
v2 = grid2->verts[l + 1 + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[l + offset2].xyz;
|
|
v2 = grid2->verts[(l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert column into grid2 right after after column l
|
|
if (m) row = grid2->height-1;
|
|
else row = 0;
|
|
grid2 = R_GridInsertColumn( grid2, l+1, row,
|
|
grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (grid2->height >= MAX_GRID_SIZE)
|
|
break;
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->height-1; l++) {
|
|
//
|
|
v1 = grid1->verts[k + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[k - 2 + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert row into grid2 right after after row l
|
|
if (m) column = grid2->width-1;
|
|
else column = 0;
|
|
grid2 = R_GridInsertRow( grid2, l+1, column,
|
|
grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
|
|
if (!grid2)
|
|
break;
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (n = 0; n < 2; n++) {
|
|
//
|
|
if (n) offset1 = grid1->width-1;
|
|
else offset1 = 0;
|
|
if (R_MergedHeightPoints(grid1, offset1))
|
|
continue;
|
|
for (k = grid1->height-1; k > 1; k -= 2) {
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if ( grid2->width >= MAX_GRID_SIZE )
|
|
break;
|
|
if (m) offset2 = (grid2->height-1) * grid2->width;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->width-1; l++) {
|
|
//
|
|
v1 = grid1->verts[grid1->width * k + offset1].xyz;
|
|
v2 = grid2->verts[l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
|
|
v2 = grid2->verts[l + 1 + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[l + offset2].xyz;
|
|
v2 = grid2->verts[(l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert column into grid2 right after after column l
|
|
if (m) row = grid2->height-1;
|
|
else row = 0;
|
|
grid2 = R_GridInsertColumn( grid2, l+1, row,
|
|
grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
for (m = 0; m < 2; m++) {
|
|
|
|
if (grid2->height >= MAX_GRID_SIZE)
|
|
break;
|
|
if (m) offset2 = grid2->width-1;
|
|
else offset2 = 0;
|
|
for ( l = 0; l < grid2->height-1; l++) {
|
|
//
|
|
v1 = grid1->verts[grid1->width * k + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
|
|
v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) > .1)
|
|
continue;
|
|
if ( fabs(v1[1] - v2[1]) > .1)
|
|
continue;
|
|
if ( fabs(v1[2] - v2[2]) > .1)
|
|
continue;
|
|
//
|
|
v1 = grid2->verts[grid2->width * l + offset2].xyz;
|
|
v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
|
|
if ( fabs(v1[0] - v2[0]) < .01 &&
|
|
fabs(v1[1] - v2[1]) < .01 &&
|
|
fabs(v1[2] - v2[2]) < .01)
|
|
continue;
|
|
//
|
|
//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
|
|
// insert row into grid2 right after after row l
|
|
if (m) column = grid2->width-1;
|
|
else column = 0;
|
|
grid2 = R_GridInsertRow( grid2, l+1, column,
|
|
grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (surfaceType_t*)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_TryStitchPatch
|
|
|
|
This function will try to stitch patches in the same LoD group together for the highest LoD.
|
|
|
|
Only single missing vertice cracks will be fixed.
|
|
|
|
Vertices will be joined at the patch side a crack is first found, at the other side
|
|
of the patch (on the same row or column) the vertices will not be joined and cracks
|
|
might still appear at that side.
|
|
===============
|
|
*/
|
|
int R_TryStitchingPatch( int grid1num ) {
|
|
int j, numstitches;
|
|
srfGridMesh_t *grid1, *grid2;
|
|
|
|
numstitches = 0;
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
|
|
for ( j = 0; j < s_worldData.numsurfaces; j++ ) {
|
|
//
|
|
grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
|
|
// if this surface is not a grid
|
|
if ( grid2->surfaceType != SF_GRID ) continue;
|
|
// grids in the same LOD group should have the exact same lod radius
|
|
if ( grid1->lodRadius != grid2->lodRadius ) continue;
|
|
// grids in the same LOD group should have the exact same lod origin
|
|
if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
|
|
if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
|
|
if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
|
|
//
|
|
while (R_StitchPatches(grid1num, j))
|
|
{
|
|
numstitches++;
|
|
}
|
|
}
|
|
return numstitches;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_StitchAllPatches
|
|
===============
|
|
*/
|
|
void R_StitchAllPatches( void ) {
|
|
int i, stitched, numstitches;
|
|
srfGridMesh_t *grid1;
|
|
|
|
numstitches = 0;
|
|
do
|
|
{
|
|
stitched = qfalse;
|
|
for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
|
|
//
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
|
|
// if this surface is not a grid
|
|
if ( grid1->surfaceType != SF_GRID )
|
|
continue;
|
|
//
|
|
if ( grid1->lodStitched )
|
|
continue;
|
|
//
|
|
grid1->lodStitched = qtrue;
|
|
stitched = qtrue;
|
|
//
|
|
numstitches += R_TryStitchingPatch( i );
|
|
}
|
|
}
|
|
while (stitched);
|
|
ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches );
|
|
}
|
|
|
|
|
|
static void R_MovePatchSurfacesToHunk()
|
|
{
|
|
for (int i = 0; i < s_worldData.numsurfaces; ++i) {
|
|
srfGridMesh_t* grid = (srfGridMesh_t *) s_worldData.surfaces[i].data;
|
|
if ( grid->surfaceType != SF_GRID )
|
|
continue;
|
|
|
|
int size = (grid->width * grid->height - 1) * sizeof( drawVert_t ) + sizeof( *grid );
|
|
srfGridMesh_t* hunkgrid = (srfGridMesh_t*)RI_New<byte>( size );
|
|
Com_Memcpy(hunkgrid, grid, size);
|
|
|
|
hunkgrid->widthLodError = RI_New<float>( grid->width );
|
|
Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 );
|
|
|
|
hunkgrid->heightLodError = RI_New<float>( grid->height );
|
|
Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 );
|
|
|
|
R_FreeSurfaceGridMesh( grid );
|
|
|
|
s_worldData.surfaces[i].data = (surfaceType_t*)hunkgrid;
|
|
}
|
|
}
|
|
|
|
|
|
// !!! the CM code duplicates virtually all of these functions
|
|
// they really should be shared, especially since unpacking etc is so clunky
|
|
|
|
template <typename T> const T* ReadLump( const lump_t* lump, int* c )
|
|
{
|
|
const T* p = (const T*)(fileBase + lump->fileofs);
|
|
if (lump->filelen % sizeof(T))
|
|
ri.Error( ERR_DROP, "LoadMap: funny lump size in %s", s_worldData.name );
|
|
if (c)
|
|
*c = lump->filelen / sizeof(T);
|
|
return p;
|
|
}
|
|
|
|
|
|
static void R_LoadSurfaces( const lump_t* surfs, const lump_t* verts, const lump_t* indexLump )
|
|
{
|
|
const dsurface_t* in = ReadLump<dsurface_t>( surfs, &s_worldData.numsurfaces );
|
|
const drawVert_t* dv = ReadLump<drawVert_t>( verts, NULL );
|
|
const int* indexes = ReadLump<int>( indexLump, NULL );
|
|
|
|
msurface_t* out = RI_New<msurface_t>( s_worldData.numsurfaces );
|
|
|
|
int numFaces = 0, numMeshes = 0, numTriSurfs = 0, numFlares = 0;
|
|
|
|
s_worldData.surfaces = out;
|
|
for (int i = 0 ; i < s_worldData.numsurfaces; ++i, ++in, ++out)
|
|
{
|
|
switch ( LittleLong( in->surfaceType ) ) {
|
|
case MST_PATCH:
|
|
ParseMesh( in, dv, out );
|
|
numMeshes++;
|
|
break;
|
|
case MST_TRIANGLE_SOUP:
|
|
ParseTriSurf( in, dv, out, indexes );
|
|
numTriSurfs++;
|
|
break;
|
|
case MST_PLANAR:
|
|
ParseFace( in, dv, out, indexes );
|
|
numFaces++;
|
|
break;
|
|
case MST_FLARE:
|
|
ParseFlare( in, dv, out, indexes );
|
|
numFlares++;
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "Bad surfaceType" );
|
|
}
|
|
}
|
|
|
|
#ifdef PATCH_STITCHING
|
|
R_StitchAllPatches();
|
|
#endif
|
|
|
|
R_FixSharedVertexLodError();
|
|
|
|
#ifdef PATCH_STITCHING
|
|
R_MovePatchSurfacesToHunk();
|
|
#endif
|
|
|
|
ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n",
|
|
numFaces, numMeshes, numTriSurfs, numFlares );
|
|
}
|
|
|
|
|
|
static void R_LoadSubmodels( const lump_t* l )
|
|
{
|
|
int count;
|
|
const dmodel_t* in = ReadLump<dmodel_t>( l, &count );
|
|
|
|
s_worldData.bmodels = RI_New<bmodel_t>( count );
|
|
|
|
bmodel_t* out = s_worldData.bmodels;
|
|
for (int i = 0; i < count; ++i, ++in, ++out) {
|
|
model_t* model = R_AllocModel();
|
|
assert( model ); // we should always have enough space for these
|
|
|
|
model->type = MOD_BRUSH;
|
|
model->bmodel = out;
|
|
Com_sprintf( model->name, sizeof( model->name ), "*%d", i );
|
|
|
|
for (int j = 0; j < 3; ++j) {
|
|
out->bounds[0][j] = LittleFloat( in->mins[j] );
|
|
out->bounds[1][j] = LittleFloat( in->maxs[j] );
|
|
}
|
|
|
|
out->firstSurface = s_worldData.surfaces + LittleLong( in->firstSurface );
|
|
out->numSurfaces = LittleLong( in->numSurfaces );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static void R_SetParent( mnode_t* node, mnode_t* parent )
|
|
{
|
|
node->parent = parent;
|
|
if (node->contents != -1)
|
|
return;
|
|
R_SetParent( node->children[0], node );
|
|
R_SetParent( node->children[1], node );
|
|
}
|
|
|
|
|
|
static void R_LoadNodesAndLeafs( const lump_t* nodeLump, const lump_t* leafLump )
|
|
{
|
|
int numNodes, numLeafs;
|
|
const dnode_t* in = ReadLump<dnode_t>( nodeLump, &numNodes );
|
|
const dleaf_t* inLeaf = ReadLump<dleaf_t>( leafLump, &numLeafs );
|
|
|
|
s_worldData.numnodes = numNodes + numLeafs;
|
|
s_worldData.nodes = RI_New<mnode_t>( s_worldData.numnodes );
|
|
|
|
mnode_t* out = s_worldData.nodes;
|
|
for (int i = 0; i < numNodes; ++i, ++in, ++out)
|
|
{
|
|
for (int j = 0; j < 3; ++j) {
|
|
out->mins[j] = LittleLong( in->mins[j] );
|
|
out->maxs[j] = LittleLong( in->maxs[j] );
|
|
}
|
|
|
|
out->contents = CONTENTS_NODE; // differentiate from leafs
|
|
|
|
int p = LittleLong( in->planeNum );
|
|
out->plane = s_worldData.planes + p;
|
|
|
|
p = LittleLong( in->children[0] );
|
|
out->children[0] = s_worldData.nodes + ((p >= 0) ? p : numNodes + (-1 - p));
|
|
p = LittleLong( in->children[1] );
|
|
out->children[1] = s_worldData.nodes + ((p >= 0) ? p : numNodes + (-1 - p));
|
|
}
|
|
|
|
for (int i = 0; i < numLeafs; ++i, ++inLeaf, ++out)
|
|
{
|
|
for (int j = 0; j < 3; ++j) {
|
|
out->mins[j] = LittleLong( inLeaf->mins[j] );
|
|
out->maxs[j] = LittleLong( inLeaf->maxs[j] );
|
|
}
|
|
|
|
out->cluster = LittleLong( inLeaf->cluster );
|
|
out->area = LittleLong( inLeaf->area );
|
|
|
|
if ( out->cluster >= s_worldData.numClusters )
|
|
s_worldData.numClusters = out->cluster + 1;
|
|
|
|
out->firstmarksurface = s_worldData.marksurfaces + LittleLong( inLeaf->firstLeafSurface );
|
|
out->nummarksurfaces = LittleLong( inLeaf->numLeafSurfaces );
|
|
}
|
|
|
|
// chain descendants
|
|
R_SetParent( s_worldData.nodes, NULL );
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static void R_LoadShaders( const lump_t* l )
|
|
{
|
|
const dshader_t* in = ReadLump<dshader_t>( l, &s_worldData.numShaders );
|
|
|
|
s_worldData.shaders = RI_New<dshader_t>( s_worldData.numShaders );
|
|
|
|
dshader_t* out = s_worldData.shaders;
|
|
Com_Memcpy( out, in, s_worldData.numShaders * sizeof(*out) );
|
|
|
|
for (int i = 0; i < s_worldData.numShaders; ++i) {
|
|
out[i].surfaceFlags = LittleLong( out[i].surfaceFlags );
|
|
out[i].contentFlags = LittleLong( out[i].contentFlags );
|
|
}
|
|
}
|
|
|
|
|
|
static void R_LoadMarksurfaces( const lump_t* l )
|
|
{
|
|
const int* in = ReadLump<int>( l, &s_worldData.nummarksurfaces );
|
|
|
|
s_worldData.marksurfaces = RI_New<msurface_t*>( s_worldData.nummarksurfaces );
|
|
|
|
for (int i = 0; i < s_worldData.nummarksurfaces; ++i)
|
|
s_worldData.marksurfaces[i] = s_worldData.surfaces + LittleLong( in[i] );
|
|
}
|
|
|
|
|
|
static void R_LoadPlanes( const lump_t* l )
|
|
{
|
|
const dplane_t* in = ReadLump<dplane_t>( l, &s_worldData.numplanes );
|
|
|
|
s_worldData.planes = RI_New<cplane_t>( s_worldData.numplanes );
|
|
|
|
cplane_t* out = s_worldData.planes;
|
|
for (int i = 0; i < s_worldData.numplanes; ++i, ++in, ++out)
|
|
{
|
|
out->signbits = 0;
|
|
for (int j = 0; j < 3; ++j) {
|
|
out->normal[j] = LittleFloat( in->normal[j] );
|
|
if (out->normal[j] < 0) {
|
|
out->signbits |= (1 << j);
|
|
}
|
|
}
|
|
out->dist = LittleFloat( in->dist );
|
|
out->type = PlaneTypeForNormal( out->normal );
|
|
}
|
|
}
|
|
|
|
|
|
static unsigned ColorBytes4( float r, float g, float b, float a )
|
|
{
|
|
unsigned i;
|
|
|
|
( (byte *)&i )[0] = r * 255;
|
|
( (byte *)&i )[1] = g * 255;
|
|
( (byte *)&i )[2] = b * 255;
|
|
( (byte *)&i )[3] = a * 255;
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
static void R_LoadFogs( const lump_t* l, const lump_t* brushesLump, const lump_t* sidesLump )
|
|
{
|
|
const dfog_t* fogs = ReadLump<dfog_t>( l, &s_worldData.numfogs );
|
|
|
|
// fog[0] is an empty but referenced element, so we have to alloc that even if the map has none
|
|
// ideally, someone would fix all the callers to not just blindly deref s_worldData.fogs, but >:(
|
|
++s_worldData.numfogs;
|
|
s_worldData.fogs = RI_New<fog_t>( s_worldData.numfogs );
|
|
if (s_worldData.numfogs == 1)
|
|
return;
|
|
|
|
fog_t* out = s_worldData.fogs + 1;
|
|
|
|
int numBrushes, numSides;
|
|
const dbrush_t* brushes = ReadLump<dbrush_t>( brushesLump, &numBrushes );
|
|
const dbrushside_t* sides = ReadLump<dbrushside_t>( sidesLump, &numSides );
|
|
|
|
for (int i = 1; i < s_worldData.numfogs; ++i, ++fogs, ++out)
|
|
{
|
|
out->originalBrushNumber = LittleLong( fogs->brushNum );
|
|
|
|
if ( out->originalBrushNumber >= numBrushes )
|
|
ri.Error( ERR_DROP, "fog brushNumber out of range" );
|
|
|
|
const dbrush_t* brush = brushes + out->originalBrushNumber;
|
|
|
|
int firstSide = LittleLong( brush->firstSide );
|
|
if ( firstSide > numSides - 6 )
|
|
ri.Error( ERR_DROP, "fog brush sideNumber out of range" );
|
|
int sideNum = firstSide, planeNum;
|
|
|
|
// brushes are always sorted with the axial sides first
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[1][0] = s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[1][1] = s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
planeNum = LittleLong( sides[ sideNum ].planeNum );
|
|
out->bounds[1][2] = s_worldData.planes[ planeNum ].dist;
|
|
++sideNum;
|
|
|
|
// get information from the shader for fog parameters
|
|
shader_t* shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue );
|
|
|
|
out->parms = shader->fogParms;
|
|
|
|
out->colorInt = ColorBytes4(
|
|
shader->fogParms.color[0] * tr.identityLight,
|
|
shader->fogParms.color[1] * tr.identityLight,
|
|
shader->fogParms.color[2] * tr.identityLight,
|
|
1.0 );
|
|
|
|
out->tcScale = 1.0f / (8 * max( 1.0f, shader->fogParms.depthForOpaque ));
|
|
|
|
// set the gradient vector
|
|
sideNum = LittleLong( fogs->visibleSide );
|
|
|
|
if ( sideNum == -1 ) {
|
|
out->hasSurface = qfalse;
|
|
} else {
|
|
out->hasSurface = qtrue;
|
|
planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum );
|
|
VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface );
|
|
out->surface[3] = -s_worldData.planes[ planeNum ].dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void R_LoadLightGrid( const lump_t* l )
|
|
{
|
|
int i;
|
|
world_t* w = &s_worldData;
|
|
|
|
w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0];
|
|
w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1];
|
|
w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2];
|
|
|
|
float* wMins = w->bmodels[0].bounds[0];
|
|
float* wMaxs = w->bmodels[0].bounds[1];
|
|
|
|
vec3_t maxs;
|
|
for (i = 0; i < 3; ++i) {
|
|
w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] );
|
|
maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] );
|
|
w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1;
|
|
}
|
|
|
|
int numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
|
|
|
|
if ( l->filelen != numGridPoints * 8 ) {
|
|
ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" );
|
|
w->lightGridData = NULL;
|
|
return;
|
|
}
|
|
|
|
w->lightGridData = RI_New<byte>( l->filelen );
|
|
Com_Memcpy( w->lightGridData, fileBase + l->fileofs, l->filelen );
|
|
|
|
// deal with overbright bits
|
|
for (i = 0; i < numGridPoints; ++i) {
|
|
R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] );
|
|
R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] );
|
|
}
|
|
}
|
|
|
|
|
|
static void R_LoadEntities( const lump_t* l )
|
|
{
|
|
world_t* w = &s_worldData;
|
|
w->lightGridSize[0] = 64;
|
|
w->lightGridSize[1] = 64;
|
|
w->lightGridSize[2] = 128;
|
|
|
|
const char* p = (const char*)(fileBase + l->fileofs);
|
|
|
|
// store for reference by the cgame
|
|
w->entityString = RI_New<char>( l->filelen + 1 );
|
|
strcpy( w->entityString, p );
|
|
w->entityParsePoint = w->entityString;
|
|
|
|
const char* token = COM_ParseExt( &p, qtrue );
|
|
if (!*token || *token != '{')
|
|
return;
|
|
|
|
// only parse the world spawn
|
|
while ( 1 ) {
|
|
// parse key
|
|
token = COM_ParseExt( &p, qtrue );
|
|
|
|
if ( !*token || *token == '}' ) {
|
|
break;
|
|
}
|
|
|
|
char keyname[MAX_TOKEN_CHARS];
|
|
Q_strncpyz(keyname, token, sizeof(keyname));
|
|
|
|
// parse value
|
|
token = COM_ParseExt( &p, qtrue );
|
|
|
|
if ( !*token || *token == '}' ) {
|
|
break;
|
|
}
|
|
|
|
char value[MAX_TOKEN_CHARS];
|
|
Q_strncpyz(value, token, sizeof(value));
|
|
|
|
// check for a different grid size
|
|
if (!Q_stricmp(keyname, "gridsize")) {
|
|
sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] );
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
qbool R_GetEntityToken( char* buffer, int size )
|
|
{
|
|
const char* s = COM_Parse( &s_worldData.entityParsePoint );
|
|
Q_strncpyz( buffer, s, size );
|
|
|
|
if ( !s_worldData.entityParsePoint || !s[0] ) {
|
|
s_worldData.entityParsePoint = s_worldData.entityString;
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
// called directly from cgame
|
|
|
|
void RE_LoadWorldMap( const char* name )
|
|
{
|
|
int i;
|
|
|
|
if ( tr.worldMapLoaded )
|
|
ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" );
|
|
|
|
tr.worldMapLoaded = qtrue;
|
|
|
|
byte* buffer;
|
|
ri.FS_ReadFile( name, (void**)&buffer );
|
|
if ( !buffer )
|
|
ri.Error( ERR_DROP, "RE_LoadWorldMap: %s not found", name );
|
|
|
|
// clear tr.world so if the level fails to load, the next
|
|
// try will not look at the partially loaded version
|
|
tr.world = NULL;
|
|
|
|
Com_Memset( &s_worldData, 0, sizeof( s_worldData ) );
|
|
Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );
|
|
|
|
Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) );
|
|
COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName));
|
|
|
|
dheader_t* header = (dheader_t*)buffer;
|
|
fileBase = (byte*)header;
|
|
|
|
i = LittleLong(header->version);
|
|
if ( i != BSP_VERSION )
|
|
ri.Error( ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", name, i, BSP_VERSION );
|
|
|
|
// swap all the lumps
|
|
for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
|
|
((int *)header)[i] = LittleLong ( ((int *)header)[i]);
|
|
}
|
|
|
|
byte* startMarker = (byte*)ri.Hunk_Alloc( 0, h_low );
|
|
|
|
R_LoadShaders( &header->lumps[LUMP_SHADERS] );
|
|
R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS] );
|
|
R_LoadPlanes (&header->lumps[LUMP_PLANES]);
|
|
R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] );
|
|
R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] );
|
|
R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]);
|
|
R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
|
|
R_LoadSubmodels (&header->lumps[LUMP_MODELS]);
|
|
R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] );
|
|
R_LoadEntities( &header->lumps[LUMP_ENTITIES] );
|
|
R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] );
|
|
|
|
s_worldData.dataSize = (byte*)ri.Hunk_Alloc( 0, h_low ) - startMarker;
|
|
|
|
// only set tr.world now that we know the entire level has loaded properly
|
|
tr.world = &s_worldData;
|
|
|
|
ri.FS_FreeFile( buffer );
|
|
}
|
|
|
|
|
|
const char* R_GetMapName()
|
|
{
|
|
return s_worldData.baseName[0] != '\0' ? s_worldData.baseName : "";
|
|
}
|
|
|