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