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