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