mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
637 lines
14 KiB
C++
637 lines
14 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "renderer/tr_local.h"
|
|
|
|
#include "renderer/Image.h"
|
|
|
|
/*
|
|
|
|
all uncompressed
|
|
uncompressed normal maps
|
|
|
|
downsample images
|
|
|
|
16 meg Dynamic cache
|
|
|
|
Anisotropic texturing
|
|
|
|
Trilinear on all
|
|
Trilinear on normal maps, bilinear on others
|
|
Bilinear on all
|
|
|
|
|
|
Manager
|
|
|
|
->List
|
|
->Print
|
|
->Reload( bool force )
|
|
|
|
Anywhere that an image name is used (diffusemaps, bumpmaps, specularmaps, lights, etc),
|
|
an imageProgram can be specified.
|
|
|
|
This allows load time operations, like heightmap-to-normalmap conversion and image
|
|
composition, to be automatically handled in a way that supports timestamped reloads.
|
|
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
R_HeightmapToNormalMap
|
|
|
|
it is not possible to convert a heightmap into a normal map
|
|
properly without knowing the texture coordinate stretching.
|
|
We can assume constant and equal ST vectors for walls, but not for characters.
|
|
=================
|
|
*/
|
|
static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) {
|
|
int i, j;
|
|
byte *depth;
|
|
|
|
scale = scale / 256;
|
|
|
|
// copy and convert to grey scale
|
|
j = width * height;
|
|
depth = (byte *)R_StaticAlloc( j );
|
|
for ( i = 0 ; i < j ; i++ ) {
|
|
depth[i] = ( data[i*4] + data[i*4+1] + data[i*4+2] ) / 3;
|
|
}
|
|
|
|
idVec3 dir, dir2;
|
|
for ( i = 0 ; i < height ; i++ ) {
|
|
for ( j = 0 ; j < width ; j++ ) {
|
|
int d1, d2, d3, d4;
|
|
int a1, a3, a4;
|
|
|
|
// FIXME: look at five points?
|
|
|
|
// look at three points to estimate the gradient
|
|
a1 = d1 = depth[ ( i * width + j ) ];
|
|
d2 = depth[ ( i * width + ( ( j + 1 ) & ( width - 1 ) ) ) ];
|
|
a3 = d3 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + j ) ];
|
|
a4 = d4 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) ) ];
|
|
|
|
d2 -= d1;
|
|
d3 -= d1;
|
|
|
|
dir[0] = -d2 * scale;
|
|
dir[1] = -d3 * scale;
|
|
dir[2] = 1;
|
|
dir.NormalizeFast();
|
|
|
|
a1 -= a3;
|
|
a4 -= a3;
|
|
|
|
dir2[0] = -a4 * scale;
|
|
dir2[1] = a1 * scale;
|
|
dir2[2] = 1;
|
|
dir2.NormalizeFast();
|
|
|
|
dir += dir2;
|
|
dir.NormalizeFast();
|
|
|
|
a1 = ( i * width + j ) * 4;
|
|
data[ a1 + 0 ] = (byte)(dir[0] * 127 + 128);
|
|
data[ a1 + 1 ] = (byte)(dir[1] * 127 + 128);
|
|
data[ a1 + 2 ] = (byte)(dir[2] * 127 + 128);
|
|
data[ a1 + 3 ] = 255;
|
|
}
|
|
}
|
|
|
|
|
|
R_StaticFree( depth );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_ImageScale
|
|
=================
|
|
*/
|
|
static void R_ImageScale( byte *data, int width, int height, float scale[4] ) {
|
|
int i, j;
|
|
int c;
|
|
|
|
c = width * height * 4;
|
|
|
|
for ( i = 0 ; i < c ; i++ ) {
|
|
j = (byte)(data[i] * scale[i&3]);
|
|
if ( j < 0 ) {
|
|
j = 0;
|
|
} else if ( j > 255 ) {
|
|
j = 255;
|
|
}
|
|
data[i] = j;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_InvertAlpha
|
|
=================
|
|
*/
|
|
static void R_InvertAlpha( byte *data, int width, int height ) {
|
|
int i;
|
|
int c;
|
|
|
|
c = width * height* 4;
|
|
|
|
for ( i = 0 ; i < c ; i+=4 ) {
|
|
data[i+3] = 255 - data[i+3];
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_InvertColor
|
|
=================
|
|
*/
|
|
static void R_InvertColor( byte *data, int width, int height ) {
|
|
int i;
|
|
int c;
|
|
|
|
c = width * height* 4;
|
|
|
|
for ( i = 0 ; i < c ; i+=4 ) {
|
|
data[i+0] = 255 - data[i+0];
|
|
data[i+1] = 255 - data[i+1];
|
|
data[i+2] = 255 - data[i+2];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
R_AddNormalMaps
|
|
|
|
===================
|
|
*/
|
|
static void R_AddNormalMaps( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
|
|
int i, j;
|
|
byte *newMap;
|
|
|
|
// resample pic2 to the same size as pic1
|
|
if ( width2 != width1 || height2 != height1 ) {
|
|
newMap = R_Dropsample( data2, width2, height2, width1, height1 );
|
|
data2 = newMap;
|
|
} else {
|
|
newMap = NULL;
|
|
}
|
|
|
|
// add the normal change from the second and renormalize
|
|
for ( i = 0 ; i < height1 ; i++ ) {
|
|
for ( j = 0 ; j < width1 ; j++ ) {
|
|
byte *d1, *d2;
|
|
idVec3 n;
|
|
float len;
|
|
|
|
d1 = data1 + ( i * width1 + j ) * 4;
|
|
d2 = data2 + ( i * width1 + j ) * 4;
|
|
|
|
n[0] = ( d1[0] - 128 ) / 127.0;
|
|
n[1] = ( d1[1] - 128 ) / 127.0;
|
|
n[2] = ( d1[2] - 128 ) / 127.0;
|
|
|
|
// There are some normal maps that blend to 0,0,0 at the edges
|
|
// this screws up compression, so we try to correct that here by instead fading it to 0,0,1
|
|
len = n.LengthFast();
|
|
if ( len < 1.0f ) {
|
|
n[2] = idMath::Sqrt(1.0 - (n[0]*n[0]) - (n[1]*n[1]));
|
|
}
|
|
|
|
n[0] += ( d2[0] - 128 ) / 127.0;
|
|
n[1] += ( d2[1] - 128 ) / 127.0;
|
|
n.Normalize();
|
|
|
|
d1[0] = (byte)(n[0] * 127 + 128);
|
|
d1[1] = (byte)(n[1] * 127 + 128);
|
|
d1[2] = (byte)(n[2] * 127 + 128);
|
|
d1[3] = 255;
|
|
}
|
|
}
|
|
|
|
if ( newMap ) {
|
|
R_StaticFree( newMap );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_SmoothNormalMap
|
|
================
|
|
*/
|
|
static void R_SmoothNormalMap( byte *data, int width, int height ) {
|
|
byte *orig;
|
|
int i, j, k, l;
|
|
idVec3 normal;
|
|
byte *out;
|
|
static float factors[3][3] = {
|
|
{ 1, 1, 1 },
|
|
{ 1, 1, 1 },
|
|
{ 1, 1, 1 }
|
|
};
|
|
|
|
orig = (byte *)R_StaticAlloc( width * height * 4 );
|
|
memcpy( orig, data, width * height * 4 );
|
|
|
|
for ( i = 0 ; i < width ; i++ ) {
|
|
for ( j = 0 ; j < height ; j++ ) {
|
|
normal = vec3_origin;
|
|
for ( k = -1 ; k < 2 ; k++ ) {
|
|
for ( l = -1 ; l < 2 ; l++ ) {
|
|
byte *in;
|
|
|
|
in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4;
|
|
|
|
// ignore 000 and -1 -1 -1
|
|
if ( in[0] == 0 && in[1] == 0 && in[2] == 0 ) {
|
|
continue;
|
|
}
|
|
if ( in[0] == 128 && in[1] == 128 && in[2] == 128 ) {
|
|
continue;
|
|
}
|
|
|
|
normal[0] += factors[k+1][l+1] * ( in[0] - 128 );
|
|
normal[1] += factors[k+1][l+1] * ( in[1] - 128 );
|
|
normal[2] += factors[k+1][l+1] * ( in[2] - 128 );
|
|
}
|
|
}
|
|
normal.Normalize();
|
|
out = data + ( j * width + i ) * 4;
|
|
out[0] = (byte)(128 + 127 * normal[0]);
|
|
out[1] = (byte)(128 + 127 * normal[1]);
|
|
out[2] = (byte)(128 + 127 * normal[2]);
|
|
}
|
|
}
|
|
|
|
R_StaticFree( orig );
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
R_ImageAdd
|
|
|
|
===================
|
|
*/
|
|
static void R_ImageAdd( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
|
|
int i, j;
|
|
int c;
|
|
byte *newMap;
|
|
|
|
// resample pic2 to the same size as pic1
|
|
if ( width2 != width1 || height2 != height1 ) {
|
|
newMap = R_Dropsample( data2, width2, height2, width1, height1 );
|
|
data2 = newMap;
|
|
} else {
|
|
newMap = NULL;
|
|
}
|
|
|
|
|
|
c = width1 * height1 * 4;
|
|
|
|
for ( i = 0 ; i < c ; i++ ) {
|
|
j = data1[i] + data2[i];
|
|
if ( j > 255 ) {
|
|
j = 255;
|
|
}
|
|
data1[i] = j;
|
|
}
|
|
|
|
if ( newMap ) {
|
|
R_StaticFree( newMap );
|
|
}
|
|
}
|
|
|
|
|
|
// we build a canonical token form of the image program here
|
|
static char parseBuffer[MAX_IMAGE_NAME];
|
|
|
|
/*
|
|
===================
|
|
AppendToken
|
|
===================
|
|
*/
|
|
static void AppendToken( idToken &token ) {
|
|
// add a leading space if not at the beginning
|
|
if ( parseBuffer[0] ) {
|
|
idStr::Append( parseBuffer, MAX_IMAGE_NAME, " " );
|
|
}
|
|
idStr::Append( parseBuffer, MAX_IMAGE_NAME, token.c_str() );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
MatchAndAppendToken
|
|
===================
|
|
*/
|
|
static void MatchAndAppendToken( idLexer &src, const char *match ) {
|
|
if ( !src.ExpectTokenString( match ) ) {
|
|
return;
|
|
}
|
|
// a matched token won't need a leading space
|
|
idStr::Append( parseBuffer, MAX_IMAGE_NAME, match );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ParseImageProgram_r
|
|
|
|
If pic is NULL, the timestamps will be filled in, but no image will be generated
|
|
If both pic and timestamps are NULL, it will just advance past it, which can be
|
|
used to parse an image program from a text stream.
|
|
===================
|
|
*/
|
|
static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *height,
|
|
ID_TIME_T *timestamps, textureDepth_t *depth ) {
|
|
idToken token;
|
|
float scale;
|
|
ID_TIME_T timestamp;
|
|
|
|
src.ReadToken( &token );
|
|
AppendToken( token );
|
|
|
|
if ( !token.Icmp( "heightmap" ) ) {
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
|
|
return false;
|
|
}
|
|
|
|
MatchAndAppendToken( src, "," );
|
|
|
|
src.ReadToken( &token );
|
|
AppendToken( token );
|
|
scale = token.GetFloatValue();
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_HeightmapToNormalMap( *pic, *width, *height, scale );
|
|
if ( depth ) {
|
|
*depth = TD_BUMP;
|
|
}
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "addnormals" ) ) {
|
|
byte *pic2;
|
|
int width2, height2;
|
|
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
|
|
return false;
|
|
}
|
|
|
|
MatchAndAppendToken( src, "," );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
|
|
if ( pic ) {
|
|
R_StaticFree( *pic );
|
|
*pic = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 );
|
|
R_StaticFree( pic2 );
|
|
if ( depth ) {
|
|
*depth = TD_BUMP;
|
|
}
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "smoothnormals" ) ) {
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( pic ) {
|
|
R_SmoothNormalMap( *pic, *width, *height );
|
|
if ( depth ) {
|
|
*depth = TD_BUMP;
|
|
}
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "add" ) ) {
|
|
byte *pic2;
|
|
int width2, height2;
|
|
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
|
|
return false;
|
|
}
|
|
|
|
MatchAndAppendToken( src, "," );
|
|
|
|
if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
|
|
if ( pic ) {
|
|
R_StaticFree( *pic );
|
|
*pic = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_ImageAdd( *pic, *width, *height, pic2, width2, height2 );
|
|
R_StaticFree( pic2 );
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "scale" ) ) {
|
|
float scale[4];
|
|
int i;
|
|
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
MatchAndAppendToken( src, "," );
|
|
src.ReadToken( &token );
|
|
AppendToken( token );
|
|
scale[i] = token.GetFloatValue();
|
|
}
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_ImageScale( *pic, *width, *height, scale );
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "invertAlpha" ) ) {
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_InvertAlpha( *pic, *width, *height );
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "invertColor" ) ) {
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
// process it
|
|
if ( pic ) {
|
|
R_InvertColor( *pic, *width, *height );
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "makeIntensity" ) ) {
|
|
int i;
|
|
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
// copy red to green, blue, and alpha
|
|
if ( pic ) {
|
|
int c;
|
|
c = *width * *height * 4;
|
|
for ( i = 0 ; i < c ; i+=4 ) {
|
|
(*pic)[i+1] =
|
|
(*pic)[i+2] =
|
|
(*pic)[i+3] = (*pic)[i];
|
|
}
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
if ( !token.Icmp( "makeAlpha" ) ) {
|
|
int i;
|
|
|
|
MatchAndAppendToken( src, "(" );
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
// average RGB into alpha, then set RGB to white
|
|
if ( pic ) {
|
|
int c;
|
|
c = *width * *height * 4;
|
|
for ( i = 0 ; i < c ; i+=4 ) {
|
|
(*pic)[i+3] = ( (*pic)[i+0] + (*pic)[i+1] + (*pic)[i+2] ) / 3;
|
|
(*pic)[i+0] =
|
|
(*pic)[i+1] =
|
|
(*pic)[i+2] = 255;
|
|
}
|
|
}
|
|
|
|
MatchAndAppendToken( src, ")" );
|
|
return true;
|
|
}
|
|
|
|
// if we are just parsing instead of loading or checking,
|
|
// don't do the R_LoadImage
|
|
if ( !timestamps && !pic ) {
|
|
return true;
|
|
}
|
|
|
|
// load it as an image
|
|
R_LoadImage( token.c_str(), pic, width, height, ×tamp, true );
|
|
|
|
if ( timestamp == -1 ) {
|
|
return false;
|
|
}
|
|
|
|
// add this to the timestamp
|
|
if ( timestamps ) {
|
|
if ( timestamp > *timestamps ) {
|
|
*timestamps = timestamp;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
R_LoadImageProgram
|
|
===================
|
|
*/
|
|
void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamps, textureDepth_t *depth ) {
|
|
idLexer src;
|
|
|
|
src.LoadMemory( name, strlen(name), name );
|
|
src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
|
|
parseBuffer[0] = 0;
|
|
if ( timestamps ) {
|
|
*timestamps = 0;
|
|
}
|
|
|
|
R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
|
|
|
|
src.FreeSource();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ParsePastImageProgram
|
|
===================
|
|
*/
|
|
const char *R_ParsePastImageProgram( idLexer &src ) {
|
|
parseBuffer[0] = 0;
|
|
R_ParseImageProgram_r( src, NULL, NULL, NULL, NULL, NULL );
|
|
return parseBuffer;
|
|
}
|