#include "hyper.h" namespace hr { EX namespace intra { EX bool in; #if HDR /** information per every space connected with intra-portals */ struct intra_data { gamedata gd; geometryinfo gi; int wallindex; }; #endif EX vector data; /** index of the space we are currently in */ EX int current; /** map cells to their intra spaces */ EX map intra_id; #if HDR /** information about portal (one side) */ struct portal_data { int kind; hyperpoint v0; ld d; transmatrix T; transmatrix iT; hyperpoint co0; hyperpoint co1; ld scale; /* convert h to portal coordinates ('poco') to usual coordinates */ hyperpoint to_poco(hyperpoint h) const; /* convert h from portal coordinates ('poco') to usual coordinates */ hyperpoint from_poco(hyperpoint h) const; }; #endif hyperpoint portal_data::to_poco(hyperpoint h) const { if(prod && kind == 1) { auto dec = product_decompose(h); h = dec.second; h[0] /= h[2]; h[1] /= h[2]; h[2] = dec.first - d; h[3] = 1; if(d<0) h[2] = -h[2], h[0] = -h[0]; return h; } else if(prod && kind == 0) { h = T * h; ld z = product_decompose(h).first; h /= exp(z); auto h1 = h; h[0] = asin_auto(h1[1]); h[1] = z; h[2] = asin_auto_clamp(h1[0] / cos_auto(h[0])); h[3] = 1; return h; } else if(hyperbolic && bt::in()) { h = T * bt::minkowski_to_bt(h); for(int i: {0,1,2}) h[i] *= (log(2)/2); return h; } else if(sol) return T * h; else { h = T * h; h /= h[3]; return h; } } hyperpoint portal_data::from_poco(hyperpoint h) const { if(prod && kind == 1) { ld xd = h[2]; if(d<0) xd = -xd, h[0] = -h[0]; h[2] = 1; auto z = product_decompose(h).first; return h * exp(d+xd-z); } else if(prod && kind == 0) { auto h0 = h; h[0] = sin_auto(h0[2]) * cos_auto(h0[0]); h[1] = sin_auto(h0[0]); h[2] = cos_auto(h0[0]) * cos_auto(h0[2]); h[3] = 1; return iT * h * exp(h0[1]); } else if(hyperbolic && bt::in()) { for(int i: {0,1,2}) h[i] *= (2/log(2)); return bt::bt_to_minkowski(iT * h); } else if(sol) return iT * h; else { h[3] = 1; return normalize(iT * h); } } EX portal_data make_portal(cellwalker cw, int spin) { auto& ss = currentmap->get_cellshape(cw.at); auto fac = ss.faces[cw.spin]; portal_data id; id.scale = 1; auto gg = geometry; if(prod && cw.spin == cw.at->type - 1) { id.kind = 1; id.d = product_decompose(fac[0]).first; id.v0 = C0 * exp(id.d); } else if(prod && cw.spin == cw.at->type - 2) { id.kind = 1; id.d = product_decompose(fac[0]).first; id.v0 = C0 * exp(id.d); } else if(prod) { id.kind = 0; id.v0 = Hypc; id.scale = cgi.plevel; for(auto p: fac) id.v0 += p; id.v0 /= sqrt(abs(intval(id.v0, Hypc))); id.T = cpush(0, -hdist0(id.v0)) * spintox(id.v0); for(int i=0; i<3; i++) id.T[3][i] = id.T[i][3] = i==3; if(debugflags & DF_GEOM) for(int a=0; a<4; a++) { hyperpoint h = fac[a]; println(hlog, kz(h), " -> ", kz(spintox(id.v0)*h), " -> ", kz(cpush(0, -hdist0(id.v0))) * kz(spintox(id.v0) * h), " -> ", kz(id.to_poco(h))); } } else if(bt::in()) { for(int i=0; i= isize(fac)) i1 = 0; int i2 = i1+1; if(i2 >= isize(fac)) i2 = 0; if(hypot_d(3, 2*fac[i1] - fac[i] - fac[i2]) < 1e-3) fac.erase(fac.begin()+i1); } id.kind = 0; id.v0 = Hypc; id.T = Id; for(auto& h: fac) h[3] = 1; for(auto p: fac) id.v0 += p; id.v0 /= isize(fac); dynamicval g(geometry, gCubeTiling); id.T = gpushxto0(id.v0); for(auto p: fac) { if(abs((id.T * p)[2]) > 1e-3 && abs((id.T * p)[0]) < 1e-3) id.T = cspin(2, 0, 90*degree) * id.T; if(abs((id.T * p)[2]) > 1e-3 && abs((id.T * p)[1]) < 1e-3) id.T = cspin(2, 1, 90*degree) * id.T; } if((id.T * C03)[2] > 0) id.T = cspin(2, 0, 180*degree) * id.T; vector v; geometry = gg; for(auto f: fac) v.push_back(id.to_poco(final_coords(f))); geometry = gCubeTiling; ld sca = 1; for(int i=0; i connections; EX connection_data* find_connection(int a, int b) { for(auto& p: connections) if(intra_id[p.first.at] == a && p.second.target_world == b) return &p.second; return nullptr; } EX void switch_to(int id) { if(current == id) return; data[current].gd.storegame(); current = id; ginf[gProduct] = data[current].gi; data[current].gd.restoregame(); } void connect_portal_1(cellwalker cw1, cellwalker cw2, int spin) { auto& p = connections[cw1]; p.source_world = intra_id.at(cw1.at); p.target_world = intra_id.at(cw2.at); p.scw = cw1; p.tcw = cw2; switch_to(intra_id.at(cw1.at)); int pspin = 0, nspin = 0; if(spin > 0) pspin = spin; else nspin = -spin; p.id1 = make_portal(cw1, nspin); switch_to(intra_id.at(cw2.at)); p.id2 = make_portal(cw2, pspin); p.spin_value = spin; if(1) { dynamicval g(geometry, gCubeTiling); transmatrix T1; set_column(T1, 0, p.id1.co0); set_column(T1, 1, p.id1.co1); set_column(T1, 2, hyperpoint(0,0,p.id1.scale,0)); set_column(T1, 3, C03); transmatrix T2; set_column(T2, 0, p.id2.co0); set_column(T2, 1, p.id2.co1); set_column(T2, 2, hyperpoint(0,0,-p.id2.scale,0)); set_column(T2, 3, C03); if(debugflags & DF_GEOM) for(int i=0; i<4; i++) println(hlog, "mapping ", get_column(T1, i), " to ", get_column(T2, i)); p.T = T2 * inverse(T1); if(det(p.T) < 0) { set_column(T2, 0, p.id2.co1); set_column(T2, 1, p.id2.co0); p.T = T2 * inverse(T1); } } } EX vector> full_sample_list; EX void connect_portal(cellwalker cw1, cellwalker cw2, int spin) { connect_portal_1(cw1, cw2, spin); connect_portal_1(cw2, cw1, -spin); } /** make currentmap into one of the spaces in intra */ EX void become() { check_cgi(); cgi.require_shapes(); auto& ac = currentmap->allcells(); current = isize(data); for(cell *c: ac) intra_id[c] = current; for(cell *c: ac) currentmap->wall_offset(c); for(cell *c: ac) c->item = itNone; data.emplace_back(); data.back().gd.storegame(); data.back().gi = ginf[gProduct]; auto v = hybrid::gen_sample_list(); int gi = 0; if(full_sample_list.size()) { gi = full_sample_list.back().first; full_sample_list.pop_back(); } data.back().wallindex = gi; for(auto x: v) full_sample_list.emplace_back(x.first + gi, x.second); sightranges[geometry] = 10; } /** after called become() on some spaces, actually start intra */ EX void start(int id IS(0)) { in = true; current = id; data[current].gd.restoregame(); ginf[gProduct] = data[current].gi; again: int missing = 0; for(auto p: intra_id) for(int i=0; itype; i++) { cell *c1 = p.first->move(i); if(!c1) continue; if(intra_id.count(c1) == 0) { intra_id[c1] = p.second; missing++; } } if(missing) goto again; } #if HDR /** a convenience struct to switch back after a temporary switch_to */ struct resetter { int ic; resetter() { ic = current; } ~resetter() { if(in) switch_to(ic); } }; #endif EX void may_switch_to(cell *c) { if(in) switch_to(intra_id[c]); } EX int full_wall_offset(cell *c) { int wo = currentmap->wall_offset(c); if(in) wo += data[intra_id[c]].wallindex; return wo; } EX void shift_view_portal(hyperpoint H) { shift_view(H); if(!through_portal()) return; shift_view(-H); ld minv = 0, maxv = 1; for(int i=0; i<30; i++) { ld t = (minv + maxv) / 2; shift_view(H * t); bool b = through_portal(); if(b) maxv = t; else minv = t; shift_view(H * -t); } println(hlog, "maxv = ", maxv); shift_view(H * maxv); check_portal_movement(); shift_view(H * (1 - maxv)); } EX const connection_data* through_portal() { transmatrix iView = view_inverse(View); ld dist = hdist0(iView * C0); int nei = -1; for(int i=0; itype; i++) { ld dist1 = hdist0(currentmap->ray_iadj(centerover, i) * iView * C0); if(dist1 < dist) nei = i, dist = dist1; } auto cw1 = cellwalker(centerover, nei); return at_or_null(connections, cw1); } EX void check_portal_movement() { auto p = through_portal(); ld c = camera_speed; if(p) { ld eps = 1e-3; c /= p->id1.scale; anims::cycle_length /= p->id1.scale; array ds; /* camera, forward, upward */ ds[0] = inverse(View) * C0; ds[1] = inverse(get_shift_view_of(ctangent(2, -eps), View)) * C0; ds[2] = inverse(get_shift_view_of(ctangent(1, +eps), View)) * C0; for(auto& h: ds) h = p->id1.to_poco(h); /* reset the original */ View = Id; NLP = Id; switch_to(p->target_world); centerover = p->tcw.at; if(1) { dynamicval g(geometry, gCubeTiling); for(auto& h: ds) h = p->T * h; } for(auto& h: ds) h = p->id2.from_poco(h); set_view(ds[0], ds[1], ds[2]); c *= p->id2.scale; anims::cycle_length *= p->id2.scale; camera_speed = c; } } vector unconnected; void erase_unconnected(cellwalker cw) { for(int i=0; i= 0 && point_direction < centerover->type; dialog::addItem(XLAT("move to the next space"), 'm'); dialog::add_action([] { int ic = (current + 1) % isize(data); switch_to(ic); }); bool in_list = false; for(cellwalker x: unconnected) if(x == cw) in_list = true; if(!valid) ; else if(connections.count(cw)) { dialog::addItem(XLAT("disconnect this portal"), 'd'); dialog::add_action([cw] { auto tcw = connections[cw].tcw; unconnected.push_back(tcw); connections.erase(cw); connections.erase(tcw); }); } else if(in_list) { dialog::addItem(XLAT("remove %1 from the list", lalign(0, cw)), 'r'); dialog::add_action([cw] { erase_unconnected(cw); }); } else { dialog::addItem(XLAT("add to list"), 'a'); dialog::add_action([cw] { unconnected.push_back(cw); }); for(auto p: unconnected) { dialog::addItem(XLAT("connect " + lalign(0, p)), '1'); dialog::add_action([p, cw] { connect_portal(cw, p, edit_spin); erase_unconnected(p); }); } dialog::addSelItem(XLAT("portal orientation"), its(edit_spin), 'o'); dialog::add_action([] { edit_spin = edit_spin + 1; }); } dialog::display(); } #if HDR struct portal_to_save { cellwalker cw1; cellwalker cw2; int spin; bool mirrored; }; #endif EX vector portals_to_save; EX void prepare_to_save() { portals_to_save.clear(); for(auto c: connections) if(c.second.scw < c.second.tcw) { portals_to_save.emplace_back(portal_to_save{c.second.scw, c.second.tcw, c.second.spin_value, false}); } } EX void load_saved_portals() { for(const auto& p: portals_to_save) connect_portal(p.cw1, p.cw2, p.spin); } auto hooks1 = addHook(hooks_o_key, 90, [] (o_funcs& v) { if(intra::in) v.push_back(named_dialog(XLAT("manage portals"), show_portals)); }) + arg::add3("-intra-add", [] { start_game(); become(); }) + arg::add3("-intra-start", [] { start_game(); become(); start(0); }) + arg::add3("-be-square", [] { check_cgi(); cgi.require_basics(); PIU( vid.plevel_factor = cgi.edgelen / cgi.scalefactor ); check_cgi(); cgi.require_basics(); }); EX } }