2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
32 uncompressed normal maps
41 Trilinear on normal maps, bilinear on others
49 ->Reload( bool force )
53 #include "../idlib/precompiled.h"
62 Anywhere that an image name is used (diffusemaps, bumpmaps, specularmaps, lights, etc),
63 an imageProgram can be specified.
65 This allows load time operations, like heightmap-to-normalmap conversion and image
66 composition, to be automatically handled in a way that supports timestamped reloads.
72 R_HeightmapToNormalMap
74 it is not possible to convert a heightmap into a normal map
75 properly without knowing the texture coordinate stretching.
76 We can assume constant and equal ST vectors for walls, but not for characters.
79 static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) {
85 // copy and convert to grey scale
87 depth = (byte *)R_StaticAlloc( j );
88 for ( i = 0 ; i < j ; i++ ) {
89 depth[i] = ( data[i*4] + data[i*4+1] + data[i*4+2] ) / 3;
93 for ( i = 0 ; i < height ; i++ ) {
94 for ( j = 0 ; j < width ; j++ ) {
98 // FIXME: look at five points?
100 // look at three points to estimate the gradient
101 a1 = d1 = depth[ ( i * width + j ) ];
102 a2 = d2 = depth[ ( i * width + ( ( j + 1 ) & ( width - 1 ) ) ) ];
103 a3 = d3 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + j ) ];
104 a4 = d4 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) ) ];
109 dir[0] = -d2 * scale;
110 dir[1] = -d3 * scale;
117 dir2[0] = -a4 * scale;
118 dir2[1] = a1 * scale;
120 dir2.NormalizeFast();
125 a1 = ( i * width + j ) * 4;
126 data[ a1 + 0 ] = (byte)(dir[0] * 127 + 128);
127 data[ a1 + 1 ] = (byte)(dir[1] * 127 + 128);
128 data[ a1 + 2 ] = (byte)(dir[2] * 127 + 128);
129 data[ a1 + 3 ] = 255;
134 R_StaticFree( depth );
143 static void R_ImageScale( byte *data, int width, int height, float scale[4] ) {
147 c = width * height * 4;
149 for ( i = 0 ; i < c ; i++ ) {
150 j = (byte)(data[i] * scale[i&3]);
153 } else if ( j > 255 ) {
165 static void R_InvertAlpha( byte *data, int width, int height ) {
169 c = width * height* 4;
171 for ( i = 0 ; i < c ; i+=4 ) {
172 data[i+3] = 255 - data[i+3];
181 static void R_InvertColor( byte *data, int width, int height ) {
185 c = width * height* 4;
187 for ( i = 0 ; i < c ; i+=4 ) {
188 data[i+0] = 255 - data[i+0];
189 data[i+1] = 255 - data[i+1];
190 data[i+2] = 255 - data[i+2];
201 static void R_AddNormalMaps( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
205 // resample pic2 to the same size as pic1
206 if ( width2 != width1 || height2 != height1 ) {
207 newMap = R_Dropsample( data2, width2, height2, width1, height1 );
213 // add the normal change from the second and renormalize
214 for ( i = 0 ; i < height1 ; i++ ) {
215 for ( j = 0 ; j < width1 ; j++ ) {
220 d1 = data1 + ( i * width1 + j ) * 4;
221 d2 = data2 + ( i * width1 + j ) * 4;
223 n[0] = ( d1[0] - 128 ) / 127.0;
224 n[1] = ( d1[1] - 128 ) / 127.0;
225 n[2] = ( d1[2] - 128 ) / 127.0;
227 // There are some normal maps that blend to 0,0,0 at the edges
228 // this screws up compression, so we try to correct that here by instead fading it to 0,0,1
229 len = n.LengthFast();
231 n[2] = idMath::Sqrt(1.0 - (n[0]*n[0]) - (n[1]*n[1]));
234 n[0] += ( d2[0] - 128 ) / 127.0;
235 n[1] += ( d2[1] - 128 ) / 127.0;
238 d1[0] = (byte)(n[0] * 127 + 128);
239 d1[1] = (byte)(n[1] * 127 + 128);
240 d1[2] = (byte)(n[2] * 127 + 128);
246 R_StaticFree( newMap );
255 static void R_SmoothNormalMap( byte *data, int width, int height ) {
260 static float factors[3][3] = {
266 orig = (byte *)R_StaticAlloc( width * height * 4 );
267 memcpy( orig, data, width * height * 4 );
269 for ( i = 0 ; i < width ; i++ ) {
270 for ( j = 0 ; j < height ; j++ ) {
271 normal = vec3_origin;
272 for ( k = -1 ; k < 2 ; k++ ) {
273 for ( l = -1 ; l < 2 ; l++ ) {
276 in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4;
278 // ignore 000 and -1 -1 -1
279 if ( in[0] == 0 && in[1] == 0 && in[2] == 0 ) {
282 if ( in[0] == 128 && in[1] == 128 && in[2] == 128 ) {
286 normal[0] += factors[k+1][l+1] * ( in[0] - 128 );
287 normal[1] += factors[k+1][l+1] * ( in[1] - 128 );
288 normal[2] += factors[k+1][l+1] * ( in[2] - 128 );
292 out = data + ( j * width + i ) * 4;
293 out[0] = (byte)(128 + 127 * normal[0]);
294 out[1] = (byte)(128 + 127 * normal[1]);
295 out[2] = (byte)(128 + 127 * normal[2]);
299 R_StaticFree( orig );
309 static void R_ImageAdd( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
314 // resample pic2 to the same size as pic1
315 if ( width2 != width1 || height2 != height1 ) {
316 newMap = R_Dropsample( data2, width2, height2, width1, height1 );
323 c = width1 * height1 * 4;
325 for ( i = 0 ; i < c ; i++ ) {
326 j = data1[i] + data2[i];
334 R_StaticFree( newMap );
339 // we build a canonical token form of the image program here
340 static char parseBuffer[MAX_IMAGE_NAME];
347 static void AppendToken( idToken &token ) {
348 // add a leading space if not at the beginning
349 if ( parseBuffer[0] ) {
350 idStr::Append( parseBuffer, MAX_IMAGE_NAME, " " );
352 idStr::Append( parseBuffer, MAX_IMAGE_NAME, token.c_str() );
360 static void MatchAndAppendToken( idLexer &src, const char *match ) {
361 if ( !src.ExpectTokenString( match ) ) {
364 // a matched token won't need a leading space
365 idStr::Append( parseBuffer, MAX_IMAGE_NAME, match );
370 R_ParseImageProgram_r
372 If pic is NULL, the timestamps will be filled in, but no image will be generated
373 If both pic and timestamps are NULL, it will just advance past it, which can be
374 used to parse an image program from a text stream.
377 static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *height,
378 ID_TIME_T *timestamps, textureDepth_t *depth ) {
383 src.ReadToken( &token );
384 AppendToken( token );
386 if ( !token.Icmp( "heightmap" ) ) {
387 MatchAndAppendToken( src, "(" );
389 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
393 MatchAndAppendToken( src, "," );
395 src.ReadToken( &token );
396 AppendToken( token );
397 scale = token.GetFloatValue();
401 R_HeightmapToNormalMap( *pic, *width, *height, scale );
407 MatchAndAppendToken( src, ")" );
411 if ( !token.Icmp( "addnormals" ) ) {
415 MatchAndAppendToken( src, "(" );
417 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
421 MatchAndAppendToken( src, "," );
423 if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
425 R_StaticFree( *pic );
433 R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 );
434 R_StaticFree( pic2 );
440 MatchAndAppendToken( src, ")" );
444 if ( !token.Icmp( "smoothnormals" ) ) {
445 MatchAndAppendToken( src, "(" );
447 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
452 R_SmoothNormalMap( *pic, *width, *height );
458 MatchAndAppendToken( src, ")" );
462 if ( !token.Icmp( "add" ) ) {
466 MatchAndAppendToken( src, "(" );
468 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
472 MatchAndAppendToken( src, "," );
474 if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
476 R_StaticFree( *pic );
484 R_ImageAdd( *pic, *width, *height, pic2, width2, height2 );
485 R_StaticFree( pic2 );
488 MatchAndAppendToken( src, ")" );
492 if ( !token.Icmp( "scale" ) ) {
496 MatchAndAppendToken( src, "(" );
498 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
500 for ( i = 0 ; i < 4 ; i++ ) {
501 MatchAndAppendToken( src, "," );
502 src.ReadToken( &token );
503 AppendToken( token );
504 scale[i] = token.GetFloatValue();
509 R_ImageScale( *pic, *width, *height, scale );
512 MatchAndAppendToken( src, ")" );
516 if ( !token.Icmp( "invertAlpha" ) ) {
517 MatchAndAppendToken( src, "(" );
519 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
523 R_InvertAlpha( *pic, *width, *height );
526 MatchAndAppendToken( src, ")" );
530 if ( !token.Icmp( "invertColor" ) ) {
531 MatchAndAppendToken( src, "(" );
533 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
537 R_InvertColor( *pic, *width, *height );
540 MatchAndAppendToken( src, ")" );
544 if ( !token.Icmp( "makeIntensity" ) ) {
547 MatchAndAppendToken( src, "(" );
549 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
551 // copy red to green, blue, and alpha
554 c = *width * *height * 4;
555 for ( i = 0 ; i < c ; i+=4 ) {
558 (*pic)[i+3] = (*pic)[i];
562 MatchAndAppendToken( src, ")" );
566 if ( !token.Icmp( "makeAlpha" ) ) {
569 MatchAndAppendToken( src, "(" );
571 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
573 // average RGB into alpha, then set RGB to white
576 c = *width * *height * 4;
577 for ( i = 0 ; i < c ; i+=4 ) {
578 (*pic)[i+3] = ( (*pic)[i+0] + (*pic)[i+1] + (*pic)[i+2] ) / 3;
585 MatchAndAppendToken( src, ")" );
589 // if we are just parsing instead of loading or checking,
590 // don't do the R_LoadImage
591 if ( !timestamps && !pic ) {
595 // load it as an image
596 R_LoadImage( token.c_str(), pic, width, height, ×tamp, true );
598 if ( timestamp == -1 ) {
602 // add this to the timestamp
604 if ( timestamp > *timestamps ) {
605 *timestamps = timestamp;
618 void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamps, textureDepth_t *depth ) {
621 src.LoadMemory( name, strlen(name), name );
622 src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
629 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
636 R_ParsePastImageProgram
639 const char *R_ParsePastImageProgram( idLexer &src ) {
641 R_ParseImageProgram_r( src, NULL, NULL, NULL, NULL, NULL );