hyperrogue/intra.cpp

620 lines
17 KiB
C++
Raw Normal View History

2021-09-16 19:30:26 +00:00
#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<intra_data> data;
/** index of the space we are currently in */
2021-09-16 19:58:35 +00:00
EX int current;
2021-09-16 19:30:26 +00:00
2021-10-07 10:52:28 +00:00
/** portal debugging flags */
EX int debug_portal;
2021-09-16 19:30:26 +00:00
/** map cells to their intra spaces */
EX map<cell*, int> 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;
if(bt::in()) {
h = PIU( deparabolic13(h) );
h[2] = dec.first - d;
h[3] = 1;
}
else {
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];
2021-09-16 19:30:26 +00:00
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;
}
2021-10-03 16:06:15 +00:00
else if(hyperbolic && bt::in()) {
2021-10-07 10:52:47 +00:00
h = deparabolic13(h);
h[3] = 1;
tie(h[0], h[1], h[2]) = make_tuple(h[1], h[2], h[0]);
h = T * h;
h[2] *= exp(h[1]);
return h;
}
else if(sol) {
h = T * h;
h[2] *= exp(-h[1]);
2021-10-03 16:06:15 +00:00
return h;
}
2021-09-16 19:30:26 +00:00
else {
h = T * h;
h /= h[3];
return h;
}
}
hyperpoint portal_data::from_poco(hyperpoint h) const {
if(prod && kind == 1) {
2021-09-16 20:04:24 +00:00
ld xd = h[2];
if(d<0) xd = -xd, h[0] = -h[0];
if(bt::in()) {
h[2] = 0;
return PIU( parabolic13(h) ) * exp(d+xd);
}
2021-09-16 19:30:26 +00:00
h[2] = 1;
auto z = product_decompose(h).first;
2021-09-16 20:04:24 +00:00
return h * exp(d+xd-z);
2021-09-16 19:30:26 +00:00
}
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]);
2021-09-16 19:30:26 +00:00
}
2021-10-03 16:06:15 +00:00
else if(hyperbolic && bt::in()) {
2021-10-07 10:52:47 +00:00
h[2] *= exp(-h[1]);
h = iT * h;
return hr::parabolic13(h[0], h[1]) * xpush0(h[2]);
2021-10-03 16:06:15 +00:00
}
2021-10-07 10:52:47 +00:00
else if(sol) {
h[2] *= exp(h[1]);
2021-10-03 16:06:15 +00:00
return iT * h;
2021-10-07 10:52:47 +00:00
}
2021-09-16 19:30:26 +00:00
else {
h[3] = 1;
return normalize(iT * h);
}
}
EX portal_data make_portal(cellwalker cw, int spin) {
2021-09-16 19:30:26 +00:00
auto& ss = currentmap->get_cellshape(cw.at);
2021-10-03 08:19:45 +00:00
auto fac = ss.faces[cw.spin];
2021-09-16 19:30:26 +00:00
portal_data id;
id.scale = 1;
2021-10-03 16:06:15 +00:00
auto gg = geometry;
if(prod && cw.spin >= cw.at->type - 2) {
id.kind = 1;
id.d = product_decompose(fac[0]).first;
id.v0 = C0 * exp(id.d);
if(bt::in()) {
fac.pop_back();
id.scale = log(2)/2;
}
2021-09-16 19:30:26 +00:00
}
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)));
hyperpoint h = fac[0];
h /= sqrt(abs(intval(h, Hypc)));
id.T = cspin(0, 1, -90*degree) * spintox(gpushxto0(id.v0) * h) * gpushxto0(id.v0);
2021-09-16 19:30:26 +00:00
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)));
}
}
2021-10-03 16:06:15 +00:00
else if(bt::in()) {
2021-10-07 10:52:47 +00:00
hyperpoint removed = Hypc;
2021-10-03 16:06:15 +00:00
for(int i=0; i<isize(fac); i++) {
int i1 = i+1; if(i1 >= isize(fac)) i1 = 0;
int i2 = i1+1; if(i2 >= isize(fac)) i2 = 0;
2021-10-07 10:52:47 +00:00
if(hypot_d(3, 2*fac[i1] - fac[i] - fac[i2]) < 1e-3) {
removed = fac[i1];
2021-10-03 16:06:15 +00:00
fac.erase(fac.begin()+i1);
2021-10-07 10:52:47 +00:00
}
2021-10-03 16:06:15 +00:00
}
2021-10-03 08:19:45 +00:00
id.kind = 0;
id.v0 = Hypc;
id.T = Id;
for(auto& h: fac) h[3] = 1;
2021-10-07 10:52:47 +00:00
auto fac1 = fac;
auto to_coords = [] (hyperpoint& p) {
if(hyperbolic) {
p[0] *= bt::xy_mul();
p[1] *= bt::xy_mul();
p[2] *= log(2) / 2;
}
};
for(auto& p: fac1)
to_coords(p);
to_coords(removed);
for(auto p: fac1) id.v0 += p;
2021-10-03 08:19:45 +00:00
id.v0 /= isize(fac);
dynamicval<eGeometry> g(geometry, gCubeTiling);
id.T = gpushxto0(id.v0);
2021-10-07 10:52:47 +00:00
for(auto p: fac1) {
2021-10-03 08:19:45 +00:00
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;
2021-10-07 10:52:47 +00:00
if(abs((id.T * removed)[0]) > 1e-2) id.T = cspin(0, 1, 90*degree) * id.T;
if((id.T * removed)[1] < -1e-2) id.T = cspin(0, 1, 180*degree) * id.T;
2021-10-03 16:06:15 +00:00
vector<hyperpoint> 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<isize(v); i++)
sca *= sqhypot_d(3, v[i] - v[(1+i) % isize(v)]);
sca = pow(sca, .5 / isize(v));
id.scale = sca / 2;
2021-10-03 08:19:45 +00:00
}
2021-09-16 19:30:26 +00:00
else {
id.kind = 0;
id.v0 = Hypc;
for(auto p: fac) id.v0 += p;
id.v0 = normalize(id.v0);
id.T = cpush(2, -hdist0(id.v0)) * cspin(2, 0, 90*degree) * spintox(id.v0);
}
id.iT = inverse(id.T);
if(MDIM == 3) for(int i=0; i<4; i++) id.iT[3][i] = id.iT[i][3] = i==3;
int first = spin;
int second = spin + 1;
first = gmod(first, isize(fac));
second = gmod(second, isize(fac));
2021-10-03 08:19:45 +00:00
id.co0 = id.to_poco(final_coords(fac[first]));
id.co1 = id.to_poco(final_coords(fac[second]));
2021-10-07 10:52:28 +00:00
if(debug_portal) for(auto p: fac) {
auto p1 = final_coords(p);
auto p2 = id.to_poco(p1);
auto p3 = id.from_poco(p2);
println(hlog, p, " > ", p1, " > ", p2, " > ", p3);
}
2021-09-16 19:30:26 +00:00
return id;
}
#if HDR
/** information about connection (portal-to-portal) */
struct connection_data {
int source_world;
int target_world;
cellwalker scw, tcw;
portal_data id1;
portal_data id2;
transmatrix T;
int spin_value;
bool mirrored; /* not implemented */
2021-09-16 19:30:26 +00:00
};
#endif
EX map<cellwalker, connection_data> connections;
EX connection_data* find_connection(int a, int b) {
for(auto& p: connections)
2021-10-07 10:53:01 +00:00
if(intra_id.at(p.first.at) == a && p.second.target_world == b)
2021-09-16 19:30:26 +00:00
return &p.second;
return nullptr;
}
EX void switch_to(int id) {
2021-09-16 19:58:35 +00:00
if(current == id) return;
data[current].gd.storegame();
current = id;
ginf[gProduct] = data[current].gi;
data[current].gd.restoregame();
2021-09-16 19:30:26 +00:00
}
void connect_portal_1(cellwalker cw1, cellwalker cw2, int spin) {
2021-09-16 19:30:26 +00:00
auto& p = connections[cw1];
2021-10-03 16:04:52 +00:00
p.source_world = intra_id.at(cw1.at);
p.target_world = intra_id.at(cw2.at);
2021-09-16 19:30:26 +00:00
p.scw = cw1;
p.tcw = cw2;
2021-10-03 16:04:52 +00:00
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;
2021-09-16 19:30:26 +00:00
if(1) {
dynamicval<eGeometry> 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);
2021-10-07 10:52:28 +00:00
if(debug_portal) for(int i=0; i<4; i++)
println(hlog, "mapping [", p.source_world, "]", get_column(T1, i), " to [", p.target_world, "] ", get_column(T2, i));
2021-09-16 19:30:26 +00:00
p.T = T2 * inverse(T1);
2021-09-30 13:02:16 +00:00
2021-10-07 10:52:28 +00:00
if(debug_portal)
println(hlog, "det = ", det(p.T));
2021-09-30 13:02:16 +00:00
if(det(p.T) < 0) {
set_column(T2, 0, p.id2.co1);
set_column(T2, 1, p.id2.co0);
p.T = T2 * inverse(T1);
}
2021-09-16 19:30:26 +00:00
}
}
EX vector<pair<int, cell*>> 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);
2021-09-16 19:30:26 +00:00
}
/** make currentmap into one of the spaces in intra */
EX void become() {
2021-09-17 11:49:31 +00:00
check_cgi();
cgi.require_shapes();
2021-09-16 19:30:26 +00:00
auto& ac = currentmap->allcells();
2021-09-16 19:58:35 +00:00
current = isize(data);
2021-09-16 19:30:26 +00:00
for(cell *c: ac)
2021-09-16 19:58:35 +00:00
intra_id[c] = current;
2021-09-16 19:30:26 +00:00
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;
2021-09-16 19:58:35 +00:00
current = id;
data[current].gd.restoregame();
ginf[gProduct] = data[current].gi;
again:
int missing = 0;
for(auto p: intra_id)
for(int i=0; i<p.first->type; 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;
2021-09-16 19:30:26 +00:00
}
#if HDR
/** a convenience struct to switch back after a temporary switch_to */
struct resetter {
int ic;
2021-09-16 19:58:35 +00:00
resetter() { ic = current; }
~resetter() { if(in) switch_to(ic); }
2021-09-16 19:30:26 +00:00
};
#endif
EX void may_switch_to(cell *c) {
2021-10-07 10:53:01 +00:00
if(in) switch_to(intra_id.at(c));
2021-09-16 19:30:26 +00:00
}
EX int full_wall_offset(cell *c) {
int wo = currentmap->wall_offset(c);
2021-10-07 10:53:01 +00:00
if(in) wo += data[intra_id.at(c)].wallindex;
2021-09-16 19:30:26 +00:00
return wo;
}
2021-10-07 10:52:28 +00:00
ld dsdet(array<hyperpoint, 4> ds) {
transmatrix T;
set_column(T, 0, ds[1]-ds[0]);
set_column(T, 1, ds[2]-ds[0]);
set_column(T, 2, ds[3]-ds[0]);
return det3(T);
}
EX void analyze_orthonormal(array<hyperpoint, 4> ds, ld sca) {
transmatrix T = gpushxto0(ds[0]);
vector<ld> orths;
for(int i: {1,2,3}) {
ds[i] = T * ds[i];
if(prod) ds[i][2]--;
}
for(int i=0; i<3; i++)
for(int j=0; j<3; j++)
orths.push_back(dot_d(3, ds[i+1], ds[j+1]) * sca);
println(hlog, "orths = ", kz(orths));
}
2021-10-07 10:43:45 +00:00
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() {
2021-09-16 19:30:26 +00:00
transmatrix iView = view_inverse(View);
ld dist = hdist0(iView * C0);
int nei = -1;
for(int i=0; i<centerover->type; i++) {
ld dist1 = hdist0(currentmap->ray_iadj(centerover, i) * iView * C0);
if(dist1 < dist) nei = i, dist = dist1;
}
auto cw1 = cellwalker(centerover, nei);
2021-10-07 10:43:45 +00:00
return at_or_null(connections, cw1);
}
EX void check_portal_movement() {
auto p = through_portal();
2021-09-16 19:30:26 +00:00
ld c = camera_speed;
if(p) {
2021-10-07 10:53:12 +00:00
ld eps = 1e-5;
2021-09-16 19:30:26 +00:00
c /= p->id1.scale;
anims::cycle_length /= p->id1.scale;
2021-10-07 10:52:28 +00:00
ld ss = pow(eps, -2);
2021-09-16 19:30:26 +00:00
2021-10-07 10:52:28 +00:00
array<hyperpoint, 4> ds; /* camera, forward, upward */
2021-09-16 19:30:26 +00:00
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;
2021-10-07 10:52:28 +00:00
ds[3] = inverse(get_shift_view_of(ctangent(0, +eps), View)) * C0;
if(debug_portal) {
println(hlog, "at = ", ds[0], " det = ", dsdet(ds), " bt = ", bt::minkowski_to_bt(ds[0]));
analyze_orthonormal(ds, ss);
}
2021-09-16 19:30:26 +00:00
for(auto& h: ds) h = p->id1.to_poco(h);
2021-10-07 10:52:28 +00:00
if(debug_portal) {
println(hlog, "poco: at = ", ds[0], " det = ", dsdet(ds));
dynamicval<eGeometry> g(geometry, gCubeTiling);
analyze_orthonormal(ds, ss);
}
2021-09-16 19:30:26 +00:00
/* reset the original */
View = Id; NLP = Id;
switch_to(p->target_world);
centerover = p->tcw.at;
if(1) {
dynamicval<eGeometry> g(geometry, gCubeTiling);
for(auto& h: ds) h = p->T * h;
}
2021-10-07 10:52:28 +00:00
if(debug_portal) {
println(hlog, "poco2: at = ", ds[0], " det = ", dsdet(ds));
dynamicval<eGeometry> g(geometry, gCubeTiling);
analyze_orthonormal(ds, ss);
}
2021-09-16 19:30:26 +00:00
for(auto& h: ds) h = p->id2.from_poco(h);
2021-10-07 10:52:28 +00:00
if(debug_portal) {
println(hlog, "goal: at = ", ds[0], " det = ", dsdet(ds));
analyze_orthonormal(ds, ss);
}
2021-09-16 19:30:26 +00:00
set_view(ds[0], ds[1], ds[2]);
2021-10-07 10:52:28 +00:00
if(debug_portal) {
array<hyperpoint, 4> xds; /* camera, forward, upward */
xds[0] = inverse(View) * C0;
xds[1] = inverse(get_shift_view_of(ctangent(2, -eps), View)) * C0;
xds[2] = inverse(get_shift_view_of(ctangent(1, +eps), View)) * C0;
xds[3] = inverse(get_shift_view_of(ctangent(0, +eps), View)) * C0;
println(hlog, "goal: at = ", xds[0], " det = ", dsdet(xds), " bt = ", bt::minkowski_to_bt(xds[0]));
}
2021-09-16 19:30:26 +00:00
c *= p->id2.scale;
anims::cycle_length *= p->id2.scale;
camera_speed = c;
}
}
2021-09-17 08:26:51 +00:00
vector<cellwalker> unconnected;
void erase_unconnected(cellwalker cw) {
for(int i=0; i<isize(unconnected); i++)
if(unconnected[i] == cw)
unconnected.erase(unconnected.begin() + i);
}
int edit_spin;
2021-09-17 08:26:51 +00:00
void show_portals() {
gamescreen(1);
dialog::init(XLAT("manage portals"));
cellwalker cw(centerover, point_direction);
bool valid = point_direction >= 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);
});
2021-10-07 10:52:28 +00:00
if(debug_portal) {
dialog::addItem(XLAT("debug"), 'd');
dialog::add_action([] {
ld eps = 1e-5;
array<hyperpoint, 4> 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;
ds[3] = inverse(get_shift_view_of(ctangent(0, +eps), View)) * C0;
set_view(ds[0], ds[1], ds[2]);
});
}
2021-09-17 08:26:51 +00:00
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);
2021-09-17 08:26:51 +00:00
erase_unconnected(p);
});
}
dialog::addSelItem(XLAT("portal orientation"), its(edit_spin), 'o');
dialog::add_action([] { edit_spin = edit_spin + 1; });
2021-10-07 10:52:28 +00:00
if(debug_portal) {
dialog::addItem(XLAT("mirror connection"), 'm');
dialog::add_action([cw] { connect_portal(cw, cw, edit_spin); });
}
2021-09-17 08:26:51 +00:00
}
dialog::display();
}
#if HDR
struct portal_to_save {
cellwalker cw1;
cellwalker cw2;
int spin;
bool mirrored;
};
#endif
EX vector<portal_to_save> 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);
}
2021-09-17 08:26:51 +00:00
auto hooks1 =
addHook(hooks_o_key, 90, [] (o_funcs& v) {
if(intra::in) v.push_back(named_dialog(XLAT("manage portals"), show_portals));
2021-09-30 17:05:15 +00:00
})
+ arg::add3("-intra-add", [] { start_game(); become(); })
2021-10-01 05:56:57 +00:00
+ arg::add3("-intra-start", [] { start_game(); become(); start(0); })
+ arg::add3("-be-square", [] {
check_cgi();
cgi.require_basics();
2021-10-03 16:07:46 +00:00
PIU( vid.plevel_factor = cgi.edgelen / cgi.scalefactor );
2021-10-01 05:56:57 +00:00
check_cgi();
cgi.require_basics();
2021-10-07 10:52:28 +00:00
})
+ arg::add3("-debug-portal", [] { arg::shift(); debug_portal = arg::argi(); });
2021-10-01 05:56:57 +00:00
2021-09-17 08:26:51 +00:00
2021-09-16 19:30:26 +00:00
EX }
}