Xonotic
radarmap.qc
Go to the documentation of this file.
1 #include "radarmap.qh"
2 #ifdef RADARMAP
3 
4 #include <common/command/_mod.qh>
6 #include <common/util.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
entity world
Definition: csprogsdefs.qc:15
void GotoNextMap(float reinit)
entity() spawn
vector maxs
Definition: csprogsdefs.qc:113
string netname
Definition: powerups.qc:20
const float FILE_READ
Definition: csprogsdefs.qc:231
vector size
Definition: csprogsdefs.qc:114
float ltime
Definition: progsdefs.qc:107
vector mins
Definition: csprogsdefs.qc:113
float cnt
Definition: powerups.qc:24
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
#define NULL
Definition: post.qh:17
#define LOG_INFO(...)
Definition: log.qh:70
vector trace_endpos
Definition: csprogsdefs.qc:37
float nextthink
Definition: csprogsdefs.qc:121
vector(float skel, float bonenum) _skel_get_boneabs_hidden
const float FILE_WRITE
Definition: csprogsdefs.qc:233
vector v
Definition: ent_cs.qc:116
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
float count
Definition: powerups.qc:22
#define LOG_TRACE(...)
Definition: log.qh:81
float frame
primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4)
Definition: anim.qh:6
#define setthink(e, f)
bool RadarMap_Make(int argc)
Definition: radarmap.qh:3
float trace_startsolid
Definition: csprogsdefs.qc:35
float MOVE_WORLDONLY
float time
Definition: csprogsdefs.qc:16
float trace_fraction
Definition: csprogsdefs.qc:36