2 #include <common/command/command.qh>
5 #include "../g_world.qh"
6 #include "../g_subs.qh"
8 #include <common/util.qh>
10 #include <lib/csqcmodel/sv_model.qh>
12 // ===============================================
13 // Generates radar map images for use in the HUD
14 // ===============================================
16 float FullTraceFraction(vector a, vector mi, vector ma, vector b)
29 while (vlen(c - b) > 1)
33 tracebox(c, mi, ma, b, MOVE_WORLDONLY, world);
36 if (!trace_startsolid)
38 black += vlen(trace_endpos - c);
42 n += tracebox_inverted(c, mi, ma, b, MOVE_WORLDONLY, world, false, world);
44 white += vlen(trace_endpos - c);
48 if (n > 200) LOG_TRACE("HOLY SHIT! FullTraceFraction: ", ftos(n), " total traces, ", ftos(m), " iterations\n");
50 return white / (black + white);
52 float RadarMapAtPoint_Trace(float x, float y, float w, float h, float zmin, float zsize, float q)
57 ma = '1 0 0' * w + '0 1 0' * h;
58 a = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
59 b = '1 0 0' * x + '0 1 0' * y + '0 0 1' * (zsize + zmin);
61 return FullTraceFraction(a, mi, ma, b);
63 float RadarMapAtPoint_LineBlock(float x, float y, float w, float h, float zmin, float zsize, float q)
70 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
73 dz = (zsize / q) * '0 0 1';
74 ma = '1 0 0' * w + '0 1 0' * h + dz;
75 o = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
77 if (x < world.absmin.x - w) return 0;
78 if (y < world.absmin.y - h) return 0;
79 if (x > world.absmax.x) return 0;
80 if (y > world.absmax.y) return 0;
83 for (i = 0; i < q; ++i)
86 v1 = v2 = o + dz * i + mi;
87 v1_x += random() * (ma.x - mi.x);
88 v1_y += random() * (ma.y - mi.y);
89 v1_z += random() * (ma.z - mi.z);
90 v2_x += random() * (ma.x - mi.x);
91 v2_y += random() * (ma.y - mi.y);
92 v2_z += random() * (ma.z - mi.z);
93 traceline(v1, v2, MOVE_WORLDONLY, world);
94 if (trace_startsolid || trace_fraction < 1) ++r;
98 float RadarMapAtPoint_Block(float x, float y, float w, float h, float zmin, float zsize, float q)
105 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
108 dz = (zsize / q) * '0 0 1';
109 ma = '1 0 0' * w + '0 1 0' * h + dz;
110 o = '1 0 0' * x + '0 1 0' * y + '0 0 1' * zmin;
112 if (x < world.absmin.x - w) return 0;
113 if (y < world.absmin.y - h) return 0;
114 if (x > world.absmax.x) return 0;
115 if (y > world.absmax.y) return 0;
118 for (i = 0; i < q; ++i)
120 tracebox(o + dz * i, mi, ma, o + dz * i, MOVE_WORLDONLY, world);
121 if (trace_startsolid) ++r;
125 float RadarMapAtPoint_Sample(float x, float y, float w, float h, float zmin, float zsize, float q)
129 q *= 4; // choose q so it matches the regular algorithm in speed
132 // 256q-1 is the ideal sample count to map equal amount of sample values to one pixel value
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;
142 for (i = 0; i < q; ++i)
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) ++c;
154 void sharpen_set(int x, float v)
156 sharpen_buffer[x + 2 * RADAR_WIDTH_MAX] = v;
158 float sharpen_getpixel(int x, int y)
161 if (x >= RADAR_WIDTH_MAX) return 0;
164 return sharpen_buffer[x + y * RADAR_WIDTH_MAX];
166 float sharpen_get(float x, float a)
168 float sum = sharpen_getpixel(x, 1);
169 if (a == 0) return sum;
171 sum -= sharpen_getpixel(x - 1, 0);
172 sum -= sharpen_getpixel(x - 1, 1);
173 sum -= sharpen_getpixel(x - 1, 2);
174 sum -= sharpen_getpixel(x + 1, 0);
175 sum -= sharpen_getpixel(x + 1, 1);
176 sum -= sharpen_getpixel(x + 1, 2);
177 sum -= sharpen_getpixel(x, 0);
178 sum -= sharpen_getpixel(x, 2);
179 return bound(0, sum * a, 1);
181 void sharpen_shift(int w)
183 for (int i = 0; i < w; ++i)
185 sharpen_buffer[i] = sharpen_buffer[i + RADAR_WIDTH_MAX];
186 sharpen_buffer[i + RADAR_WIDTH_MAX] = sharpen_buffer[i + 2 * RADAR_WIDTH_MAX];
187 sharpen_buffer[i + 2 * RADAR_WIDTH_MAX] = 0;
190 void sharpen_init(int w)
192 for (int i = 0; i < w; ++i)
194 sharpen_buffer[i] = 0;
195 sharpen_buffer[i + RADAR_WIDTH_MAX] = 0;
196 sharpen_buffer[i + 2 * RADAR_WIDTH_MAX] = 0;
201 if (radarmapper.count & 4)
205 else if (radarmapper.count & 2)
207 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"));
213 void RadarMap_Think()
218 // size: pixel width/height
219 // maxs: cell width/height
228 get_mi_min_max_texcoords(1);
229 self.mins = mi_picmin;
230 self.maxs_x = (mi_picmax.x - mi_picmin.x) / self.size.x;
231 self.maxs_y = (mi_picmax.y - mi_picmin.y) / self.size.y;
232 self.maxs_z = mi_max.z - mi_min.z;
233 LOG_INFO("Picture mins/maxs: ", ftos(self.maxs.x), " and ", ftos(self.maxs.y), " should match\n");
234 self.netname = strzone(strcat("gfx/", mi_shortname, "_radar.xpm"));
235 if (!(self.count & 1))
237 self.cnt = fopen(self.netname, FILE_READ);
238 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.tga"), FILE_READ);
239 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.png"), FILE_READ);
240 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_radar.jpg"), FILE_READ);
241 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.tga"), FILE_READ);
242 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.png"), FILE_READ);
243 if (self.cnt < 0) self.cnt = fopen(strcat("gfx/", mi_shortname, "_mini.jpg"), FILE_READ);
248 LOG_INFO(self.netname, " already exists, aborting (you may want to specify --force)\n");
253 self.cnt = fopen(self.netname, FILE_WRITE);
256 LOG_INFO("Error writing ", self.netname, "\n");
261 LOG_INFO("Writing to ", self.netname, "...\n");
262 fputs(self.cnt, "/* XPM */\n");
263 fputs(self.cnt, "static char *RadarMap[] = {\n");
264 fputs(self.cnt, "/* columns rows colors chars-per-pixel */\n");
265 fputs(self.cnt, strcat("\"", ftos(self.size.x), " ", ftos(self.size.y), " 256 2\",\n"));
266 for (i = 0; i < 256; ++i)
268 si = substring(doublehex, i * 2, 2);
269 fputs(self.cnt, strcat("\"", si, " c #", si, si, si, "\",\n"));
272 self.nextthink = time;
273 sharpen_init(self.size.x);
275 else if (self.frame <= self.size.y)
277 // fill the sharpen buffer with this line
278 sharpen_shift(self.size.x);
285 for (x = 0; x < self.size.x; ++x)
287 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);
292 for (x = 0; x < self.size.x; ++x)
294 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);
299 for (x = 0; x < self.size.x; ++x)
301 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);
306 for (x = 0; x < self.size.x; ++x)
308 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);
314 // do we have enough lines?
317 // write a pixel line
318 fputs(self.cnt, "\"");
319 for (x = 0; x < self.size.x; ++x)
321 l = sharpen_get(x, self.ltime);
322 fputs(self.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
324 if (self.frame == self.size.y)
326 fputs(self.cnt, "\"\n");
330 fputs(self.cnt, "\",\n");
331 LOG_INFO(ftos(self.size.y - self.frame), " lines left\n");
335 // is this the last line? then write back the missing line
336 if (self.frame == self.size.y)
338 sharpen_shift(self.size.x);
339 // write a pixel line
340 fputs(self.cnt, "\"");
341 for (x = 0; x < self.size.x; ++x)
343 l = sharpen_get(x, self.ltime);
344 fputs(self.cnt, substring(doublehex, 2 * floor(l * 256.0), 2));
346 if (self.frame == self.size.y)
348 fputs(self.cnt, "\"\n");
352 fputs(self.cnt, "\",\n");
353 LOG_INFO(ftos(self.size.y - self.frame), " lines left\n");
358 self.nextthink = time;
363 fputs(self.cnt, "};\n");
365 LOG_INFO("Finished. Please edit data/", self.netname, " with an image editing application and place it in the TGA format in the gfx folder.\n");
370 float RadarMap_Make(float argc)
376 radarmapper = new(radarmapper);
377 radarmapper.think = RadarMap_Think;
378 radarmapper.nextthink = time;
379 radarmapper.count = 8; // default to the --trace method, as it is faster now
380 radarmapper.ltime = 1;
381 radarmapper.size = '512 512 1';
382 for (i = 1; i < argc; ++i)
387 { radarmapper.count |= 1;
391 { radarmapper.count |= 2;
395 { radarmapper.count |= 4;
399 { radarmapper.count &= ~24;
403 { radarmapper.count &= ~24;
404 radarmapper.count |= 8;
408 { radarmapper.count &= ~24;
409 radarmapper.count |= 16;
413 { radarmapper.count |= 24;
418 radarmapper.count = stof(argv(i));
420 } // for the recursive call
423 radarmapper.ltime = stof(argv(i));
425 } // for the recursive call
426 case "--res": // minor alias
429 radarmapper.size_x = stof(argv(i));
431 radarmapper.size_y = stof(argv(i));
434 case "--qual": // minor alias
437 radarmapper.size_z = stof(argv(i));
449 if (radarmapper) // after doing the arguments, see if we successfully went forward.
451 LOG_INFO("Radarmap entity spawned.\n");
452 return true; // if so, don't print usage.