Declare more ints as ints
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / radarmap.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../../dpdefs/progsdefs.qh"
5     #include "../../dpdefs/dpextensions.qh"
6     #include "../../common/util.qh"
7     #include "../defs.qh"
8     #include "radarmap.qh"
9     #include "../../csqcmodellib/sv_model.qh"
10 #endif
11
12 // ===============================================
13 //      Generates radar map images for use in the HUD
14 // ===============================================
15
16 float FullTraceFraction(vector a, vector mi, vector ma, vector b)
17 {
18         vector c;
19         float white, black;
20
21         white = 0.001;
22         black = 0.001;
23
24         c = a;
25
26         float n, m;
27         n = m = 0;
28
29         while(vlen(c - b) > 1)
30         {
31                 ++m;
32
33                 tracebox(c, mi, ma, b, MOVE_WORLDONLY, world);
34                 ++n;
35
36                 if(!trace_startsolid)
37                 {
38                         black += vlen(trace_endpos - c);
39                         c = trace_endpos;
40                 }
41
42                 n += tracebox_inverted(c, mi, ma, b, MOVE_WORLDONLY, world, false, world);
43
44                 white += vlen(trace_endpos - c);
45                 c = trace_endpos;
46         }
47
48         if(n > 200)
49                 dprint("HOLY SHIT! FullTraceFraction: ", ftos(n), " total traces, ", ftos(m), " iterations\n");
50
51         return white / (black + white);
52 }
53 float RadarMapAtPoint_Trace(float x, float y, float w, float h, float zmin, float zsize, float q)
54 {
55         vector a, b, mi, ma;
56
57         mi = '0 0 0';
58         ma = '1 0 0' * w + '0 1 0' * h;
59         a = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
60         b = '1 0 0' * x + '0 1 0' * y + '0 0 1' * (zsize + zmin);
61
62         return FullTraceFraction(a, mi, ma, b);
63 }
64 float RadarMapAtPoint_LineBlock(float x, float y, float w, float h, float zmin, float zsize, float q)
65 {
66         vector o, mi, ma;
67         float i, r;
68         vector dz;
69
70         q = 256 * q - 1;
71         // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
72
73         mi = '0 0 0';
74         dz = (zsize / q) * '0 0 1';
75         ma = '1 0 0' * w + '0 1 0' * h + dz;
76         o = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
77
78         if(x < world.absmin.x - w)
79                 return 0;
80         if(y < world.absmin.y - h)
81                 return 0;
82         if(x > world.absmax.x)
83                 return 0;
84         if(y > world.absmax.y)
85                 return 0;
86
87         r = 0;
88         for(i = 0; i < q; ++i)
89         {
90                 vector v1, v2;
91                 v1 = v2 = o + dz * i + mi;
92                 v1_x += random() * (ma.x - mi.x);
93                 v1_y += random() * (ma.y - mi.y);
94                 v1_z += random() * (ma.z - mi.z);
95                 v2_x += random() * (ma.x - mi.x);
96                 v2_y += random() * (ma.y - mi.y);
97                 v2_z += random() * (ma.z - mi.z);
98                 traceline(v1, v2, MOVE_WORLDONLY, world);
99                 if(trace_startsolid || trace_fraction < 1)
100                         ++r;
101         }
102         return r / q;
103 }
104 float RadarMapAtPoint_Block(float x, float y, float w, float h, float zmin, float zsize, float q)
105 {
106         vector o, mi, ma;
107         float i, r;
108         vector dz;
109
110         q = 256 * q - 1;
111         // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
112
113         mi = '0 0 0';
114         dz = (zsize / q) * '0 0 1';
115         ma = '1 0 0' * w + '0 1 0' * h + dz;
116         o = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
117
118         if(x < world.absmin.x - w)
119                 return 0;
120         if(y < world.absmin.y - h)
121                 return 0;
122         if(x > world.absmax.x)
123                 return 0;
124         if(y > world.absmax.y)
125                 return 0;
126
127         r = 0;
128         for(i = 0; i < q; ++i)
129         {
130                 tracebox(o + dz * i, mi, ma, o + dz * i, MOVE_WORLDONLY, world);
131                 if(trace_startsolid)
132                         ++r;
133         }
134         return r / q;
135 }
136 float RadarMapAtPoint_Sample(float x, float y, float w, float h, float zmin, float zsize, float q)
137 {
138         vector a, b, mi, ma;
139
140         q *= 4; // choose q so it matches the regular algorithm in speed
141
142         q = 256 * q - 1;
143         // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
144
145         mi = '0 0 0';
146         ma = '1 0 0' * w + '0 1 0' * h;
147         a = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
148         b = '1 0 0' * w + '0 1 0' * h + '0 0 1' * zsize;
149
150         float c, i;
151         c = 0;
152
153         for(i = 0; i < q; ++i)
154         {
155                 vector v;
156                 v.x = a.x + random() * b.x;
157                 v.y = a.y + random() * b.y;
158                 v.z = a.z + random() * b.z;
159                 traceline(v, v, MOVE_WORLDONLY, world);
160                 if(trace_startsolid)
161                         ++c;
162         }
163
164         return c / q;
165 }
166 void sharpen_set(int x, float v)
167 {
168         sharpen_buffer[x + 2 * RADAR_WIDTH_MAX] = v;
169 }
170 float sharpen_getpixel(int x, int y)
171 {
172         if(x < 0)
173                 return 0;
174         if(x >= RADAR_WIDTH_MAX)
175                 return 0;
176         if(y < 0)
177                 return 0;
178         if(y > 2)
179                 return 0;
180         return sharpen_buffer[x + y * RADAR_WIDTH_MAX];
181 }
182 float sharpen_get(float x, float a)
183 {
184         float sum = sharpen_getpixel(x, 1);
185         if(a == 0)
186                 return sum;
187         sum *= (8 + 1/a);
188         sum -= sharpen_getpixel(x - 1, 0);
189         sum -= sharpen_getpixel(x - 1, 1);
190         sum -= sharpen_getpixel(x - 1, 2);
191         sum -= sharpen_getpixel(x + 1, 0);
192         sum -= sharpen_getpixel(x + 1, 1);
193         sum -= sharpen_getpixel(x + 1, 2);
194         sum -= sharpen_getpixel(x, 0);
195         sum -= sharpen_getpixel(x, 2);
196         return bound(0, sum * a, 1);
197 }
198 void sharpen_shift(int w)
199 {
200         for(int i = 0; i < w; ++i)
201         {
202                 sharpen_buffer[i] = sharpen_buffer[i + RADAR_WIDTH_MAX];
203                 sharpen_buffer[i + RADAR_WIDTH_MAX] = sharpen_buffer[i + 2 * RADAR_WIDTH_MAX];
204                 sharpen_buffer[i + 2 * RADAR_WIDTH_MAX] = 0;
205         }
206 }
207 void sharpen_init(int w)
208 {
209         for(int i = 0; i < w; ++i)
210         {
211                 sharpen_buffer[i] = 0;
212                 sharpen_buffer[i + RADAR_WIDTH_MAX] = 0;
213                 sharpen_buffer[i + 2 * RADAR_WIDTH_MAX] = 0;
214         }
215 }
216 void RadarMap_Next()
217 {
218         if(radarmapper.count & 4)
219         {
220                 localcmd("quit\n");
221         }
222         else if(radarmapper.count & 2)
223         {
224                 localcmd(strcat("defer 1 \"sv_cmd radarmap --flags ", ftos(radarmapper.count), strcat(" --res ", ftos(radarmapper.size.x), " ", ftos(radarmapper.size.y), " --sharpen ", ftos(radarmapper.ltime), " --qual ", ftos(radarmapper.size.z)), "\"\n"));
225                 GotoNextMap(0);
226         }
227         remove(radarmapper);
228         radarmapper = world;
229 }
230 void RadarMap_Think()
231 {
232         // rough map entity
233         //   cnt: current line
234         //   size: pixel width/height
235         //   maxs: cell width/height
236         //   frame: counter
237
238         float i, x, l;
239         string si;
240
241         if(self.frame == 0)
242         {
243                 // initialize
244                 get_mi_min_max_texcoords(1);
245                 self.mins = mi_picmin;
246                 self.maxs_x = (mi_picmax.x - mi_picmin.x) / self.size.x;
247                 self.maxs_y = (mi_picmax.y - mi_picmin.y) / self.size.y;
248                 self.maxs_z = mi_max.z - mi_min.z;
249                 print("Picture mins/maxs: ", ftos(self.maxs.x), " and ", ftos(self.maxs.y), " should match\n");
250                 self.netname = strzone(strcat("gfx/", mi_shortname, "_radar.xpm"));
251                 if(!(self.count & 1))
252                 {
253                         self.cnt = fopen(self.netname, FILE_READ);
254                         if(self.cnt < 0)
255                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.tga"), FILE_READ);
256                         if(self.cnt < 0)
257                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.png"), FILE_READ);
258                         if(self.cnt < 0)
259                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.jpg"), FILE_READ);
260                         if(self.cnt < 0)
261                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.tga"), FILE_READ);
262                         if(self.cnt < 0)
263                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.png"), FILE_READ);
264                         if(self.cnt < 0)
265                                 self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.jpg"), FILE_READ);
266                         if(self.cnt >= 0)
267                         {
268                                 fclose(self.cnt);
269
270                                 print(self.netname, " already exists, aborting (you may want to specify --force)\n");
271                                 RadarMap_Next();
272                                 return;
273                         }
274                 }
275                 self.cnt = fopen(self.netname, FILE_WRITE);
276                 if(self.cnt < 0)
277                 {
278                         print("Error writing ", self.netname, "\n");
279                         remove(self);
280                         radarmapper = world;
281                         return;
282                 }
283                 print("Writing to ", self.netname, "...\n");
284                 fputs(self.cnt, "/* XPM */\n");
285                 fputs(self.cnt, "static char *RadarMap[] = {\n");
286                 fputs(self.cnt, "/* columns rows colors chars-per-pixel */\n");
287                 fputs(self.cnt, strcat("\"", ftos(self.size.x), " ", ftos(self.size.y), " 256 2\",\n"));
288                 for(i = 0; i < 256; ++i)
289                 {
290                         si = substring(doublehex, i*2, 2);
291                         fputs(self.cnt, strcat("\"", si, " c #", si, si, si, "\",\n"));
292                 }
293                 self.frame += 1;
294                 self.nextthink = time;
295                 sharpen_init(self.size.x);
296         }
297         else if(self.frame <= self.size.y)
298         {
299                 // fill the sharpen buffer with this line
300                 sharpen_shift(self.size.x);
301                 i = self.count & 24;
302
303                 switch(i)
304                 {
305                         case 0:
306                         default:
307                                 for(x = 0; x < self.size.x; ++x)
308                                 {
309                                         l = RadarMapAtPoint_Block(self.mins.x + x * self.maxs.x, self.mins.y + (self.size.y - self.frame) * self.maxs.y, self.maxs.x, self.maxs.y, self.mins.z, self.maxs.z, self.size.z);
310                                         sharpen_set(x, l);
311                                 }
312                                 break;
313                         case 8:
314                                 for(x = 0; x < self.size.x; ++x)
315                                 {
316                                         l = RadarMapAtPoint_Trace(self.mins.x + x * self.maxs.x, self.mins.y + (self.size.y - self.frame) * self.maxs.y, self.maxs.x, self.maxs.y, self.mins.z, self.maxs.z, self.size.z);
317                                         sharpen_set(x, l);
318                                 }
319                                 break;
320                         case 16:
321                                 for(x = 0; x < self.size.x; ++x)
322                                 {
323                                         l = RadarMapAtPoint_Sample(self.mins.x + x * self.maxs.x, self.mins.y + (self.size.y - self.frame) * self.maxs.y, self.maxs.x, self.maxs.y, self.mins.z, self.maxs.z, self.size.z);
324                                         sharpen_set(x, l);
325                                 }
326                                 break;
327                         case 24:
328                                 for(x = 0; x < self.size.x; ++x)
329                                 {
330                                         l = RadarMapAtPoint_LineBlock(self.mins.x + x * self.maxs.x, self.mins.y + (self.size.y - self.frame) * self.maxs.y, self.maxs.x, self.maxs.y, self.mins.z, self.maxs.z, self.size.z);
331                                         sharpen_set(x, l);
332                                 }
333                                 break;
334                 }
335
336                 // do we have enough lines?
337                 if(self.frame >= 2)
338                 {
339                         // write a pixel line
340                         fputs(self.cnt, "\"");
341                         for(x = 0; x < self.size.x; ++x)
342                         {
343                                 l = sharpen_get(x, self.ltime);
344                                 fputs(self.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
345                         }
346                         if(self.frame == self.size.y)
347                                 fputs(self.cnt, "\"\n");
348                         else
349                         {
350                                 fputs(self.cnt, "\",\n");
351                                 print(ftos(self.size.y - self.frame), " lines left\n");
352                         }
353                 }
354
355                 // is this the last line? then write back the missing line
356                 if(self.frame == self.size.y)
357                 {
358                         sharpen_shift(self.size.x);
359                         // write a pixel line
360                         fputs(self.cnt, "\"");
361                         for(x = 0; x < self.size.x; ++x)
362                         {
363                                 l = sharpen_get(x, self.ltime);
364                                 fputs(self.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
365                         }
366                         if(self.frame == self.size.y)
367                                 fputs(self.cnt, "\"\n");
368                         else
369                         {
370                                 fputs(self.cnt, "\",\n");
371                                 print(ftos(self.size.y - self.frame), " lines left\n");
372                         }
373                 }
374
375                 self.frame += 1;
376                 self.nextthink = time;
377         }
378         else
379         {
380                 // close the file
381                 fputs(self.cnt, "};\n");
382                 fclose(self.cnt);
383                 print("Finished. Please edit data/", self.netname, " with an image editing application and place it in the TGA format in the gfx folder.\n");
384                 RadarMap_Next();
385         }
386 }
387
388 float RadarMap_Make(float argc)
389 {
390         float i;
391
392         if(!radarmapper)
393         {
394                 radarmapper = spawn();
395                 radarmapper.classname = "radarmapper";
396                 radarmapper.think = RadarMap_Think;
397                 radarmapper.nextthink = time;
398                 radarmapper.count = 8; // default to the --trace method, as it is faster now
399                 radarmapper.ltime = 1;
400                 radarmapper.size = '512 512 1';
401                 for(i = 1; i < argc; ++i)
402                 {
403                         switch(argv(i))
404                         {
405                                 case "--force": { radarmapper.count |= 1; break; }
406                                 case "--loop": { radarmapper.count |= 2; break; }
407                                 case "--quit": { radarmapper.count |= 4; break; }
408                                 case "--block": { radarmapper.count &= ~24; break; }
409                                 case "--trace": { radarmapper.count &= ~24; radarmapper.count |= 8; break; }
410                                 case "--sample": { radarmapper.count &= ~24; radarmapper.count |= 16; break; }
411                                 case "--lineblock": { radarmapper.count |= 24; break; }
412                                 case "--flags": { ++i; radarmapper.count = stof(argv(i)); break; } // for the recursive call
413                                 case "--sharpen": { ++i; radarmapper.ltime = stof(argv(i)); break; } // for the recursive call
414                                 case "--res": // minor alias
415                                 case "--resolution": { ++i; radarmapper.size_x = stof(argv(i)); ++i; radarmapper.size_y = stof(argv(i)); break; }
416                                 case "--qual": // minor alias
417                                 case "--quality": { ++i; radarmapper.size_z = stof(argv(i)); break; }
418
419                                 default:
420                                         i = argc;
421                                         remove(radarmapper);
422                                         radarmapper = world;
423                                         break;
424                         }
425                 }
426
427                 if(radarmapper) // after doing the arguments, see if we successfully went forward.
428                 {
429                         print("Radarmap entity spawned.\n");
430                         return true; // if so, don't print usage.
431                 }
432         }
433
434         return false;
435 }