ioef/q3map/terrain.c
2005-08-28 17:54:51 +00:00

1255 lines
31 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "qbsp.h"
#include <assert.h>
#define SURF_WIDTH 2048
#define SURF_HEIGHT 2048
#define GROW_VERTS 512
#define GROW_INDICES 512
#define GROW_SURFACES 128
#define VectorSet(v, x, y, z) v[0] = x;v[1] = y;v[2] = z;
void QuakeTextureVecs( plane_t *plane, vec_t shift[2], vec_t rotate, vec_t scale[2], vec_t mappingVecs[2][4] );
typedef struct {
shaderInfo_t *shader;
int x, y;
int maxVerts;
int numVerts;
drawVert_t *verts;
int maxIndexes;
int numIndexes;
int *indexes;
} terrainSurf_t;
static terrainSurf_t *surfaces = NULL;
static terrainSurf_t *lastSurface = NULL;
static int numsurfaces = 0;
static int maxsurfaces = 0;
/*
================
ShaderForLayer
================
*/
shaderInfo_t *ShaderForLayer( int minlayer, int maxlayer, const char *shadername ) {
char shader[ MAX_QPATH ];
if ( minlayer == maxlayer ) {
sprintf( shader, "textures/%s_%d", shadername, maxlayer );
} else {
sprintf( shader, "textures/%s_%dto%d", shadername, minlayer, maxlayer );
}
return ShaderInfoForShader( shader );
}
/*
================
CompareVert
================
*/
qboolean CompareVert( drawVert_t *v1, drawVert_t *v2, qboolean checkst ) {
int i;
for( i = 0; i < 3; i++ ) {
if ( floor( v1->xyz[ i ] + 0.1 ) != floor( v2->xyz[ i ] + 0.1 ) ) {
return qfalse;
}
if ( checkst && ( ( v1->st[ 0 ] != v2->st[ 0 ] ) || ( v1->st[ 1 ] != v2->st[ 1 ] ) ) ) {
return qfalse;
}
}
return qtrue;
}
/*
================
LoadAlphaMap
================
*/
byte *LoadAlphaMap( int *num_layers, int *alphawidth, int *alphaheight ) {
int *alphamap32;
byte *alphamap;
const char *alphamapname;
char ext[ 128 ];
int width;
int height;
int layers;
int size;
int i;
assert( alphawidth );
assert( alphaheight );
assert( num_layers );
layers = atoi( ValueForKey( mapent, "layers" ) );
if ( layers < 1 ) {
Error ("SetTerrainTextures: invalid value for 'layers' (%d)", layers );
}
alphamapname = ValueForKey( mapent, "alphamap" );
if ( !alphamapname[ 0 ] ) {
Error ("LoadAlphaMap: No alphamap specified on terrain" );
}
ExtractFileExtension( alphamapname, ext);
if ( !Q_stricmp( ext, "tga" ) ) {
Load32BitImage( ExpandGamePath( alphamapname ), &alphamap32, &width, &height );
size = width * height;
alphamap = malloc( size );
for( i = 0; i < size; i++ ) {
alphamap[ i ] = ( ( alphamap32[ i ] & 0xff ) * layers ) / 256;
if ( alphamap[ i ] >= layers ) {
alphamap[ i ] = layers - 1;
}
}
} else {
Load256Image( ExpandGamePath( alphamapname ), &alphamap, NULL, &width, &height );
size = width * height;
for( i = 0; i < size; i++ ) {
if ( alphamap[ i ] >= layers ) {
alphamap[ i ] = layers - 1;
}
}
}
if ( ( width < 2 ) || ( height < 2 ) ) {
Error ("LoadAlphaMap: alphamap width/height must be at least 2x2." );
}
*num_layers = layers;
*alphawidth = width;
*alphaheight = height;
return alphamap;
}
/*
================
CalcTerrainSize
================
*/
void CalcTerrainSize( vec3_t mins, vec3_t maxs, vec3_t size ) {
bspbrush_t *brush;
int i;
const char *key;
// calculate the size of the terrain
ClearBounds( mins, maxs );
for( brush = mapent->brushes; brush != NULL; brush = brush->next ) {
AddPointToBounds( brush->mins, mins, maxs );
AddPointToBounds( brush->maxs, mins, maxs );
}
key = ValueForKey( mapent, "min" );
if ( key[ 0 ] ) {
GetVectorForKey( mapent, "min", mins );
}
key = ValueForKey( mapent, "max" );
if ( key[ 0 ] ) {
GetVectorForKey( mapent, "max", maxs );
}
for( i = 0; i < 3; i++ ) {
mins[ i ] = floor( mins[ i ] + 0.1 );
maxs[ i ] = floor( maxs[ i ] + 0.1 );
}
VectorSubtract( maxs, mins, size );
if ( ( size[ 0 ] <= 0 ) || ( size[ 1 ] <= 0 ) ) {
Error ("CalcTerrainSize: Invalid terrain size: %fx%f", size[ 0 ], size[ 1 ] );
}
}
/*
==================
IsTriangleDegenerate
Returns qtrue if all three points are collinear or backwards
===================
*/
#define COLINEAR_AREA 10
static qboolean IsTriangleDegenerate( drawVert_t *points, int a, int b, int c ) {
vec3_t v1, v2, v3;
float d;
VectorSubtract( points[b].xyz, points[a].xyz, v1 );
VectorSubtract( points[c].xyz, points[a].xyz, v2 );
CrossProduct( v1, v2, v3 );
d = VectorLength( v3 );
// assume all very small or backwards triangles will cause problems
if ( d < COLINEAR_AREA ) {
return qtrue;
}
return qfalse;
}
/*
===============
SideAsTriFan
The surface can't be represented as a single tristrip without
leaving a degenerate triangle (and therefore a crack), so add
a point in the middle and create (points-1) triangles in fan order
===============
*/
static void SideAsTriFan( terrainSurf_t *surf, int *index, int num ) {
int i;
int colorSum[4];
drawVert_t *mid, *v;
// make sure we have enough space for a new vert
if ( surf->numVerts >= surf->maxVerts ) {
surf->maxVerts += GROW_VERTS;
surf->verts = realloc( surf->verts, surf->maxVerts * sizeof( *surf->verts ) );
}
// create a new point in the center of the face
mid = &surf->verts[ surf->numVerts ];
surf->numVerts++;
colorSum[0] = colorSum[1] = colorSum[2] = colorSum[3] = 0;
for (i = 0 ; i < num; i++ ) {
v = &surf->verts[ index[ i ] ];
VectorAdd( mid->xyz, v->xyz, mid->xyz );
mid->st[0] += v->st[0];
mid->st[1] += v->st[1];
mid->lightmap[0] += v->lightmap[0];
mid->lightmap[1] += v->lightmap[1];
colorSum[0] += v->color[0];
colorSum[1] += v->color[1];
colorSum[2] += v->color[2];
colorSum[3] += v->color[3];
}
mid->xyz[0] /= num;
mid->xyz[1] /= num;
mid->xyz[2] /= num;
mid->st[0] /= num;
mid->st[1] /= num;
mid->lightmap[0] /= num;
mid->lightmap[1] /= num;
mid->color[0] = colorSum[0] / num;
mid->color[1] = colorSum[1] / num;
mid->color[2] = colorSum[2] / num;
mid->color[3] = colorSum[3] / num;
// fill in indices in trifan order
if ( surf->numIndexes + num * 3 > surf->maxIndexes ) {
surf->maxIndexes = surf->numIndexes + num * 3;
surf->indexes = realloc( surf->indexes, surf->maxIndexes * sizeof( *surf->indexes ) );
}
for ( i = 0 ; i < num; i++ ) {
surf->indexes[ surf->numIndexes++ ] = surf->numVerts - 1;
surf->indexes[ surf->numIndexes++ ] = index[ i ];
surf->indexes[ surf->numIndexes++ ] = index[ (i+1) % ( surf->numVerts - 1 ) ];
}
}
/*
================
SideAsTristrip
Try to create indices that make (points-2) triangles in tristrip order
================
*/
#define MAX_INDICES 1024
static void SideAsTristrip( terrainSurf_t *surf, int *index, int num ) {
int i;
int rotate;
int numIndices;
int ni;
int a, b, c;
int indices[ MAX_INDICES ];
// determine the triangle strip order
numIndices = ( num - 2 ) * 3;
if ( numIndices > MAX_INDICES ) {
Error( "MAX_INDICES exceeded for surface" );
}
// try all possible orderings of the points looking
// for a strip order that isn't degenerate
for ( rotate = 0 ; rotate < num; rotate++ ) {
for ( ni = 0, i = 0 ; i < num - 2 - i ; i++ ) {
a = index[ ( num - 1 - i + rotate ) % num ];
b = index[ ( i + rotate ) % num ];
c = index[ ( num - 2 - i + rotate ) % num ];
if ( IsTriangleDegenerate( surf->verts, a, b, c ) ) {
break;
}
indices[ni++] = a;
indices[ni++] = b;
indices[ni++] = c;
if ( i + 1 != num - 1 - i ) {
a = index[ ( num - 2 - i + rotate ) % num ];
b = index[ ( i + rotate ) % num ];
c = index[ ( i + 1 + rotate ) % num ];
if ( IsTriangleDegenerate( surf->verts, a, b, c ) ) {
break;
}
indices[ni++] = a;
indices[ni++] = b;
indices[ni++] = c;
}
}
if ( ni == numIndices ) {
break; // got it done without degenerate triangles
}
}
// if any triangle in the strip is degenerate,
// render from a centered fan point instead
if ( ni < numIndices ) {
SideAsTriFan( surf, index, num );
return;
}
// a normal tristrip
if ( surf->numIndexes + ni > surf->maxIndexes ) {
surf->maxIndexes = surf->numIndexes + ni;
surf->indexes = realloc( surf->indexes, surf->maxIndexes * sizeof( *surf->indexes ) );
}
memcpy( surf->indexes + surf->numIndexes, indices, ni * sizeof( *surf->indexes ) );
surf->numIndexes += ni;
}
/*
================
CreateTerrainSurface
================
*/
void CreateTerrainSurface( terrainSurf_t *surf, shaderInfo_t *shader ) {
int i, j, k;
drawVert_t *out;
drawVert_t *in;
mapDrawSurface_t *newsurf;
newsurf = AllocDrawSurf();
newsurf->miscModel = qtrue;
newsurf->shaderInfo = shader;
newsurf->lightmapNum = -1;
newsurf->fogNum = -1;
newsurf->numIndexes = surf->numIndexes;
newsurf->numVerts = surf->numVerts;
// copy the indices
newsurf->indexes = malloc( surf->numIndexes * sizeof( *newsurf->indexes ) );
memcpy( newsurf->indexes, surf->indexes, surf->numIndexes * sizeof( *newsurf->indexes ) );
// allocate the vertices
newsurf->verts = malloc( surf->numVerts * sizeof( *newsurf->verts ) );
memset( newsurf->verts, 0, surf->numVerts * sizeof( *newsurf->verts ) );
// calculate the surface verts
out = newsurf->verts;
for( i = 0; i < newsurf->numVerts; i++, out++ ) {
VectorCopy( surf->verts[ i ].xyz, out->xyz );
// set the texture coordinates
out->st[ 0 ] = surf->verts[ i ].st[ 0 ];
out->st[ 1 ] = surf->verts[ i ].st[ 1 ];
// the colors will be set by the lighting pass
out->color[0] = 255;
out->color[1] = 255;
out->color[2] = 255;
out->color[3] = surf->verts[ i ].color[ 3 ];
// calculate the vertex normal
VectorClear( out->normal );
for( j = 0; j < numsurfaces; j++ ) {
in = surfaces[ j ].verts;
for( k = 0; k < surfaces[ j ].numVerts; k++, in++ ) {
if ( CompareVert( out, in, qfalse ) ) {
VectorAdd( out->normal, in->normal, out->normal );
}
}
}
VectorNormalize( out->normal, out->normal );
}
}
/*
================
EmitTerrainVerts
================
*/
void EmitTerrainVerts( side_t *side, terrainSurf_t *surf, int maxlayer, int alpha[ MAX_POINTS_ON_WINDING ], qboolean projecttexture ) {
int i;
int j;
drawVert_t *vert;
int *indices;
int numindices;
int maxindices;
int xyplane;
vec3_t xynorm = { 0, 0, 1 };
vec_t shift[ 2 ] = { 0, 0 };
vec_t scale[ 2 ] = { 0.5, 0.5 };
float vecs[ 2 ][ 4 ];
static int numtimes = 0;
numtimes++;
if ( !surf->verts ) {
surf->numVerts = 0;
surf->maxVerts = GROW_VERTS;
surf->verts = malloc( surf->maxVerts * sizeof( *surf->verts ) );
surf->numIndexes = 0;
surf->maxIndexes = GROW_INDICES;
surf->indexes = malloc( surf->maxIndexes * sizeof( *surf->indexes ) );
}
// calculate the texture coordinate vectors
xyplane = FindFloatPlane( xynorm, 0 );
QuakeTextureVecs( &mapplanes[ xyplane ], shift, 0, scale, vecs );
// emit the vertexes
numindices = 0;
maxindices = surf->maxIndexes;
indices = malloc ( maxindices * sizeof( *indices ) );
for ( i = 0; i < side->winding->numpoints; i++ ) {
vert = &surf->verts[ surf->numVerts ];
// set the final alpha value--0 for texture 1, 255 for texture 2
if ( alpha[ i ] < maxlayer ) {
vert->color[3] = 0;
} else {
vert->color[3] = 255;
}
vert->xyz[ 0 ] = floor( side->winding->p[ i ][ 0 ] + 0.1f );
vert->xyz[ 1 ] = floor( side->winding->p[ i ][ 1 ] + 0.1f );
vert->xyz[ 2 ] = floor( side->winding->p[ i ][ 2 ] + 0.1f );
// set the texture coordinates
if ( projecttexture ) {
vert->st[0] = ( vecs[0][3] + DotProduct( vecs[ 0 ], vert->xyz ) ) / surf->shader->width;
vert->st[1] = ( vecs[1][3] + DotProduct( vecs[ 1 ], vert->xyz ) ) / surf->shader->height;
} else {
vert->st[0] = ( side->vecs[0][3] + DotProduct( side->vecs[ 0 ], vert->xyz ) ) / surf->shader->width;
vert->st[1] = ( side->vecs[1][3] + DotProduct( side->vecs[ 1 ], vert->xyz ) ) / surf->shader->height;
}
VectorCopy( mapplanes[ side->planenum ].normal, vert->normal );
for( j = 0; j < surf->numVerts; j++ ) {
if ( CompareVert( vert, &surf->verts[ j ], qtrue ) ) {
break;
}
}
if ( numindices >= maxindices ) {
maxindices += GROW_INDICES;
indices = realloc( indices, maxindices * sizeof( *indices ) );
}
if ( j != surf->numVerts ) {
indices[ numindices++ ] = j;
} else {
indices[ numindices++ ] = surf->numVerts;
surf->numVerts++;
if ( surf->numVerts >= surf->maxVerts ) {
surf->maxVerts += GROW_VERTS;
surf->verts = realloc( surf->verts, surf->maxVerts * sizeof( *surf->verts ) );
}
}
}
SideAsTristrip( surf, indices, numindices );
free( indices );
}
/*
================
SurfaceForShader
================
*/
terrainSurf_t *SurfaceForShader( shaderInfo_t *shader, int x, int y ) {
int i;
if ( lastSurface && ( lastSurface->shader == shader ) && ( lastSurface->x == x ) && ( lastSurface->y == y ) ) {
return lastSurface;
}
lastSurface = surfaces;
for( i = 0; i < numsurfaces; i++, lastSurface++ ) {
if ( ( lastSurface->shader == shader ) && ( lastSurface->x == x ) && ( lastSurface->y == y ) ) {
return lastSurface;
}
}
if ( numsurfaces >= maxsurfaces ) {
maxsurfaces += GROW_SURFACES;
surfaces = realloc( surfaces, maxsurfaces * sizeof( *surfaces ) );
memset( surfaces + numsurfaces + 1, 0, ( maxsurfaces - numsurfaces - 1 ) * sizeof( *surfaces ) );
}
lastSurface= &surfaces[ numsurfaces++ ];
lastSurface->shader = shader;
lastSurface->x = x;
lastSurface->y = y;
return lastSurface;
}
/*
================
SetTerrainTextures
================
*/
void SetTerrainTextures( void ) {
int i;
int x, y;
int layer;
int minlayer, maxlayer;
float s, t;
float min_s, min_t;
int alpha[ MAX_POINTS_ON_WINDING ];
shaderInfo_t *si, *terrainShader;
bspbrush_t *brush;
side_t *side;
const char *shadername;
vec3_t mins, maxs;
vec3_t size;
int surfwidth, surfheight, surfsize;
terrainSurf_t *surf;
byte *alphamap;
int alphawidth, alphaheight;
int num_layers;
extern qboolean onlyents;
if ( onlyents ) {
return;
}
shadername = ValueForKey( mapent, "shader" );
if ( !shadername[ 0 ] ) {
Error ("SetTerrainTextures: shader not specified" );
}
alphamap = LoadAlphaMap( &num_layers, &alphawidth, &alphaheight );
mapent->firstDrawSurf = numMapDrawSurfs;
// calculate the size of the terrain
CalcTerrainSize( mins, maxs, size );
surfwidth = ( size[ 0 ] + SURF_WIDTH - 1 ) / SURF_WIDTH;
surfheight = ( size[ 1 ] + SURF_HEIGHT - 1 ) / SURF_HEIGHT;
surfsize = surfwidth * surfheight;
lastSurface = NULL;
numsurfaces = 0;
maxsurfaces = 0;
for( i = num_layers; i > 0; i-- ) {
maxsurfaces += i * surfsize;
}
surfaces = malloc( maxsurfaces * sizeof( *surfaces ) );
memset( surfaces, 0, maxsurfaces * sizeof( *surfaces ) );
terrainShader = ShaderInfoForShader( "textures/common/terrain" );
for( brush = mapent->brushes; brush != NULL; brush = brush->next ) {
// only create surfaces for sides marked as terrain
for( side = brush->sides; side < &brush->sides[ brush->numsides ]; side++ ) {
if ( !side->shaderInfo ) {
continue;
}
if ( ( ( side->surfaceFlags | side->shaderInfo->surfaceFlags ) & SURF_NODRAW ) && !strstr( side->shaderInfo->shader, "terrain" ) ) {
continue;
}
minlayer = num_layers;
maxlayer = 0;
// project each point of the winding onto the alphamap to determine which
// textures to blend
min_s = 1.0;
min_t = 1.0;
for( i = 0; i < side->winding->numpoints; i++ ) {
s = floor( side->winding->p[ i ][ 0 ] + 0.1f - mins[ 0 ] ) / size[ 0 ];
t = floor( side->winding->p[ i ][ 1 ] + 0.1f - mins[ 0 ] ) / size[ 1 ];
if ( s < 0 ) {
s = 0;
}
if ( t < 0 ) {
t = 0;
}
if ( s >= 1.0 ) {
s = 1.0;
}
if ( t >= 1.0 ) {
t = 1.0;
}
if ( s < min_s ) {
min_s = s;
}
if ( t < min_t ) {
min_t = t;
}
x = ( alphawidth - 1 ) * s;
y = ( alphaheight - 1 ) * t;
layer = alphamap[ x + y * alphawidth ];
if ( layer < minlayer ) {
minlayer = layer;
}
if ( layer > maxlayer ) {
maxlayer = layer;
}
alpha[ i ] = layer;
}
x = min_s * surfwidth;
if ( x >= surfwidth ) {
x = surfwidth - 1;
}
y = min_t * surfheight;
if ( y >= surfheight ) {
y = surfheight - 1;
}
if ( strstr( side->shaderInfo->shader, "terrain" ) ) {
si = ShaderForLayer( minlayer, maxlayer, shadername );
if ( showseams ) {
for( i = 0; i < side->winding->numpoints; i++ ) {
if ( ( alpha[ i ] != minlayer ) && ( alpha[ i ] != maxlayer ) ) {
si = ShaderInfoForShader( "textures/common/white" );
break;
}
}
}
surf = SurfaceForShader( si, x, y );
EmitTerrainVerts( side, surf, maxlayer, alpha, qtrue );
} else {
si = side->shaderInfo;
side->shaderInfo = terrainShader;
surf = SurfaceForShader( si, x, y );
EmitTerrainVerts( side, surf, maxlayer, alpha, qfalse );
}
}
}
// create the final surfaces
for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
if ( surf->numVerts ) {
CreateTerrainSurface( surf, surf->shader );
}
}
//
// clean up any allocated memory
//
for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
if ( surf->verts ) {
free( surf->verts );
free( surf->indexes );
}
}
free( alphamap );
free( surfaces );
surfaces = NULL;
lastSurface = NULL;
numsurfaces = 0;
maxsurfaces = 0;
}
/*****************************************************************************
New terrain code
******************************************************************************/
typedef struct terrainFace_s {
shaderInfo_t *shaderInfo;
//texdef_t texdef;
float vecs[ 2 ][ 4 ]; // texture coordinate mapping
} terrainFace_t;
typedef struct terrainVert_s {
vec3_t xyz;
terrainFace_t tri;
} terrainVert_t;
typedef struct terrainMesh_s {
float scale_x;
float scale_y;
vec3_t origin;
int width, height;
terrainVert_t *map;
} terrainMesh_t;
terrainVert_t *Terrain_GetVert( terrainMesh_t *pm, int x, int y ) {
return &pm->map[ x + y * pm->width ];
}
void Terrain_GetTriangles( terrainMesh_t *pm, int x, int y, terrainVert_t **verts ) {
if ( ( x + y ) & 1 ) {
// first tri
verts[ 0 ] = Terrain_GetVert( pm, x, y );
verts[ 1 ] = Terrain_GetVert( pm, x, y + 1 );
verts[ 2 ] = Terrain_GetVert( pm, x + 1, y + 1 );
// second tri
verts[ 3 ] = verts[ 2 ];
verts[ 4 ] = Terrain_GetVert( pm, x + 1, y );
verts[ 5 ] = verts[ 0 ];
} else {
// first tri
verts[ 0 ] = Terrain_GetVert( pm, x, y );
verts[ 1 ] = Terrain_GetVert( pm, x, y + 1 );
verts[ 2 ] = Terrain_GetVert( pm, x + 1, y );
// second tri
verts[ 3 ] = verts[ 2 ];
verts[ 4 ] = verts[ 1 ];
verts[ 5 ] = Terrain_GetVert( pm, x + 1, y + 1 );
}
}
/*
================
EmitTerrainVerts2
================
*/
void EmitTerrainVerts2( terrainSurf_t *surf, terrainVert_t **verts, int alpha[ 3 ] ) {
int i;
int j;
drawVert_t *vert;
int *indices;
int numindices;
int maxindices;
int xyplane;
vec3_t xynorm = { 0, 0, 1 };
vec_t shift[ 2 ] = { 0, 0 };
vec_t scale[ 2 ] = { 0.5, 0.5 };
float vecs[ 2 ][ 4 ];
vec4_t plane;
if ( !surf->verts ) {
surf->numVerts = 0;
surf->maxVerts = GROW_VERTS;
surf->verts = malloc( surf->maxVerts * sizeof( *surf->verts ) );
surf->numIndexes = 0;
surf->maxIndexes = GROW_INDICES;
surf->indexes = malloc( surf->maxIndexes * sizeof( *surf->indexes ) );
}
// calculate the texture coordinate vectors
xyplane = FindFloatPlane( xynorm, 0 );
QuakeTextureVecs( &mapplanes[ xyplane ], shift, 0, scale, vecs );
// emit the vertexes
numindices = 0;
maxindices = surf->maxIndexes;
assert( maxindices >= 0 );
indices = malloc ( maxindices * sizeof( *indices ) );
PlaneFromPoints( plane, verts[ 0 ]->xyz, verts[ 1 ]->xyz, verts[ 2 ]->xyz );
for ( i = 0; i < 3; i++ ) {
vert = &surf->verts[ surf->numVerts ];
if ( alpha[ i ] ) {
vert->color[3] = 255;
} else {
vert->color[3] = 0;
}
vert->xyz[ 0 ] = floor( verts[ i ]->xyz[ 0 ] + 0.1f );
vert->xyz[ 1 ] = floor( verts[ i ]->xyz[ 1 ] + 0.1f );
vert->xyz[ 2 ] = floor( verts[ i ]->xyz[ 2 ] + 0.1f );
// set the texture coordinates
vert->st[0] = ( vecs[0][3] + DotProduct( vecs[ 0 ], vert->xyz ) ) / surf->shader->width;
vert->st[1] = ( vecs[1][3] + DotProduct( vecs[ 1 ], vert->xyz ) ) / surf->shader->height;
VectorCopy( plane, vert->normal );
for( j = 0; j < surf->numVerts; j++ ) {
if ( CompareVert( vert, &surf->verts[ j ], qtrue ) ) {
break;
}
}
if ( numindices >= maxindices ) {
maxindices += GROW_INDICES;
indices = realloc( indices, maxindices * sizeof( *indices ) );
}
if ( j != surf->numVerts ) {
indices[ numindices++ ] = j;
} else {
indices[ numindices++ ] = surf->numVerts;
surf->numVerts++;
if ( surf->numVerts >= surf->maxVerts ) {
surf->maxVerts += GROW_VERTS;
surf->verts = realloc( surf->verts, surf->maxVerts * sizeof( *surf->verts ) );
}
}
}
SideAsTristrip( surf, indices, numindices );
free( indices );
}
int MapPlaneFromPoints( vec3_t p0, vec3_t p1, vec3_t p2 );
void QuakeTextureVecs( plane_t *plane, vec_t shift[2], vec_t rotate, vec_t scale[2], vec_t mappingVecs[2][4] );
qboolean RemoveDuplicateBrushPlanes( bspbrush_t *b );
void SetBrushContents( bspbrush_t *b );
void AddBrushSide( vec3_t v1, vec3_t v2, vec3_t v3, shaderInfo_t *terrainShader ) {
side_t *side;
int planenum;
side = &buildBrush->sides[ buildBrush->numsides ];
memset( side, 0, sizeof( *side ) );
buildBrush->numsides++;
side->shaderInfo = terrainShader;
// find the plane number
planenum = MapPlaneFromPoints( v1, v2, v3 );
side->planenum = planenum;
}
void MakeBrushFromTriangle( vec3_t v1, vec3_t v2, vec3_t v3, shaderInfo_t *terrainShader ) {
bspbrush_t *b;
vec3_t d1;
vec3_t d2;
vec3_t d3;
VectorSet( d1, v1[ 0 ], v1[ 1 ], MIN_WORLD_COORD + 10 ); //FIXME
VectorSet( d2, v2[ 0 ], v2[ 1 ], MIN_WORLD_COORD + 10 );
VectorSet( d3, v3[ 0 ], v3[ 1 ], MIN_WORLD_COORD + 10 );
buildBrush->numsides = 0;
buildBrush->detail = qfalse;
AddBrushSide( v1, v2, v3, terrainShader );
AddBrushSide( v1, d1, v2, terrainShader );
AddBrushSide( v2, d2, v3, terrainShader );
AddBrushSide( v3, d3, v1, terrainShader );
AddBrushSide( d3, d2, d1, terrainShader );
buildBrush->portalareas[0] = -1;
buildBrush->portalareas[1] = -1;
buildBrush->entitynum = num_entities-1;
buildBrush->brushnum = entitySourceBrushes;
// if there are mirrored planes, the entire brush is invalid
if ( !RemoveDuplicateBrushPlanes( buildBrush ) ) {
return;
}
// get the content for the entire brush
SetBrushContents( buildBrush );
buildBrush->contents |= CONTENTS_DETAIL;
b = FinishBrush();
if ( !b ) {
return;
}
}
void MakeTerrainIntoBrushes( terrainMesh_t *tm ) {
int index[ 6 ];
int y;
int x;
terrainVert_t *verts;
shaderInfo_t *terrainShader;
terrainShader = ShaderInfoForShader( "textures/common/terrain" );
verts = tm->map;
for( y = 0; y < tm->height - 1; y++ ) {
for( x = 0; x < tm->width - 1; x++ ) {
if ( ( x + y ) & 1 ) {
// first tri
index[ 0 ] = x + y * tm->width;
index[ 1 ] = x + ( y + 1 ) * tm->width;
index[ 2 ] = ( x + 1 ) + ( y + 1 ) * tm->width;
index[ 3 ] = ( x + 1 ) + ( y + 1 ) * tm->width;
index[ 4 ] = ( x + 1 ) + y * tm->width;
index[ 5 ] = x + y * tm->width;
} else {
// first tri
index[ 0 ] = x + y * tm->width;
index[ 1 ] = x + ( y + 1 ) * tm->width;
index[ 2 ] = ( x + 1 ) + y * tm->width;
index[ 3 ] = ( x + 1 ) + y * tm->width;
index[ 4 ] = x + ( y + 1 ) * tm->width;
index[ 5 ] = ( x + 1 ) + ( y + 1 ) * tm->width;
}
MakeBrushFromTriangle( verts[ index[ 0 ] ].xyz, verts[ index[ 1 ] ].xyz, verts[ index[ 2 ] ].xyz, terrainShader );
MakeBrushFromTriangle( verts[ index[ 3 ] ].xyz, verts[ index[ 4 ] ].xyz, verts[ index[ 5 ] ].xyz, terrainShader );
}
}
}
void Terrain_ParseFace( terrainFace_t *face ) {
shaderInfo_t *si;
vec_t shift[ 2 ];
vec_t rotate;
vec_t scale[ 2 ];
char name[ MAX_QPATH ];
char shader[ MAX_QPATH ];
plane_t p;
// read the texturedef
GetToken( qfalse );
strcpy( name, token );
GetToken( qfalse );
shift[ 0 ] = atof(token);
GetToken( qfalse );
shift[ 1 ] = atof( token );
GetToken( qfalse );
rotate = atof( token );
GetToken( qfalse );
scale[ 0 ] = atof( token );
GetToken( qfalse );
scale[ 1 ] = atof( token );
// find default flags and values
sprintf( shader, "textures/%s", name );
si = ShaderInfoForShader( shader );
face->shaderInfo = si;
//face->texdef = si->texdef;
// skip over old contents
GetToken( qfalse );
// skip over old flags
GetToken( qfalse );
// skip over old value
GetToken( qfalse );
//Surface_Parse( &face->texdef );
//Surface_BuildTexdef( &face->texdef );
// make a fake horizontal plane
VectorSet( p.normal, 0, 0, 1 );
p.dist = 0;
p.type = PlaneTypeForNormal( p.normal );
QuakeTextureVecs( &p, shift, rotate, scale, face->vecs );
}
#define MAX_TERRAIN_TEXTURES 128
static int numtextures = 0;;
static shaderInfo_t *textures[ MAX_TERRAIN_TEXTURES ];
void Terrain_AddTexture( shaderInfo_t *texture ) {
int i;
if ( !texture ) {
return;
}
for( i = 0; i < numtextures; i++ ) {
if ( textures[ i ] == texture ) {
return;
}
}
if ( numtextures >= MAX_TERRAIN_TEXTURES ) {
Error( "Too many textures on terrain" );
return;
}
textures[ numtextures++ ] = texture;
}
int LayerForShader( shaderInfo_t *shader ) {
int i;
int l;
l = strlen( shader->shader );
for( i = l - 1; i >= 0; i-- ) {
if ( shader->shader[ i ] == '_' ) {
return atoi( &shader->shader[ i + 1 ] );
break;
}
}
return 0;
}
/*
=================
ParseTerrain
Creates a mapDrawSurface_t from the terrain text
=================
*/
void ParseTerrain( void ) {
int i, j;
int x, y;
int x1, y1;
terrainMesh_t t;
int index;
terrainVert_t *verts[ 6 ];
int num_layers;
int layer, minlayer, maxlayer;
int alpha[ 6 ];
shaderInfo_t *si, *terrainShader;
int surfwidth, surfheight, surfsize;
terrainSurf_t *surf;
char shadername[ MAX_QPATH ];
mapent->firstDrawSurf = numMapDrawSurfs;
memset( &t, 0, sizeof( t ) );
MatchToken( "{" );
// get width
GetToken( qtrue );
t.width = atoi( token );
// get height
GetToken( qfalse );
t.height = atoi( token );
// get scale_x
GetToken( qfalse );
t.scale_x = atof( token );
// get scale_y
GetToken( qfalse );
t.scale_y = atof( token );
// get origin
GetToken( qtrue );
t.origin[ 0 ] = atof( token );
GetToken( qfalse );
t.origin[ 1 ] = atof( token );
GetToken( qfalse );
t.origin[ 2 ] = atof( token );
t.map = malloc( t.width * t.height * sizeof( t.map[ 0 ] ) );
if ( t.width <= 0 || t.height <= 0 ) {
Error( "ParseTerrain: bad size" );
}
numtextures = 0;
index = 0;
for ( i = 0; i < t.height; i++ ) {
for( j = 0; j < t.width; j++, index++ ) {
// get height
GetToken( qtrue );
t.map[ index ].xyz[ 0 ] = t.origin[ 0 ] + t.scale_x * ( float )j;
t.map[ index ].xyz[ 1 ] = t.origin[ 1 ] + t.scale_y * ( float )i;
t.map[ index ].xyz[ 2 ] = t.origin[ 2 ] + atof( token );
Terrain_ParseFace( &t.map[ index ].tri );
Terrain_AddTexture( t.map[ index ].tri.shaderInfo );
}
}
MatchToken( "}" );
MatchToken( "}" );
MakeTerrainIntoBrushes( &t );
surfwidth = ( ( t.scale_x * t.width ) + SURF_WIDTH - 1 ) / SURF_WIDTH;
surfheight = ( ( t.scale_y * t.height ) + SURF_HEIGHT - 1 ) / SURF_HEIGHT;
surfsize = surfwidth * surfheight;
//FIXME
num_layers = 0;
for( i = 0; i < numtextures; i++ ) {
layer = LayerForShader( textures[ i ] ) + 1;
if ( layer > num_layers ) {
num_layers = layer;
}
}
num_layers = 4;
memset( alpha, 0, sizeof( alpha ) );
lastSurface = NULL;
numsurfaces = 0;
maxsurfaces = 0;
for( i = num_layers; i > 0; i-- ) {
maxsurfaces += i * surfsize;
}
surfaces = malloc( maxsurfaces * sizeof( *surfaces ) );
memset( surfaces, 0, maxsurfaces * sizeof( *surfaces ) );
terrainShader = ShaderInfoForShader( "textures/common/terrain" );
// get the shadername
if ( Q_strncasecmp( textures[ 0 ]->shader, "textures/", 9 ) == 0 ) {
strcpy( shadername, &textures[ 0 ]->shader[ 9 ] );
} else {
strcpy( shadername, textures[ 0 ]->shader );
}
j = strlen( shadername );
for( i = j - 1; i >= 0; i-- ) {
if ( shadername[ i ] == '_' ) {
shadername[ i ] = 0;
break;
}
}
for( y = 0; y < t.height - 1; y++ ) {
for( x = 0; x < t.width - 1; x++ ) {
Terrain_GetTriangles( &t, x, y, verts );
x1 = ( ( float )x / ( float )( t.width - 1 ) ) * surfwidth;
if ( x1 >= surfwidth ) {
x1 = surfwidth - 1;
}
y1 = ( ( float )y / ( float )( t.height - 1 ) ) * surfheight;
if ( y1 >= surfheight ) {
y1 = surfheight - 1;
}
maxlayer = minlayer = LayerForShader( verts[ 0 ]->tri.shaderInfo );
for( i = 0; i < 3; i++ ) {
layer = LayerForShader( verts[ i ]->tri.shaderInfo );
if ( layer < minlayer ) {
minlayer = layer;
}
if ( layer > maxlayer ) {
maxlayer = layer;
}
}
for( i = 0; i < 3; i++ ) {
layer = LayerForShader( verts[ i ]->tri.shaderInfo );
if ( layer > minlayer ) {
alpha[ i ] = 1.0f;
} else {
alpha[ i ] = 0.0f;
}
}
si = ShaderForLayer( minlayer, maxlayer, shadername );
surf = SurfaceForShader( si, x1, y1 );
EmitTerrainVerts2( surf, &verts[ 0 ], &alpha[ 0 ] );
// second triangle
maxlayer = minlayer = LayerForShader( verts[ 3 ]->tri.shaderInfo );
for( i = 3; i < 6; i++ ) {
layer = LayerForShader( verts[ i ]->tri.shaderInfo );
if ( layer < minlayer ) {
minlayer = layer;
}
if ( layer > maxlayer ) {
maxlayer = layer;
}
}
for( i = 3; i < 6; i++ ) {
layer = LayerForShader( verts[ i ]->tri.shaderInfo );
if ( layer > minlayer ) {
alpha[ i ] = 1.0f;
} else {
alpha[ i ] = 0.0f;
}
}
si = ShaderForLayer( minlayer, maxlayer, shadername );
surf = SurfaceForShader( si, x1, y1 );
EmitTerrainVerts2( surf, &verts[ 3 ], &alpha[ 3 ] );
}
}
// create the final surfaces
for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
if ( surf->numVerts ) {
CreateTerrainSurface( surf, surf->shader );
}
}
//
// clean up any allocated memory
//
for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
if ( surf->verts ) {
free( surf->verts );
free( surf->indexes );
}
}
free( surfaces );
surfaces = NULL;
lastSurface = NULL;
numsurfaces = 0;
maxsurfaces = 0;
free( t.map );
}