]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/renderer/Image_program.cpp
hello world
[icculus/iodoom3.git] / neo / renderer / Image_program.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
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.
13
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.
18
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/>.
21
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.
23
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.
25
26 ===========================================================================
27 */
28
29 /*
30
31 all uncompressed
32 uncompressed normal maps
33
34 downsample images
35
36 16 meg Dynamic cache
37
38 Anisotropic texturing
39
40 Trilinear on all
41 Trilinear on normal maps, bilinear on others
42 Bilinear on all
43
44
45 Manager
46
47 ->List
48 ->Print
49 ->Reload( bool force )
50
51 */
52
53 #include "../idlib/precompiled.h"
54 #pragma hdrstop
55
56 // tr_imageprogram.c
57
58 #include "tr_local.h"
59
60 /*
61
62 Anywhere that an image name is used (diffusemaps, bumpmaps, specularmaps, lights, etc),
63 an imageProgram can be specified.
64
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.
67
68 */
69
70 /*
71 =================
72 R_HeightmapToNormalMap
73
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.
77 =================
78 */
79 static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) {
80         int             i, j;
81         byte    *depth;
82
83         scale = scale / 256;
84
85         // copy and convert to grey scale
86         j = width * height;
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;
90         }
91
92         idVec3  dir, dir2;
93         for ( i = 0 ; i < height ; i++ ) {
94                 for ( j = 0 ; j < width ; j++ ) {
95                         int             d1, d2, d3, d4;
96                         int             a1, a2, a3, a4;
97
98                         // FIXME: look at five points?
99
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 ) ) ) ];
105
106                         d2 -= d1;
107                         d3 -= d1;
108
109                         dir[0] = -d2 * scale;
110                         dir[1] = -d3 * scale;
111                         dir[2] = 1;
112                         dir.NormalizeFast();
113
114                         a1 -= a3;
115                         a4 -= a3;
116
117                         dir2[0] = -a4 * scale;
118                         dir2[1] = a1 * scale;
119                         dir2[2] = 1;
120                         dir2.NormalizeFast();
121         
122                         dir += dir2;
123                         dir.NormalizeFast();
124
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;
130                 }
131         }
132
133
134         R_StaticFree( depth );
135 }
136
137
138 /*
139 =================
140 R_ImageScale
141 =================
142 */
143 static void R_ImageScale( byte *data, int width, int height, float scale[4] ) {
144         int             i, j;
145         int             c;
146
147         c = width * height * 4;
148
149         for ( i = 0 ; i < c ; i++ ) {
150                 j = (byte)(data[i] * scale[i&3]);
151                 if ( j < 0 ) {
152                         j = 0;
153                 } else if ( j > 255 ) {
154                         j = 255;
155                 }
156                 data[i] = j;
157         }
158 }
159
160 /*
161 =================
162 R_InvertAlpha
163 =================
164 */
165 static void R_InvertAlpha( byte *data, int width, int height ) {
166         int             i;
167         int             c;
168
169         c = width * height* 4;
170
171         for ( i = 0 ; i < c ; i+=4 ) {
172                 data[i+3] = 255 - data[i+3];
173         }
174 }
175
176 /*
177 =================
178 R_InvertColor
179 =================
180 */
181 static void R_InvertColor( byte *data, int width, int height ) {
182         int             i;
183         int             c;
184
185         c = width * height* 4;
186
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];
191         }
192 }
193
194
195 /*
196 ===================
197 R_AddNormalMaps
198
199 ===================
200 */
201 static void R_AddNormalMaps( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
202         int             i, j;
203         byte    *newMap;
204
205         // resample pic2 to the same size as pic1
206         if ( width2 != width1 || height2 != height1 ) {
207                 newMap = R_Dropsample( data2, width2, height2, width1, height1 );
208                 data2 = newMap;
209         } else {
210                 newMap = NULL;
211         }
212
213         // add the normal change from the second and renormalize
214         for ( i = 0 ; i < height1 ; i++ ) {
215                 for ( j = 0 ; j < width1 ; j++ ) {
216                         byte    *d1, *d2;
217                         idVec3  n;
218                         float   len;
219
220                         d1 = data1 + ( i * width1 + j ) * 4;
221                         d2 = data2 + ( i * width1 + j ) * 4;
222
223                         n[0] = ( d1[0] - 128 ) / 127.0;
224                         n[1] = ( d1[1] - 128 ) / 127.0;
225                         n[2] = ( d1[2] - 128 ) / 127.0;
226
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();
230                         if ( len < 1.0f ) {
231                                 n[2] = idMath::Sqrt(1.0 - (n[0]*n[0]) - (n[1]*n[1]));
232                         }
233
234                         n[0] += ( d2[0] - 128 ) / 127.0;
235                         n[1] += ( d2[1] - 128 ) / 127.0;
236                         n.Normalize();
237
238                         d1[0] = (byte)(n[0] * 127 + 128);
239                         d1[1] = (byte)(n[1] * 127 + 128);
240                         d1[2] = (byte)(n[2] * 127 + 128);
241                         d1[3] = 255;
242                 }
243         }
244
245         if ( newMap ) {
246                 R_StaticFree( newMap );
247         }
248 }
249
250 /*
251 ================
252 R_SmoothNormalMap
253 ================
254 */
255 static void R_SmoothNormalMap( byte *data, int width, int height ) {
256         byte    *orig;
257         int             i, j, k, l;
258         idVec3  normal;
259         byte    *out;
260         static float    factors[3][3] = {
261                 { 1, 1, 1 },
262                 { 1, 1, 1 },
263                 { 1, 1, 1 }
264         };
265
266         orig = (byte *)R_StaticAlloc( width * height * 4 );
267         memcpy( orig, data, width * height * 4 );
268
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++ ) {
274                                         byte    *in;
275
276                                         in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4;
277
278                                         // ignore 000 and -1 -1 -1
279                                         if ( in[0] == 0 && in[1] == 0 && in[2] == 0 ) {
280                                                 continue;
281                                         }
282                                         if ( in[0] == 128 && in[1] == 128 && in[2] == 128 ) {
283                                                 continue;
284                                         }
285
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 );
289                                 }
290                         }
291                         normal.Normalize();
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]);
296                 }
297         }
298
299         R_StaticFree( orig );
300 }
301
302
303 /*
304 ===================
305 R_ImageAdd
306
307 ===================
308 */
309 static void R_ImageAdd( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) {
310         int             i, j;
311         int             c;
312         byte    *newMap;
313
314         // resample pic2 to the same size as pic1
315         if ( width2 != width1 || height2 != height1 ) {
316                 newMap = R_Dropsample( data2, width2, height2, width1, height1 );
317                 data2 = newMap;
318         } else {
319                 newMap = NULL;
320         }
321
322
323         c = width1 * height1 * 4;
324
325         for ( i = 0 ; i < c ; i++ ) {
326                 j = data1[i] + data2[i];
327                 if ( j > 255 ) {
328                         j = 255;
329                 }
330                 data1[i] = j;
331         }
332
333         if ( newMap ) {
334                 R_StaticFree( newMap );
335         }
336 }
337
338
339 // we build a canonical token form of the image program here
340 static char parseBuffer[MAX_IMAGE_NAME];
341
342 /*
343 ===================
344 AppendToken
345 ===================
346 */
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, " " );
351         }
352         idStr::Append( parseBuffer, MAX_IMAGE_NAME, token.c_str() );
353 }
354
355 /*
356 ===================
357 MatchAndAppendToken
358 ===================
359 */
360 static void MatchAndAppendToken( idLexer &src, const char *match ) {
361         if ( !src.ExpectTokenString( match ) ) {
362                 return;
363         }
364         // a matched token won't need a leading space
365         idStr::Append( parseBuffer, MAX_IMAGE_NAME, match );
366 }
367
368 /*
369 ===================
370 R_ParseImageProgram_r
371
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.
375 ===================
376 */
377 static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *height,
378                                                                   ID_TIME_T *timestamps, textureDepth_t *depth ) {
379         idToken         token;
380         float           scale;
381         ID_TIME_T               timestamp;
382
383         src.ReadToken( &token );
384         AppendToken( token );
385
386         if ( !token.Icmp( "heightmap" ) ) {
387                 MatchAndAppendToken( src, "(" );
388
389                 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
390                         return false;
391                 }
392
393                 MatchAndAppendToken( src, "," );
394
395                 src.ReadToken( &token );
396                 AppendToken( token );
397                 scale = token.GetFloatValue();
398                 
399                 // process it
400                 if ( pic ) {
401                         R_HeightmapToNormalMap( *pic, *width, *height, scale );
402                         if ( depth ) {
403                                 *depth = TD_BUMP;
404                         }
405                 }
406
407                 MatchAndAppendToken( src, ")" );
408                 return true;
409         }
410
411         if ( !token.Icmp( "addnormals" ) ) {
412                 byte    *pic2;
413                 int             width2, height2;
414
415                 MatchAndAppendToken( src, "(" );
416
417                 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
418                         return false;
419                 }
420
421                 MatchAndAppendToken( src, "," );
422
423                 if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
424                         if ( pic ) {
425                                 R_StaticFree( *pic );
426                                 *pic = NULL;
427                         }
428                         return false;
429                 }
430                 
431                 // process it
432                 if ( pic ) {
433                         R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 );
434                         R_StaticFree( pic2 );
435                         if ( depth ) {
436                                 *depth = TD_BUMP;
437                         }
438                 }
439
440                 MatchAndAppendToken( src, ")" );
441                 return true;
442         }
443
444         if ( !token.Icmp( "smoothnormals" ) ) {
445                 MatchAndAppendToken( src, "(" );
446
447                 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
448                         return false;
449                 }
450
451                 if ( pic ) {
452                         R_SmoothNormalMap( *pic, *width, *height );
453                         if ( depth ) {
454                                 *depth = TD_BUMP;
455                         }
456                 }
457
458                 MatchAndAppendToken( src, ")" );
459                 return true;
460         }
461
462         if ( !token.Icmp( "add" ) ) {
463                 byte    *pic2;
464                 int             width2, height2;
465
466                 MatchAndAppendToken( src, "(" );
467
468                 if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) {
469                         return false;
470                 }
471
472                 MatchAndAppendToken( src, "," );
473
474                 if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) {
475                         if ( pic ) {
476                                 R_StaticFree( *pic );
477                                 *pic = NULL;
478                         }
479                         return false;
480                 }
481                 
482                 // process it
483                 if ( pic ) {
484                         R_ImageAdd( *pic, *width, *height, pic2, width2, height2 );
485                         R_StaticFree( pic2 );
486                 }
487
488                 MatchAndAppendToken( src, ")" );
489                 return true;
490         }
491
492         if ( !token.Icmp( "scale" ) ) {
493                 float   scale[4];
494                 int             i;
495
496                 MatchAndAppendToken( src, "(" );
497
498                 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
499
500                 for ( i = 0 ; i < 4 ; i++ ) {
501                         MatchAndAppendToken( src, "," );
502                         src.ReadToken( &token );
503                         AppendToken( token );
504                         scale[i] = token.GetFloatValue();
505                 }
506
507                 // process it
508                 if ( pic ) {
509                         R_ImageScale( *pic, *width, *height, scale );
510                 }
511
512                 MatchAndAppendToken( src, ")" );
513                 return true;
514         }
515
516         if ( !token.Icmp( "invertAlpha" ) ) {
517                 MatchAndAppendToken( src, "(" );
518
519                 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
520
521                 // process it
522                 if ( pic ) {
523                         R_InvertAlpha( *pic, *width, *height );
524                 }
525
526                 MatchAndAppendToken( src, ")" );
527                 return true;
528         }
529
530         if ( !token.Icmp( "invertColor" ) ) {
531                 MatchAndAppendToken( src, "(" );
532
533                 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
534
535                 // process it
536                 if ( pic ) {
537                         R_InvertColor( *pic, *width, *height );
538                 }
539
540                 MatchAndAppendToken( src, ")" );
541                 return true;
542         }
543
544         if ( !token.Icmp( "makeIntensity" ) ) {
545                 int             i;
546
547                 MatchAndAppendToken( src, "(" );
548
549                 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
550
551                 // copy red to green, blue, and alpha
552                 if ( pic ) {
553                         int             c;
554                         c = *width * *height * 4;
555                         for ( i = 0 ; i < c ; i+=4 ) {
556                                 (*pic)[i+1] = 
557                                 (*pic)[i+2] = 
558                                 (*pic)[i+3] = (*pic)[i];
559                         }
560                 }
561
562                 MatchAndAppendToken( src, ")" );
563                 return true;
564         }
565
566         if ( !token.Icmp( "makeAlpha" ) ) {
567                 int             i;
568
569                 MatchAndAppendToken( src, "(" );
570
571                 R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
572
573                 // average RGB into alpha, then set RGB to white
574                 if ( pic ) {
575                         int             c;
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;
579                                 (*pic)[i+0] = 
580                                 (*pic)[i+1] = 
581                                 (*pic)[i+2] = 255;
582                         }
583                 }
584
585                 MatchAndAppendToken( src, ")" );
586                 return true;
587         }
588
589         // if we are just parsing instead of loading or checking,
590         // don't do the R_LoadImage
591         if ( !timestamps && !pic ) {
592                 return true;
593         }
594
595         // load it as an image
596         R_LoadImage( token.c_str(), pic, width, height, &timestamp, true );
597
598         if ( timestamp == -1 ) {
599                 return false;
600         }
601
602         // add this to the timestamp
603         if ( timestamps ) {
604                 if ( timestamp > *timestamps ) {
605                         *timestamps = timestamp;
606                 }
607         }
608
609         return true;
610 }
611
612
613 /*
614 ===================
615 R_LoadImageProgram
616 ===================
617 */
618 void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamps, textureDepth_t *depth ) {
619         idLexer src;
620
621         src.LoadMemory( name, strlen(name), name );
622         src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
623
624         parseBuffer[0] = 0;
625         if ( timestamps ) {
626                 *timestamps = 0;
627         }
628
629         R_ParseImageProgram_r( src, pic, width, height, timestamps, depth );
630
631         src.FreeSource();
632 }
633
634 /*
635 ===================
636 R_ParsePastImageProgram
637 ===================
638 */
639 const char *R_ParsePastImageProgram( idLexer &src ) {
640         parseBuffer[0] = 0;
641         R_ParseImageProgram_r( src, NULL, NULL, NULL, NULL, NULL );
642         return parseBuffer;
643 }
644