From 5cb531b89b3f7233c6a411d4801c4d998ab8ae0b Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Thu, 5 Nov 2026 11:35:22 +0100 Subject: [PATCH] rug:: redesigned menus, hilbert rug, improved finger mode interface --- control.cpp | 3 - language-cz.cpp | 6 +- language-fr.cpp | 6 +- language-pl.cpp | 6 +- language-ru.cpp | 6 +- language-zh.cpp | 5 +- rug.cpp | 490 ++++++++++++++++++++++++++---------------------- surface.cpp | 418 ++++++++++++++++++++++++++++++----------- 8 files changed, 581 insertions(+), 359 deletions(-) diff --git a/control.cpp b/control.cpp index 43223f7e..1e38ad46 100644 --- a/control.cpp +++ b/control.cpp @@ -1719,9 +1719,6 @@ EX int get_direction_key(int sym, int uni) { } EX bool gmodekeys(int sym, int uni) { - #if CAP_RUG - if(rug::rugged && rug::handlekeys(sym, uni)) return true; - #endif if(NUMBERKEY == '6') { vid.grid = !vid.grid; return true; } if(NUMBERKEY == '7') { vid.darkhepta = !vid.darkhepta; return true; } diff --git a/language-cz.cpp b/language-cz.cpp index d83f2c10..33b01344 100644 --- a/language-cz.cpp +++ b/language-cz.cpp @@ -6958,8 +6958,7 @@ S( "are great to draw hyperbolic tesselations on. While they look great, only a small part " "of the hyperbolic plane can be visibly represented in this way, so they are not " "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu.", + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect.", "V tomto menu si můžeš vybrat z několika známých hladkých ploch s konstantní " "zápornou křivostí. Vzhledem k tomu, že i hyperbolická rovina má konstantní " @@ -6968,8 +6967,7 @@ S( "pouze malou část hyperbolické roviny, takže na hraní HyperRogue se nehodí; " "v některých směrech se však jejich pokrytí táhne velmi daleko, a proto se " "dá efekt někdy vylepšit tím, že v konfiguraci grafiky zvýšíš dohled a " - "v nastavení hyperského koberce velikost textury. Kvůli pohodlí je z tohoto " - "menu možné vybrat i jiné 3D modely." + "v nastavení hyperského koberce velikost textury." ) // coverage diff --git a/language-fr.cpp b/language-fr.cpp index f61dab1f..21963f8d 100644 --- a/language-fr.cpp +++ b/language-fr.cpp @@ -6626,8 +6626,7 @@ S( "are great to draw hyperbolic tesselations on. While they look great, only a small part " "of the hyperbolic plane can be visibly represented in this way, so they are not " "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu.", + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect.", "Dans ce menu vous pouvez choisir plusieurs surfaces lisses de courbure négative constante. " "Comme la courbure d'un plan hyperbolique est aussi une constante négative, ces surfaces " @@ -6635,8 +6634,7 @@ S( "une petite partie du plan hyperbolique peut être représenté de cette manière, donc ils ne sont " "pas pratiques pour jouer à HyperRogue ; cependant la représentation s'étend loin dans des directions, " "spécifiées, donc augmenter d'abord la distance de vue dans la configuration graphique et taille de " - "texture dans les réglages du tapis hypersan peut augmenter l'effet. Vous pouvez aussi choisir d'autres " - "modèles 3D depuis ce menu." + "texture dans les réglages du tapis hypersan peut augmenter l'effet." ) // coverage diff --git a/language-pl.cpp b/language-pl.cpp index 134d5e8c..ae29fafd 100644 --- a/language-pl.cpp +++ b/language-pl.cpp @@ -6737,8 +6737,7 @@ S( "are great to draw hyperbolic tesselations on. While they look great, only a small part " "of the hyperbolic plane can be visibly represented in this way, so they are not " "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu.", + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect.", "W tym trybie możesz wybrać spośród kilku znanych powierzchni o stałej ujemnej krzywiźnie. " "Krzywizna płaszczyzny hiperbolicznej jest również stała ujemna, także powierzchnie te " @@ -6746,8 +6745,7 @@ S( "płaszczyzny hiperbolicznej może być pokazana w ten sposób, także nie nadają się " "one zbyt dobrze do grania w HyperRogue; pokrycie zato wybiega daleko w określonych kierunkach, " "także powiększenie pola widzenia w konfiguracji grafiki oraz rozmiaru tekstury w ustawieniach " - "hiperskiego dywanu może poprawić efekt. Dla wygody możesz również wybrać inne tryby 3D " - "z tego menu." + "hiperskiego dywanu może poprawić efekt." ) // coverage diff --git a/language-ru.cpp b/language-ru.cpp index cf1b1388..71326017 100644 --- a/language-ru.cpp +++ b/language-ru.cpp @@ -7014,8 +7014,7 @@ S( "are great to draw hyperbolic tesselations on. While they look great, only a small part " "of the hyperbolic plane can be visibly represented in this way, so they are not " "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu.", + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect.", "В этом меню можно выбрать одну их нескольких известных гладких поверхностей постоянной отрицательной кривизны. " "Так как кривизна гиперболической плоскости также постоянна и отрицательна, на таких поверхностях " @@ -7023,8 +7022,7 @@ S( "гиперболической плоскости будет при этом видна, так что играть на них " "в HyperRogue неудобно; однако покрытие распространяется далеко в определённых направлениях, " "увеличение дальности обзора в настройках графики и размера текстуры " - "в настройках гиперсидского ковра могут помочь. Для удобства можно выбрать другую " - "модель из этого меню." + "в настройках гиперсидского ковра могут помочь." ) // coverage diff --git a/language-zh.cpp b/language-zh.cpp index f3320833..91ce69e9 100644 --- a/language-zh.cpp +++ b/language-zh.cpp @@ -6040,10 +6040,9 @@ S( "are great to draw hyperbolic tesselations on. While they look great, only a small part " "of the hyperbolic plane can be visibly represented in this way, so they are not " "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu.", + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect.", - "在该菜单中你可以选择多种负曲率的已知光滑曲面。由于双曲面的曲率也是负常数,这些曲面很适合用于绘制双曲密铺。虽然看起来不错,但只有一些双曲面能以这种方式表示出来,并不适合游玩HyperRogue;不过,在某些特定方向的覆盖会延伸很远,因此在图形设置中增加视野范围并在超波斯地毯设置中增加纹理尺寸可能会提升效果。出于方便考虑,你也可以在该菜单选择其他3D模型。" + "在该菜单中你可以选择多种负曲率的已知光滑曲面。由于双曲面的曲率也是负常数,这些曲面很适合用于绘制双曲密铺。虽然看起来不错,但只有一些双曲面能以这种方式表示出来,并不适合游玩HyperRogue;不过,在某些特定方向的覆盖会延伸很远,因此在图形设置中增加视野范围并在超波斯地毯设置中增加纹理尺寸可能会提升效果。" ) // coverage diff --git a/rug.cpp b/rug.cpp index c1170493..05a6c3c9 100644 --- a/rug.cpp +++ b/rug.cpp @@ -32,6 +32,7 @@ bool computed = false; #if HDR struct edge { struct rugpoint *target; + struct rugpoint *os, *ot; ld len; }; @@ -282,10 +283,10 @@ EX rugpoint *findOrAddRugpoint(shiftpoint h, double dist) { return r ? r : addRugpoint(h, dist); } -void addNewEdge(rugpoint *e1, rugpoint *e2, ld len = 1) { +void addNewEdge(rugpoint *e1, rugpoint *e2, rugpoint *os, rugpoint *ot, ld len = 1) { edge e; e.len = len; - e.target = e2; e1->edges.push_back(e); - e.target = e1; e2->edges.push_back(e); + e.target = e2; e.os = os; e.ot = ot; e1->edges.push_back(e); + e.target = e1; e.os = ot; e.ot = os; e2->edges.push_back(e); } EX bool edge_exists(rugpoint *e1, rugpoint *e2) { @@ -295,9 +296,9 @@ EX bool edge_exists(rugpoint *e1, rugpoint *e2) { return false; } -void addEdge(rugpoint *e1, rugpoint *e2, ld len = 1) { +void addEdge(rugpoint *e1, rugpoint *e2, rugpoint *os, rugpoint *ot, ld len = 1) { if(!edge_exists(e1, e2)) - addNewEdge(e1, e2, len); + addNewEdge(e1, e2, os, ot, len); } void add_anticusp_edge(rugpoint *e1, rugpoint *e2, ld len = 1) { @@ -309,9 +310,16 @@ void add_anticusp_edge(rugpoint *e1, rugpoint *e2, ld len = 1) { } EX void addTriangle(rugpoint *t1, rugpoint *t2, rugpoint *t3, ld len IS(1)) { - addEdge(t1->getglue(), t2->getglue(), len); - addEdge(t2->getglue(), t3->getglue(), len); - addEdge(t3->getglue(), t1->getglue(), len); + addEdge(t1->getglue(), t2->getglue(), t1, t2, len); + addEdge(t2->getglue(), t3->getglue(), t2, t3, len); + addEdge(t3->getglue(), t1->getglue(), t3, t1, len); + triangles.push_back(triangle(t1,t2,t3)); + } + +EX void addTriangleCalc(rugpoint *t1, rugpoint *t2, rugpoint *t3) { + addEdge(t1->getglue(), t2->getglue(), t1, t2, hdist(t1->h, t2->h) * modelscale); + addEdge(t2->getglue(), t3->getglue(), t2, t3, hdist(t2->h, t3->h) * modelscale); + addEdge(t3->getglue(), t1->getglue(), t3, t1, hdist(t3->h, t1->h) * modelscale); triangles.push_back(triangle(t1,t2,t3)); } @@ -427,7 +435,7 @@ ld clifford_torus::compute_mx() { return mx; } -#if MAXMDIM >= 4 +#if MAXMDIM >= 4 && CAP_SURFACE EX void buildTorusRug() { calcparam_rug(); @@ -448,6 +456,7 @@ EX void buildTorusRug() { hyperpoint onscreen; shiftpoint h1 = gmatrix[gs] * ct.torus_to_actual(h); applymodel(h1, onscreen); + r->h = h1; r->x1 = onscreen[0]; r->y1 = onscreen[1]; @@ -507,8 +516,8 @@ EX void buildTorusRug() { for(int yy=0; yy= 4 - if(euclid && closed_manifold) { + #if MAXMDIM >= 4 && CAP_SURFACE + if(surface::sh == surface::dsTorus) { good_shape = true; buildTorusRug(); return; @@ -1200,8 +1213,7 @@ EX void init_model() { precision_increases = 0; #if CAP_CRYSTAL - if(cryst && surface::sh == surface::dsNone) { - surface::sh = surface::dsCrystal; + if(cryst && surface::sh == surface::dsCrystal) { crystal::init_rotation(); good_shape = true; return; @@ -1272,47 +1284,18 @@ ld protractor = 0; #define CAP_HOLDKEYS (CAP_SDL && !ISWEB) -EX bool handlekeys(int sym, int uni) { - USING_NATIVE_GEOMETRY; - if(NUMBERKEY == '2') { - ld bdist = 1e12; - if(finger_center) - finger_center = NULL; - else { - for(auto p: points) { - ld cdist = hdist(p->getglue()->h, mouseh); - if(cdist < bdist) - bdist = cdist, finger_center = p->getglue(); - } +EX void activate_finger() { + ld bdist = 1e12; + if(finger_center) + finger_center = NULL; + else { + for(auto p: points) { + ld cdist = hdist(p->getglue()->h, mouseh); + if(cdist < bdist) + bdist = cdist, finger_center = p->getglue(); } - if(renderonce) renderlate+=10; - return true; } - else if(NUMBERKEY == '3') { - #if CAP_CRYSTAL - if(in_crystal()) - crystal::switch_z_coordinate(); - else - #endif - rotate_view(cspin180(0, 2)); - return true; - } - else if(NUMBERKEY == '4') { - #if CAP_CRYSTAL - if(in_crystal()) - crystal::flip_z(); - else - #endif - rotate_view(cspin90(0, 2)); - return true; - } - #if CAP_CRYSTAL - else if(sym == SDLK_HOME && in_crystal()) { - crystal::next_home_orientation(); - return true; - } - #endif - else return false; + if(renderonce) renderlate+=10; } EX void finger_on(int coord, ld val) { @@ -1374,8 +1357,10 @@ EX void actDraw() { int besti; +#if HDR static constexpr ld RADAR_INF = 1e12; -ld radar_distance = RADAR_INF; +#endif +EX ld radar_distance = RADAR_INF; EX shiftpoint gethyper(ld x, ld y) { @@ -1552,195 +1537,254 @@ EX void rug_geometry_choice() { dialog::display(); } -EX void show() { +EX void init_rug_page(const string& s) { cmode = sm::SIDE | sm::MAYDARK | sm::PANNING; gamescreen(); - dialog::init(XLAT("hypersian rug mode"), iinf[itPalace].color, 150, 100); - - dialog::addBoolItem(XLAT("enable the Hypersian Rug mode"), rug::rugged, 'u'); - - dialog::addBoolItem(XLAT("render the texture only once"), (renderonce), 'o'); - #if CAP_SDL - dialog::addBoolItem(XLAT("render texture without OpenGL"), (rendernogl), 'g'); - #else - rendernogl = false; - #endif - dialog::addSelItem(XLAT("texture size"), its(texturesize)+"x"+its(texturesize), 's'); + dialog::init(s, iinf[itPalace].color, 150, 100); + } - dialog::addSelItem(XLAT("vertex limit"), its(vertex_limit), 'v'); - if(rug::rugged) - dialog::lastItem().value += " (" + its(qvalid) + ")"; - - dialog::addSelItem(XLAT("model distance"), fts(model_distance), 'd'); - if(rug::rugged) { - dialog::addSelItem(XLAT("projection"), models::get_model_name(rconf.model), 'p'); - } - else dialog::addBreak(100); +EX void close_rug_page() { + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + } + +EX void show_hyperrug_settings() { + + init_rug_page("Hypersian Rug"); + + #if CAP_SURFACE + surface::shape_option(surface::dsHypersian, 'u', false); + #else + dialog::addBoolItem(XLAT("enable"), rug::rugged, 'u'); + dialog::add_action([] { + if(rug::rugged) rug::close(); + else rug::init(); + }); + #endif if(!rug::rugged) { dynamicval g(geometry, gwhere); dialog::addSelItem(XLAT("native geometry"), geometry_name(), 'n'); if(gwhere == rgElliptic) dialog::lastItem().value += " (e)"; + #if MAXMDIM >= 4 + dialog::add_action([] { if(!rug::rugged) pushScreen(rug_geometry_choice); }); + #endif } - else - dialog::addSelItem(XLAT("radar"), radar_distance == RADAR_INF ? "∞" : fts(radar_distance, 4), 'r'); - dialog::addSelItem(XLAT("model scale factor"), fts(modelscale), 'm'); + else dialog::addBreak(100); + dialog::addSelItem(XLAT("maximum error"), fts(err_zero), 'e'); + if(rug::rugged) + dialog::lastItem().value += " (" + fts(err_zero_current) + ")"; + dialog::add_action([] { + if(good_shape) good_shape = false; + dialog::editNumber(err_zero, 1e-9, 1, .1, 1e-3, XLAT("maximum error"), + XLAT("New points are added when the current error in the model is smaller than this value.") + ); + dialog::scaleLog(); + dialog::get_di().reaction = [] () { err_zero_current = err_zero; }; + }); if(rug::rugged) dialog::addSelItem(XLAT("model iterations"), its(queueiter), 0); - dialog::addItem(XLAT("stereo vision config"), 'f'); - // dialog::addSelItem(XLAT("protractor"), fts(protractor / degree) + "°", 'f'); - if(!good_shape) { - dialog::addSelItem(XLAT("maximum error"), fts(err_zero), 'e'); - if(rug::rugged) - dialog::lastItem().value += " (" + fts(err_zero_current) + ")"; - } - #if ISMOBILE - dialog::addSelItem(XLAT("move on touch"), fts(move_on_touch), 'G'); - #endif dialog::addSelItem(XLAT("anti-crossing"), fts(anticusp_factor), 'A'); - dialog::addBoolItem(XLAT("3D monsters/walls on the surface"), spatial_rug, 'S'); - dialog::add_action([] () { spatial_rug = !spatial_rug; }); - edit_levellines('L'); + dialog::add_action([] { + dialog::editNumber(anticusp_factor, 0, 1.5, .1, 0, XLAT("anti-crossing"), + XLAT("The anti-crossing algorithm prevents the model from crossing itself, " + "by preventing points which should not be close from being close. " + "The bigger number, the more sensitive it is, but the embedding is slower. Set 0 to disable.") + ); + }); -#if CAP_SURFACE - if(hyperbolic) - dialog::addItem(XLAT("smooth surfaces"), 'c'); - else - dialog::addBreak(100); -#endif - - dialog::addBreak(50); dialog::addHelp(); - dialog::addBack(); + dialog::add_action([] { gotoHelp(makehelp()); }); - dialog::display(); - keyhandler = [] (int sym, int uni) { - handlePanning(sym, uni); - dialog::handleNavigation(sym, uni); + close_rug_page(); + } - if(uni == 'h' || uni == SDLK_F1) gotoHelp(makehelp()); - else if(uni == 'u') { - if(rug::rugged) rug::close(); - else { - #if CAP_SURFACE - surface::sh = surface::dsNone; - #endif - rug::init(); +EX void show_torus_settings() { + init_rug_page("conformal torus"); + #if CAP_SURFACE + surface::shape_option(surface::dsTorus, 'u', false); + #else + dialog::addBoolItem(XLAT("enable"), rug::rugged, 'u'); + dialog::add_action([] { + if(rug::rugged) rug::close(); + else rug::init(); + }); + #endif + if(!rug::rugged) { + dynamicval g(geometry, gwhere); + dialog::addSelItem(XLAT("native geometry"), geometry_name(), 'n'); + if(gwhere == rgElliptic) dialog::lastItem().value += " (e)"; + #if MAXMDIM >= 4 + dialog::add_action([] { if(!rug::rugged) pushScreen(rug_geometry_choice); }); + #endif + } + else dialog::addBreak(100); + + close_rug_page(); + } + +EX void show_crystal_settings() { + init_rug_page("crystal viewer"); + #if CAP_SURFACE + surface::shape_option(surface::dsCrystal, 'u', false); + #endif + + if(rug::in_crystal()) { + dialog::addItem("next home orientation", 'h'); + dialog::add_action(crystal::next_home_orientation); + + dialog::addItem("switch Z coordinate", 's'); + dialog::add_action(crystal::switch_z_coordinate); + + dialog::addItem("flip Z coordinate", 'f'); + dialog::add_action(crystal::flip_z); + } + + rug::close_rug_page(); + } + +EX void show_gfx_settings() { + + init_rug_page("rug mode settings"); + + dialog::addBoolItem_action(XLAT("render the texture only once"), (renderonce), 'o'); + #if CAP_SDL + if(!rug::rugged) dialog::addBoolItem_action(XLAT("render texture without OpenGL"), (rendernogl), 'g'); + #else + rendernogl = false; + #endif + dialog::addSelItem(XLAT("texture size"), its(texturesize)+"x"+its(texturesize), 's'); + dialog::add_action([] { + texturesize *= 2; if(texturesize == 8192) texturesize = 64; + change_texturesize(); + }); + dialog::addSelItem(XLAT("vertex limit"), its(vertex_limit), 'v'); + if(rug::rugged) + dialog::lastItem().value += " (" + its(qvalid) + ")"; + dialog::add_action([] { + dialog::editNumber(vertex_limit, 0, 50000, 500, 3000, ("vertex limit"), + XLAT("The more vertices, the more accurate the Hypersian Rug model is. " + "However, a number too high might make the model slow to compute and render.") + ); + dialog::get_ne().reaction = [] () { err_zero_current = err_zero; }; + }); + dialog::addSelItem(XLAT("model distance"), fts(model_distance), 'd'); + dialog::add_action([] { + dialog::editNumber(model_distance, -10, 10, .1, 1, XLAT("model distance"), + XLAT("In the perspective projection, this sets the distance from the camera to the center of the model. " + "In the orthogonal projection this just controls the scale.") + ); + old_distance = model_distance; + dialog::get_di().reaction = [] () { + if(rug::rugged && perspective()) { + using_rugview rv; + shift_view(ztangent(old_distance - model_distance)); } - } - else if(uni == 'R') - dialog::editNumber(finger_range, 0, 1, .01, .1, XLAT("finger range"), - XLAT("Press 1 to enable the finger mode.") - ); - else if(uni == 'F') - dialog::editNumber(finger_force, 0, 1, .01, .1, XLAT("finger force"), - XLAT("Press 1 to enable the finger force.") - ); - else if(uni == 'o') - renderonce = !renderonce; - else if(uni == 'G') { - dialog::editNumber(move_on_touch, -1, 1, .1, 0, XLAT("move on touch"), ""); - dialog::get_ne().extra_options = anims::rug_angle_options; - } - else if(uni == 'A') { - dialog::editNumber(anticusp_factor, 0, 1.5, .1, 0, XLAT("anti-crossing"), - XLAT("The anti-crossing algorithm prevents the model from crossing itself, " - "by preventing points which should not be close from being close. " - "The bigger number, the more sensitive it is, but the embedding is slower. Set 0 to disable.") - ); - } - else if(uni == 'v') { - dialog::editNumber(vertex_limit, 0, 50000, 500, 3000, ("vertex limit"), - XLAT("The more vertices, the more accurate the Hypersian Rug model is. " - "However, a number too high might make the model slow to compute and render.") - ); - dialog::get_ne().reaction = [] () { err_zero_current = err_zero; }; - } - else if(uni == 'r') - addMessage(XLAT("This just shows the 'z' coordinate of the selected point.")); - else if(uni == 'm') { - dialog::editNumber(modelscale, 0.1, 10, rugged ? .01 : .1, 1, XLAT("model scale factor"), - XLAT("This is relevant when the native geometry is not Euclidean. " - "For example, if the native geometry is spherical, and scale < 1, a 2d sphere will be rendered as a subsphere; " - "if the native geometry is hyperbolic, and scale > 1, a hyperbolic plane will be rendered as an equidistant surface. ") - ); - dialog::scaleLog(); - if(rug::rugged) { - static bool adjust_points = true; - static bool camera_center = false; - static bool adjust_edges = true; - static bool adjust_distance = true; - static ld last; - last = modelscale; - dialog::get_ne().extra_options = [] () { - dialog::addBoolItem_action(XLAT("adjust points"), adjust_points, 'P'); - if(adjust_points) - dialog::addBoolItem_action(XLAT("center on camera"), camera_center, 'C'); - else - dialog::addBreak(100); - dialog::addBoolItem_action(XLAT("adjust edges"), adjust_edges, 'E'); - dialog::addBoolItem_action(XLAT("adjust distance"), adjust_distance, 'D'); - }; - dialog::get_ne().reaction = [] () { - if(!last || !modelscale) return; - if(!camera_center) push_all_points(2, model_distance); - for(auto p:points) { - if(adjust_edges) for(auto& e: p->edges) e.len *= modelscale / last; - if(adjust_points) p->native *= modelscale / last; - enqueue(p); - } - if(adjust_distance) model_distance = model_distance * modelscale / last; - last = modelscale; - good_shape = false; - if(!camera_center) push_all_points(2, -model_distance); - }; - } - } - else if(uni == 'p') { - pushScreen(models::model_menu); - } - else if(uni == 'd') { - dialog::editNumber(model_distance, -10, 10, .1, 1, XLAT("model distance"), - XLAT("In the perspective projection, this sets the distance from the camera to the center of the model. " - "In the orthogonal projection this just controls the scale.") - ); old_distance = model_distance; - dialog::get_di().reaction = [] () { - if(rug::rugged && perspective()) { - using_rugview rv; - shift_view(ztangent(old_distance - model_distance)); + }; + }); + dialog::addSelItem(XLAT("model scale factor"), fts(modelscale), 'm'); + dialog::add_action([] { + dialog::editNumber(modelscale, 0.1, 10, rugged ? .01 : .1, 1, XLAT("model scale factor"), + XLAT("This is relevant when the native geometry is not Euclidean. " + "For example, if the native geometry is spherical, and scale < 1, a 2d sphere will be rendered as a subsphere; " + "if the native geometry is hyperbolic, and scale > 1, a hyperbolic plane will be rendered as an equidistant surface. ") + ); + dialog::scaleLog(); + if(rug::rugged) { + static bool adjust_points = true; + static bool camera_center = false; + static bool adjust_edges = true; + static bool adjust_distance = true; + static ld last; + last = modelscale; + dialog::get_ne().extra_options = [] () { + dialog::addBoolItem_action(XLAT("adjust points"), adjust_points, 'P'); + if(adjust_points) + dialog::addBoolItem_action(XLAT("center on camera"), camera_center, 'C'); + else + dialog::addBreak(100); + dialog::addBoolItem_action(XLAT("adjust edges"), adjust_edges, 'E'); + dialog::addBoolItem_action(XLAT("adjust distance"), adjust_distance, 'D'); + }; + dialog::get_ne().reaction = [] () { + if(!last || !modelscale) return; + if(!camera_center) push_all_points(2, model_distance); + for(auto p:points) { + if(adjust_edges) for(auto& e: p->edges) e.len *= modelscale / last; + if(adjust_points) p->native *= modelscale / last; + enqueue(p); } - old_distance = model_distance; + if(adjust_distance) model_distance = model_distance * modelscale / last; + last = modelscale; + good_shape = false; + if(!camera_center) push_all_points(2, -model_distance); }; } - else if(uni == 'e') { - dialog::editNumber(err_zero, 1e-9, 1, .1, 1e-3, XLAT("maximum error"), - XLAT("New points are added when the current error in the model is smaller than this value.") - ); - dialog::scaleLog(); - dialog::get_di().reaction = [] () { err_zero_current = err_zero; }; - } - else if(uni == 'f') - pushScreen(showStereo); - #if MAXMDIM >= 4 - else if(uni == 'n' && !rug::rugged) - pushScreen(rug_geometry_choice); + }); + if(rug::rugged) { + dialog::addSelItem(XLAT("projection"), models::get_model_name(rconf.model), 'p'); + dialog::add_action_push(models::model_menu); + } + else dialog::addBreak(100); + + dialog::addItem(XLAT("stereo vision config"), 'f'); + dialog::add_action_push(showStereo); + + #if ISMOBILE + dialog::addSelItem(XLAT("move on touch"), fts(move_on_touch), 'G'); + dialog::add_action([] { + dialog::editNumber(move_on_touch, -1, 1, .1, 0, XLAT("move on touch"), ""); + dialog::get_ne().extra_options = anims::rug_angle_options; + }); + #endif + dialog::addBoolItem_action(XLAT("3D monsters/walls on the surface"), spatial_rug, 'S'); + edit_levellines('L'); + + close_rug_page(); + } + +EX void show() { + init_rug_page("hypersian rug mode"); + + #if CAP_SURFACE + if(!rug::rugged && torus_available()) surface::sh = surface::dsTorus; + surface::shape_option(surface::sh, 'u'); + dialog::addBreak(100); + #endif + + dialog::addItem("Hypersian Rug settings", 'r'); + dialog::add_action_push(show_hyperrug_settings); + + if(torus_available()) { + dialog::addItem("torus settings", 't'); + dialog::add_action_push(show_torus_settings); + } + + if(cryst) { + dialog::addItem("Crystal settings", 'C'); + dialog::add_action_push(show_crystal_settings); + } + + if(hyperbolic) { + #if CAP_SURFACE + dialog::addItem("constant curvature surfaces", 'c'); + dialog::add_action_push(surface::show_smooth); + + dialog::addItem("Hilbert Rug", 'i'); + dialog::add_action_push(surface::show_hilbert); #endif -#if CAP_SDL - else if(uni == 'g' && !rug::rugged) - rendernogl = !rendernogl; -#endif - else if(uni == 's') { - texturesize *= 2; - if(texturesize == 8192) texturesize = 64; - change_texturesize(); - } -#if CAP_SURFACE - else if(uni == 'c') - pushScreen(surface::show_surfaces); -#endif - else if(handlekeys(sym, uni)) ; - else if(doexiton(sym, uni)) popScreen(); - }; + } + + dialog::addItem("general settings", 'g'); + dialog::add_action_push(show_gfx_settings); + + #if CAP_SURFACE + dialog::addItem("other features", 'o'); + dialog::add_action_push(surface::show_other); + #endif + + dialog::display(); } EX void select() { diff --git a/surface.cpp b/surface.cpp index 4b76414e..2db8ceba 100644 --- a/surface.cpp +++ b/surface.cpp @@ -16,11 +16,11 @@ EX namespace surface { ld sech(ld d) { return 1 / cosh(d); } #if HDR -enum eShape { dsNone, dsTractricoid, dsDini, dsKuen, dsHyperlike, dsHyperboloid, dsHemisphere, dsCrystal }; +enum eShape { dsHypersian, dsTorus, dsTractricoid, dsDini, dsKuen, dsHyperlike, dsHyperboloid, dsHemisphere, dsCrystal, dsHilbert }; #endif -string shape_name[] = { "hypersian rug", "tractricoid", "Dini's surface", "Kuen surface", "concave barrel", - "hyperboloid", "hemisphere", "crystal" }; +string shape_name[] = { "hypersian rug", "conformal torus", "tractricoid", "Dini's surface", "Kuen surface", "concave barrel", + "hyperboloid", "hemisphere", "crystal", "Hilbert rug" }; EX eShape sh; @@ -367,7 +367,8 @@ int dexp_comb_colors[16] = { 0x123456, 0x123456, 0x123456, 0x123456, 0x123456, 0x123456, 0x123456 }; -int coverage_style; +EX int coverage_style; +EX bool approx_quality; EX vector > coverage; #if CAP_KUEN_MAP @@ -409,7 +410,7 @@ void draw_kuen_map() { } #endif -void full_mesh() { +EX void full_mesh() { rug::clear_model(); rug::buildRug(); rug::qvalid = 0; @@ -596,21 +597,24 @@ void run_other() { } EX void run_shape(eShape s) { + if(s == sh && rug::rugged) { rug::close(); return; } coverage.clear(); need_mouseh = true; sh = s; if(rug::rugged) rug::close(); - rug::gwhere = rug::rgEuclid; + if(!among(s, dsHypersian, dsTorus, dsCrystal)) rug::gwhere = rug::rgEuclid; rug::init(); // if(!rug::rugged) rug::reopen(); pushScreen(history::progress_screen); - if(sh != dsNone) rug::good_shape = true; + if(sh != dsHypersian) rug::good_shape = true; switch(sh) { - case dsNone: + case dsHypersian: + case dsTorus: + case dsCrystal: break; case dsKuen: @@ -631,16 +635,19 @@ EX void run_shape(eShape s) { case dsHemisphere: run_function([] (hyperpoint h) { h = h / h[2]; h[2] = sqrt(1 - sqhypot_d(2, h)); return h; }); - break; + break; + + case dsHilbert: + run_hilbert(); } - if(sh != dsNone) rug::good_shape = true; + if(sh != dsHypersian) rug::good_shape = true; rug::qvalid = 0; popScreen(); - if(sh != dsHyperboloid && sh != dsHemisphere && sh != dsNone) { + if(sh != dsHyperboloid && sh != dsHemisphere && sh != dsHypersian) { ld minx = 1e9, maxx = -1e9; @@ -660,7 +667,7 @@ EX void run_shape(eShape s) { void cancel_shape() { if(sh) { - sh = dsNone; + sh = dsHypersian; rug::good_shape = false; rug::qvalid = 0; for(auto p: rug::points) @@ -674,108 +681,136 @@ void cancel_shape() { cell *coverage_center; transmatrix coverage_matrix; -EX void show_surfaces() { - cmode = sm::SIDE; - gamescreen(); - dialog::init(XLAT("constant curvature surfaces"), iinf[itPalace].color, 150, 0); - - bool b = rug::rugged || coverage_style; +// approximations - dialog::addBoolItem(XLAT("tractricoid"), b && sh == dsTractricoid, '1'); - dialog::addBoolItem(XLAT("concave barrel"), b && sh == dsHyperlike, '2'); - dialog::addSelItem(" " + XLAT("parameter"), fts(hyper_b), '@'); - dialog::addBoolItem(XLAT("Dini's surface"), b && sh == dsDini, '3'); - dialog::addSelItem(" " + XLAT("parameter") + " ", fts(dini_b), '#'); - dialog::addBoolItem(XLAT("Kuen surface"), b && sh == dsKuen, '4'); - - dialog::addBreak(50); - - dialog::addTitle(XLAT("other 3D models"), iinf[itPalace].color, 150); +hyperpoint hilbert_at(hyperpoint s, hyperpoint e, hyperpoint d, hyperpoint as, hyperpoint ae, ld steps, ld t) { + hyperpoint ctr = (e+d)/2; + hyperpoint f = e+(d-s); + if(steps == 0) return as * (1-t) * (1-t) + ae * t * t + ctr * (1 - t*t - (1-t) * (1-t)); + if(steps < 1) return lerp(hilbert_at(s, e, d, as, ae, 0, t), hilbert_at(s, e, d, as, ae, 1, t), steps); + t *= 4; + int ti = int(t); + if(ti == 4) ti = 3; + t -= ti; + if(ti == 0) return hilbert_at(s, (s+d)/2, (s+e)/2, (s+as)/2, (s+d+e+d)/4, steps-1, t); + if(ti == 1) return hilbert_at((s+d)/2, ctr, d, (s+d+e+d)/4, (e+d+d+f)/4, steps-1, t); + if(ti == 2) return hilbert_at(ctr, (s+d)/2 + (e-s), d + (e-s)/2, (e+d+d+f)/4, (e+d+f+e)/4, steps-1, t); + if(ti == 3) return hilbert_at((s+d)/2 + (e-s), e, ctr, (e+d+f+e)/4, (e+ae)/2, steps-1, t); + throw hr_exception("hilbert_at error"); + } - dialog::addBoolItem(XLAT("Hypersian Rug"), b && sh == dsNone, '5'); - dialog::addBoolItem(XLAT("Minkowski hyperboloid"), b && sh == dsHyperboloid, '6'); - dialog::addBoolItem(XLAT("hemisphere"), b && sh == dsHemisphere, '7'); +ld approx_length_at(ld s) { + if(s < 0.5) s -= (s - 0.5) / 2; + return exp(0.5) * pow(2, s);; + } - dialog::addBreak(100); +ld approx_length_to_s(ld len) { + ld s = log(len / exp(0.5)) / log(2); + if(s < 0.5) s += (s - 0.5); + return s; + } + +bool circular = false; +int qty = 7; +ld hscale = 1; + +EX void run_hilbert() { + full_mesh(); + rug::good_shape = true; + rug::qvalid = 0; + + for(auto p: rug::points) { + hyperpoint& pt = p->native; + hyperpoint h = p->h.h; + + if(circular) { + ld r = hdist0(h); + ld phi = atan2(h); + + ld length_factor = r == 0 ? 1 : sinh(r) / r; + ld s = approx_length_to_s((approx_length_at(0.0) * length_factor)); + + ld t = phi * qty / TAU; + ld tz = floor(t); + ld x0 = -1 + 2 * tz; + ld x1 = +1 + 2 * tz; + hyperpoint h = hilbert_at(point3(x0,-1,1), point3(x1,-1,1), point3(x0,1,1), point3(x0,0,1), point3(x1,0,1), s, t-tz); + h *= TAU / (2 * qty); + pt = cspin(0, 1, h[0]) * cspin(1, 2, h[1]) * point3(0, 1, 0) * r; + } + else { + ld y = asin_auto(h[1]); + ld x = asin_auto_clamp(h[0] / cos_auto(y)); + + ld length_factor = cos_auto(y); + ld s = approx_length_to_s((approx_length_at(0.5) * length_factor)); + + ld t = x / approx_length_at(0.5) / hscale + .5; + ld tz = floor(t); + ld x0 = -1 + 2 * tz; + ld x1 = +1 + 2 * tz; + pt = hilbert_at(point3(x0,-1,1), point3(x1,-1,1), point3(x0,1,1), point3(x0,0,1), point3(x1,0,1), s, t-tz); + + pt[0] *= hscale; + pt[1] *= hscale; + pt[2] = y; + } + + pt *= rug::modelscale; + + pt[3] = 1; + + p->valid = true; + } + } + +// menu + +EX void shape_option(eShape s, char c, bool named IS(true)) { + bool b = rug::rugged || coverage_style || approx_quality; + dialog::addBoolItem(named ? XLAT(shape_name[s]) : XLAT("enable"), b && sh == s, c); + dialog::add_action([s] { run_shape(s); }); + } + +EX void show_smooth() { + rug::init_rug_page(XLAT("constant curvature surfaces")); + + shape_option(dsTractricoid, 't'); + + shape_option(dsHyperlike, 'b'); + dialog::addSelItem(" " + XLAT("parameter"), fts(hyper_b), 'B'); + dialog::add_action([] { + dialog::editNumber(hyper_b, -1, 1, .05, 1, XLAT("parameter"), + XLAT("Controls the inner radius.") + ); + dialog::get_ne().reaction = [] () { + if(sh == dsHyperlike) run_shape(sh); + }; + }); + + shape_option(dsDini, 'd'); + dialog::addSelItem(" " + XLAT("parameter") + " ", fts(dini_b), 'D'); + dialog::add_action([] { + dialog::editNumber(dini_b, -1, 1, .05, .15, XLAT("parameter"), + XLAT("The larger the number, the more twisted it is.") + ); + }); + + shape_option(dsKuen, 'k'); dialog::addSelItem(XLAT("precision"), its(precision), 'p'); + dialog::add_action([] { + dialog::editNumber(precision, 1, 10000, 0, 100, XLAT("precision"), + XLAT("Computing these models involves integrals and differential equations, which are currently solved numerically. This controls the precision.") + ); + dialog::get_ne().step = .1; + dialog::scaleLog(); + }); + string cstyles[4] = { "OFF", "on surface", "on H² (static)", "on H² (dynamic)" }; - if((rug::rugged && sh && sh != dsHyperboloid && sh != dsHemisphere) || coverage_style) + if((rug::rugged && sh && sh != dsHyperboloid && sh != dsHemisphere) || coverage_style) { dialog::addSelItem(XLAT("display coverage"), cstyles[coverage_style], 'c'); - else dialog::addBreak(100); - - #if CAP_FILES - if(rug::rugged) - dialog::addItem(XLAT("save the current embedding"), 's'); - else - dialog::addItem(XLAT("load a saved embedding"), 's'); - #endif - - dialog::addHelp(); - dialog::addBack(); - - dialog::display(); - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - if(uni == 'h' || uni == SDLK_F1) - gotoHelp(XLAT( - "In this menu you can choose from several known smooth surfaces of constant negative curvature. " - "Since the curvature of hyperbolic plane is also constant negative, these surfaces " - "are great to draw hyperbolic tesselations on. While they look great, only a small part " - "of the hyperbolic plane can be visibly represented in this way, so they are not " - "good for playing HyperRogue; however, the coverage extends far away in specific directions, " - "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect. " - "For convenience, you can also choose other 3D models from this menu." - )); - - #if CAP_FILES - else if(uni == 's') { - static string rugname = "saved.rug"; - if(rug::rugged) - dialog::openFileDialog(rugname, XLAT("save embedding to:"), ".rug", [] () { rug::rug_save(rugname); return true; }); - else - dialog::openFileDialog(rugname, XLAT("load embedding from:"), ".rug", [] () { rug::init(); rug::rug_load(rugname); return true; }); - } - #endif - else if(uni == '1') - run_shape(dsTractricoid); - else if(uni == '2') - run_shape(dsHyperlike); - else if(uni == '3') - run_shape(dsDini); - else if(uni == '4') - run_shape(dsKuen); - else if(uni == '5') - run_shape(dsNone); - else if(uni == '6') - run_shape(dsHyperboloid); - else if(uni == '7') - run_shape(dsHemisphere); - - else if(uni == '@') { - dialog::editNumber(hyper_b, -1, 1, .05, 1, XLAT("parameter"), - XLAT("Controls the inner radius.") - ); - dialog::get_ne().reaction = [] () { - if(sh == dsHyperlike) run_shape(sh); - }; - } - else if(uni == 'x') - for(auto p: rug::points) - p->native = p->surface_point.params; - else if(uni == '#') - dialog::editNumber(dini_b, -1, 1, .05, .15, XLAT("parameter"), - XLAT("The larger the number, the more twisted it is.") - ); - else if(uni == 'p') { - dialog::editNumber(precision, 1, 10000, 0, 100, XLAT("precision"), - XLAT("Computing these models involves integrals and differential equations, which are currently solved numerically. This controls the precision.") - ); - dialog::get_ne().step = .1; - dialog::scaleLog(); - } - else if(uni == 'c') { + dialog::add_action([] { coverage_style = (1 + coverage_style) % 4; if(coverage_style == 0) { rug::reopen(); @@ -784,10 +819,135 @@ EX void show_surfaces() { if(rug::rugged) rug::close(); } coverage_matrix = inverse(unshift(ggmatrix(coverage_center = cwt.at))); + }); + } + else dialog::addBreak(100); + + dialog::addHelp(); + dialog::add_action([] { + gotoHelp(XLAT( + "In this menu you can choose from several known smooth surfaces of constant negative curvature. " + "Since the curvature of hyperbolic plane is also constant negative, these surfaces " + "are great to draw hyperbolic tesselations on. While they look great, only a small part " + "of the hyperbolic plane can be visibly represented in this way, so they are not " + "good for playing HyperRogue; however, the coverage extends far away in specific directions, " + "so first increasing sight range in graphics config and texture size in hypersian rug settings may improve the effect." + )); + }); + + rug::close_rug_page(); + } + +EX void display_approx() { + dialog::addBoolItem_action(XLAT("approximation quality"), approx_quality, 'A'); + dialog::addCustom(50, [] { + if(approx_quality) { + int mid = (dialog::top + dialog::tothei) / 2; + int siz = vid.fsize / 2; + for(int s=0; s<=10; s++) { + ld r = exp(lerp(log(v_approx_min), log(v_approx_max), s/10.)); + color_t col = rainbow_color(1, s / 10.); + displaystr(dialog::dcenter + (dialog::dialogwidth - 3 * vid.fsize) * lerp(-0.5, 0.5, s/10.), mid, 0, siz, format("%.3f", r), col, 8); + } } - else if(rug::handlekeys(sym, uni)) ; - else if(doexiton(sym, uni)) popScreen(); - }; + }); + if(approx_quality) { + dialog::addSelItem(XLAT("set scale to"), format("%lf..%lf", approx_min, approx_max), 'S'); + dialog::add_action([] { v_approx_max = approx_max; v_approx_min = approx_min; }); + } + else dialog::addBreak(100); + } + +EX void show_hilbert() { + rug::init_rug_page(XLAT("Hilbert Rug")); + + shape_option(dsHilbert, 'u', false); + + dialog::addBoolItem(XLAT("circular mode"), circular, 'C'); + dialog::add_action([] { circular = !circular; if(sh == dsHilbert) run_shape(dsHilbert); }); + + if(circular) { + dialog::addSelItem(XLAT("scale"), its(qty), 'Q'); + dialog::add_action([] { + dialog::editNumber(qty, 1, 100, 1, 7, XLAT("scale"), + XLAT("The smaller the number, the flatter it is.") + ); + dialog::get_ne().reaction = [] { if(sh == dsHilbert) run_shape(dsHilbert); }; + }); + } + else { + dialog::addSelItem(XLAT("scale"), fts(hscale), 'S'); + dialog::add_action([] { + dialog::editNumber(hscale, 1e-3, 1e3, .05, 1, XLAT("scale"), + XLAT("The smaller the number, the flatter it is.") + ); + dialog::get_ne().reaction = [] { if(sh == dsHilbert) run_shape(dsHilbert); }; + }); + } + + rug::close_rug_page(); + } + +string finger_help = + "Press 1 while mouse is over the surface to enable the finger force. In this mode, " + "pressing arrow keys affects the shape of the surface (simulated pulling)."; + +EX void show_other() { + rug::init_rug_page(XLAT("other features")); + + dialog::addTitle(XLAT("other 3D models"), iinf[itPalace].color, 150); + + shape_option(dsHyperboloid, 'h'); + shape_option(dsHemisphere, 's'); + + dialog::addBreak(100); + + #if CAP_FILES + if(rug::rugged) + dialog::addItem(XLAT("save the current embedding"), 's'); + else + dialog::addItem(XLAT("load a saved embedding"), 's'); + dialog::add_action([] { + static string rugname = "saved.rug"; + if(rug::rugged) + dialog::openFileDialog(rugname, XLAT("save embedding to:"), ".rug", [] () { rug::rug_save(rugname); return true; }); + else + dialog::openFileDialog(rugname, XLAT("load embedding from:"), ".rug", [] () { rug::init(); rug::rug_load(rugname); return true; }); + }); + #endif + + if(rug::rugged) { + dialog::addSelItem(XLAT("radar"), rug::radar_distance == rug::RADAR_INF ? "∞" : fts(rug::radar_distance, 4), 'r'); + dialog::add_action([] { + addMessage(XLAT("This just shows the 'z' coordinate of the selected point.")); + }); + } + else dialog::addBreak(100); + + dialog::addItem(XLAT("surface_point to native"), 'x'); + dialog::add_action([] { + for(auto p: rug::points) + p->native = p->surface_point.params; + }); + + display_approx(); + + dialog::add_key_action('1', rug::activate_finger); + + dialog::addSelItem(XLAT("finger range"), fts(rug::finger_range), 'R'); + dialog::add_action([] { + dialog::editNumber(rug::finger_range, 0, 1, .01, .1, XLAT("finger range"), + XLAT(finger_help) + ); + }); + dialog::addSelItem(XLAT("finger force"), fts(rug::finger_force), 'F'); + dialog::add_action([] { + dialog::editNumber(rug::finger_force, 0, 1, .01, .1, XLAT("finger force"), + XLAT(finger_help) + ); + }); + + rug::close_rug_page(); } #if CAP_COMMANDLINE @@ -835,8 +995,10 @@ int surface_args() { run_shape(dsHyperboloid); } - else if(argis("-d:surface")) - launch_dialog(show_surfaces); + else if(argis("-d:surface")) { + launch_dialog(rug::show); + launch_dialog(show_smooth); + } else return 1; return 0; @@ -847,7 +1009,12 @@ int surface_args() { auto surface_hook = addHook(hooks_args, 100, surface_args); #endif -void display_coverage() { +EX ld approx_min; +EX ld approx_max; +EX ld v_approx_min = 0.5; +EX ld v_approx_max = 1.5; + +void display_coverage_and_approx_quality() { shiftmatrix M = coverage_style == 3 ? ggmatrix(coverage_center) * coverage_matrix @@ -858,9 +1025,32 @@ void display_coverage() { queuestr(M * p.first, 10, s0+char(p.second), dexp_comb_colors[(p.second >> 8) & 15]); /* if(p->valid && p->surface_point.remaining_distance == 0) queuestr(p->h, 10, "x", dexp_colors[p->dexp_id]); */ + + if(approx_quality) { + vid.linewidth *= 4; + approx_min = INF, approx_max = 0; + for(auto p: rug::points) { + for(auto& e: p->edges) { + if(e.os > e.ot) continue; + if(!e.os->valid || !e.ot->valid) continue; + ld d; + if(1) { + USING_NATIVE_GEOMETRY; + d = hdist(e.ot->native, e.os->native); + } + ld ratio = d / e.len; + if(ratio < approx_min) approx_min = ratio; + if(ratio > approx_max) approx_max = ratio; + ld z = log(ratio) / log(v_approx_max / v_approx_min); + color_t col = (rainbow_color(1, z) << 8) | 0xFF; + queueline(e.ot->h, e.os->h, col, 0); + } + } + vid.linewidth /= 4; + } } -auto surface_hook2 = addHook(hooks_frame, 0, display_coverage); +auto surface_hook2 = addHook(hooks_frame, 0, display_coverage_and_approx_quality); EX } #endif