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