Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
radarmap.qc
Go to the documentation of this file.
1#include "radarmap.qh"
2#ifdef RADARMAP
3
6#include <common/util.qh>
9#include <server/world.qh>
10
11// ===============================================
12// Generates radar map images for use in the HUD
13// ===============================================
14
15float 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
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}
51float 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}
62float 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}
97float 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}
124float 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}
153void sharpen_set(int b, float v)
154{
155 sharpen_buffer[b + 2 * RADAR_WIDTH_MAX] = v;
156}
157float 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}
165float 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}
180void 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}
189void 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}
198void 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}
212void 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
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
368bool 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
float frame
primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4)
Definition anim.qh:6
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
string netname
Definition powerups.qc:20
float cnt
Definition powerups.qc:24
float count
Definition powerups.qc:22
void get_mi_min_max_texcoords(float mode)
Definition util.qc:768
float tracebox_inverted(vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
Definition util.qc:23
string mi_shortname
Definition util.qh:126
vector mi_picmin
Definition util.qh:131
vector mi_min
Definition util.qh:127
vector mi_max
Definition util.qh:128
vector mi_picmax
Definition util.qh:132
vector mins
const float FILE_READ
const float FILE_WRITE
float time
vector trace_endpos
float trace_startsolid
vector maxs
vector size
float nextthink
float MOVE_WORLDONLY
float trace_fraction
void GotoNextMap(float reinit)
#define LOG_INFO(...)
Definition log.qh:65
#define LOG_TRACE(...)
Definition log.qh:76
void localcmd(string command,...)
void fclose(float fhandle)
float stof(string val,...)
void fputs(float fhandle, string s)
float bound(float min, float value, float max)
string substring(string s, float start, float length)
float fopen(string filename, float mode)
float random(void)
float vlen(vector v)
string ftos(float f)
float floor(float f)
string strzone(string s)
string argv(float n)
float ltime
Definition net.qc:10
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define NULL
Definition post.qh:14
#define world
Definition post.qh:15
bool RadarMap_Make(int argc)
Definition radarmap.qh:3
#define setthink(e, f)
vector
Definition self.qh:92
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8