diff --git a/graph.cpp b/graph.cpp index 14b1e3fe..78bd2bc3 100644 --- a/graph.cpp +++ b/graph.cpp @@ -4630,6 +4630,8 @@ EX void drawthemap() { profile_stop(4); drawFlashes(); + mapeditor::draw_dtshapes(); + if(multi::players > 1 && !shmup::on) { if(multi::centerplayer != -1) cwtV = multi::whereis[multi::centerplayer]; diff --git a/hyper.h b/hyper.h index 208d75b3..25ce4b3f 100644 --- a/hyper.h +++ b/hyper.h @@ -13,8 +13,8 @@ #define _HYPER_H_ // version numbers -#define VER "11.3i" -#define VERNUM_HEX 0xA829 +#define VER "11.3j" +#define VERNUM_HEX 0xA82A #include "sysconfig.h" diff --git a/mapeditor.cpp b/mapeditor.cpp index b964c992..660b4c68 100644 --- a/mapeditor.cpp +++ b/mapeditor.cpp @@ -10,11 +10,224 @@ namespace hr { EX namespace mapeditor { + EX bool drawing_tool; + #if HDR enum eShapegroup { sgPlayer, sgMonster, sgItem, sgFloor, sgWall }; static const int USERSHAPEGROUPS = 5; #endif + + color_t dtfill; + /* drawing_tool shapes */ + struct dtshape { + cell *where; + color_t col, fill; + ld lw; + + virtual void rotate(const transmatrix& T) = 0; + virtual void save(hstream& hs) = 0; + virtual dtshape* load(hstream& hs) = 0; + virtual void draw(const transmatrix& V) = 0; + virtual ld distance(hyperpoint h) = 0; + }; + + struct dtline : dtshape { + + hyperpoint s, e; + + void rotate(const transmatrix& T) override { + s = T * s; + e = T * e; + } + void save(hstream& hs) override { + hs.write(1); + hs.write(s); + hs.write(e); + } + dtshape *load(hstream& hs) override { + hs.read(s); + hs.read(e); + return this; + } + void draw(const transmatrix& V) override { + queueline(V*s, V*e, col, 2 + vid.linequality); + } + ld distance(hyperpoint h) override { + return hdist(s, h) + hdist(e, h) - hdist(s, e); + } + }; + + struct dtcircle : dtshape { + + hyperpoint s; ld radius; + + void rotate(const transmatrix& T) override { + s = T * s; + } + void save(hstream& hs) override { + hs.write(2); + hs.write(s); + hs.write(radius); + } + dtshape *load(hstream& hs) override { + hs.read(s); + hs.read(radius); + return this; + } + void draw(const transmatrix& V) override { + ld len = sin_auto(radius); + int ll = ceil(360 * len); + transmatrix W = V * rgpushxto0(s); + for(int i=0; i<=ll; i++) + curvepoint(W * xspinpush0(360*degree*i/ll, radius)); + queuecurve(col, fill, PPR::LINE); + } + + ld distance(hyperpoint h) override { + return abs(hdist(s, h) - radius); + } + }; + + struct dtfree : dtshape { + + vector lh; + + void rotate(const transmatrix& T) override { + for(auto& h: lh) h = T * h; + } + void save(hstream& hs) override { + hs.write(3); + hs.write(lh); + } + dtshape *load(hstream& hs) override { + hs.read(lh); + return this; + } + void draw(const transmatrix& V) override { + for(auto& p: lh) curvepoint(V*p); + queuecurve(col, fill, PPR::LINE); + } + + ld distance(hyperpoint h) override { + ld mind = 99; + for(auto h1: lh) mind = min(mind, hdist(h, h1)); + return mind; + } + }; + + vector> dtshapes; + + EX void draw_dtshapes() { + for(auto& shp: dtshapes) { + if(shp == nullptr) continue; + auto& sh = *shp; + cell*& c = sh.where; + for(const transmatrix& V: current_display->all_drawn_copies[c]) { + dynamicval lw(vid.linewidth, vid.linewidth * sh.lw); + sh.draw(V); + } + } + } + + /** dtshapes takes ownership of sh */ + void dt_add(cell *where, dtshape *sh) { + sh->where = where; + sh->col = texture::config.paint_color; + sh->fill = dtfill; + sh->lw = texture::penwidth * 100; + + dtshapes.push_back(unique_ptr(sh)); + } + + EX void dt_add_line(hyperpoint h1, hyperpoint h2, int maxl) { + if(hdist(h1, h2) > 1 && maxl > 0) { + hyperpoint h3 = mid(h1, h2); + dt_add_line(h1, h3, maxl-1); + dt_add_line(h3, h2, maxl-1); + return; + } + cell *b = centerover; + transmatrix T = rgpushxto0(h1); + + auto T1 = inverse(ggmatrix(b)) * T; + virtualRebase(b, T1); + + auto l = new dtline; + l->s = tC0(T1); + l->e = T1 * gpushxto0(h1) * h2; + dt_add(b, l); + } + + EX void dt_add_circle(hyperpoint h1, hyperpoint h2) { + cell *b = centerover; + + auto d = hdist(h1, h2); + + h1 = inverse(ggmatrix(b)) * h1; + virtualRebase(b, h1); + + auto l = new dtcircle; + l->s = h1; + l->radius = d; + dt_add(b, l); + } + + dtshape *load_shape(hstream& hs) { + char type = hs.get(); + switch(type) { + case 1: + return (new dtline)->load(hs); + case 2: + return (new dtcircle)->load(hs); + case 3: + return (new dtfree)->load(hs); + default: + return nullptr; + } + } + + dtfree *cfree; + cell *cfree_at; + transmatrix cfree_T; + + EX void dt_add_free(hyperpoint h) { + + cell *b = centerover; + transmatrix T = rgpushxto0(h); + auto T1 = inverse(ggmatrix(b)) * T; + virtualRebase(b, T1); + + if(cfree) + cfree->lh.push_back(cfree_T * h); + + if(b != cfree_at && !(dtfill && cfree_at)) { + cfree = new dtfree; + dt_add(b, cfree); + cfree_T = T1 * gpushxto0(h); + cfree->lh.push_back(cfree_T * h); + cfree_at = b; + } + } + + EX void dt_erase(hyperpoint h) { + ld nearest = 1; + int nearest_id = -1; + int id = -1; + for(auto& shp: dtshapes) { + id++; + if(shp == nullptr) continue; + auto& sh = *shp; + cell*& c = sh.where; + for(const transmatrix& V: current_display->all_drawn_copies[c]) { + ld dist = sh.distance(inverse(V) * h); + if(dist < nearest) nearest = dist, nearest_id = id; + } + } + if(nearest_id >= 0) + dtshapes.erase(dtshapes.begin() + nearest_id); + } + hyperpoint lstart; cell *lstartcell; ld front_edit = 0.5; @@ -101,6 +314,39 @@ namespace mapstream { vector cellbyid; vector relspin; + void load_drawing_tool(fhstream& hs) { + using namespace mapeditor; + if(hs.vernum < 0xA82A) return; + int i = hs.get(); + while(i--) { + auto sh = load_shape(hs); + if(!sh) continue; + hs.read(sh->col); + hs.read(sh->fill); + hs.read(sh->lw); + int id = hs.get(); + sh->where = cellbyid[id]; + sh->rotate(spin(currentmap->spin_angle(sh->where, relspin[id]) - currentmap->spin_angle(sh->where, 0))); + dtshapes.push_back(unique_ptr(sh)); + } + } + + void save_drawing_tool(hstream& hs) { + using namespace mapeditor; + hs.write(isize(dtshapes)); + for(auto& shp: dtshapes) { + if(shp == nullptr) { hs.write(0); } + else { + auto& sh = *shp; + sh.save(hs); + hs.write(sh.col); + hs.write(sh.fill); + hs.write(sh.lw); + hs.write(cellids[sh.where]); + } + } + } + void addToQueue(cell* c) { if(cellids.count(c)) return; @@ -325,6 +571,8 @@ namespace mapstream { int32_t n = -1; f.write(n); int32_t id = cellids.count(cwt.at) ? cellids[cwt.at] : -1; f.write(id); + + save_drawing_tool(f); f.write(vid.always3); f.write(mutantphase); @@ -507,6 +755,8 @@ namespace mapstream { savecount = 0; savetime = 0; cheater = 1; + load_drawing_tool(f); + dynamicval a3(vid.always3, vid.always3); if(f.vernum >= 0xA616) { f.read(vid.always3); geom3::apply_always3(); } @@ -983,6 +1233,32 @@ namespace mapeditor { if(d == -1 && fix) d = hrand(mouseover->type); return cellwalker(mouseover, d); } + + void save_level() { + dialog::openFileDialog(levelfile, XLAT("level to save:"), ".lev", [] () { + if(mapstream::saveMap(levelfile.c_str())) { + addMessage(XLAT("Map saved to %1", levelfile)); + return true; + } + else { + addMessage(XLAT("Failed to save map to %1", levelfile)); + return false; + } + }); + } + + void load_level() { + dialog::openFileDialog(levelfile, XLAT("level to load:"), ".lev", [] () { + if(mapstream::loadMap(levelfile.c_str())) { + addMessage(XLAT("Map loaded from %1", levelfile)); + return true; + } + else { + addMessage(XLAT("Failed to load map from %1", levelfile)); + return false; + } + }); + } void showList() { dialog::v.clear(); @@ -1085,29 +1361,8 @@ namespace mapeditor { else if(sym == SDLK_F5) { restart_game(); } - else if(sym == SDLK_F2) { - dialog::openFileDialog(levelfile, XLAT("level to save:"), ".lev", [] () { - if(mapstream::saveMap(levelfile.c_str())) { - addMessage(XLAT("Map saved to %1", levelfile)); - return true; - } - else { - addMessage(XLAT("Failed to save map to %1", levelfile)); - return false; - } - }); - } - else if(sym == SDLK_F3) - dialog::openFileDialog(levelfile, XLAT("level to load:"), ".lev", [] () { - if(mapstream::loadMap(levelfile.c_str())) { - addMessage(XLAT("Map loaded from %1", levelfile)); - return true; - } - else { - addMessage(XLAT("Failed to load map from %1", levelfile)); - return false; - } - }); + else if(sym == SDLK_F2) save_level(); + else if(sym == SDLK_F3) load_level(); #if CAP_SHOT else if(sym == SDLK_F6) { pushScreen(shot::menu); @@ -1363,6 +1618,9 @@ namespace mapeditor { usershape *us = NULL; bool intexture = false; + (void) intexture; + + bool freedraw = drawing_tool; #if CAP_TEXTURE if(texture::config.tstate == texture::tsActive) { @@ -1371,12 +1629,27 @@ namespace mapeditor { line2 = ""; texture::config.data.update(); intexture = true; + freedraw = true; + drawing_tool = false; } -#else - if(0); #endif + + if(drawing_tool && !mouseout()) { + initquickqueue(); + dynamicval lw(vid.linewidth, vid.linewidth * texture::penwidth * 100); + if(holdmouse && mousekey == 'c') + queue_hcircle(rgpushxto0(lstart), hdist(lstart, mouseh)); + else if(holdmouse && mousekey == 'l') + queueline(lstart, mouseh, texture::config.paint_color, 4 + vid.linequality, PPR::LINE); + else if(!holdmouse) { + transmatrix T = rgpushxto0(mouseh); + queueline(T * xpush0(-.1), T * xpush0(.1), texture::config.paint_color); + queueline(T * ypush0(-.1), T * ypush0(.1), texture::config.paint_color); + } + quickqueue(); + } - else { + if(!freedraw) { sg = drawcellShapeGroup(); @@ -1419,7 +1692,7 @@ namespace mapeditor { // displayButton(8, 8+fs*9, XLAT("l = lands"), 'l', 0); displayfr(8, 8+fs, 2, vid.fsize, line1, 0xC0C0C0, 0); - if(!intexture) { + if(!freedraw) { if(sg == sgFloor) displayButton(8, 8+fs*2, line2 + XLAT(" (r = complex tesselations)"), 'r', 0); else @@ -1466,17 +1739,21 @@ namespace mapeditor { } #if CAP_TEXTURE - else if(texture::config.tstate == texture::tsActive) { + else if(freedraw) { displayButton(8, 8+fs*2, XLAT(texture::texturesym ? "0 = symmetry" : "0 = asymmetry"), '0', 0); if(mousekey == 'g') displayButton(8, 8+fs*16, XLAT("p = grid color"), 'p', 0); else displayButton(8, 8+fs*16, XLAT("p = color"), 'p', 0); + if(drawing_tool) + displayButton(8, 8+fs*17, XLAT("f = fill") + (dtfill ? " (on)" : " (off)"), 'f', 0); displayButton(8, 8+fs*4, XLAT("b = brush size: %1", fts(texture::penwidth)), 'b', 0); displayButton(8, 8+fs*5, XLAT("u = undo"), 'u', 0); displaymm('d', 8, 8+fs*7, 2, vid.fsize, XLAT("d = draw"), 0); displaymm('l', 8, 8+fs*8, 2, vid.fsize, XLAT("l = line"), 0); displaymm('c', 8, 8+fs*9, 2, vid.fsize, XLAT("c = circle"), 0); + if(drawing_tool) + displaymm('e', 8, 8+fs*10, 2, vid.fsize, XLAT("e = erase"), 0); int s = isize(texture::config.data.pixels_to_draw); if(s) displaymm(0, 8, 8+fs*11, 2, vid.fsize, its(s), 0); } @@ -1498,7 +1775,7 @@ namespace mapeditor { displaymm('g', vid.xres-8, 8+fs*4, 2, vid.fsize, XLAT("g = grid"), 16); #if CAP_TEXTURE - if(intexture) for(int i=0; i<10; i++) { + if(freedraw) for(int i=0; i<10; i++) { if(8 + fs * (6+i) < vid.yres - 8 - fs * 7) displayColorButton(vid.xres-8, 8+fs*(6+i), "###", 1000 + i, 16, 1, dialog::displaycolor(texture_colors[i+1])); @@ -1506,7 +1783,7 @@ namespace mapeditor { getcstat = 2000+i; } - if(texture::config.tstate != texture::tsActive) + if(!freedraw) displaymm('e', vid.xres-8, 8+fs, 2, vid.fsize, XLAT("e = edit this"), 16); #endif @@ -1933,9 +2210,14 @@ namespace mapeditor { if(mkuni == 'g') coldcenter = ccenter, ccenter = mh, clickused = true; - if(uni == 'd' || uni == 'l' || uni == 'c') + if(uni == 'd' || uni == 'l' || uni == 'c' || uni == 'e') mousekey = uni; + if(drawing_tool) { + if(sym == SDLK_F2) save_level(); + if(sym == SDLK_F3) load_level(); + } + if(uni == ' ' && (cheater || autocheat)) { popScreen(); pushScreen(showMapEditor); @@ -2001,32 +2283,54 @@ namespace mapeditor { if(sym == SDLK_F10) popScreen(); -#if CAP_TEXTURE - if(texture::config.tstate == texture::tsActive) { + (void)clickused; + + + bool freedraw = drawing_tool; + #if CAP_TEXTURE + bool intexture = texture::config.tstate == texture::tsActive; + freedraw |= intexture; + #else + always_false intexture; + #endif + + if(freedraw) { int tcolor = (texture::config.paint_color >> 8) | ((texture::config.paint_color & 0xFF) << 24); if(uni == '-' && !clickused) { - if(mousekey == 'l' || mousekey == 'c') { + if(mousekey == 'e') { + dt_erase(mouseh); + clickused = true; + } + else if(mousekey == 'l' || mousekey == 'c') { if(!holdmouse) lstart = mouseh, lstartcell = mouseover, holdmouse = true; } - else { + else if(intexture) { if(!holdmouse) texture::config.data.undoLock(); texture::drawPixel(mouseover, mouseh, tcolor); holdmouse = true; lstartcell = NULL; } + else { + dt_add_free(mouseh); + holdmouse = true; + } } if(sym == PSEUDOKEY_RELEASE) { printf("release\n"); - if(mousekey == 'l') { + if(mousekey == 'l' && intexture) { texture::config.data.undoLock(); texture::where = mouseover; texture::drawPixel(mouseover, mouseh, tcolor); texture::drawLine(mouseh, lstart, tcolor); lstartcell = NULL; } - if(mousekey == 'c') { + else if(mousekey == 'l') { + dt_add_line(mouseh, lstart, 10); + lstartcell = NULL; + } + else if(mousekey == 'c' && intexture) { texture::config.data.undoLock(); ld rad = hdist(lstart, mouseh); int circp = int(1 + 3 * (circlelength(rad) / texture::penwidth)); @@ -2037,6 +2341,14 @@ namespace mapeditor { texture::drawPixel(T * xspinpush0(2 * M_PI * i / circp, rad), tcolor); lstartcell = NULL; } + else if(mousekey == 'c') { + dt_add_circle(lstart, mouseh); + lstartcell = NULL; + } + else { + cfree = nullptr; + cfree_at = nullptr; + } } if(uni >= 1000 && uni < 1010) @@ -2057,13 +2369,16 @@ namespace mapeditor { dialog::openColorDialog(texture::config.paint_color, texture_colors); } + if(uni == 'f') { + if(dtfill == texture::config.paint_color) + dtfill = 0; + else + dtfill = texture::config.paint_color; + } + if(uni == 'b') dialog::editNumber(texture::penwidth, 0, 0.1, 0.005, 0.02, XLAT("brush size"), XLAT("brush size")); } -#else - (void)clickused; - if(0); -#endif else { dslayer %= USERLAYERS; @@ -2137,6 +2452,9 @@ namespace mapeditor { if(!cheater) patterns::displaycodes = false; if(!cheater) patterns::whichShape = 0; modelcell.clear(); + mapeditor::dtshapes.clear(); + mapeditor::cfree = nullptr; + mapeditor::cfree_at = nullptr; }) + addHook(hooks_removecells, 0, [] () { modelcell.clear(); @@ -2159,8 +2477,7 @@ namespace mapeditor { transmatrix textrans; -#if CAP_TEXTURE - void queue_hcircle(transmatrix Ctr, ld radius) { + EX void queue_hcircle(transmatrix Ctr, ld radius) { vector pts; int circp = int(6 * pow(2, vid.linequality)); if(radius > 0.04) circp *= 2; @@ -2172,7 +2489,6 @@ namespace mapeditor { curvepoint(pts[0]); queuecurve(texture::config.paint_color, 0, PPR::LINE); } -#endif #if CAP_POLY EX bool haveUserShape(eShapegroup group, int id) { diff --git a/menus.cpp b/menus.cpp index 7a98e75a..91af8d9d 100644 --- a/menus.cpp +++ b/menus.cpp @@ -433,13 +433,25 @@ EX void showCreative() { #endif #if CAP_EDIT - dialog::addItem(XLAT("vector graphics editor"), 'g'); + dialog::addItem(XLAT("shape editor"), 'g'); dialog::add_action([] { + mapeditor::drawing_tool = false; pushScreen(mapeditor::showDrawEditor); mapeditor::initdraw(cwt.at); }); #endif +#if CAP_EDIT + dialog::addItem(XLAT("drawing tool"), 'd'); + dialog::add_action([] { + dialog::cheat_if_confirmed([] { + mapeditor::drawing_tool = true; + pushScreen(mapeditor::showDrawEditor); + mapeditor::initdraw(cwt.at); + }); + }); +#endif + // display modes #if CAP_MODEL if(GDIM == 2) {