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