1 /*
2 ===========================================================================
3 Copyright (C) 1997-2006 Id Software, Inc.
4
5 This file is part of Quake 2 Tools source code.
6
7 Quake 2 Tools source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake 2 Tools source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake 2 Tools source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 #include "qdata.h"
24
25 char mip_prefix[1024]; // directory to dump the textures in
26
27 qboolean colormap_issued;
28 byte colormap_palette[768];
29
30 /*
31 ==============
32 RemapZero
33
34 Replaces all 0 bytes in an image with the closest palette entry.
35 This is because NT won't let us change index 0, so any palette
36 animation leaves those pixels untouched.
37 ==============
38 */
RemapZero(byte * pixels,byte * palette,int width,int height)39 void RemapZero (byte *pixels, byte *palette, int width, int height)
40 {
41 int i, c;
42 int alt_zero;
43 int value, best;
44
45 alt_zero = 0;
46 best = 9999999;
47 for (i=1 ; i<255 ; i++)
48 {
49 value = palette[i*3+0]+palette[i*3+1]+palette[i*3+2];
50 if (value < best)
51 {
52 best = value;
53 alt_zero = i;
54 }
55 }
56
57 c = width*height;
58 for (i=0 ; i<c ; i++)
59 if (pixels[i] == 0)
60 pixels[i] = alt_zero;
61 }
62
63 /*
64 ==============
65 Cmd_Grab
66
67 $grab filename x y width height
68 ==============
69 */
Cmd_Grab(void)70 void Cmd_Grab (void)
71 {
72 int xl,yl,w,h,y;
73 byte *cropped;
74 char savename[1024];
75 char dest[1024];
76
77 GetToken (false);
78
79 if (token[0] == '/' || token[0] == '\\')
80 sprintf (savename, "%s%s.pcx", gamedir, token+1);
81 else
82 sprintf (savename, "%spics/%s.pcx", gamedir, token);
83
84 if (g_release)
85 {
86 if (token[0] == '/' || token[0] == '\\')
87 sprintf (dest, "%s.pcx", token+1);
88 else
89 sprintf (dest, "pics/%s.pcx", token);
90
91 ReleaseFile (dest);
92 return;
93 }
94
95 GetToken (false);
96 xl = atoi (token);
97 GetToken (false);
98 yl = atoi (token);
99 GetToken (false);
100 w = atoi (token);
101 GetToken (false);
102 h = atoi (token);
103
104 if (xl<0 || yl<0 || w<0 || h<0 || xl+w>byteimagewidth || yl+h>byteimageheight)
105 Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h);
106
107 // crop it to the proper size
108 cropped = malloc (w*h);
109 for (y=0 ; y<h ; y++)
110 {
111 memcpy (cropped+y*w, byteimage+(y+yl)*byteimagewidth+xl, w);
112 }
113
114 // save off the new image
115 printf ("saving %s\n", savename);
116 CreatePath (savename);
117 WritePCXfile (savename, cropped, w, h, lbmpalette);
118
119 free (cropped);
120 }
121
122 /*
123 ==============
124 Cmd_Raw
125
126 $grab filename x y width height
127 ==============
128 */
Cmd_Raw(void)129 void Cmd_Raw (void)
130 {
131 int xl,yl,w,h,y;
132 byte *cropped;
133 char savename[1024];
134 char dest[1024];
135
136 GetToken (false);
137
138 sprintf (savename, "%s%s.lmp", gamedir, token);
139
140 if (g_release)
141 {
142 sprintf (dest, "%s.lmp", token);
143 ReleaseFile (dest);
144 return;
145 }
146
147 GetToken (false);
148 xl = atoi (token);
149 GetToken (false);
150 yl = atoi (token);
151 GetToken (false);
152 w = atoi (token);
153 GetToken (false);
154 h = atoi (token);
155
156 if (xl<0 || yl<0 || w<0 || h<0 || xl+w>byteimagewidth || yl+h>byteimageheight)
157 Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h);
158
159 // crop it to the proper size
160 cropped = malloc (w*h);
161 for (y=0 ; y<h ; y++)
162 {
163 memcpy (cropped+y*w, byteimage+(y+yl)*byteimagewidth+xl, w);
164 }
165
166 // save off the new image
167 printf ("saving %s\n", savename);
168 CreatePath (savename);
169
170 SaveFile (savename, cropped, w*h);
171
172 free (cropped);
173 }
174
175 /*
176 =============================================================================
177
178 COLORMAP GRABBING
179
180 =============================================================================
181 */
182
183 /*
184 ===============
185 BestColor
186 ===============
187 */
BestColor(int r,int g,int b,int start,int stop)188 byte BestColor (int r, int g, int b, int start, int stop)
189 {
190 int i;
191 int dr, dg, db;
192 int bestdistortion, distortion;
193 int bestcolor;
194 byte *pal;
195
196 //
197 // let any color go to 0 as a last resort
198 //
199 bestdistortion = 256*256*4;
200 bestcolor = 0;
201
202 pal = colormap_palette + start*3;
203 for (i=start ; i<= stop ; i++)
204 {
205 dr = r - (int)pal[0];
206 dg = g - (int)pal[1];
207 db = b - (int)pal[2];
208 pal += 3;
209 distortion = dr*dr + dg*dg + db*db;
210 if (distortion < bestdistortion)
211 {
212 if (!distortion)
213 return i; // perfect match
214
215 bestdistortion = distortion;
216 bestcolor = i;
217 }
218 }
219
220 return bestcolor;
221 }
222
223
224 /*
225 ==============
226 Cmd_Colormap
227
228 $colormap filename
229
230 the brightes colormap is first in the table (FIXME: reverse this now?)
231
232 64 rows of 256 : lightmaps
233 256 rows of 256 : translucency table
234 ==============
235 */
Cmd_Colormap(void)236 void Cmd_Colormap (void)
237 {
238 int levels, brights;
239 int l, c;
240 float frac, red, green, blue;
241 float range;
242 byte *cropped, *lump_p;
243 char savename[1024];
244 char dest[1024];
245
246 colormap_issued = true;
247 if (!g_release)
248 memcpy (colormap_palette, lbmpalette, 768);
249
250 if (!TokenAvailable ())
251 { // just setting colormap_issued
252 return;
253 }
254
255 GetToken (false);
256 sprintf (savename, "%spics/%s.pcx", gamedir, token);
257
258 if (g_release)
259 {
260 sprintf (dest, "pics/%s.pcx", token);
261 ReleaseFile (dest);
262 return;
263 }
264
265 range = 2;
266 levels = 64;
267 brights = 1; // ignore 255 (transparent)
268
269 cropped = malloc((levels+256)*256);
270 lump_p = cropped;
271
272 // shaded levels
273 for (l=0;l<levels;l++)
274 {
275 frac = range - range*(float)l/(levels-1);
276 for (c=0 ; c<256-brights ; c++)
277 {
278 red = lbmpalette[c*3];
279 green = lbmpalette[c*3+1];
280 blue = lbmpalette[c*3+2];
281
282 red = (int)(red*frac+0.5);
283 green = (int)(green*frac+0.5);
284 blue = (int)(blue*frac+0.5);
285
286 //
287 // note: 254 instead of 255 because 255 is the transparent color, and we
288 // don't want anything remapping to that
289 // don't use color 0, because NT can't remap that (or 255)
290 //
291 *lump_p++ = BestColor(red,green,blue, 1, 254);
292 }
293
294 // fullbrights allways stay the same
295 for ( ; c<256 ; c++)
296 *lump_p++ = c;
297 }
298
299 // 66% transparancy table
300 for (l=0;l<255;l++)
301 {
302 for (c=0 ; c<255 ; c++)
303 {
304 red = lbmpalette[c*3]*0.33 + lbmpalette[l*3]*0.66;
305 green = lbmpalette[c*3+1]*0.33 + lbmpalette[l*3+1]*0.66;
306 blue = lbmpalette[c*3+2]*0.33 + lbmpalette[l*3+2]*0.66;
307
308 *lump_p++ = BestColor(red,green,blue, 1, 254);
309 }
310 *lump_p++ = 255;
311 }
312 for (c=0 ; c<256 ; c++)
313 *lump_p++ = 255;
314
315 // save off the new image
316 printf ("saving %s\n", savename);
317 CreatePath (savename);
318 WritePCXfile (savename, cropped, 256, levels+256, lbmpalette);
319
320 free (cropped);
321 }
322
323 /*
324 =============================================================================
325
326 MIPTEX GRABBING
327
328 =============================================================================
329 */
330
331 byte pixdata[256];
332
333 int d_red, d_green, d_blue;
334
335 byte palmap[32][32][32];
336 qboolean palmap_built;
337
338 /*
339 =============
340 FindColor
341 =============
342 */
FindColor(int r,int g,int b)343 int FindColor (int r, int g, int b)
344 {
345 int bestcolor;
346
347 if (r > 255)
348 r = 255;
349 if (r < 0)
350 r = 0;
351 if (g > 255)
352 g = 255;
353 if (g < 0)
354 g = 0;
355 if (b > 255)
356 b = 255;
357 if (b < 0)
358 b = 0;
359 #ifndef TABLECOLORS
360 bestcolor = BestColor (r, g, b, 0, 254);
361 #else
362 bestcolor = palmap[r>>3][g>>3][b>>3];
363 #endif
364
365 return bestcolor;
366 }
367
368
BuildPalmap(void)369 void BuildPalmap (void)
370 {
371 #ifdef TABLECOLORS
372 int r, g, b;
373 int bestcolor;
374
375 if (palmap_built)
376 return;
377 palmap_built = true;
378
379 for (r=4 ; r<256 ; r+=8)
380 {
381 for (g=4 ; g<256 ; g+=8)
382 {
383 for (b=4 ; b<256 ; b+=8)
384 {
385 bestcolor = BestColor (r, g, b, 1, 254);
386 palmap[r>>3][g>>3][b>>3] = bestcolor;
387 }
388 }
389 }
390 #endif
391
392 if (!colormap_issued)
393 Error ("You must issue a $colormap command first");
394
395 }
396
397 /*
398 =============
399 AveragePixels
400 =============
401 */
AveragePixels(int count)402 byte AveragePixels (int count)
403 {
404 int r,g,b;
405 int i;
406 int vis;
407 int pix;
408 int bestcolor;
409 byte *pal;
410 int fullbright;
411
412 vis = 0;
413 r = g = b = 0;
414 fullbright = 0;
415 for (i=0 ; i<count ; i++)
416 {
417 pix = pixdata[i];
418
419 r += lbmpalette[pix*3];
420 g += lbmpalette[pix*3+1];
421 b += lbmpalette[pix*3+2];
422 vis++;
423 }
424
425 r /= vis;
426 g /= vis;
427 b /= vis;
428
429 // error diffusion
430 r += d_red;
431 g += d_green;
432 b += d_blue;
433
434 //
435 // find the best color
436 //
437 bestcolor = FindColor (r, g, b);
438
439 // error diffusion
440 pal = colormap_palette + bestcolor*3;
441 d_red = r - (int)pal[0];
442 d_green = g - (int)pal[1];
443 d_blue = b - (int)pal[2];
444
445 return bestcolor;
446 }
447
448
449 typedef enum
450 {
451 pt_contents,
452 pt_flags,
453 pt_animvalue,
454 pt_flagvalue
455 } parmtype_t;
456
457 typedef struct
458 {
459 char *name;
460 int flags;
461 parmtype_t type;
462 } mipparm_t;
463
464 mipparm_t mipparms[] =
465 {
466 // utility content attributes
467 {"water", CONTENTS_WATER, pt_contents},
468 {"slime", CONTENTS_SLIME, pt_contents}, // mildly damaging
469 {"lava", CONTENTS_LAVA, pt_contents}, // very damaging
470 {"window", CONTENTS_WINDOW, pt_contents}, // solid, but doesn't eat internal textures
471 {"mist", CONTENTS_MIST, pt_contents}, // non-solid window
472 {"origin", CONTENTS_ORIGIN, pt_contents}, // center of rotating brushes
473 {"playerclip", CONTENTS_PLAYERCLIP, pt_contents},
474 {"monsterclip", CONTENTS_MONSTERCLIP, pt_contents},
475
476 // utility surface attributes
477 {"hint", SURF_HINT, pt_flags},
478 {"skip", SURF_SKIP, pt_flags},
479 {"light", SURF_LIGHT, pt_flagvalue}, // value is the light quantity
480
481 // texture chaining
482 {"anim", 0, pt_animvalue}, // value is the next animation
483
484 // server attributes
485 {"slick", SURF_SLICK, pt_flags},
486
487 // drawing attributes
488 {"sky", SURF_SKY, pt_flags},
489 {"warping", SURF_WARP, pt_flags}, // only valid with 64x64 textures
490 {"trans33", SURF_TRANS33, pt_flags}, // translucent should allso set fullbright
491 {"trans66", SURF_TRANS66, pt_flags},
492 {"flowing", SURF_FLOWING, pt_flags}, // flow direction towards angle 0
493 {"nodraw", SURF_NODRAW, pt_flags}, // for clip textures and trigger textures
494
495 {NULL, 0, pt_contents}
496 };
497
498
499
500 /*
501 ==============
502 Cmd_Mip
503
504 $mip filename x y width height <OPTIONS>
505 must be multiples of sixteen
506 SURF_WINDOW
507 ==============
508 */
Cmd_Mip(void)509 void Cmd_Mip (void)
510 {
511 int x,y,xl,yl,xh,yh,w,h;
512 byte *screen_p, *source;
513 int linedelta;
514 miptex_t *qtex;
515 int miplevel, mipstep;
516 int xx, yy, pix;
517 int count;
518 int flags, value, contents;
519 mipparm_t *mp;
520 char lumpname[64];
521 byte *lump_p;
522 char filename[1024];
523 char animname[64];
524
525 GetToken (false);
526 strcpy (lumpname, token);
527
528 GetToken (false);
529 xl = atoi (token);
530 GetToken (false);
531 yl = atoi (token);
532 GetToken (false);
533 w = atoi (token);
534 GetToken (false);
535 h = atoi (token);
536
537 if ( (w & 15) || (h & 15) )
538 Error ("line %i: miptex sizes must be multiples of 16", scriptline);
539
540 flags = 0;
541 contents = 0;
542 value = 0;
543
544 animname[0] = 0;
545
546 // get optional flags and values
547 while (TokenAvailable ())
548 {
549 GetToken (false);
550
551 for (mp=mipparms ; mp->name ; mp++)
552 {
553 if (!strcmp(mp->name, token))
554 {
555 switch (mp->type)
556 {
557 case pt_animvalue:
558 GetToken (false); // specify the next animation frame
559 strcpy (animname, token);
560 break;
561 case pt_flags:
562 flags |= mp->flags;
563 break;
564 case pt_contents:
565 contents |= mp->flags;
566 break;
567 case pt_flagvalue:
568 flags |= mp->flags;
569 GetToken (false); // specify the light value
570 value = atoi(token);
571 break;
572 }
573 break;
574 }
575 }
576 if (!mp->name)
577 Error ("line %i: unknown parm %s", scriptline, token);
578 }
579
580 sprintf (filename, "%stextures/%s/%s.wal", gamedir, mip_prefix, lumpname);
581 if (g_release)
582 return; // textures are only released by $maps
583
584 xh = xl+w;
585 yh = yl+h;
586
587 qtex = malloc (sizeof(miptex_t) + w*h*2);
588 memset (qtex, 0, sizeof(miptex_t));
589
590 qtex->width = LittleLong(w);
591 qtex->height = LittleLong(h);
592 qtex->flags = LittleLong(flags);
593 qtex->contents = LittleLong(contents);
594 qtex->value = LittleLong(value);
595 sprintf (qtex->name, "%s/%s", mip_prefix, lumpname);
596 if (animname[0])
597 sprintf (qtex->animname, "%s/%s", mip_prefix, animname);
598
599 lump_p = (byte *)(&qtex->value+1);
600
601 screen_p = byteimage + yl*byteimagewidth + xl;
602 linedelta = byteimagewidth - w;
603
604 source = lump_p;
605 qtex->offsets[0] = LittleLong(lump_p - (byte *)qtex);
606
607 for (y=yl ; y<yh ; y++)
608 {
609 for (x=xl ; x<xh ; x++)
610 {
611 pix = *screen_p++;
612 if (pix == 255)
613 pix = 1; // should never happen
614 *lump_p++ = pix;
615 }
616 screen_p += linedelta;
617 }
618
619 //
620 // subsample for greater mip levels
621 //
622 d_red = d_green = d_blue = 0; // no distortion yet
623
624 for (miplevel = 1 ; miplevel<4 ; miplevel++)
625 {
626 qtex->offsets[miplevel] = LittleLong(lump_p - (byte *)qtex);
627
628 mipstep = 1<<miplevel;
629 for (y=0 ; y<h ; y+=mipstep)
630 {
631
632 for (x = 0 ; x<w ; x+= mipstep)
633 {
634 count = 0;
635 for (yy=0 ; yy<mipstep ; yy++)
636 for (xx=0 ; xx<mipstep ; xx++)
637 {
638 pixdata[count] = source[ (y+yy)*w + x + xx ];
639 count++;
640 }
641 *lump_p++ = AveragePixels (count);
642 }
643 }
644 }
645
646 //
647 // dword align the size
648 //
649 while ((int)lump_p&3)
650 *lump_p++ = 0;
651
652 //
653 // write it out
654 //
655 printf ("writing %s\n", filename);
656 SaveFile (filename, (byte *)qtex, lump_p - (byte *)qtex);
657
658 free (qtex);
659 }
660
661 /*
662 ===============
663 Cmd_Mippal
664 ===============
665 */
Cmd_Mippal(void)666 void Cmd_Mippal (void)
667 {
668 colormap_issued = true;
669 if (g_release)
670 return;
671
672 memcpy (colormap_palette, lbmpalette, 768);
673
674 BuildPalmap();
675 }
676
677
678 /*
679 ===============
680 Cmd_Mipdir
681 ===============
682 */
Cmd_Mipdir(void)683 void Cmd_Mipdir (void)
684 {
685 char filename[1024];
686
687 GetToken (false);
688 strcpy (mip_prefix, token);
689 // create the directory if needed
690 sprintf (filename, "%stextures", gamedir, mip_prefix);
691 Q_mkdir (filename);
692 sprintf (filename, "%stextures/%s", gamedir, mip_prefix);
693 Q_mkdir (filename);
694 }
695
696
697 /*
698 =============================================================================
699
700 ENVIRONMENT MAP GRABBING
701
702 Creates six pcx files from tga files without any palette edge seams
703 also copies the tga files for GL rendering.
704 =============================================================================
705 */
706
707 // 3dstudio environment map suffixes
708 char *suf[6] = {"rt", "ft", "lf", "bk", "up", "dn"};
709
710 /*
711 =================
712 Cmd_Environment
713 =================
714 */
Cmd_Environment(void)715 void Cmd_Environment (void)
716 {
717 char name[1024];
718 int i, x, y;
719 byte image[256*256];
720 byte *tga;
721
722 GetToken (false);
723
724 if (g_release)
725 {
726 for (i=0 ; i<6 ; i++)
727 {
728 sprintf (name, "env/%s%s.pcx", token, suf[i]);
729 ReleaseFile (name);
730 sprintf (name, "env/%s%s.tga", token, suf[i]);
731 ReleaseFile (name);
732 }
733 return;
734 }
735 // get the palette
736 BuildPalmap ();
737
738 sprintf (name, "%senv/", gamedir);
739 CreatePath (name);
740
741 // convert the images
742 for (i=0 ; i<6 ; i++)
743 {
744 sprintf (name, "%senv/%s%s.tga", gamedir, token, suf[i]);
745 printf ("loading %s...\n", name);
746 LoadTGA (name, &tga, NULL, NULL);
747
748 for (y=0 ; y<256 ; y++)
749 {
750 for (x=0 ; x<256 ; x++)
751 {
752 image[y*256+x] = FindColor (tga[(y*256+x)*4+0],tga[(y*256+x)*4+1],tga[(y*256+x)*4+2]);
753 }
754 }
755 free (tga);
756 sprintf (name, "%senv/%s%s.pcx", gamedir, token, suf[i]);
757 if (FileTime (name) != -1)
758 printf ("%s already exists, not overwriting.\n", name);
759 else
760 WritePCXfile (name, image, 256, 256, colormap_palette);
761 }
762 }
763
764