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>
11 // ===============================================
12 // Generates radar map images for use in the HUD
13 // ===============================================
15 float FullTraceFraction(vector a, vector mi, vector ma, vector b)
28 while (vdist(c - b, >, 1))
32 tracebox(c, mi, ma, b, MOVE_WORLDONLY, NULL);
35 if (!trace_startsolid)
37 black += vlen(trace_endpos - c);
41 n += tracebox_inverted(c, mi, ma, b, MOVE_WORLDONLY, NULL, false, NULL);
43 white += vlen(trace_endpos - c);
47 if (n > 200) LOG_TRACE("HOLY SHIT! FullTraceFraction: ", ftos(n), " total traces, ", ftos(m), " iterations");
49 return white / (black + white);
51 float RadarMapAtPoint_Trace(float e, float f, float w, float h, float zmin, float zsize, float q)
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);
60 return FullTraceFraction(a, mi, ma, b);
62 float RadarMapAtPoint_LineBlock(float e, float f, float w, float h, float zmin, float zsize, float q)
69 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
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;
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;
82 for (i = 0; i < q; ++i)
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;
97 float RadarMapAtPoint_Block(float e, float f, float w, float h, float zmin, float zsize, float q)
104 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
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;
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;
117 for (i = 0; i < q; ++i)
119 tracebox(o + dz * i, mi, ma, o + dz * i, MOVE_WORLDONLY, NULL);
120 if (trace_startsolid) ++r;
124 float RadarMapAtPoint_Sample(float e, float f, float w, float h, float zmin, float zsize, float q)
128 q *= 4; // choose q so it matches the regular algorithm in speed
131 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
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;
141 for (i = 0; i < q; ++i)
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;
153 void sharpen_set(int b, float v)
155 sharpen_buffer[b + 2 * RADAR_WIDTH_MAX] = v;
157 float sharpen_getpixel(int b, int c)
160 if (b >= RADAR_WIDTH_MAX) return 0;
163 return sharpen_buffer[b + c * RADAR_WIDTH_MAX];
165 float sharpen_get(float b, float a)
167 float sum = sharpen_getpixel(b, 1);
168 if (a == 0) return sum;
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);
180 void sharpen_shift(int w)
182 for (int i = 0; i < w; ++i)
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;
189 void sharpen_init(int w)
191 for (int i = 0; i < w; ++i)
193 sharpen_buffer[i] = 0;
194 sharpen_buffer[i + RADAR_WIDTH_MAX] = 0;
195 sharpen_buffer[i + 2 * RADAR_WIDTH_MAX] = 0;
200 if (radarmapper.count & 4)
204 else if (radarmapper.count & 2)
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"));
212 void RadarMap_Think(entity this)
216 // size: pixel width/height
217 // maxs: cell width/height
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))
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);
246 LOG_INFO(this.netname, " already exists, aborting (you may want to specify --force)");
251 this.cnt = fopen(this.netname, FILE_WRITE);
254 LOG_INFO("Error writing ", this.netname);
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)
266 si = substring(doublehex, i * 2, 2);
267 fputs(this.cnt, strcat("\"", si, " c #", si, si, si, "\",\n"));
270 this.nextthink = time;
271 sharpen_init(this.size.x);
273 else if (this.frame <= this.size.y)
275 // fill the sharpen buffer with this line
276 sharpen_shift(this.size.x);
283 for (x = 0; x < this.size.x; ++x)
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);
290 for (x = 0; x < this.size.x; ++x)
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);
297 for (x = 0; x < this.size.x; ++x)
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);
304 for (x = 0; x < this.size.x; ++x)
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);
312 // do we have enough lines?
315 // write a pixel line
316 fputs(this.cnt, "\"");
317 for (x = 0; x < this.size.x; ++x)
319 l = sharpen_get(x, this.ltime);
320 fputs(this.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
322 if (this.frame == this.size.y)
324 fputs(this.cnt, "\"\n");
328 fputs(this.cnt, "\",\n");
329 LOG_INFO(ftos(this.size.y - this.frame), " lines left");
333 // is this the last line? then write back the missing line
334 if (this.frame == this.size.y)
336 sharpen_shift(this.size.x);
337 // write a pixel line
338 fputs(this.cnt, "\"");
339 for (x = 0; x < this.size.x; ++x)
341 l = sharpen_get(x, this.ltime);
342 fputs(this.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
344 if (this.frame == this.size.y)
346 fputs(this.cnt, "\"\n");
350 fputs(this.cnt, "\",\n");
351 LOG_INFO(ftos(this.size.y - this.frame), " lines left");
356 this.nextthink = time;
361 fputs(this.cnt, "};\n");
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.");
368 bool RadarMap_Make(int argc)
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)
385 { radarmapper.count |= 1;
389 { radarmapper.count |= 2;
393 { radarmapper.count |= 4;
397 { radarmapper.count &= ~24;
401 { radarmapper.count &= ~24;
402 radarmapper.count |= 8;
406 { radarmapper.count &= ~24;
407 radarmapper.count |= 16;
411 { radarmapper.count |= 24;
416 radarmapper.count = stof(argv(i));
418 } // for the recursive call
421 radarmapper.ltime = stof(argv(i));
423 } // for the recursive call
424 case "--res": // minor alias
427 radarmapper.size_x = stof(argv(i));
429 radarmapper.size_y = stof(argv(i));
432 case "--qual": // minor alias
435 radarmapper.size_z = stof(argv(i));
447 if (radarmapper) // after doing the arguments, see if we successfully went forward.
449 LOG_INFO("Radarmap entity spawned.");
450 return true; // if so, don't print usage.