/* =========================================================================== 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 #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 ); }