2020-03-29 11:09:10 +00:00
|
|
|
#ifndef _ROGUEVIZ_H_
|
|
|
|
#define _ROGUEVIZ_H_
|
2018-05-22 20:09:16 +00:00
|
|
|
// See: http://www.roguetemple.com/z/hyper/rogueviz.php
|
|
|
|
|
2020-03-29 12:37:39 +00:00
|
|
|
#include "../hyper.h"
|
|
|
|
|
2020-03-29 15:37:08 +00:00
|
|
|
#define RVPATH HYPERPATH "rogueviz/"
|
|
|
|
|
2020-05-15 10:04:29 +00:00
|
|
|
#ifndef CAP_NCONF
|
|
|
|
#define CAP_NCONF 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef CAP_RVSLIDES
|
|
|
|
#define CAP_RVSLIDES (CAP_TOUR && !ISWEB)
|
|
|
|
#endif
|
|
|
|
|
2017-12-01 23:24:07 +00:00
|
|
|
namespace rogueviz {
|
2018-06-10 23:58:31 +00:00
|
|
|
using namespace hr;
|
2020-03-29 13:37:26 +00:00
|
|
|
|
|
|
|
constexpr flagtype RV_GRAPH = 1;
|
|
|
|
constexpr flagtype RV_WHICHWEIGHT = 2; // sag
|
|
|
|
constexpr flagtype RV_AUTO_MAXWEIGHT = 4; // sag
|
|
|
|
constexpr flagtype RV_COMPRESS_LABELS = 8; // do not display some labels
|
|
|
|
constexpr flagtype RV_COLOR_TREE = 16; // color vertex together with tree parents
|
|
|
|
constexpr flagtype RV_HAVE_WEIGHT = 32; // edges have weights
|
|
|
|
constexpr flagtype RV_INVERSE_WEIGHT = 64; // edit weight, not 1/weight
|
|
|
|
|
|
|
|
inline flagtype vizflags;
|
2020-03-29 15:37:08 +00:00
|
|
|
extern string weight_label;
|
|
|
|
extern ld maxweight;
|
2022-07-23 13:38:44 +00:00
|
|
|
extern ld ggamma;
|
|
|
|
extern bool highlight_target;
|
2021-04-07 16:32:00 +00:00
|
|
|
|
|
|
|
extern int vertex_shape;
|
2022-07-23 13:38:44 +00:00
|
|
|
extern int search_for;
|
2018-06-10 23:58:31 +00:00
|
|
|
|
|
|
|
void drawExtra();
|
|
|
|
void close();
|
2017-12-01 23:24:07 +00:00
|
|
|
|
2021-03-30 23:10:45 +00:00
|
|
|
void init(flagtype flags);
|
2018-07-09 16:17:40 +00:00
|
|
|
|
2022-07-23 13:38:44 +00:00
|
|
|
void graph_rv_hooks();
|
|
|
|
|
2018-07-09 16:17:40 +00:00
|
|
|
struct edgetype {
|
|
|
|
double visible_from;
|
2019-06-01 15:09:50 +00:00
|
|
|
double visible_from_hi;
|
|
|
|
double visible_from_help;
|
|
|
|
unsigned color, color_hi;
|
2018-07-09 16:17:40 +00:00
|
|
|
string name;
|
|
|
|
};
|
2020-03-29 15:37:08 +00:00
|
|
|
|
|
|
|
edgetype *add_edgetype(const string& name);
|
2018-07-09 16:17:40 +00:00
|
|
|
|
|
|
|
static const unsigned DEFAULT_COLOR = 0x471293B5;
|
|
|
|
|
|
|
|
extern edgetype default_edgetype;
|
|
|
|
|
2018-09-25 01:47:42 +00:00
|
|
|
extern vector<shared_ptr<edgetype>> edgetypes;
|
2018-06-10 23:58:31 +00:00
|
|
|
|
|
|
|
struct edgeinfo {
|
|
|
|
int i, j;
|
|
|
|
double weight, weight2;
|
|
|
|
vector<glvertex> prec;
|
2019-04-23 10:49:07 +00:00
|
|
|
basic_textureinfo tinf;
|
2018-06-10 23:58:31 +00:00
|
|
|
cell *orig;
|
|
|
|
int lastdraw;
|
2018-07-09 16:17:40 +00:00
|
|
|
edgetype *type;
|
|
|
|
edgeinfo(edgetype *t) { orig = NULL; lastdraw = -1; type = t; }
|
2018-06-10 23:58:31 +00:00
|
|
|
};
|
2020-03-29 15:37:08 +00:00
|
|
|
|
|
|
|
extern vector<edgeinfo*> edgeinfos;
|
|
|
|
void addedge0(int i, int j, edgeinfo *ei);
|
|
|
|
void addedge(int i, int j, edgeinfo *ei);
|
|
|
|
void addedge(int i, int j, double wei, bool subdiv, edgetype *t);
|
|
|
|
extern vector<int> legend;
|
|
|
|
extern vector<cell*> named;
|
2017-12-14 01:53:39 +00:00
|
|
|
|
2022-07-12 08:52:04 +00:00
|
|
|
int readLabel(fhstream& f);
|
|
|
|
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_TEXTURE
|
2019-06-01 15:06:15 +00:00
|
|
|
struct rvimage {
|
|
|
|
basic_textureinfo tinf;
|
|
|
|
texture::texture_data tdata;
|
|
|
|
vector<hyperpoint> vertices;
|
|
|
|
};
|
2021-06-25 11:43:33 +00:00
|
|
|
#endif
|
2021-04-23 18:53:16 +00:00
|
|
|
|
|
|
|
extern int brm_limit;
|
2019-06-01 15:06:15 +00:00
|
|
|
|
2018-06-10 23:58:31 +00:00
|
|
|
struct colorpair {
|
2018-09-04 17:53:42 +00:00
|
|
|
color_t color1, color2;
|
2018-06-10 23:58:31 +00:00
|
|
|
char shade;
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_TEXTURE
|
2019-06-01 15:06:15 +00:00
|
|
|
shared_ptr<rvimage> img;
|
2021-06-25 11:43:33 +00:00
|
|
|
#endif
|
2019-07-03 02:57:52 +00:00
|
|
|
colorpair(color_t col = 0xC0C0C0FF) { shade = 0; color1 = color2 = col; }
|
2018-06-10 23:58:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct vertexdata {
|
|
|
|
vector<pair<int, edgeinfo*> > edges;
|
|
|
|
string name;
|
|
|
|
colorpair cp;
|
|
|
|
edgeinfo *virt;
|
|
|
|
bool special;
|
|
|
|
int data;
|
|
|
|
string *info;
|
|
|
|
shmup::monster *m;
|
|
|
|
vertexdata() { virt = NULL; m = NULL; info = NULL; special = false; }
|
|
|
|
};
|
|
|
|
|
|
|
|
extern vector<vertexdata> vdata;
|
2017-12-14 01:53:39 +00:00
|
|
|
|
2018-07-05 07:55:11 +00:00
|
|
|
void storeall(int from = 0);
|
2021-03-30 22:08:20 +00:00
|
|
|
|
|
|
|
extern vector<reaction_t> cleanup;
|
2021-03-31 10:19:31 +00:00
|
|
|
|
|
|
|
void do_cleanup();
|
2021-03-30 22:08:20 +00:00
|
|
|
|
2021-10-17 15:02:34 +00:00
|
|
|
inline void on_cleanup_or_next(const reaction_t& del) {
|
2022-05-06 10:41:18 +00:00
|
|
|
#if CAP_TOUR
|
2021-10-17 15:02:34 +00:00
|
|
|
if(tour::on) tour::on_restore(del);
|
2022-05-06 10:41:18 +00:00
|
|
|
else
|
|
|
|
#endif
|
|
|
|
cleanup.push_back(del);
|
2021-10-17 15:02:34 +00:00
|
|
|
}
|
|
|
|
|
2022-09-24 07:14:31 +00:00
|
|
|
template<class T> void rv_change(T& variable, const T& value) {
|
|
|
|
T backup = variable;
|
|
|
|
variable = value;
|
|
|
|
on_cleanup_or_next([backup, &variable] { variable = backup; });
|
|
|
|
}
|
|
|
|
|
2021-03-30 22:08:20 +00:00
|
|
|
template<class T, class U> void rv_hook(hookset<T>& m, int prio, U&& hook) {
|
|
|
|
int p = addHook(m, prio, hook);
|
|
|
|
auto del = [&m, p] {
|
|
|
|
delHook(m, p);
|
|
|
|
};
|
2021-10-17 15:02:34 +00:00
|
|
|
on_cleanup_or_next(del);
|
2021-03-30 22:08:20 +00:00
|
|
|
}
|
2017-12-01 23:24:07 +00:00
|
|
|
|
2017-12-14 01:53:39 +00:00
|
|
|
extern bool showlabels;
|
2018-06-10 23:58:31 +00:00
|
|
|
|
|
|
|
extern bool rog3;
|
|
|
|
extern bool rvwarp;
|
|
|
|
|
2019-04-22 12:46:33 +00:00
|
|
|
extern colorpair dftcolor;
|
2020-03-29 12:41:24 +00:00
|
|
|
|
2020-04-11 18:47:14 +00:00
|
|
|
inline hookset<void(vertexdata&, cell*, shmup::monster*, int)> hooks_drawvertex;
|
|
|
|
inline hookset<bool(edgeinfo*, bool store)> hooks_alt_edges;
|
2020-03-29 15:37:08 +00:00
|
|
|
inline purehookset hooks_rvmenu;
|
2020-04-11 18:47:14 +00:00
|
|
|
inline hookset<bool()> hooks_rvmenu_replace;
|
|
|
|
inline hookset<bool(int&, string&, FILE*)> hooks_readcolor;
|
2020-03-29 15:37:08 +00:00
|
|
|
inline purehookset hooks_close;
|
2020-03-29 12:41:24 +00:00
|
|
|
|
2019-03-15 11:45:57 +00:00
|
|
|
void readcolor(const string& cfname);
|
|
|
|
|
|
|
|
void close();
|
|
|
|
extern bool showlabels;
|
2021-06-25 11:43:33 +00:00
|
|
|
|
2020-09-11 09:41:23 +00:00
|
|
|
namespace pres {
|
2020-03-29 11:40:31 +00:00
|
|
|
using namespace hr::tour;
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_RVSLIDES
|
2021-03-30 21:01:29 +00:00
|
|
|
inline hookset<void(string, vector<slide>&)> hooks_build_rvtour;
|
2020-03-29 11:40:31 +00:00
|
|
|
slide *gen_rvtour();
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_TEXTURE
|
2022-09-24 07:14:31 +00:00
|
|
|
void draw_texture(texture::texture_data& tex, ld dx = 0, ld dy = 0, ld scale = 1);
|
2021-06-25 11:43:33 +00:00
|
|
|
#endif
|
2020-03-29 15:37:08 +00:00
|
|
|
|
2022-09-24 07:14:31 +00:00
|
|
|
extern map<string, texture::texture_data> textures;
|
|
|
|
|
2021-04-04 11:57:53 +00:00
|
|
|
template<class T, class U> function<void(presmode)> roguevizslide(char c, const T& t, const U& f) {
|
2021-04-02 22:57:37 +00:00
|
|
|
return [c,t,f] (presmode mode) {
|
|
|
|
f(mode);
|
2020-03-29 15:37:08 +00:00
|
|
|
patterns::canvasback = 0x101010;
|
|
|
|
setCanvas(mode, c);
|
|
|
|
if(mode == 1 || mode == pmGeometryStart) t();
|
|
|
|
|
|
|
|
if(mode == 3 || mode == pmGeometry || mode == pmGeometryReset) {
|
|
|
|
rogueviz::close();
|
|
|
|
shmup::clearMonsters();
|
2020-04-07 15:15:40 +00:00
|
|
|
if(mode == pmGeometryReset && !(slides[currentslide].flags & QUICKGEO)) t();
|
2020-03-29 15:37:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
slidecommand = "toggle the player";
|
|
|
|
if(mode == 4)
|
|
|
|
mapeditor::drawplayer = !mapeditor::drawplayer;
|
|
|
|
pd_from = NULL;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-04 11:57:53 +00:00
|
|
|
template<class T> function<void(presmode)> roguevizslide(char c, const T& t) { return roguevizslide(c, t, [] (presmode mode) {}); }
|
|
|
|
|
2020-03-29 15:37:08 +00:00
|
|
|
template<class T, class U>
|
|
|
|
function<void(presmode)> roguevizslide_action(char c, const T& t, const U& act) {
|
|
|
|
return [c,t,act] (presmode mode) {
|
|
|
|
patterns::canvasback = 0x101010;
|
|
|
|
setCanvas(mode, c);
|
|
|
|
if(mode == pmStart || mode == pmGeometryStart) t();
|
|
|
|
|
|
|
|
act(mode);
|
|
|
|
|
|
|
|
if(mode == pmStop || mode == pmGeometry || mode == pmGeometryReset) {
|
|
|
|
rogueviz::close();
|
|
|
|
shmup::clearMonsters();
|
2020-04-07 15:15:40 +00:00
|
|
|
if(mode == pmGeometryReset && !(slides[currentslide].flags & QUICKGEO)) t();
|
2020-03-29 15:37:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-03-30 21:01:54 +00:00
|
|
|
|
|
|
|
void add_end(vector<slide>& s);
|
|
|
|
|
|
|
|
template<class T, class U> void add_temporary_hook(int mode, hookset<T>& m, int prio, U&& hook) {
|
|
|
|
using namespace tour;
|
|
|
|
if(mode == pmStart) {
|
|
|
|
int p = addHook(m, prio, hook);
|
|
|
|
on_restore([&m, p] {
|
|
|
|
delHook(m, p);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* maks graphs in presentations */
|
|
|
|
struct grapher {
|
|
|
|
|
|
|
|
ld minx, miny, maxx, maxy;
|
|
|
|
|
|
|
|
shiftmatrix T;
|
|
|
|
|
|
|
|
grapher(ld _minx, ld _miny, ld _maxx, ld _maxy);
|
|
|
|
void line(hyperpoint h1, hyperpoint h2, color_t col);
|
|
|
|
void arrow(hyperpoint h1, hyperpoint h2, ld sca, color_t col = 0xFF);
|
|
|
|
shiftmatrix pos(ld x, ld y, ld sca);
|
|
|
|
};
|
|
|
|
|
|
|
|
void add_stat(presmode mode, const bool_reaction_t& stat);
|
|
|
|
void compare_projections(presmode mode, eModel a, eModel b);
|
|
|
|
void no_other_hud(presmode mode);
|
2022-08-05 17:55:40 +00:00
|
|
|
void non_game_slide(presmode mode);
|
2022-08-05 18:34:56 +00:00
|
|
|
void non_game_slide_scroll(presmode mode);
|
2022-06-08 16:05:05 +00:00
|
|
|
void white_screen(presmode mode, color_t col = 0xFFFFFFFF);
|
2021-03-30 21:01:54 +00:00
|
|
|
void empty_screen(presmode mode, color_t col = 0xFFFFFFFF);
|
2022-09-24 07:14:31 +00:00
|
|
|
void show_picture(presmode mode, string s, flagtype flags = 0);
|
|
|
|
void sub_picture(string s, flagtype flags = 0, ld dx = 0, ld dy = 0, ld scale = 1);
|
2022-04-07 18:57:18 +00:00
|
|
|
void show_animation(presmode mode, string s, int sx, int sy, int frames, int fps);
|
2021-03-30 21:01:54 +00:00
|
|
|
void use_angledir(presmode mode, bool reset);
|
2021-03-31 10:18:42 +00:00
|
|
|
void slide_error(presmode mode, string s);
|
2021-03-30 21:01:54 +00:00
|
|
|
|
2022-07-21 01:28:54 +00:00
|
|
|
static const flagtype LATEX_COLOR = 1;
|
|
|
|
|
2022-04-12 11:02:08 +00:00
|
|
|
void show_latex(presmode mode, string s);
|
2022-07-21 01:28:54 +00:00
|
|
|
void dialog_add_latex(string s, color_t color, int size = 100, flagtype flag = 0);
|
|
|
|
void dialog_may_latex(string latex, string normal, color_t col = dialog::dialogcolor, int size = 100, flagtype flag = 0);
|
2022-08-05 21:55:24 +00:00
|
|
|
void uses_game(presmode mode, string name, reaction_t launcher, reaction_t restore);
|
|
|
|
void latex_slide(presmode mode, string s, flagtype flags = 0, int size = 100);
|
2022-09-24 07:14:31 +00:00
|
|
|
|
|
|
|
inline purehookset hooks_latex_slide;
|
2022-04-12 11:02:08 +00:00
|
|
|
|
2021-03-30 21:01:54 +00:00
|
|
|
inline ld angle = 0;
|
|
|
|
inline int dir = -1;
|
|
|
|
hyperpoint p2(ld x, ld y);
|
2021-06-25 11:43:33 +00:00
|
|
|
#endif
|
2021-03-30 21:01:54 +00:00
|
|
|
}
|
2020-03-21 09:15:11 +00:00
|
|
|
|
|
|
|
void createViz(int id, cell *c, transmatrix at);
|
2020-03-29 15:37:08 +00:00
|
|
|
|
|
|
|
extern map<string, int> labeler;
|
2021-06-25 12:00:31 +00:00
|
|
|
bool id_known(const string& s);
|
2020-03-29 15:37:08 +00:00
|
|
|
int getid(const string& s);
|
|
|
|
int getnewid(string s);
|
|
|
|
extern string fname;
|
|
|
|
|
2021-06-25 12:03:11 +00:00
|
|
|
bool rv_ignore(char c);
|
|
|
|
|
2020-03-29 15:37:08 +00:00
|
|
|
colorpair perturb(colorpair cp);
|
2020-07-29 21:34:00 +00:00
|
|
|
void queuedisk(const shiftmatrix& V, const colorpair& cp, bool legend, const string* info, int i);
|
2020-03-29 15:37:08 +00:00
|
|
|
|
2021-03-30 18:59:22 +00:00
|
|
|
/* 3D models */
|
|
|
|
|
|
|
|
namespace objmodels {
|
|
|
|
|
|
|
|
using tf_result = pair<int, hyperpoint>;
|
|
|
|
|
|
|
|
using transformer = std::function<tf_result(hyperpoint)>;
|
|
|
|
using subdivider = std::function<int(vector<hyperpoint>&)>;
|
|
|
|
|
2021-03-31 10:55:31 +00:00
|
|
|
inline ld prec = 1;
|
2021-03-30 18:59:22 +00:00
|
|
|
|
2022-03-20 08:52:32 +00:00
|
|
|
inline bool shift_to_ctr = false;
|
|
|
|
|
2021-03-30 18:59:22 +00:00
|
|
|
struct object {
|
|
|
|
hpcshape sh;
|
|
|
|
basic_textureinfo tv;
|
|
|
|
color_t color;
|
|
|
|
};
|
|
|
|
|
2021-03-31 08:53:59 +00:00
|
|
|
struct model_data : gi_extension {
|
2021-03-31 10:55:31 +00:00
|
|
|
ld prec_used;
|
2021-03-31 08:53:59 +00:00
|
|
|
vector<shared_ptr<object>> objs;
|
2022-03-20 08:52:32 +00:00
|
|
|
vector<int> objindex;
|
2021-03-31 08:53:59 +00:00
|
|
|
void render(const shiftmatrix& V);
|
|
|
|
};
|
|
|
|
|
2021-04-07 21:23:40 +00:00
|
|
|
inline tf_result default_transformer(hyperpoint h) { return {0, direct_exp(h) };}
|
2021-03-31 08:53:59 +00:00
|
|
|
|
|
|
|
inline int default_subdivider(vector<hyperpoint>& hys) {
|
|
|
|
if(euclid) return 1;
|
|
|
|
ld maxlen = prec * max(hypot_d(3, hys[1] - hys[0]), max(hypot_d(3, hys[2] - hys[0]), hypot_d(3, hys[2] - hys[1])));
|
|
|
|
return int(ceil(maxlen));
|
|
|
|
}
|
2021-03-30 18:59:22 +00:00
|
|
|
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_TEXTURE
|
2021-03-30 18:59:22 +00:00
|
|
|
struct model {
|
|
|
|
|
|
|
|
string path, fname;
|
2021-03-31 08:53:59 +00:00
|
|
|
reaction_t preparer;
|
2021-03-30 18:59:22 +00:00
|
|
|
transformer tf;
|
|
|
|
subdivider sd;
|
|
|
|
|
2021-03-30 21:00:51 +00:00
|
|
|
bool is_available, av_checked;
|
|
|
|
|
2021-03-30 18:59:22 +00:00
|
|
|
model(string path = "", string fn = "",
|
2021-03-31 08:53:59 +00:00
|
|
|
transformer tf = default_transformer,
|
|
|
|
reaction_t prep = [] {},
|
|
|
|
subdivider sd = default_subdivider
|
|
|
|
) : path(path), fname(fn), preparer(prep), tf(tf), sd(sd) { av_checked = false; }
|
2021-03-30 18:59:22 +00:00
|
|
|
|
|
|
|
map<string, texture::texture_data> materials;
|
|
|
|
map<string, color_t> colors;
|
|
|
|
|
|
|
|
/* private */
|
2021-03-31 08:53:59 +00:00
|
|
|
void load_obj(model_data& objects);
|
2021-03-30 18:59:22 +00:00
|
|
|
|
2021-03-31 08:53:59 +00:00
|
|
|
model_data& get();
|
|
|
|
|
|
|
|
void render(const shiftmatrix& V) { get().render(V); }
|
2021-03-30 21:00:51 +00:00
|
|
|
|
|
|
|
bool available();
|
2021-03-30 18:59:22 +00:00
|
|
|
};
|
2021-03-31 10:55:31 +00:00
|
|
|
|
|
|
|
void add_model_settings();
|
2021-06-25 11:43:33 +00:00
|
|
|
#endif
|
2021-03-30 18:59:22 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-06-25 11:43:33 +00:00
|
|
|
#if CAP_RVSLIDES
|
|
|
|
#define addHook_rvslides(x, y) addHook(rogueviz::pres::hooks_build_rvtour, x, y)
|
|
|
|
#define addHook_slideshows(x, y) addHook(tour::ss::hooks_extra_slideshows, x, y)
|
|
|
|
#define addHook_rvtour(x, y) addHook(pres::hooks_build_rvtour, x, y)
|
|
|
|
#else
|
|
|
|
#define addHook_rvslides(x, y) 0
|
|
|
|
#define addHook_slideshows(x, y) 0
|
|
|
|
#define addHook_rvtour(x, y) 0
|
|
|
|
#endif
|
2022-08-23 19:49:25 +00:00
|
|
|
|
|
|
|
/* parallelize a computation */
|
|
|
|
inline int threads = 1;
|
|
|
|
|
2022-09-29 13:13:30 +00:00
|
|
|
#if CAP_THREAD
|
2022-08-23 19:49:25 +00:00
|
|
|
template<class T> auto parallelize(long long N, T action) -> decltype(action(0,0)) {
|
|
|
|
if(threads == 1) return action(0,N);
|
|
|
|
std::vector<std::thread> v;
|
|
|
|
typedef decltype(action(0,0)) Res;
|
|
|
|
std::vector<Res> results(threads);
|
|
|
|
for(int k=0; k<threads; k++)
|
|
|
|
v.emplace_back([&,k] () {
|
|
|
|
results[k] = action(N*k/threads, N*(k+1)/threads);
|
|
|
|
});
|
|
|
|
for(std::thread& t:v) t.join();
|
|
|
|
Res res = 0;
|
|
|
|
for(Res r: results) res += r;
|
|
|
|
return res;
|
|
|
|
}
|
2022-09-29 13:13:30 +00:00
|
|
|
#endif
|
2022-08-23 19:49:25 +00:00
|
|
|
|
2019-03-15 11:45:57 +00:00
|
|
|
}
|
2018-06-10 23:58:31 +00:00
|
|
|
|
2020-03-29 11:09:10 +00:00
|
|
|
#endif
|