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